From c01c9f6de15af8c6f6a96192dd3b9c4963c93eec Mon Sep 17 00:00:00 2001 From: Dheepak Krishnamurthy Date: Wed, 27 Sep 2023 12:02:23 -0400 Subject: [PATCH] WIP --- Cargo.lock | 1 + Cargo.toml | 2 +- src/app.rs | 2 +- src/components/task_report.rs | 76 ++++++++--- src/config.rs | 229 ++++++++++++++++++---------------- 5 files changed, 186 insertions(+), 124 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index db94e89f..72916fe2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1998,6 +1998,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3e785f863a3af4c800a2a669d0b64c879b538738e352607e2624d03f868dc01" dependencies = [ "crossterm", + "serde", "unicode-width", ] diff --git a/Cargo.toml b/Cargo.toml index e26c93b3..ffa06e10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,7 +49,7 @@ toml = "0.8.0" tracing = "0.1.37" tracing-error = "0.2.0" tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } -tui-input = "0.8.0" +tui-input = { version = "0.8.0", features = ["serde"] } unicode-segmentation = "1.10.1" unicode-truncate = "0.2.0" unicode-width = "0.1.10" diff --git a/src/app.rs b/src/app.rs index 4ff3b0a4..893e4e6a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -35,7 +35,7 @@ impl App { pub fn new(tick_rate: f64, frame_rate: f64, report: &str) -> Result { let app = TaskReport::new().report(report.into()); let mut config = Config::new()?; - config.taskwarrior_config()?; + config.taskwarrior.taskwarrior_config(report)?; let mode = Mode::TaskReport; Ok(Self { tick_rate, diff --git a/src/components/task_report.rs b/src/components/task_report.rs index 85b7b581..8df6eb97 100644 --- a/src/components/task_report.rs +++ b/src/components/task_report.rs @@ -8,7 +8,7 @@ use ratatui::{prelude::*, widgets::*}; use serde_derive::{Deserialize, Serialize}; use task_hookrs::{import::import, status::TaskStatus, task::Task, uda::UDAValue}; use tokio::sync::mpsc::UnboundedSender; -use tui_input::backend::crossterm::EventHandler; +use tui_input::{backend::crossterm::EventHandler, Input}; use unicode_truncate::UnicodeTruncateStr; use unicode_width::UnicodeWidthStr; use uuid::Uuid; @@ -55,26 +55,49 @@ const VIRTUAL_TAGS: [&str; 34] = [ "TEMPLATE", ]; +#[derive(Default)] +pub enum Mode { + #[default] + Report, + Filter, + Add, + Annotate, + Subprocess, + Log, + Modify, + HelpPopup, + ContextMenu, + Jump, + DeletePrompt, + UndoPrompt, + DonePrompt, + Error, +} + #[derive(Default)] pub struct TaskReport { - pub config: Config, + pub columns: Vec, pub command_tx: Option>, + pub config: Config, + pub current_context: String, + pub current_context_filter: String, + pub current_filter: String, + pub current_selection: usize, + pub current_selection_id: Option, + pub current_selection_uuid: Option, + pub date_time_vague_precise: bool, + pub description_width: usize, + pub input: Input, + pub labels: Vec, pub last_export: Option, pub report: String, - pub filter: String, - pub current_context_filter: String, - pub tasks: Vec, - pub rows: Vec>, pub row_heights: Vec, + pub rows: Vec>, pub state: TableState, - pub columns: Vec, - pub labels: Vec, - pub date_time_vague_precise: bool, + pub task_details: HashMap, + pub tasks: Vec, pub virtual_tags: Vec, - pub description_width: usize, - pub current_selection: usize, - pub current_selection_id: Option, - pub current_selection_uuid: Option, + pub mode: Mode, } impl TaskReport { @@ -432,6 +455,29 @@ impl TaskReport { } } + pub fn get_context(&mut self) -> Result<()> { + let output = std::process::Command::new("task").arg("_get").arg("rc.context").output()?; + self.current_context = String::from_utf8_lossy(&output.stdout).to_string(); + self.current_context = self.current_context.strip_suffix('\n').unwrap_or("").to_string(); + + // support new format for context + let output = std::process::Command::new("task") + .arg("_get") + .arg(format!("rc.context.{}.read", self.current_context)) + .output()?; + self.current_context_filter = String::from_utf8_lossy(&output.stdout).to_string(); + self.current_context_filter = self.current_context_filter.strip_suffix('\n').unwrap_or("").to_string(); + + // If new format is not used, check if old format is used + if self.current_context_filter.is_empty() { + let output = + std::process::Command::new("task").arg("_get").arg(format!("rc.context.{}", self.current_context)).output()?; + self.current_context_filter = String::from_utf8_lossy(&output.stdout).to_string(); + self.current_context_filter = self.current_context_filter.strip_suffix('\n').unwrap_or("").to_string(); + } + Ok(()) + } + pub fn task_export(&mut self) -> Result<()> { let mut task = std::process::Command::new("task"); @@ -443,7 +489,9 @@ impl TaskReport { .arg("rc._forcecolor=off"); // .arg("rc.verbose:override=false"); - if let Some(args) = shlex::split(format!(r#"rc.report.{}.filter='{}'"#, self.report, self.filter.trim()).trim()) { + if let Some(args) = + shlex::split(format!(r#"rc.report.{}.filter='{}'"#, self.report, self.current_filter.trim()).trim()) + { for arg in args { task.arg(arg); } diff --git a/src/config.rs b/src/config.rs index 8a22bf16..5ea7c6a4 100644 --- a/src/config.rs +++ b/src/config.rs @@ -15,18 +15,133 @@ const CONFIG: &str = include_str!("../.config/config.json5"); #[derive(Clone, Debug, Serialize, Deserialize, Default)] pub struct TaskwarriorConfig { - #[serde(default)] pub rule_precedence_color: Vec, - #[serde(default)] pub uda_priority_values: Vec, - #[serde(default)] pub weekstart: bool, - #[serde(default)] pub due: usize, - #[serde(default)] pub color: HashMap, - #[serde(default)] pub data_location: String, + pub filter: String, +} + +impl TaskwarriorConfig { + pub fn taskwarrior_config(&mut self, report: &str) -> Result<()> { + let output = std::process::Command::new("task") + .arg("rc.color=off") + .arg("rc._forcecolor=off") + .arg("rc.defaultwidth=0") + .arg("show") + .output()?; + + if !output.status.success() { + let output = std::process::Command::new("task").arg("diagnostics").output()?; + return Err(color_eyre::eyre::eyre!( + "Unable to run `task show`.\n{}\n{}\nPlease check your configuration or open a issue on github.", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + )); + } + + let data = String::from_utf8_lossy(&output.stdout); + + self.rule_precedence_color(&data); + self.uda_priority_values(&data); + self.weekstart(&data); + self.due(&data); + self.data_location(&data); + self.color(&data); + self.filter(&data, &report); + Ok(()) + } + + fn color(&mut self, data: &str) { + let mut color = HashMap::new(); + for line in data.split('\n') { + if line.starts_with("color.") { + let mut i = line.split(' '); + let attribute = i.next(); + let line = i.collect::>().join(" "); + let line = line.trim_start_matches(' '); + let style = parse_style(line); + if let Some(attr) = attribute { + color.insert(attr.to_string(), style); + }; + } + } + log::info!("{color:?}"); + self.color = color; + } + + fn filter(&mut self, data: &str, report: &str) { + let filter = if report == "all" { + "".into() + } else if let Some(s) = + // TODO: move this to configuration struct + Self::try_get_config(format!("uda.taskwarrior-tui.task-report.{}.filter", report).as_str(), data) + { + s + } else { + Self::get_config(format!("report.{}.filter", report).as_str(), data).unwrap_or_default() + }; + let filter = if filter.trim_start().trim_end().is_empty() { filter } else { format!("{} ", filter) }; + self.filter = filter; + } + + fn data_location(&mut self, data: &str) { + self.data_location = Self::get_config("data.location", data).unwrap(); + } + + fn rule_precedence_color(&mut self, data: &str) { + let data = Self::get_config("rule.precedence.color", data).unwrap(); + self.rule_precedence_color = data.split(',').map(ToString::to_string).collect::>(); + } + + fn weekstart(&mut self, data: &str) { + let data = Self::try_get_config("weekstart", data).unwrap_or_default(); + self.weekstart = data.eq_ignore_ascii_case("Monday"); + } + + fn due(&mut self, data: &str) { + self.due = Self::try_get_config("due", data).unwrap_or_default().parse::().unwrap_or(7) + } + + fn uda_priority_values(&mut self, data: &str) { + let data = Self::get_config("uda.priority.values", data).unwrap(); + self.uda_priority_values = data.split(',').map(ToString::to_string).collect::>(); + } + + fn get_config(config: &str, data: &str) -> Result { + Self::try_get_config(config, data).ok_or(color_eyre::eyre::eyre!("Unable to parse `task show {config}`")) + } + + fn try_get_config(config: &str, data: &str) -> Option { + let mut config_lines = Vec::new(); + + for line in data.split('\n') { + if config_lines.is_empty() { + if line.starts_with(config) { + config_lines.push(line.trim_start_matches(config).trim_start().trim_end().to_string()); + } else { + let config = &config.replace('-', "_"); + if line.starts_with(config) { + config_lines.push(line.trim_start_matches(config).trim_start().trim_end().to_string()); + } + } + } else { + if !line.starts_with(" ") { + return Some(config_lines.join(" ")); + } + + config_lines.push(line.trim_start().trim_end().to_string()); + } + } + + if !config_lines.is_empty() { + return Some(config_lines.join(" ")); + } + + None + } } #[derive(Clone, Debug, Serialize, Deserialize, Default)] @@ -131,108 +246,6 @@ impl Config { Ok(cfg) } - - pub fn taskwarrior_config(&mut self) -> Result<()> { - let output = std::process::Command::new("task") - .arg("rc.color=off") - .arg("rc._forcecolor=off") - .arg("rc.defaultwidth=0") - .arg("show") - .output()?; - - if !output.status.success() { - let output = std::process::Command::new("task").arg("diagnostics").output()?; - return Err(color_eyre::eyre::eyre!( - "Unable to run `task show`.\n{}\n{}\nPlease check your configuration or open a issue on github.", - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )); - } - - let data = String::from_utf8_lossy(&output.stdout); - - self.rule_precedence_color(&data); - self.uda_priority_values(&data); - self.weekstart(&data); - self.due(&data); - self.data_location(&data); - self.color(&data); - Ok(()) - } - - fn color(&mut self, data: &str) { - let mut color = HashMap::new(); - for line in data.split('\n') { - if line.starts_with("color.") { - let mut i = line.split(' '); - let attribute = i.next(); - let line = i.collect::>().join(" "); - let line = line.trim_start_matches(' '); - let style = parse_style(line); - if let Some(attr) = attribute { - color.insert(attr.to_string(), style); - }; - } - } - log::info!("{color:?}"); - self.taskwarrior.color = color; - } - - fn data_location(&mut self, data: &str) { - self.taskwarrior.data_location = get_config("data.location", data).unwrap(); - } - - fn rule_precedence_color(&mut self, data: &str) { - let data = get_config("rule.precedence.color", data).unwrap(); - self.taskwarrior.rule_precedence_color = data.split(',').map(ToString::to_string).collect::>(); - } - - fn weekstart(&mut self, data: &str) { - let data = try_get_config("weekstart", data).unwrap_or_default(); - self.taskwarrior.weekstart = data.eq_ignore_ascii_case("Monday"); - } - - fn due(&mut self, data: &str) { - self.taskwarrior.due = try_get_config("due", data).unwrap_or_default().parse::().unwrap_or(7) - } - - fn uda_priority_values(&mut self, data: &str) { - let data = get_config("uda.priority.values", data).unwrap(); - self.taskwarrior.uda_priority_values = data.split(',').map(ToString::to_string).collect::>(); - } -} - -fn get_config(config: &str, data: &str) -> Result { - try_get_config(config, data).ok_or(color_eyre::eyre::eyre!("Unable to parse `task show {config}`")) -} - -fn try_get_config(config: &str, data: &str) -> Option { - let mut config_lines = Vec::new(); - - for line in data.split('\n') { - if config_lines.is_empty() { - if line.starts_with(config) { - config_lines.push(line.trim_start_matches(config).trim_start().trim_end().to_string()); - } else { - let config = &config.replace('-', "_"); - if line.starts_with(config) { - config_lines.push(line.trim_start_matches(config).trim_start().trim_end().to_string()); - } - } - } else { - if !line.starts_with(" ") { - return Some(config_lines.join(" ")); - } - - config_lines.push(line.trim_start().trim_end().to_string()); - } - } - - if !config_lines.is_empty() { - return Some(config_lines.join(" ")); - } - - None } #[derive(Clone, Debug, Default, Deref, DerefMut)]