mirror of
https://github.com/kdheepak/taskwarrior-tui.git
synced 2025-08-26 12:17:19 +02:00
Merge pull request #124 from kdheepak/key-binding
This commit is contained in:
commit
59aaa82891
4 changed files with 287 additions and 94 deletions
181
src/app.rs
181
src/app.rs
|
@ -3,6 +3,7 @@ use crate::config;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::help::Help;
|
use crate::help::Help;
|
||||||
|
use crate::keyconfig::KeyConfig;
|
||||||
use crate::table::{Row, Table, TableState};
|
use crate::table::{Row, Table, TableState};
|
||||||
use crate::task_report::TaskReportTable;
|
use crate::task_report::TaskReportTable;
|
||||||
use crate::util::Key;
|
use crate::util::Key;
|
||||||
|
@ -157,11 +158,14 @@ pub struct TTApp {
|
||||||
pub help_popup: Help,
|
pub help_popup: Help,
|
||||||
pub contexts: Vec<Context>,
|
pub contexts: Vec<Context>,
|
||||||
pub last_export: Option<SystemTime>,
|
pub last_export: Option<SystemTime>,
|
||||||
|
pub keyconfig: KeyConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TTApp {
|
impl TTApp {
|
||||||
pub fn new() -> Result<Self, Box<dyn Error>> {
|
pub fn new() -> Result<Self, Box<dyn Error>> {
|
||||||
let c = Config::default()?;
|
let c = Config::default()?;
|
||||||
|
let mut kc = KeyConfig::default();
|
||||||
|
kc.update()?;
|
||||||
let mut app = Self {
|
let mut app = Self {
|
||||||
should_quit: false,
|
should_quit: false,
|
||||||
task_table_state: TableState::default(),
|
task_table_state: TableState::default(),
|
||||||
|
@ -182,6 +186,7 @@ impl TTApp {
|
||||||
help_popup: Help::new(),
|
help_popup: Help::new(),
|
||||||
contexts: vec![],
|
contexts: vec![],
|
||||||
last_export: None,
|
last_export: None,
|
||||||
|
keyconfig: kc,
|
||||||
};
|
};
|
||||||
for c in app.config.filter.chars() {
|
for c in app.config.filter.chars() {
|
||||||
app.filter.insert(c, 1);
|
app.filter.insert(c, 1);
|
||||||
|
@ -1204,7 +1209,7 @@ impl TTApp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn task_start_or_stop(&mut self) -> Result<(), String> {
|
pub fn task_start_stop(&mut self) -> Result<(), String> {
|
||||||
if self.tasks.lock().unwrap().is_empty() {
|
if self.tasks.lock().unwrap().is_empty() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
@ -1410,7 +1415,6 @@ impl TTApp {
|
||||||
let reference = TimeZone::from_utc_datetime(now.offset(), d);
|
let reference = TimeZone::from_utc_datetime(now.offset(), d);
|
||||||
let now = TimeZone::from_utc_datetime(now.offset(), &now.naive_utc());
|
let now = TimeZone::from_utc_datetime(now.offset(), &now.naive_utc());
|
||||||
let d = d.clone();
|
let d = d.clone();
|
||||||
dbg!(reference, now);
|
|
||||||
if (reference - chrono::Duration::nanoseconds(1)).month() == now.month() {
|
if (reference - chrono::Duration::nanoseconds(1)).month() == now.month() {
|
||||||
add_tag(&mut task, "MONTH".to_string());
|
add_tag(&mut task, "MONTH".to_string());
|
||||||
}
|
}
|
||||||
|
@ -1460,49 +1464,56 @@ impl TTApp {
|
||||||
events: &Events,
|
events: &Events,
|
||||||
) -> Result<(), Box<dyn Error>> {
|
) -> Result<(), Box<dyn Error>> {
|
||||||
match self.mode {
|
match self.mode {
|
||||||
AppMode::TaskReport => match input {
|
AppMode::TaskReport => {
|
||||||
Key::Ctrl('c') | Key::Char('q') => self.should_quit = true,
|
if input == self.keyconfig.quit || input == Key::Ctrl('c') {
|
||||||
Key::Char('r') => self.update(true)?,
|
self.should_quit = true;
|
||||||
Key::End | Key::Char('G') => self.task_report_bottom(),
|
} else if input == self.keyconfig.refresh {
|
||||||
Key::Home => self.task_report_top(),
|
self.update(true)?;
|
||||||
Key::Char('g') => {
|
} else if input == self.keyconfig.go_to_bottom || input == Key::End {
|
||||||
if let Event::Input(Key::Char('g')) = events.next()? {
|
self.task_report_bottom();
|
||||||
self.task_report_top()
|
} else if input == self.keyconfig.go_to_top || input == Key::Home {
|
||||||
|
self.task_report_top();
|
||||||
|
} else if input == Key::Down || input == self.keyconfig.down {
|
||||||
|
self.task_report_next();
|
||||||
|
} else if input == Key::Up || input == self.keyconfig.up {
|
||||||
|
self.task_report_previous();
|
||||||
|
} else if input == Key::PageDown || input == self.keyconfig.page_down {
|
||||||
|
self.task_report_next_page();
|
||||||
|
} else if input == Key::PageUp || input == self.keyconfig.page_up {
|
||||||
|
self.task_report_previous_page();
|
||||||
|
} else if input == self.keyconfig.done {
|
||||||
|
match self.task_done() {
|
||||||
|
Ok(_) => self.update(true)?,
|
||||||
|
Err(e) => {
|
||||||
|
self.mode = AppMode::TaskError;
|
||||||
|
self.error = e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
} else if input == self.keyconfig.delete {
|
||||||
Key::Down | Key::Char('j') => self.task_report_next(),
|
match self.task_delete() {
|
||||||
Key::Up | Key::Char('k') => self.task_report_previous(),
|
Ok(_) => self.update(true)?,
|
||||||
Key::PageDown | Key::Char('J') => self.task_report_next_page(),
|
Err(e) => {
|
||||||
Key::PageUp | Key::Char('K') => self.task_report_previous_page(),
|
self.mode = AppMode::TaskError;
|
||||||
Key::Char('d') => match self.task_done() {
|
self.error = e;
|
||||||
Ok(_) => self.update(true)?,
|
}
|
||||||
Err(e) => {
|
|
||||||
self.mode = AppMode::TaskError;
|
|
||||||
self.error = e;
|
|
||||||
}
|
}
|
||||||
},
|
} else if input == self.keyconfig.start_stop {
|
||||||
Key::Char('x') => match self.task_delete() {
|
match self.task_start_stop() {
|
||||||
Ok(_) => self.update(true)?,
|
Ok(_) => self.update(true)?,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
self.mode = AppMode::TaskError;
|
self.mode = AppMode::TaskError;
|
||||||
self.error = e;
|
self.error = e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
} else if input == self.keyconfig.undo {
|
||||||
Key::Char('s') => match self.task_start_or_stop() {
|
match self.task_undo() {
|
||||||
Ok(_) => self.update(true)?,
|
Ok(_) => self.update(true)?,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
self.mode = AppMode::TaskError;
|
self.mode = AppMode::TaskError;
|
||||||
self.error = e;
|
self.error = e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
} else if input == self.keyconfig.edit {
|
||||||
Key::Char('u') => match self.task_undo() {
|
|
||||||
Ok(_) => self.update(true)?,
|
|
||||||
Err(e) => {
|
|
||||||
self.mode = AppMode::TaskError;
|
|
||||||
self.error = e;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Key::Char('e') => {
|
|
||||||
events.pause_key_capture(terminal);
|
events.pause_key_capture(terminal);
|
||||||
let r = self.task_edit();
|
let r = self.task_edit();
|
||||||
events.resume_key_capture(terminal);
|
events.resume_key_capture(terminal);
|
||||||
|
@ -1513,8 +1524,7 @@ impl TTApp {
|
||||||
self.error = e;
|
self.error = e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else if input == self.keyconfig.modify {
|
||||||
Key::Char('m') => {
|
|
||||||
self.mode = AppMode::TaskModify;
|
self.mode = AppMode::TaskModify;
|
||||||
match self.task_current() {
|
match self.task_current() {
|
||||||
Some(t) => {
|
Some(t) => {
|
||||||
|
@ -1523,64 +1533,51 @@ impl TTApp {
|
||||||
}
|
}
|
||||||
None => self.modify.update("", 0),
|
None => self.modify.update("", 0),
|
||||||
}
|
}
|
||||||
}
|
} else if input == self.keyconfig.shell {
|
||||||
Key::Char('!') => {
|
|
||||||
self.mode = AppMode::TaskSubprocess;
|
self.mode = AppMode::TaskSubprocess;
|
||||||
}
|
} else if input == self.keyconfig.log {
|
||||||
Key::Char('l') => {
|
|
||||||
self.mode = AppMode::TaskLog;
|
self.mode = AppMode::TaskLog;
|
||||||
}
|
} else if input == self.keyconfig.add {
|
||||||
Key::Char('a') => {
|
|
||||||
self.mode = AppMode::TaskAdd;
|
self.mode = AppMode::TaskAdd;
|
||||||
}
|
} else if input == self.keyconfig.annotate {
|
||||||
Key::Char('A') => {
|
|
||||||
self.mode = AppMode::TaskAnnotate;
|
self.mode = AppMode::TaskAnnotate;
|
||||||
}
|
} else if input == self.keyconfig.help {
|
||||||
Key::Char('?') => {
|
|
||||||
self.mode = AppMode::TaskHelpPopup;
|
self.mode = AppMode::TaskHelpPopup;
|
||||||
}
|
} else if input == self.keyconfig.filter {
|
||||||
Key::Char('/') => {
|
|
||||||
self.mode = AppMode::TaskFilter;
|
self.mode = AppMode::TaskFilter;
|
||||||
}
|
} else if input == self.keyconfig.zoom {
|
||||||
Key::Char('z') => {
|
|
||||||
self.task_report_show_info = !self.task_report_show_info;
|
self.task_report_show_info = !self.task_report_show_info;
|
||||||
}
|
} else if input == self.keyconfig.context_menu {
|
||||||
Key::Char('c') => {
|
|
||||||
self.mode = AppMode::TaskContextMenu;
|
self.mode = AppMode::TaskContextMenu;
|
||||||
}
|
} else if input == self.keyconfig.next_tab {
|
||||||
Key::Char(']') => {
|
|
||||||
self.mode = AppMode::Calendar;
|
self.mode = AppMode::Calendar;
|
||||||
}
|
}
|
||||||
_ => {}
|
}
|
||||||
},
|
AppMode::TaskContextMenu => {
|
||||||
AppMode::TaskContextMenu => match input {
|
if input == self.keyconfig.quit || input == Key::Esc {
|
||||||
Key::Esc | Key::Char('q') => {
|
|
||||||
self.mode = AppMode::TaskReport;
|
self.mode = AppMode::TaskReport;
|
||||||
}
|
} else if input == Key::Down || input == self.keyconfig.down {
|
||||||
Key::Down | Key::Char('j') => self.context_next(),
|
self.context_next();
|
||||||
Key::Up | Key::Char('k') => self.context_previous(),
|
} else if input == Key::Up || input == self.keyconfig.up {
|
||||||
Key::Char('\n') => {
|
self.context_previous();
|
||||||
|
} else if input == Key::Char('\n') {
|
||||||
self.context_select();
|
self.context_select();
|
||||||
self.get_context()?;
|
self.get_context()?;
|
||||||
}
|
}
|
||||||
_ => {}
|
}
|
||||||
},
|
AppMode::TaskHelpPopup => {
|
||||||
AppMode::TaskHelpPopup => match input {
|
if input == self.keyconfig.quit || input == Key::Esc {
|
||||||
Key::Esc | Key::Char('q') => {
|
|
||||||
self.mode = AppMode::TaskReport;
|
self.mode = AppMode::TaskReport;
|
||||||
}
|
} else if input == self.keyconfig.down {
|
||||||
Key::Char('j') => {
|
|
||||||
self.help_popup.scroll = self.help_popup.scroll.checked_add(1).unwrap_or(0);
|
self.help_popup.scroll = self.help_popup.scroll.checked_add(1).unwrap_or(0);
|
||||||
let th = (self.help_popup.text_height as u16).saturating_sub(1);
|
let th = (self.help_popup.text_height as u16).saturating_sub(1);
|
||||||
if self.help_popup.scroll > th {
|
if self.help_popup.scroll > th {
|
||||||
self.help_popup.scroll = th
|
self.help_popup.scroll = th
|
||||||
}
|
}
|
||||||
}
|
} else if input == self.keyconfig.up {
|
||||||
Key::Char('k') => {
|
|
||||||
self.help_popup.scroll = self.help_popup.scroll.saturating_sub(1);
|
self.help_popup.scroll = self.help_popup.scroll.saturating_sub(1);
|
||||||
}
|
}
|
||||||
_ => {}
|
}
|
||||||
},
|
|
||||||
AppMode::TaskModify => match input {
|
AppMode::TaskModify => match input {
|
||||||
Key::Char('\n') => match self.task_modify() {
|
Key::Char('\n') => match self.task_modify() {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
|
@ -1674,25 +1671,25 @@ impl TTApp {
|
||||||
_ => handle_movement(&mut self.filter, input),
|
_ => handle_movement(&mut self.filter, input),
|
||||||
},
|
},
|
||||||
AppMode::TaskError => self.mode = AppMode::TaskReport,
|
AppMode::TaskError => self.mode = AppMode::TaskReport,
|
||||||
AppMode::Calendar => match input {
|
AppMode::Calendar => {
|
||||||
Key::Ctrl('c') | Key::Char('q') => self.should_quit = true,
|
if input == self.keyconfig.quit || input == Key::Ctrl('c') {
|
||||||
Key::Char('[') => {
|
self.should_quit = true;
|
||||||
|
} else if input == self.keyconfig.previous_tab {
|
||||||
self.mode = AppMode::TaskReport;
|
self.mode = AppMode::TaskReport;
|
||||||
}
|
} else if input == Key::Up || input == self.keyconfig.up {
|
||||||
Key::Up | Key::Char('k') => {
|
|
||||||
if self.calendar_year > 0 {
|
if self.calendar_year > 0 {
|
||||||
self.calendar_year -= 1
|
self.calendar_year -= 1;
|
||||||
}
|
}
|
||||||
}
|
} else if input == Key::Down || input == self.keyconfig.down {
|
||||||
Key::Down | Key::Char('j') => self.calendar_year += 1,
|
self.calendar_year += 1;
|
||||||
Key::PageUp | Key::Char('K') => {
|
} else if input == Key::PageUp || input == self.keyconfig.page_up {
|
||||||
if self.calendar_year > 0 {
|
if self.calendar_year > 0 {
|
||||||
self.calendar_year -= 10
|
self.calendar_year -= 10
|
||||||
}
|
}
|
||||||
|
} else if input == Key::PageDown || input == self.keyconfig.page_down {
|
||||||
|
self.calendar_year += 10
|
||||||
}
|
}
|
||||||
Key::PageDown | Key::Char('J') => self.calendar_year += 10,
|
}
|
||||||
_ => {}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1949,7 +1946,6 @@ mod tests {
|
||||||
let caps = re.captures(&s);
|
let caps = re.captures(&s);
|
||||||
if caps.is_none() {
|
if caps.is_none() {
|
||||||
let s = String::from_utf8_lossy(&output.stderr);
|
let s = String::from_utf8_lossy(&output.stderr);
|
||||||
dbg!(s);
|
|
||||||
assert!(false);
|
assert!(false);
|
||||||
}
|
}
|
||||||
let caps = re.captures(&s).unwrap();
|
let caps = re.captures(&s).unwrap();
|
||||||
|
@ -2036,7 +2032,6 @@ mod tests {
|
||||||
"UNBLOCKED",
|
"UNBLOCKED",
|
||||||
"YEAR",
|
"YEAR",
|
||||||
] {
|
] {
|
||||||
dbg!(s, task.tags());
|
|
||||||
assert!(task.tags().unwrap().contains(&s.to_string()));
|
assert!(task.tags().unwrap().contains(&s.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
195
src/keyconfig.rs
Normal file
195
src/keyconfig.rs
Normal file
|
@ -0,0 +1,195 @@
|
||||||
|
use crate::util::Key;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::hash::Hash;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct KeyConfig {
|
||||||
|
pub quit: Key,
|
||||||
|
pub refresh: Key,
|
||||||
|
pub go_to_bottom: Key,
|
||||||
|
pub go_to_top: Key,
|
||||||
|
pub down: Key,
|
||||||
|
pub up: Key,
|
||||||
|
pub page_down: Key,
|
||||||
|
pub page_up: Key,
|
||||||
|
pub delete: Key,
|
||||||
|
pub done: Key,
|
||||||
|
pub start_stop: Key,
|
||||||
|
pub undo: Key,
|
||||||
|
pub edit: Key,
|
||||||
|
pub modify: Key,
|
||||||
|
pub shell: Key,
|
||||||
|
pub log: Key,
|
||||||
|
pub add: Key,
|
||||||
|
pub annotate: Key,
|
||||||
|
pub help: Key,
|
||||||
|
pub filter: Key,
|
||||||
|
pub zoom: Key,
|
||||||
|
pub context_menu: Key,
|
||||||
|
pub next_tab: Key,
|
||||||
|
pub previous_tab: Key,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for KeyConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
quit: Key::Char('q'),
|
||||||
|
refresh: Key::Char('r'),
|
||||||
|
go_to_bottom: Key::End,
|
||||||
|
go_to_top: Key::Home,
|
||||||
|
down: Key::Char('j'),
|
||||||
|
up: Key::Char('k'),
|
||||||
|
page_down: Key::Char('J'),
|
||||||
|
page_up: Key::Char('K'),
|
||||||
|
delete: Key::Char('d'),
|
||||||
|
done: Key::Char('x'),
|
||||||
|
start_stop: Key::Char('s'),
|
||||||
|
undo: Key::Char('u'),
|
||||||
|
edit: Key::Char('e'),
|
||||||
|
modify: Key::Char('m'),
|
||||||
|
shell: Key::Char('!'),
|
||||||
|
log: Key::Char('l'),
|
||||||
|
add: Key::Char('a'),
|
||||||
|
annotate: Key::Char('A'),
|
||||||
|
help: Key::Char('?'),
|
||||||
|
filter: Key::Char('/'),
|
||||||
|
zoom: Key::Char('z'),
|
||||||
|
context_menu: Key::Char('c'),
|
||||||
|
next_tab: Key::Char(']'),
|
||||||
|
previous_tab: Key::Char('['),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyConfig {
|
||||||
|
pub fn update(&mut self) -> Result<(), Box<dyn Error>> {
|
||||||
|
self.quit = self.get_config("taskwarrior-tui.keyconfig.quit").unwrap_or(self.quit);
|
||||||
|
self.refresh = self
|
||||||
|
.get_config("taskwarrior-tui.keyconfig.refresh")
|
||||||
|
.unwrap_or(self.refresh);
|
||||||
|
self.go_to_bottom = self
|
||||||
|
.get_config("taskwarrior-tui.keyconfig.go-to-bottom")
|
||||||
|
.unwrap_or(self.go_to_bottom);
|
||||||
|
self.go_to_top = self
|
||||||
|
.get_config("taskwarrior-tui.keyconfig.go-to-top")
|
||||||
|
.unwrap_or(self.go_to_top);
|
||||||
|
self.down = self.get_config("taskwarrior-tui.keyconfig.down").unwrap_or(self.down);
|
||||||
|
self.up = self.get_config("taskwarrior-tui.keyconfig.up").unwrap_or(self.up);
|
||||||
|
self.page_down = self
|
||||||
|
.get_config("taskwarrior-tui.keyconfig.page-down")
|
||||||
|
.unwrap_or(self.page_down);
|
||||||
|
self.page_up = self
|
||||||
|
.get_config("taskwarrior-tui.keyconfig.page-up")
|
||||||
|
.unwrap_or(self.page_up);
|
||||||
|
self.delete = self
|
||||||
|
.get_config("taskwarrior-tui.keyconfig.delete")
|
||||||
|
.unwrap_or(self.delete);
|
||||||
|
self.done = self.get_config("taskwarrior-tui.keyconfig.done").unwrap_or(self.done);
|
||||||
|
self.start_stop = self
|
||||||
|
.get_config("taskwarrior-tui.keyconfig.start-stop")
|
||||||
|
.unwrap_or(self.start_stop);
|
||||||
|
self.undo = self.get_config("taskwarrior-tui.keyconfig.undo").unwrap_or(self.undo);
|
||||||
|
self.edit = self.get_config("taskwarrior-tui.keyconfig.edit").unwrap_or(self.edit);
|
||||||
|
self.modify = self
|
||||||
|
.get_config("taskwarrior-tui.keyconfig.modify")
|
||||||
|
.unwrap_or(self.modify);
|
||||||
|
self.shell = self.get_config("taskwarrior-tui.keyconfig.shell").unwrap_or(self.shell);
|
||||||
|
self.log = self.get_config("taskwarrior-tui.keyconfig.log").unwrap_or(self.log);
|
||||||
|
self.add = self.get_config("taskwarrior-tui.keyconfig.add").unwrap_or(self.add);
|
||||||
|
self.annotate = self
|
||||||
|
.get_config("taskwarrior-tui.keyconfig.annotate")
|
||||||
|
.unwrap_or(self.annotate);
|
||||||
|
self.filter = self
|
||||||
|
.get_config("taskwarrior-tui.keyconfig.filter")
|
||||||
|
.unwrap_or(self.filter);
|
||||||
|
self.zoom = self.get_config("taskwarrior-tui.keyconfig.zoom").unwrap_or(self.zoom);
|
||||||
|
self.context_menu = self
|
||||||
|
.get_config("taskwarrior-tui.keyconfig.context-menu")
|
||||||
|
.unwrap_or(self.context_menu);
|
||||||
|
self.next_tab = self
|
||||||
|
.get_config("taskwarrior-tui.keyconfig.next-tab")
|
||||||
|
.unwrap_or(self.next_tab);
|
||||||
|
self.previous_tab = self
|
||||||
|
.get_config("taskwarrior-tui.keyconfig.previous-tab")
|
||||||
|
.unwrap_or(self.previous_tab);
|
||||||
|
self.check()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check(&self) -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut elements = vec![
|
||||||
|
&self.quit,
|
||||||
|
&self.refresh,
|
||||||
|
&self.go_to_bottom,
|
||||||
|
&self.go_to_top,
|
||||||
|
&self.down,
|
||||||
|
&self.up,
|
||||||
|
&self.page_down,
|
||||||
|
&self.page_up,
|
||||||
|
&self.delete,
|
||||||
|
&self.done,
|
||||||
|
&self.start_stop,
|
||||||
|
&self.undo,
|
||||||
|
&self.edit,
|
||||||
|
&self.modify,
|
||||||
|
&self.shell,
|
||||||
|
&self.log,
|
||||||
|
&self.add,
|
||||||
|
&self.annotate,
|
||||||
|
&self.help,
|
||||||
|
&self.filter,
|
||||||
|
&self.zoom,
|
||||||
|
&self.context_menu,
|
||||||
|
&self.next_tab,
|
||||||
|
&self.previous_tab,
|
||||||
|
];
|
||||||
|
let l = elements.len();
|
||||||
|
elements.dedup();
|
||||||
|
if l == elements.len() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err("Duplicate keys found in key config".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_config(&mut self, config: &str) -> Option<Key> {
|
||||||
|
let output = Command::new("task")
|
||||||
|
.arg("rc.color=off")
|
||||||
|
.arg("show")
|
||||||
|
.arg(config)
|
||||||
|
.output()
|
||||||
|
.expect("Unable to run `task show`");
|
||||||
|
|
||||||
|
let data = String::from_utf8_lossy(&output.stdout);
|
||||||
|
|
||||||
|
for line in data.split('\n') {
|
||||||
|
if line.starts_with(config) {
|
||||||
|
let line = line.trim_start_matches(config).trim_start().trim_end().to_string();
|
||||||
|
if line.len() == 1 {
|
||||||
|
return Some(Key::Char(line.chars().next().unwrap()));
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
} else if line.starts_with(&config.replace('-', "_")) {
|
||||||
|
let line = line
|
||||||
|
.trim_start_matches(&config.replace('-', "_"))
|
||||||
|
.trim_start()
|
||||||
|
.trim_end()
|
||||||
|
.to_string();
|
||||||
|
if line.len() == 1 {
|
||||||
|
return Some(Key::Char(line.chars().next().unwrap()));
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ mod calendar;
|
||||||
mod config;
|
mod config;
|
||||||
mod context;
|
mod context;
|
||||||
mod help;
|
mod help;
|
||||||
|
mod keyconfig;
|
||||||
mod table;
|
mod table;
|
||||||
mod task_report;
|
mod task_report;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
|
@ -11,7 +11,9 @@ use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{sync::mpsc, thread, time::Duration, time::Instant};
|
use std::{sync::mpsc, thread, time::Duration, time::Instant};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, PartialOrd)]
|
||||||
pub enum Key {
|
pub enum Key {
|
||||||
Backspace,
|
Backspace,
|
||||||
Left,
|
Left,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue