diff --git a/.envrc b/.envrc index e437630..469a352 100644 --- a/.envrc +++ b/.envrc @@ -1,4 +1,5 @@ export TASKRC=`pwd`/tests/data/.taskrc export TASKDATA=`pwd`/tests/data/.task -export XDG_STATE_HOME=`pwd`/tests/data/.config +export TASKWARRIOR_TUI_CONFIG=`pwd`/tests/data/.config +export TASKWARRIOR_TUI_DATA=`pwd`/tests/data/.data export TASKWARRIOR_TUI_LOG_LEVEL=debug diff --git a/src/app.rs b/src/app.rs index 2e3ec27..f87dcad 100644 --- a/src/app.rs +++ b/src/app.rs @@ -2546,10 +2546,13 @@ impl TaskwarriorTui { } } else if input == self.keyconfig.modify { self.mode = Mode::Tasks(Action::Modify); - self.command_history.last(); + self.command_history.reset(); self.history_status = Some(format!( " {} / {}", - self.command_history.history_index() + 1, + self.command_history + .history_index() + .unwrap_or_else(|| self.command_history.history_len().saturating_sub(1)) + .saturating_add(1), self.command_history.history_len() )); self.update_completion_list(); @@ -2598,28 +2601,37 @@ impl TaskwarriorTui { self.mode = Mode::Tasks(Action::Subprocess); } else if input == self.keyconfig.log { self.mode = Mode::Tasks(Action::Log); - self.command_history.last(); + self.command_history.reset(); self.history_status = Some(format!( " {} / {}", - self.command_history.history_index() + 1, + self.command_history + .history_index() + .unwrap_or_else(|| self.command_history.history_len().saturating_sub(1)) + .saturating_add(1), self.command_history.history_len() )); self.update_completion_list(); } else if input == self.keyconfig.add { self.mode = Mode::Tasks(Action::Add); - self.command_history.last(); + self.command_history.reset(); self.history_status = Some(format!( " {} / {}", - self.command_history.history_index() + 1, + self.command_history + .history_index() + .unwrap_or_else(|| self.command_history.history_len().saturating_sub(1)) + .saturating_add(1), self.command_history.history_len() )); self.update_completion_list(); } else if input == self.keyconfig.annotate { self.mode = Mode::Tasks(Action::Annotate); - self.command_history.last(); + self.command_history.reset(); self.history_status = Some(format!( " {} / {}", - self.command_history.history_index() + 1, + self.command_history + .history_index() + .unwrap_or_else(|| self.command_history.history_len().saturating_sub(1)) + .saturating_add(1), self.command_history.history_len() )); self.update_completion_list(); @@ -2627,10 +2639,13 @@ impl TaskwarriorTui { self.mode = Mode::Tasks(Action::HelpPopup); } else if input == self.keyconfig.filter { self.mode = Mode::Tasks(Action::Filter); - self.filter_history.last(); + self.filter_history.reset(); self.history_status = Some(format!( " {} / {}", - self.filter_history.history_index() + 1, + self.filter_history + .history_index() + .unwrap_or_else(|| self.filter_history.history_len().saturating_sub(1)) + .saturating_add(1), self.filter_history.history_len() )); self.update_completion_list(); @@ -2815,7 +2830,10 @@ impl TaskwarriorTui { self.modify.update(&s, std::cmp::min(s.len(), p)); self.history_status = Some(format!( " {} / {}", - self.command_history.history_index() + 1, + self.command_history + .history_index() + .unwrap_or_else(|| self.command_history.history_len().saturating_sub(1)) + .saturating_add(1), self.command_history.history_len() )); } @@ -2832,13 +2850,16 @@ impl TaskwarriorTui { self.modify.update(&s, std::cmp::min(s.len(), p)); self.history_status = Some(format!( " {} / {}", - self.command_history.history_index() + 1, + self.command_history + .history_index() + .unwrap_or_else(|| self.command_history.history_len().saturating_sub(1)) + .saturating_add(1), self.command_history.history_len() )); } } _ => { - self.command_history.last(); + self.command_history.reset(); handle_movement(&mut self.modify, input); self.update_input_for_completion(); } @@ -2932,7 +2953,10 @@ impl TaskwarriorTui { self.command.update(&s, std::cmp::min(s.len(), p)); self.history_status = Some(format!( " {} / {}", - self.command_history.history_index() + 1, + self.command_history + .history_index() + .unwrap_or_else(|| self.command_history.history_len().saturating_sub(1)) + .saturating_add(1), self.command_history.history_len() )); } @@ -2949,13 +2973,16 @@ impl TaskwarriorTui { self.command.update(&s, std::cmp::min(s.len(), p)); self.history_status = Some(format!( " {} / {}", - self.command_history.history_index() + 1, + self.command_history + .history_index() + .unwrap_or_else(|| self.command_history.history_len().saturating_sub(1)) + .saturating_add(1), self.command_history.history_len() )); } } _ => { - self.command_history.last(); + self.command_history.reset(); handle_movement(&mut self.command, input); self.update_input_for_completion(); } @@ -3024,7 +3051,10 @@ impl TaskwarriorTui { self.command.update(&s, std::cmp::min(s.len(), p)); self.history_status = Some(format!( " {} / {}", - self.command_history.history_index() + 1, + self.command_history + .history_index() + .unwrap_or_else(|| self.command_history.history_len().saturating_sub(1)) + .saturating_add(1), self.command_history.history_len() )); } @@ -3041,14 +3071,17 @@ impl TaskwarriorTui { self.command.update(&s, std::cmp::min(s.len(), p)); self.history_status = Some(format!( " {} / {}", - self.command_history.history_index() + 1, + self.command_history + .history_index() + .unwrap_or_else(|| self.command_history.history_len().saturating_sub(1)) + .saturating_add(1), self.command_history.history_len() )); } } _ => { - self.command_history.last(); + self.command_history.reset(); handle_movement(&mut self.command, input); self.update_input_for_completion(); } @@ -3142,7 +3175,10 @@ impl TaskwarriorTui { self.command.update(&s, std::cmp::min(s.len(), p)); self.history_status = Some(format!( " {} / {}", - self.command_history.history_index() + 1, + self.command_history + .history_index() + .unwrap_or_else(|| self.command_history.history_len().saturating_sub(1)) + .saturating_add(1), self.command_history.history_len() )); } @@ -3160,13 +3196,16 @@ impl TaskwarriorTui { self.command.update(&s, std::cmp::min(s.len(), p)); self.history_status = Some(format!( " {} / {}", - self.command_history.history_index() + 1, + self.command_history + .history_index() + .unwrap_or_else(|| self.command_history.history_len().saturating_sub(1)) + .saturating_add(1), self.command_history.history_len() )); } } _ => { - self.command_history.last(); + self.command_history.reset(); handle_movement(&mut self.command, input); self.update_input_for_completion(); } @@ -3215,7 +3254,10 @@ impl TaskwarriorTui { self.filter.update(&s, std::cmp::min(p, s.len())); self.history_status = Some(format!( " {} / {}", - self.filter_history.history_index() + 1, + self.filter_history + .history_index() + .unwrap_or_else(|| self.filter_history.history_len().saturating_sub(1)) + .saturating_add(1), self.filter_history.history_len() )); self.dirty = true; @@ -3233,7 +3275,10 @@ impl TaskwarriorTui { self.filter.update(&s, std::cmp::min(p, s.len())); self.history_status = Some(format!( " {} / {}", - self.filter_history.history_index() + 1, + self.filter_history + .history_index() + .unwrap_or_else(|| self.filter_history.history_len().saturating_sub(1)) + .saturating_add(1), self.filter_history.history_len() )); self.dirty = true; diff --git a/src/history.rs b/src/history.rs index 951d118..eb71769 100644 --- a/src/history.rs +++ b/src/history.rs @@ -7,7 +7,7 @@ use std::path::{Path, PathBuf}; pub struct HistoryContext { history: History, - history_index: usize, + history_index: Option, config_path: PathBuf, } @@ -15,17 +15,22 @@ impl HistoryContext { pub fn new(filename: &str) -> Self { let history = History::new(); - let config_dir_op = dirs::config_dir(); + let config_path = if let Ok(s) = std::env::var("TASKWARRIOR_TUI_CONFIG") { + PathBuf::from(s) + } else { + dirs::config_dir() + .map(|d| d.join("taskwarrior-tui")) + .expect("Unable to create configuration directory for taskwarrior-tui") + }; - let config_path = config_dir_op.map(|d| d.join("taskwarrior-tui")).unwrap(); - - std::fs::create_dir_all(&config_path).unwrap(); + std::fs::create_dir_all(&config_path) + .unwrap_or_else(|_| panic!("Unable to create configuration directory in {:?}", &config_path)); let config_path = config_path.join(filename); Self { history, - history_index: 0, + history_index: None, config_path, } } @@ -36,7 +41,7 @@ impl HistoryContext { } else { self.history.save(&self.config_path)?; } - self.history_index = self.history.len(); + self.history_index = None; log::debug!("Loading history of length {}", self.history.len()); Ok(()) } @@ -50,57 +55,69 @@ impl HistoryContext { &self.history } - pub fn history_index(&self) -> usize { + pub fn history_index(&self) -> Option { self.history_index } pub fn history_search(&mut self, buf: &str, dir: Direction) -> Option { log::debug!( - "Searching history for {:?} in direction {:?} with history index = {:?}", + "Searching history for {:?} in direction {:?} with history index = {:?} and history len = {:?}", buf, dir, - self.history_index() + self.history_index(), + self.history.len(), ); + if self.history.is_empty() { log::debug!("History is empty"); return None; } - if self.history_index == self.history.len().saturating_sub(1) && dir == Direction::Forward - || self.history_index == 0 && dir == Direction::Reverse - { - log::debug!("No more history left to search"); - return None; - } - let history_index = match dir { - Direction::Reverse => self.history_index.saturating_sub(1), - Direction::Forward => self - .history_index - .saturating_add(1) - .min(self.history_len().saturating_sub(1)), + + let history_index = if self.history_index().is_none() { + log::debug!("History index is none"); + match dir { + Direction::Forward => return None, + Direction::Reverse => self.history_index = Some(self.history_len().saturating_sub(1)), + } + self.history_index.unwrap() + } else { + let hi = self.history_index().unwrap(); + + if hi == self.history.len().saturating_sub(1) && dir == Direction::Forward + || hi == 0 && dir == Direction::Reverse + { + return None; + } + + match dir { + Direction::Reverse => hi.saturating_sub(1), + Direction::Forward => hi.saturating_add(1).min(self.history_len().saturating_sub(1)), + } }; + log::debug!("Using history index = {} for searching", history_index); - if let Some(history_index) = self.history.starts_with(buf, history_index, dir) { + return if let Some(history_index) = self.history.starts_with(buf, history_index, dir) { log::debug!("Found index {:?}", history_index); log::debug!("Previous index {:?}", self.history_index); - self.history_index = history_index; - Some(self.history.get(history_index).unwrap().clone()) + self.history_index = Some(history_index); + self.history.get(history_index).cloned() } else if buf.is_empty() { - self.history_index = history_index; - Some(self.history.get(history_index).unwrap().clone()) + self.history_index = Some(history_index); + self.history.get(history_index).cloned() } else { log::debug!("History index = {}. Found no match.", history_index); None - } + }; } pub fn add(&mut self, buf: &str) { if self.history.add(buf) { - self.history_index = self.history.len() - 1; + self.reset(); } } - pub fn last(&mut self) { - self.history_index = self.history.len().saturating_sub(1); + pub fn reset(&mut self) { + self.history_index = None } pub fn history_len(&self) -> usize { diff --git a/src/main.rs b/src/main.rs index e091ea3..2dcb69f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,6 +24,7 @@ use std::env; use std::error::Error; use std::io::{self, Write}; use std::panic; +use std::path::{Path, PathBuf}; use std::time::Duration; use anyhow::Result; @@ -62,19 +63,22 @@ pub fn destruct_terminal() { execute!(io::stdout(), cursor::Show).unwrap(); } -fn main() { - better_panic::install(); +pub fn initialize_logging() { + let data_local_dir = if let Ok(s) = std::env::var("TASKWARRIOR_TUI_DATA") { + PathBuf::from(s) + } else { + dirs::data_local_dir() + .expect("Unable to find data directory for taskwarrior-tui") + .join("taskwarrior-tui") + }; - let data_local_dir = dirs::data_local_dir() - .expect("Unable to open data local directory.") - .join("taskwarrior-tui"); std::fs::create_dir_all(&data_local_dir).unwrap_or_else(|_| panic!("Unable to create {:?}", data_local_dir)); let logfile = FileAppender::builder() .encoder(Box::new(PatternEncoder::new(LOG_PATTERN))) .append(false) .build(data_local_dir.join("taskwarrior-tui.log")) - .unwrap(); + .expect("Failed to build log file appender."); let levelfilter = match std::env::var("TASKWARRIOR_TUI_LOG_LEVEL") .unwrap_or_else(|_| "info".to_string()) @@ -91,9 +95,15 @@ fn main() { .appender(Appender::builder().build("logfile", Box::new(logfile))) .logger(Logger::builder().build("taskwarrior_tui", levelfilter)) .build(Root::builder().appender("logfile").build(LevelFilter::Info)) - .unwrap(); + .expect("Failed to build logging config."); - log4rs::init_config(config).unwrap(); + log4rs::init_config(config).expect("Failed to initialize logging."); +} + +fn main() { + better_panic::install(); + + initialize_logging(); let matches = cli::generate_cli_app().get_matches();