diff --git a/Cargo.lock b/Cargo.lock index 9a29df0..fd09daa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,17 +17,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "ahash" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" -dependencies = [ - "getrandom", - "once_cell", - "version_check", -] - [[package]] name = "aho-corasick" version = "1.0.5" @@ -101,15 +90,10 @@ dependencies = [ ] [[package]] -name = "async-trait" -version = "0.1.73" +name = "atomic" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.29", -] +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" [[package]] name = "autocfg" @@ -132,12 +116,6 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - [[package]] name = "bitflags" version = "1.3.2" @@ -153,15 +131,6 @@ dependencies = [ "serde", ] -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - [[package]] name = "bumpalo" version = "3.13.0" @@ -312,40 +281,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" -[[package]] -name = "config" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d379af7f68bfc21714c6c7dea883544201741d2ce8274bb12fa54f89507f52a7" -dependencies = [ - "async-trait", - "json5", - "lazy_static", - "nom", - "pathdiff", - "ron", - "rust-ini", - "serde", - "serde_json", - "toml 0.5.11", - "yaml-rust", -] - [[package]] name = "core-foundation-sys" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" -[[package]] -name = "cpufeatures" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" -dependencies = [ - "libc", -] - [[package]] name = "crossterm" version = "0.27.0" @@ -373,16 +314,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - [[package]] name = "darling" version = "0.14.4" @@ -455,16 +386,6 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - [[package]] name = "directories" version = "5.0.1" @@ -495,12 +416,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "dlv-list" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" - [[package]] name = "either" version = "1.9.0" @@ -571,6 +486,20 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "figment" +version = "0.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4547e226f4c9ab860571e070a9034192b3175580ecea38da34fcdb53a018c9a5" +dependencies = [ + "atomic", + "pear", + "serde", + "toml", + "uncased", + "version_check", +] + [[package]] name = "fnv" version = "1.0.7" @@ -675,16 +604,6 @@ dependencies = [ "byteorder", ] -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - [[package]] name = "getrandom" version = "0.2.10" @@ -702,15 +621,6 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash", -] - [[package]] name = "hashbrown" version = "0.14.0" @@ -750,7 +660,7 @@ dependencies = [ "os_info", "serde", "serde_derive", - "toml 0.7.6", + "toml", "uuid", ] @@ -796,7 +706,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" dependencies = [ "equivalent", - "hashbrown 0.14.0", + "hashbrown", ] [[package]] @@ -805,6 +715,12 @@ version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c785eefb63ebd0e33416dfcb8d6da0bf27ce752843a45632a67bf10d4d4b5c4" +[[package]] +name = "inlinable_string" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" + [[package]] name = "io-lifetimes" version = "1.0.11" @@ -840,17 +756,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "json5" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" -dependencies = [ - "pest", - "pest_derive", - "serde", -] - [[package]] name = "lazy_static" version = "1.4.0" @@ -863,12 +768,6 @@ version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" -[[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" - [[package]] name = "linux-raw-sys" version = "0.3.8" @@ -1020,16 +919,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" -[[package]] -name = "ordered-multimap" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" -dependencies = [ - "dlv-list", - "hashbrown 0.12.3", -] - [[package]] name = "os_info" version = "3.7.0" @@ -1089,56 +978,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17359afc20d7ab31fdb42bb844c8b3bb1dabd7dcf7e68428492da7f16966fcef" [[package]] -name = "pathdiff" -version = "0.2.1" +name = "pear" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" - -[[package]] -name = "pest" -version = "2.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a4d085fd991ac8d5b05a147b437791b4260b76326baf0fc60cf7c9c27ecd33" +checksum = "61a386cd715229d399604b50d1361683fe687066f42d56f54be995bc6868f71c" dependencies = [ - "memchr", - "thiserror", - "ucd-trie", + "inlinable_string", + "pear_codegen", + "yansi 1.0.0-rc.1", ] [[package]] -name = "pest_derive" -version = "2.7.3" +name = "pear_codegen" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bee7be22ce7918f641a33f08e3f43388c7656772244e2bbb2477f44cc9021a" +checksum = "da9f0f13dac8069c139e8300a6510e3f4143ecf5259c60b116a9b271b4ca0d54" dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1511785c5e98d79a05e8a6bc34b4ac2168a0e3e92161862030ad84daa223141" -dependencies = [ - "pest", - "pest_meta", "proc-macro2", + "proc-macro2-diagnostics", "quote", "syn 2.0.29", ] -[[package]] -name = "pest_meta" -version = "2.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42f0394d3123e33353ca5e1e89092e533d2cc490389f2bd6131c43c634ebc5f" -dependencies = [ - "once_cell", - "pest", - "sha2", -] - [[package]] name = "pin-project-lite" version = "0.2.12" @@ -1164,7 +1025,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" dependencies = [ "diff", - "yansi", + "yansi 0.5.1", ] [[package]] @@ -1176,6 +1037,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", + "version_check", + "yansi 1.0.0-rc.1", +] + [[package]] name = "quote" version = "1.0.33" @@ -1329,27 +1203,6 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" -[[package]] -name = "ron" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a" -dependencies = [ - "base64", - "bitflags 1.3.2", - "serde", -] - -[[package]] -name = "rust-ini" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" -dependencies = [ - "cfg-if", - "ordered-multimap", -] - [[package]] name = "rustc-demangle" version = "0.1.23" @@ -1476,17 +1329,6 @@ dependencies = [ "serde", ] -[[package]] -name = "sha2" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - [[package]] name = "sharded-slab" version = "0.1.4" @@ -1661,9 +1503,9 @@ dependencies = [ "clap", "clap_complete", "color-eyre", - "config", "crossterm", "directories", + "figment", "futures", "human-panic", "itertools", @@ -1686,6 +1528,7 @@ dependencies = [ "task-hookrs", "tokio", "tokio-util", + "toml", "tracing", "tracing-error", "tracing-subscriber", @@ -1792,15 +1635,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "toml" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" -dependencies = [ - "serde", -] - [[package]] name = "toml" version = "0.7.6" @@ -1934,16 +1768,13 @@ dependencies = [ ] [[package]] -name = "typenum" -version = "1.16.0" +name = "uncased" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" - -[[package]] -name = "ucd-trie" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +checksum = "9b9bc53168a4be7402ab86c3aad243a84dd7381d09be0eddc81280c1da95ca68" +dependencies = [ + "version_check", +] [[package]] name = "unicase" @@ -2211,17 +2042,14 @@ dependencies = [ "memchr", ] -[[package]] -name = "yaml-rust" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" -dependencies = [ - "linked-hash-map", -] - [[package]] name = "yansi" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + +[[package]] +name = "yansi" +version = "1.0.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1367295b8f788d371ce2dbc842c7b709c73ee1364d30351dd300ec2203b12377" diff --git a/Cargo.toml b/Cargo.toml index 5ef8efe..584246d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,9 +17,9 @@ cassowary = "0.3.0" chrono = "0.4.28" clap = { version = "4.4.2", features = ["std", "color", "help", "usage", "error-context", "suggestions", "derive", "cargo", "wrap_help", "unicode", "string", "unstable-styles"] } color-eyre = "0.6.2" -config = "0.13.3" crossterm = { version = "0.27.0", features = ["event-stream", "serde"] } directories = "5.0.1" +figment = { version = "0.10.10", features = ["toml", "env"] } futures = "0.3.28" human-panic = "1.2.0" itertools = "0.11.0" @@ -42,6 +42,7 @@ strip-ansi-escapes = "0.2.0" task-hookrs = "0.9.0" tokio = { version = "1.32.0", features = ["full"] } tokio-util = "0.7.8" +toml = "0.7.6" tracing = "0.1.37" tracing-error = "0.2.0" tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } diff --git a/src/action.rs b/src/action.rs index 7d8ddae..0180ab4 100644 --- a/src/action.rs +++ b/src/action.rs @@ -1,5 +1,32 @@ -#[derive(Clone, PartialEq, Eq, Debug, Copy)] +use serde_derive::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum Action { + Quit, + Refresh, + GotoBottom, + GotoTop, + GotoPageBottom, + GotoPageTop, + Down, + Up, + PageDown, + PageUp, + Delete, + Done, + ToggleStartStop, + QuickTag, + Select, + SelectAll, + Undo, + Edit, + Shell, + Help, + ToggleZoom, + Context, + Next, + Previous, + Shortcut(usize), Report, Filter, Add, diff --git a/src/app.rs b/src/app.rs index 520966d..dbfafc7 100644 --- a/src/app.rs +++ b/src/app.rs @@ -13,7 +13,7 @@ use std::{ use chrono::{DateTime, Datelike, FixedOffset, Local, NaiveDate, NaiveDateTime, TimeZone, Timelike}; use color_eyre::eyre::{anyhow, Context as AnyhowContext, Result}; use crossterm::{ - event::{DisableMouseCapture, EnableMouseCapture, KeyCode, KeyModifiers}, + event::{DisableMouseCapture, EnableMouseCapture, KeyCode, KeyEvent, KeyModifiers}, execute, style::style, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, @@ -211,7 +211,7 @@ pub struct TaskwarriorTui { } impl TaskwarriorTui { - pub async fn new(report: &str, init_event_loop: bool) -> Result { + pub async fn new(report: &str) -> Result { let output = std::process::Command::new("task") .arg("rc.color=off") .arg("rc._forcecolor=off") @@ -360,7 +360,7 @@ impl TaskwarriorTui { match event { Event::Key(keyevent) => { debug!("Received input = {:?}", keyevent); - self.handle_input(keyevent.code).await?; + self.handle_input(keyevent).await?; } Event::Mouse(mouseevent) => { debug!("tui mouseevent") @@ -2405,7 +2405,7 @@ impl TaskwarriorTui { es } - pub async fn handle_input(&mut self, input: KeyCode) -> Result<()> { + pub async fn handle_input(&mut self, input: KeyEvent) -> Result<()> { match self.mode { Mode::Tasks(_) => { self.handle_input_by_task_mode(input).await?; @@ -2462,7 +2462,7 @@ impl TaskwarriorTui { Ok(()) } - async fn handle_input_by_task_mode(&mut self, input: KeyCode) -> Result<()> { + async fn handle_input_by_task_mode(&mut self, input: KeyEvent) -> Result<()> { if let Mode::Tasks(task_mode) = &self.mode { match task_mode { Action::Report => { @@ -2923,7 +2923,7 @@ impl TaskwarriorTui { } _ => { self.command_history.reset(); - handle_movement(&mut self.modify, input, &mut self.changes); + handle_movement(&mut self.modify, input); self.update_input_for_completion(); } }, @@ -2950,7 +2950,7 @@ impl TaskwarriorTui { self.reset_command(); self.mode = Mode::Tasks(Action::Report); } - _ => handle_movement(&mut self.command, input, &mut self.changes), + _ => handle_movement(&mut self.command, input), }, Action::Log => match input { KeyCode::Esc => { @@ -3056,7 +3056,7 @@ impl TaskwarriorTui { } _ => { self.command_history.reset(); - handle_movement(&mut self.command, input, &mut self.changes); + handle_movement(&mut self.command, input); self.update_input_for_completion(); } }, @@ -3164,7 +3164,7 @@ impl TaskwarriorTui { _ => { self.command_history.reset(); - handle_movement(&mut self.command, input, &mut self.changes); + handle_movement(&mut self.command, input); self.update_input_for_completion(); } }, @@ -3192,7 +3192,7 @@ impl TaskwarriorTui { self.reset_command(); self.mode = Mode::Tasks(Action::Report); } - _ => handle_movement(&mut self.command, input, &mut self.changes), + _ => handle_movement(&mut self.command, input), }, Action::Add => match input { KeyCode::Esc => { @@ -3298,7 +3298,7 @@ impl TaskwarriorTui { } _ => { self.command_history.reset(); - handle_movement(&mut self.command, input, &mut self.changes); + handle_movement(&mut self.command, input); self.update_input_for_completion(); } }, @@ -3412,7 +3412,7 @@ impl TaskwarriorTui { // self.dirty = true; // } _ => { - handle_movement(&mut self.filter, input, &mut self.changes); + handle_movement(&mut self.filter, input); self.update_input_for_completion(); self.dirty = true; } @@ -3437,7 +3437,7 @@ impl TaskwarriorTui { } else if input == self.keyconfig.quit || input == KeyCode::Esc { self.mode = Mode::Tasks(Action::Report); } else { - handle_movement(&mut self.command, input, &mut self.changes); + handle_movement(&mut self.command, input); } } Action::DeletePrompt => { @@ -3460,7 +3460,7 @@ impl TaskwarriorTui { } else if input == self.keyconfig.quit || input == KeyCode::Esc { self.mode = Mode::Tasks(Action::Report); } else { - handle_movement(&mut self.command, input, &mut self.changes); + handle_movement(&mut self.command, input); } } Action::UndoPrompt => { @@ -3483,7 +3483,7 @@ impl TaskwarriorTui { } else if input == self.keyconfig.quit || input == KeyCode::Esc { self.mode = Mode::Tasks(Action::Report); } else { - handle_movement(&mut self.command, input, &mut self.changes); + handle_movement(&mut self.command, input); } } Action::Error => { @@ -3639,11 +3639,8 @@ impl TaskwarriorTui { } } -pub fn handle_movement(linebuffer: &mut Input, input: KeyCode, changes: &mut utils::Changeset) { - linebuffer.handle_event(&crossterm::event::Event::Key(crossterm::event::KeyEvent::new( - input, - KeyModifiers::empty(), - ))); +pub fn handle_movement(linebuffer: &mut Input, input: KeyEvent) { + linebuffer.handle_event(&crossterm::event::Event::Key(input)); } pub fn add_tag(task: &mut Task, tag: String) { diff --git a/src/config.rs b/src/config.rs index 26811d1..85bf8c7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,52 +1,246 @@ -use std::{collections::HashMap, error::Error, str}; - -use color_eyre::eyre::{eyre, Context, Result}; +use figment::{ + providers::{Env, Format, Serialized, Toml}, + Figment, +}; use ratatui::{ style::{Color, Modifier, Style}, - symbols::{bar::FULL, line::DOUBLE_VERTICAL}, + symbols::line::DOUBLE_VERTICAL, }; +use std::{collections::HashMap, error::Error, path::PathBuf, str}; -trait TaskWarriorBool { - fn get_bool(&self) -> Option; -} +use color_eyre::eyre::{eyre, Context, Result}; -impl TaskWarriorBool for String { - fn get_bool(&self) -> Option { - if self == "true" || self == "1" || self == "y" || self == "yes" || self == "on" { - Some(true) - } else if self == "false" || self == "0" || self == "n" || self == "no" || self == "off" { - Some(false) - } else { - None +use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; +use serde::de::{self, Deserialize, Deserializer, MapAccess, Visitor}; +use serde::ser::{self, Serialize, Serializer}; +use serde_derive::{Deserialize, Serialize}; + +use crate::{action::Action, keyevent::parse_key_sequence, keymap::KeyMap, utils::get_config_dir}; + +#[derive(Default, Clone, Debug)] +pub struct SerdeStyle(Style); + +impl<'de> Deserialize<'de> for SerdeStyle { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct StyleVisitor; + + impl<'de> Visitor<'de> for StyleVisitor { + type Value = SerdeStyle; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a string representation of tui::style::Style") + } + + fn visit_str(self, v: &str) -> Result { + Ok(SerdeStyle(get_tcolor(v))) + } } + + deserializer.deserialize_str(StyleVisitor) } } -impl TaskWarriorBool for str { - fn get_bool(&self) -> Option { - if self == "true" || self == "1" || self == "y" || self == "yes" || self == "on" { - Some(true) - } else if self == "false" || self == "0" || self == "n" || self == "no" || self == "off" { - Some(false) - } else { - None - } +pub fn get_tcolor(line: &str) -> Style { + let (foreground, background) = line.split_at(line.to_lowercase().find("on ").unwrap_or(line.len())); + let (mut foreground, mut background) = (String::from(foreground), String::from(background)); + background = background.replace("on ", ""); + let mut modifiers = Modifier::empty(); + if foreground.contains("bright") { + foreground = foreground.replace("bright ", ""); + background = background.replace("bright ", ""); + background.insert_str(0, "bright "); + } + foreground = foreground.replace("grey", "gray"); + background = background.replace("grey", "gray"); + if foreground.contains("underline") { + modifiers |= Modifier::UNDERLINED; + } + let foreground = foreground.replace("underline ", ""); + // TODO: use bold, bright boolean flags + if foreground.contains("bold") { + modifiers |= Modifier::BOLD; + } + // let foreground = foreground.replace("bold ", ""); + if foreground.contains("inverse") { + modifiers |= Modifier::REVERSED; + } + let foreground = foreground.replace("inverse ", ""); + let mut style = Style::default(); + if let Some(fg) = get_color_foreground(foreground.as_str()) { + style = style.fg(fg); + } + if let Some(bg) = get_color_background(background.as_str()) { + style = style.bg(bg); + } + style = style.add_modifier(modifiers); + style +} + +fn get_color_foreground(s: &str) -> Option { + let s = s.trim_start(); + let s = s.trim_end(); + if s.contains("color") { + let c = s.trim_start_matches("color").parse::().unwrap_or_default(); + Some(Color::Indexed(c)) + } else if s.contains("gray") { + let c = 232 + s.trim_start_matches("gray").parse::().unwrap_or_default(); + Some(Color::Indexed(c)) + } else if s.contains("rgb") { + let red = (s.as_bytes()[3] as char).to_digit(10).unwrap_or_default() as u8; + let green = (s.as_bytes()[4] as char).to_digit(10).unwrap_or_default() as u8; + let blue = (s.as_bytes()[5] as char).to_digit(10).unwrap_or_default() as u8; + let c = 16 + red * 36 + green * 6 + blue; + Some(Color::Indexed(c)) + } else if s == "bold black" { + Some(Color::Indexed(8)) + } else if s == "bold red" { + Some(Color::Indexed(9)) + } else if s == "bold green" { + Some(Color::Indexed(10)) + } else if s == "bold yellow" { + Some(Color::Indexed(11)) + } else if s == "bold blue" { + Some(Color::Indexed(12)) + } else if s == "bold magenta" { + Some(Color::Indexed(13)) + } else if s == "bold cyan" { + Some(Color::Indexed(14)) + } else if s == "bold white" { + Some(Color::Indexed(15)) + } else if s == "black" { + Some(Color::Indexed(0)) + } else if s == "red" { + Some(Color::Indexed(1)) + } else if s == "green" { + Some(Color::Indexed(2)) + } else if s == "yellow" { + Some(Color::Indexed(3)) + } else if s == "blue" { + Some(Color::Indexed(4)) + } else if s == "magenta" { + Some(Color::Indexed(5)) + } else if s == "cyan" { + Some(Color::Indexed(6)) + } else if s == "white" { + Some(Color::Indexed(7)) + } else { + None } } -#[derive(Debug)] -pub struct Uda { - label: String, - kind: String, - values: Option>, - default: Option, - urgency: Option, +fn get_color_background(s: &str) -> Option { + let s = s.trim_start(); + let s = s.trim_end(); + if s.contains("bright color") { + let s = s.trim_start_matches("bright "); + let c = s.trim_start_matches("color").parse::().unwrap_or_default(); + Some(Color::Indexed(c.wrapping_shl(8))) + } else if s.contains("color") { + let c = s.trim_start_matches("color").parse::().unwrap_or_default(); + Some(Color::Indexed(c)) + } else if s.contains("gray") { + let s = s.trim_start_matches("bright "); + let c = 232 + s.trim_start_matches("gray").parse::().unwrap_or_default(); + Some(Color::Indexed(c.wrapping_shl(8))) + } else if s.contains("rgb") { + let s = s.trim_start_matches("bright "); + let red = (s.as_bytes()[3] as char).to_digit(10).unwrap_or_default() as u8; + let green = (s.as_bytes()[4] as char).to_digit(10).unwrap_or_default() as u8; + let blue = (s.as_bytes()[5] as char).to_digit(10).unwrap_or_default() as u8; + let c = 16 + red * 36 + green * 6 + blue; + Some(Color::Indexed(c.wrapping_shl(8))) + } else if s == "bright black" { + Some(Color::Indexed(8)) + } else if s == "bright red" { + Some(Color::Indexed(9)) + } else if s == "bright green" { + Some(Color::Indexed(10)) + } else if s == "bright yellow" { + Some(Color::Indexed(11)) + } else if s == "bright blue" { + Some(Color::Indexed(12)) + } else if s == "bright magenta" { + Some(Color::Indexed(13)) + } else if s == "bright cyan" { + Some(Color::Indexed(14)) + } else if s == "bright white" { + Some(Color::Indexed(15)) + } else if s == "black" { + Some(Color::Indexed(0)) + } else if s == "red" { + Some(Color::Indexed(1)) + } else if s == "green" { + Some(Color::Indexed(2)) + } else if s == "yellow" { + Some(Color::Indexed(3)) + } else if s == "blue" { + Some(Color::Indexed(4)) + } else if s == "magenta" { + Some(Color::Indexed(5)) + } else if s == "cyan" { + Some(Color::Indexed(6)) + } else if s == "white" { + Some(Color::Indexed(7)) + } else { + None + } } -#[derive(Debug)] +impl Serialize for SerdeStyle { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + // Getting the foreground color string + let fg_str = color_to_string(self.0.fg.unwrap()); + + // Getting the background color string + let mut bg_str = color_to_string(self.0.bg.unwrap()); + + // If the background is not default, prepend with "on " + if bg_str != "" { + bg_str.insert_str(0, "on "); + } + + // Building the modifier string + let mut mod_str = String::new(); + let mod_val = self.0.add_modifier; + if mod_val.contains(Modifier::BOLD) { + mod_str.push_str("bold "); + } + if mod_val.contains(Modifier::UNDERLINED) { + mod_str.push_str("underline "); + } + if mod_val.contains(Modifier::REVERSED) { + mod_str.push_str("inverse "); + } + + // Constructing the final style string + let style_str = format!("{}{} {}", mod_str, fg_str, bg_str).trim().to_string(); + + serializer.serialize_str(&style_str) + } +} + +fn color_to_string(color: Color) -> String { + match color { + Color::Black => "black".to_string(), + Color::Red => "red".to_string(), + Color::Green => "green".to_string(), + // ... handle all other colors ... + _ => "".to_string(), // Default case, adjust as needed + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct Config { + pub tick_rate: usize, + pub keymap: HashMap, pub enabled: bool, - pub color: HashMap, + pub color: HashMap, pub filter: String, pub data_location: String, pub obfuscate: bool, @@ -71,22 +265,22 @@ pub struct Config { pub uda_unmark_indicator: String, pub uda_scrollbar_indicator: String, pub uda_scrollbar_area: String, - pub uda_style_report_scrollbar: Style, - pub uda_style_report_scrollbar_area: Style, + pub uda_style_report_scrollbar: SerdeStyle, + pub uda_style_report_scrollbar_area: SerdeStyle, pub uda_selection_bold: bool, pub uda_selection_italic: bool, pub uda_selection_dim: bool, pub uda_selection_blink: bool, pub uda_selection_reverse: bool, pub uda_calendar_months_per_row: usize, - pub uda_style_context_active: Style, - pub uda_style_report_selection: Style, - pub uda_style_calendar_title: Style, - pub uda_style_calendar_today: Style, - pub uda_style_navbar: Style, - pub uda_style_command: Style, - pub uda_style_report_completion_pane: Style, - pub uda_style_report_completion_pane_highlight: Style, + pub uda_style_context_active: SerdeStyle, + pub uda_style_report_selection: SerdeStyle, + pub uda_style_calendar_title: SerdeStyle, + pub uda_style_calendar_today: SerdeStyle, + pub uda_style_navbar: SerdeStyle, + pub uda_style_command: SerdeStyle, + pub uda_style_report_completion_pane: SerdeStyle, + pub uda_style_report_completion_pane_highlight: SerdeStyle, pub uda_shortcuts: Vec, pub uda_change_focus_rotate: bool, pub uda_background_process: String, @@ -97,84 +291,112 @@ pub struct Config { pub uda_task_report_prompt_on_done: bool, pub uda_task_report_date_time_vague_more_precise: bool, pub uda_context_menu_select_on_move: bool, - pub uda: Vec, } -impl Config { - pub fn new(data: &str, report: &str) -> Result { - let bool_collection = Self::get_bool_collection(); +impl Default for Config { + fn default() -> Self { + let tick_rate = 250; + + let mut task_report_keymap: KeyMap = Default::default(); + task_report_keymap.insert(parse_key_sequence("q").unwrap(), Action::Quit); + task_report_keymap.insert(parse_key_sequence("r").unwrap(), Action::Refresh); + task_report_keymap.insert(parse_key_sequence("G").unwrap(), Action::GotoBottom); + task_report_keymap.insert(parse_key_sequence("").unwrap(), Action::GotoTop); + task_report_keymap.insert(parse_key_sequence("").unwrap(), Action::GotoPageBottom); + task_report_keymap.insert(parse_key_sequence("").unwrap(), Action::GotoPageTop); + task_report_keymap.insert(parse_key_sequence("").unwrap(), Action::GotoBottom); + task_report_keymap.insert(parse_key_sequence("j").unwrap(), Action::Down); + task_report_keymap.insert(parse_key_sequence("k").unwrap(), Action::Up); + task_report_keymap.insert(parse_key_sequence("J").unwrap(), Action::PageDown); + task_report_keymap.insert(parse_key_sequence("K").unwrap(), Action::PageUp); + task_report_keymap.insert(parse_key_sequence("").unwrap(), Action::Delete); + task_report_keymap.insert(parse_key_sequence("").unwrap(), Action::Done); + task_report_keymap.insert(parse_key_sequence("s").unwrap(), Action::ToggleStartStop); + task_report_keymap.insert(parse_key_sequence("t").unwrap(), Action::QuickTag); + task_report_keymap.insert(parse_key_sequence("v").unwrap(), Action::Select); + task_report_keymap.insert(parse_key_sequence("V").unwrap(), Action::SelectAll); + task_report_keymap.insert(parse_key_sequence("u").unwrap(), Action::Undo); + task_report_keymap.insert(parse_key_sequence("e").unwrap(), Action::Edit); + task_report_keymap.insert(parse_key_sequence("m").unwrap(), Action::Modify); + task_report_keymap.insert(parse_key_sequence("!").unwrap(), Action::Shell); + task_report_keymap.insert(parse_key_sequence("l").unwrap(), Action::Log); + task_report_keymap.insert(parse_key_sequence("a").unwrap(), Action::Add); + task_report_keymap.insert(parse_key_sequence("A").unwrap(), Action::Annotate); + task_report_keymap.insert(parse_key_sequence("?").unwrap(), Action::Help); + task_report_keymap.insert(parse_key_sequence("/").unwrap(), Action::Filter); + task_report_keymap.insert(parse_key_sequence("z").unwrap(), Action::ToggleZoom); + task_report_keymap.insert(parse_key_sequence("c").unwrap(), Action::Context); + task_report_keymap.insert(parse_key_sequence("]").unwrap(), Action::Next); + task_report_keymap.insert(parse_key_sequence("[").unwrap(), Action::Previous); + task_report_keymap.insert(parse_key_sequence("1").unwrap(), Action::Shortcut(1)); + task_report_keymap.insert(parse_key_sequence("2").unwrap(), Action::Shortcut(2)); + task_report_keymap.insert(parse_key_sequence("3").unwrap(), Action::Shortcut(3)); + task_report_keymap.insert(parse_key_sequence("4").unwrap(), Action::Shortcut(4)); + task_report_keymap.insert(parse_key_sequence("5").unwrap(), Action::Shortcut(5)); + task_report_keymap.insert(parse_key_sequence("6").unwrap(), Action::Shortcut(6)); + task_report_keymap.insert(parse_key_sequence("7").unwrap(), Action::Shortcut(7)); + task_report_keymap.insert(parse_key_sequence("8").unwrap(), Action::Shortcut(8)); + task_report_keymap.insert(parse_key_sequence("9").unwrap(), Action::Shortcut(9)); + + let mut keymap: HashMap = Default::default(); + keymap.insert("task-report".into(), task_report_keymap); let enabled = true; - let obfuscate = bool_collection.get("obfuscate").copied().unwrap_or(false); - let print_empty_columns = bool_collection.get("print_empty_columns").copied().unwrap_or(false); + let color = Default::default(); + let filter = Default::default(); + let data_location = Default::default(); + let obfuscate = false; + let print_empty_columns = false; + let due = 7; // due 7 days + let weekstart = true; // starts on monday + let rule_precedence_color = Default::default(); + let uda_priority_values = Default::default(); + let uda_tick_rate = 250; + let uda_auto_insert_double_quotes_on_add = true; + let uda_auto_insert_double_quotes_on_annotate = true; + let uda_auto_insert_double_quotes_on_log = true; + let uda_prefill_task_metadata = Default::default(); + let uda_reset_filter_on_esc = true; + let uda_task_detail_prefetch = 10; + let uda_task_report_use_all_tasks_for_completion = Default::default(); + let uda_task_report_show_info = true; + let uda_task_report_looping = true; + let uda_task_report_jump_to_task_on_add = true; + let uda_selection_indicator = "\u{2022} ".to_string(); + let uda_mark_indicator = "\u{2714} ".to_string(); + let uda_unmark_indicator = " ".to_string(); + let uda_scrollbar_indicator = "█".to_string(); + let uda_scrollbar_area = "║".to_string(); + let uda_style_report_scrollbar = Default::default(); + let uda_style_report_scrollbar_area = Default::default(); + let uda_selection_bold = true; + let uda_selection_italic = Default::default(); + let uda_selection_dim = Default::default(); + let uda_selection_blink = Default::default(); + let uda_selection_reverse = Default::default(); + let uda_calendar_months_per_row = 4; + let uda_style_context_active = Default::default(); + let uda_style_report_selection = Default::default(); + let uda_style_calendar_title = Default::default(); + let uda_style_calendar_today = Default::default(); + let uda_style_navbar = Default::default(); + let uda_style_command = Default::default(); + let uda_style_report_completion_pane = Default::default(); + let uda_style_report_completion_pane_highlight = Default::default(); + let uda_shortcuts = Default::default(); + let uda_change_focus_rotate = Default::default(); + let uda_background_process = Default::default(); + let uda_background_process_period = Default::default(); + let uda_quick_tag_name = Default::default(); + let uda_task_report_prompt_on_undo = Default::default(); + let uda_task_report_prompt_on_delete = Default::default(); + let uda_task_report_prompt_on_done = Default::default(); + let uda_task_report_date_time_vague_more_precise = Default::default(); + let uda_context_menu_select_on_move = Default::default(); - let color = Self::get_color_collection(data); - let filter = Self::get_filter(data, report)?; - let filter = if filter.trim_start().trim_end().is_empty() { - filter - } else { - format!("{} ", filter) - }; - let data_location = Self::get_data_location(data); - let due = Self::get_due(data); - let weekstart = Self::get_weekstart(data); - let rule_precedence_color = Self::get_rule_precedence_color(data); - let uda_priority_values = Self::get_uda_priority_values(data); - let uda_tick_rate = Self::get_uda_tick_rate(data); - let uda_change_focus_rotate = Self::get_uda_change_focus_rotate(data); - let uda_auto_insert_double_quotes_on_add = Self::get_uda_auto_insert_double_quotes_on_add(data); - let uda_auto_insert_double_quotes_on_annotate = Self::get_uda_auto_insert_double_quotes_on_annotate(data); - let uda_auto_insert_double_quotes_on_log = Self::get_uda_auto_insert_double_quotes_on_log(data); - let uda_prefill_task_metadata = Self::get_uda_prefill_task_metadata(data); - let uda_reset_filter_on_esc = Self::get_uda_reset_filter_on_esc(data); - let uda_task_detail_prefetch = Self::get_uda_task_detail_prefetch(data); - let uda_task_report_use_all_tasks_for_completion = Self::get_uda_task_report_use_all_tasks_for_completion(data); - let uda_task_report_show_info = Self::get_uda_task_report_show_info(data); - let uda_task_report_looping = Self::get_uda_task_report_looping(data); - let uda_task_report_jump_to_task_on_add = Self::get_uda_task_report_jump_to_task_on_add(data); - let uda_selection_indicator = Self::get_uda_selection_indicator(data); - let uda_mark_indicator = Self::get_uda_mark_indicator(data); - let uda_unmark_indicator = Self::get_uda_unmark_indicator(data); - let uda_scrollbar_indicator = Self::get_uda_scrollbar_indicator(data); - let uda_scrollbar_area = Self::get_uda_scrollbar_area(data); - let uda_selection_bold = Self::get_uda_selection_bold(data); - let uda_selection_italic = Self::get_uda_selection_italic(data); - let uda_selection_dim = Self::get_uda_selection_dim(data); - let uda_selection_blink = Self::get_uda_selection_blink(data); - let uda_selection_reverse = Self::get_uda_selection_reverse(data); - let uda_calendar_months_per_row = Self::get_uda_months_per_row(data); - let uda_style_report_selection = Self::get_uda_style("report.selection", data); - let uda_style_report_scrollbar = Self::get_uda_style("report.scrollbar", data); - let uda_style_report_scrollbar_area = Self::get_uda_style("report.scrollbar.area", data); - let uda_style_calendar_title = Self::get_uda_style("calendar.title", data); - let uda_style_calendar_today = Self::get_uda_style("calendar.today", data); - let uda_style_navbar = Self::get_uda_style("navbar", data); - let uda_style_command = Self::get_uda_style("command", data); - let uda_style_context_active = Self::get_uda_style("context.active", data); - let uda_style_report_completion_pane = Self::get_uda_style("report.completion-pane", data); - let uda_style_report_completion_pane_highlight = Self::get_uda_style("report.completion-pane-highlight", data); - let uda_shortcuts = Self::get_uda_shortcuts(data); - let uda_background_process = Self::get_uda_background_process(data); - let uda_background_process_period = Self::get_uda_background_process_period(data); - let uda_style_report_selection = uda_style_report_selection.unwrap_or_default(); - let uda_style_report_scrollbar = uda_style_report_scrollbar.unwrap_or_else(|| Style::default().fg(Color::Black)); - let uda_style_report_scrollbar_area = uda_style_report_scrollbar_area.unwrap_or_default(); - let uda_style_calendar_title = uda_style_calendar_title.unwrap_or_default(); - let uda_style_calendar_today = uda_style_calendar_today.unwrap_or_else(|| Style::default().add_modifier(Modifier::BOLD)); - let uda_style_navbar = uda_style_navbar.unwrap_or_else(|| Style::default().add_modifier(Modifier::REVERSED)); - let uda_style_command = uda_style_command.unwrap_or_else(|| Style::default().add_modifier(Modifier::REVERSED)); - let uda_style_context_active = uda_style_context_active.unwrap_or_default(); - let uda_style_report_completion_pane = - uda_style_report_completion_pane.unwrap_or_else(|| Style::default().fg(Color::Black).bg(Color::Rgb(223, 223, 223))); - let uda_style_report_completion_pane_highlight = uda_style_report_completion_pane_highlight.unwrap_or(uda_style_report_completion_pane); - let uda_quick_tag_name = Self::get_uda_quick_tag_name(data); - let uda_task_report_prompt_on_undo = Self::get_uda_task_report_prompt_on_undo(data); - let uda_task_report_prompt_on_delete = Self::get_uda_task_report_prompt_on_delete(data); - let uda_task_report_prompt_on_done = Self::get_uda_task_report_prompt_on_done(data); - let uda_context_menu_select_on_move = Self::get_uda_context_menu_select_on_move(data); - let uda_task_report_date_time_vague_more_precise = Self::get_uda_task_report_date_time_vague_more_precise(data); - - Ok(Self { + Self { + tick_rate, + keymap, enabled, color, filter, @@ -186,7 +408,6 @@ impl Config { rule_precedence_color, uda_priority_values, uda_tick_rate, - uda_change_focus_rotate, uda_auto_insert_double_quotes_on_add, uda_auto_insert_double_quotes_on_annotate, uda_auto_insert_double_quotes_on_log, @@ -202,23 +423,24 @@ impl Config { uda_unmark_indicator, uda_scrollbar_indicator, uda_scrollbar_area, + uda_style_report_scrollbar, + uda_style_report_scrollbar_area, uda_selection_bold, uda_selection_italic, uda_selection_dim, uda_selection_blink, uda_selection_reverse, uda_calendar_months_per_row, - uda_style_report_selection, uda_style_context_active, + uda_style_report_selection, uda_style_calendar_title, uda_style_calendar_today, uda_style_navbar, uda_style_command, uda_style_report_completion_pane, uda_style_report_completion_pane_highlight, - uda_style_report_scrollbar, - uda_style_report_scrollbar_area, uda_shortcuts, + uda_change_focus_rotate, uda_background_process, uda_background_process_period, uda_quick_tag_name, @@ -227,498 +449,24 @@ impl Config { uda_task_report_prompt_on_done, uda_task_report_date_time_vague_more_precise, uda_context_menu_select_on_move, - uda: vec![], - }) - } - - fn get_bool_collection() -> HashMap { - HashMap::new() - } - - fn get_uda_background_process(data: &str) -> String { - Self::get_config("uda.taskwarrior-tui.background_process", data).unwrap_or_default() - } - - fn get_uda_background_process_period(data: &str) -> usize { - Self::get_config("uda.taskwarrior-tui.background_process_period", data) - .unwrap_or_default() - .parse::() - .unwrap_or(60) - } - - fn get_uda_shortcuts(data: &str) -> Vec { - let mut v = vec![]; - for s in 0..=9 { - let c = format!("uda.taskwarrior-tui.shortcuts.{}", s); - let s = Self::get_config(&c, data).unwrap_or_default(); - v.push(s); - } - v - } - - fn get_uda_style(config: &str, data: &str) -> Option