diff --git a/Cargo.lock b/Cargo.lock index 12df265..c9f51d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -118,7 +118,7 @@ checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "c3" -version = "1.2.1" +version = "1.3.0" dependencies = [ "chrono", "clap", diff --git a/Cargo.toml b/Cargo.toml index b6a62c4..1f58636 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "c3" -version = "1.2.1" +version = "1.3.0" edition = "2021" [dependencies] diff --git a/patches/clipboard-patch.diff b/patches/clipboard-patch.diff index 7f2f1c7..0dfbf61 100644 --- a/patches/clipboard-patch.diff +++ b/patches/clipboard-patch.diff @@ -1,48 +1,594 @@ +diff --git a/Cargo.lock b/Cargo.lock +index 12df265..c369f7b 100644 +--- a/Cargo.lock ++++ b/Cargo.lock +@@ -2,6 +2,12 @@ + # It is not intended for manual editing. + version = 3 + ++[[package]] ++name = "adler" ++version = "1.0.2" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" ++ + [[package]] + name = "ahash" + version = "0.8.7" +@@ -83,6 +89,24 @@ dependencies = [ + "windows-sys 0.52.0", + ] + ++[[package]] ++name = "arboard" ++version = "3.4.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "9fb4009533e8ff8f1450a5bcbc30f4242a1d34442221f72314bea1f5dc9c7f89" ++dependencies = [ ++ "clipboard-win", ++ "core-graphics", ++ "image", ++ "log", ++ "objc2", ++ "objc2-app-kit", ++ "objc2-foundation", ++ "parking_lot", ++ "windows-sys 0.48.0", ++ "x11rb", ++] ++ + [[package]] + name = "autocfg" + version = "1.1.0" +@@ -97,9 +121,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + + [[package]] + name = "bitflags" +-version = "2.4.1" ++version = "2.6.0" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" ++checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + + [[package]] + name = "block-buffer" +@@ -110,16 +134,38 @@ dependencies = [ + "generic-array", + ] + ++[[package]] ++name = "block2" ++version = "0.5.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" ++dependencies = [ ++ "objc2", ++] ++ + [[package]] + name = "bumpalo" + version = "3.14.0" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + ++[[package]] ++name = "bytemuck" ++version = "1.16.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" ++ ++[[package]] ++name = "byteorder-lite" ++version = "0.1.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" ++ + [[package]] + name = "c3" + version = "1.2.1" + dependencies = [ ++ "arboard", + "chrono", + "clap", + "clap_complete", +@@ -214,18 +260,61 @@ version = "0.7.1" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" + ++[[package]] ++name = "clipboard-win" ++version = "5.4.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" ++dependencies = [ ++ "error-code", ++] ++ + [[package]] + name = "colorchoice" + version = "1.0.0" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + ++[[package]] ++name = "core-foundation" ++version = "0.9.4" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" ++dependencies = [ ++ "core-foundation-sys", ++ "libc", ++] ++ + [[package]] + name = "core-foundation-sys" + version = "0.8.6" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + ++[[package]] ++name = "core-graphics" ++version = "0.23.2" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" ++dependencies = [ ++ "bitflags 1.3.2", ++ "core-foundation", ++ "core-graphics-types", ++ "foreign-types", ++ "libc", ++] ++ ++[[package]] ++name = "core-graphics-types" ++version = "0.1.3" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" ++dependencies = [ ++ "bitflags 1.3.2", ++ "core-foundation", ++ "libc", ++] ++ + [[package]] + name = "cpufeatures" + version = "0.2.11" +@@ -235,13 +324,22 @@ dependencies = [ + "libc", + ] + ++[[package]] ++name = "crc32fast" ++version = "1.4.2" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" ++dependencies = [ ++ "cfg-if", ++] ++ + [[package]] + name = "crossterm" + version = "0.27.0" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" + dependencies = [ +- "bitflags 2.4.1", ++ "bitflags 2.6.0", + "crossterm_winapi", + "libc", + "mio", +@@ -286,6 +384,68 @@ version = "1.9.0" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + ++[[package]] ++name = "errno" ++version = "0.3.9" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" ++dependencies = [ ++ "libc", ++ "windows-sys 0.52.0", ++] ++ ++[[package]] ++name = "error-code" ++version = "3.2.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "a0474425d51df81997e2f90a21591180b38eccf27292d755f3e30750225c175b" ++ ++[[package]] ++name = "fdeflate" ++version = "0.3.4" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" ++dependencies = [ ++ "simd-adler32", ++] ++ ++[[package]] ++name = "flate2" ++version = "1.0.30" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" ++dependencies = [ ++ "crc32fast", ++ "miniz_oxide", ++] ++ ++[[package]] ++name = "foreign-types" ++version = "0.5.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" ++dependencies = [ ++ "foreign-types-macros", ++ "foreign-types-shared", ++] ++ ++[[package]] ++name = "foreign-types-macros" ++version = "0.2.3" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" ++dependencies = [ ++ "proc-macro2", ++ "quote", ++ "syn 2.0.43", ++] ++ ++[[package]] ++name = "foreign-types-shared" ++version = "0.3.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" ++ + [[package]] + name = "generic-array" + version = "0.14.7" +@@ -296,6 +456,16 @@ dependencies = [ + "version_check", + ] + ++[[package]] ++name = "gethostname" ++version = "0.4.3" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" ++dependencies = [ ++ "libc", ++ "windows-targets 0.48.5", ++] ++ + [[package]] + name = "hashbrown" + version = "0.14.3" +@@ -350,6 +520,19 @@ dependencies = [ + "cc", + ] + ++[[package]] ++name = "image" ++version = "0.25.2" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10" ++dependencies = [ ++ "bytemuck", ++ "byteorder-lite", ++ "num-traits", ++ "png", ++ "tiff", ++] ++ + [[package]] + name = "indoc" + version = "2.0.4" +@@ -365,6 +548,12 @@ dependencies = [ + "either", + ] + ++[[package]] ++name = "jpeg-decoder" ++version = "0.3.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" ++ + [[package]] + name = "js-sys" + version = "0.3.67" +@@ -380,6 +569,12 @@ version = "0.2.151" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" + ++[[package]] ++name = "linux-raw-sys" ++version = "0.4.14" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" ++ + [[package]] + name = "lock_api" + version = "0.4.11" +@@ -405,6 +600,16 @@ dependencies = [ + "hashbrown", + ] + ++[[package]] ++name = "miniz_oxide" ++version = "0.7.4" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" ++dependencies = [ ++ "adler", ++ "simd-adler32", ++] ++ + [[package]] + name = "mio" + version = "0.8.10" +@@ -426,6 +631,105 @@ dependencies = [ + "autocfg", + ] + ++[[package]] ++name = "objc-sys" ++version = "0.3.5" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" ++ ++[[package]] ++name = "objc2" ++version = "0.5.2" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" ++dependencies = [ ++ "objc-sys", ++ "objc2-encode", ++] ++ ++[[package]] ++name = "objc2-app-kit" ++version = "0.2.2" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" ++dependencies = [ ++ "bitflags 2.6.0", ++ "block2", ++ "libc", ++ "objc2", ++ "objc2-core-data", ++ "objc2-core-image", ++ "objc2-foundation", ++ "objc2-quartz-core", ++] ++ ++[[package]] ++name = "objc2-core-data" ++version = "0.2.2" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" ++dependencies = [ ++ "bitflags 2.6.0", ++ "block2", ++ "objc2", ++ "objc2-foundation", ++] ++ ++[[package]] ++name = "objc2-core-image" ++version = "0.2.2" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" ++dependencies = [ ++ "block2", ++ "objc2", ++ "objc2-foundation", ++ "objc2-metal", ++] ++ ++[[package]] ++name = "objc2-encode" ++version = "4.0.3" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "7891e71393cd1f227313c9379a26a584ff3d7e6e7159e988851f0934c993f0f8" ++ ++[[package]] ++name = "objc2-foundation" ++version = "0.2.2" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" ++dependencies = [ ++ "bitflags 2.6.0", ++ "block2", ++ "libc", ++ "objc2", ++] ++ ++[[package]] ++name = "objc2-metal" ++version = "0.2.2" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" ++dependencies = [ ++ "bitflags 2.6.0", ++ "block2", ++ "objc2", ++ "objc2-foundation", ++] ++ ++[[package]] ++name = "objc2-quartz-core" ++version = "0.2.2" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" ++dependencies = [ ++ "bitflags 2.6.0", ++ "block2", ++ "objc2", ++ "objc2-foundation", ++ "objc2-metal", ++] ++ + [[package]] + name = "once_cell" + version = "1.19.0" +@@ -461,6 +765,19 @@ version = "1.0.14" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + ++[[package]] ++name = "png" ++version = "0.17.13" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" ++dependencies = [ ++ "bitflags 1.3.2", ++ "crc32fast", ++ "fdeflate", ++ "flate2", ++ "miniz_oxide", ++] ++ + [[package]] + name = "proc-macro2" + version = "1.0.71" +@@ -485,7 +802,7 @@ version = "0.25.0" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "a5659e52e4ba6e07b2dad9f1158f578ef84a73762625ddb51536019f34d180eb" + dependencies = [ +- "bitflags 2.4.1", ++ "bitflags 2.6.0", + "cassowary", + "crossterm", + "indoc", +@@ -507,6 +824,19 @@ dependencies = [ + "bitflags 1.3.2", + ] + ++[[package]] ++name = "rustix" ++version = "0.38.28" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" ++dependencies = [ ++ "bitflags 2.6.0", ++ "errno", ++ "libc", ++ "linux-raw-sys", ++ "windows-sys 0.52.0", ++] ++ + [[package]] + name = "rustversion" + version = "1.0.14" +@@ -560,6 +890,12 @@ dependencies = [ + "libc", + ] + ++[[package]] ++name = "simd-adler32" ++version = "0.3.7" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" ++ + [[package]] + name = "smallvec" + version = "1.11.2" +@@ -626,6 +962,17 @@ dependencies = [ + "unicode-ident", + ] + ++[[package]] ++name = "tiff" ++version = "0.9.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" ++dependencies = [ ++ "flate2", ++ "jpeg-decoder", ++ "weezl", ++] ++ + [[package]] + name = "tui-textarea" + version = "0.4.0" +@@ -733,6 +1080,12 @@ version = "0.2.90" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" + ++[[package]] ++name = "weezl" ++version = "0.1.8" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" ++ + [[package]] + name = "winapi" + version = "0.3.9" +@@ -896,6 +1249,23 @@ version = "0.52.0" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + ++[[package]] ++name = "x11rb" ++version = "0.13.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" ++dependencies = [ ++ "gethostname", ++ "rustix", ++ "x11rb-protocol", ++] ++ ++[[package]] ++name = "x11rb-protocol" ++version = "0.13.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" ++ + [[package]] + name = "zerocopy" + version = "0.7.32" diff --git a/Cargo.toml b/Cargo.toml -index 6269407..db31eef 100644 +index b6a62c4..24a9706 100644 --- a/Cargo.toml +++ b/Cargo.toml -@@ -4,5 +4,6 @@ version = "0.3.7" +@@ -4,6 +4,7 @@ version = "1.2.1" edition = "2021" [dependencies] +arboard = "3.3.0" sha1 = "0.10.1" home = "0.5.9" + ratatui = "0.25.0" diff --git a/src/todo_app/clipboard.rs b/src/todo_app/clipboard.rs -index 9ad8932..d173736 100644 +index c1fedee..d175293 100644 --- a/src/todo_app/clipboard.rs +++ b/src/todo_app/clipboard.rs -@@ -1,20 +1,32 @@ +@@ -1,20 +1,27 @@ -#[derive(Debug)] +use arboard; pub struct Clipboard { -- buffer:String, +- buffer: String, + clipboard: Option, } impl Clipboard { pub fn new() -> Self { -+ let clipboard = match arboard::Clipboard::new() { -+ Ok(some) => Some(some), -+ Err(_) => None, -+ }; -+ Clipboard { -- buffer:String::new(), -+ clipboard, +- buffer: String::new(), ++ clipboard: arboard::Clipboard::new().ok() } } -- pub fn get_text(&self) -> String { -- self.buffer.clone() -+ pub fn get_text(&mut self) -> String { + pub fn get_text(&self) -> &str { +- &self.buffer + if let Some(clipboard) = &mut self.clipboard { + if let Ok(text) = clipboard.get_text() { + return text + } + } -+ String::new() ++ "" } pub fn set_text(&mut self, text: String) { diff --git a/src/cli_app.rs b/src/cli_app.rs index a53000c..099155f 100644 --- a/src/cli_app.rs +++ b/src/cli_app.rs @@ -19,6 +19,11 @@ impl<'a> CliApp<'a> { for message in app.args.append_todo.clone() { app.append(message); } + + if let Some(path) = app.args.output_file.clone() { + app.output_list_to_path(&path); + } + for message in app.args.prepend_todo.clone() { app.prepend(message); } @@ -109,12 +114,12 @@ impl PrintTodoTree { } self.print_todo(todo, display_args); - if let Some(todo_list) = todo.dependency.todo_list() { + if let Some(todo_list) = todo.dependency.as_ref().map_or(None, |dep| dep.todo_list()) { let mut tree_child = self.tree_child(); tree_child.print_list(todo_list, display_args, restriction); } - if let Some(note) = todo.dependency.note() { + if let Some(note) = todo.dependency.as_ref().map_or(None, |dep| dep.note()) { self.print_note(note) } } @@ -138,7 +143,7 @@ impl PrintTodoTree { #[inline] fn print_prenote(&self, last_stack: Vec) { - self.print_preindention(last_stack); + self.print_preindention(&last_stack); print!(" ") } @@ -147,7 +152,7 @@ impl PrintTodoTree { if self.should_print_indention { return; } - self.print_preindention(self.last_stack.clone()); + self.print_preindention(&self.last_stack); if self.is_last { print!("└── "); } else { @@ -156,10 +161,10 @@ impl PrintTodoTree { } #[inline(always)] - fn print_preindention(&self, last_stack: Vec) { - let mut stack_iter = last_stack.into_iter(); + fn print_preindention(&self, last_stack: &Vec) { + let mut stack_iter = last_stack.iter(); stack_iter.next(); - for x in stack_iter { + for &x in stack_iter { if x { print!("│ ") } else { diff --git a/src/fileio.rs b/src/fileio.rs index 450aca1..1e0d09a 100644 --- a/src/fileio.rs +++ b/src/fileio.rs @@ -58,7 +58,12 @@ pub fn temp_path(name: &str) -> PathBuf { Ok(some) => some.as_secs(), }; let filename = format!("c3-{name}.{time}"); - let path = home_dir().unwrap().join(filename); + let tmpdir = PathBuf::from("/tmp"); + let path = if tmpdir.is_dir() { + tmpdir.join(filename) + } else { + home_dir().unwrap().join(filename) + }; path.to_path_buf() } diff --git a/src/main.rs b/src/main.rs index 1fdc76c..9a4a262 100644 --- a/src/main.rs +++ b/src/main.rs @@ -99,6 +99,10 @@ pub struct Args { #[arg(long)] append_file: Option, + /// A todo file to output to + #[arg(short='o', long)] + output_file: Option, + /// Minimal tree with no tree graphics #[arg(short = 'M', long)] minimal_tree: bool, @@ -119,6 +123,7 @@ pub struct Args { #[arg(default_value=get_todo_path().unwrap().into_os_string())] todo_path: PathBuf, + /// Generate completion for a certain shell #[arg(short = 'c', long)] completion: Option, } @@ -138,10 +143,11 @@ impl Args { if self.stdout || self.minimal_tree || self.list + || self.append_file.is_some() + || self.output_file.is_some() || !self.search_and_select.is_empty() || !self.prepend_todo.is_empty() || !self.append_todo.is_empty() - || self.append_file.is_some() { AppMode::Cli } else { diff --git a/src/todo_app.rs b/src/todo_app.rs index 985d9c0..53256da 100644 --- a/src/todo_app.rs +++ b/src/todo_app.rs @@ -1,3 +1,4 @@ +use std::fs::create_dir_all; use std::path::Path; use std::str::{FromStr, Lines}; use std::{io, path::PathBuf}; @@ -25,8 +26,8 @@ pub struct App { clipboard: Clipboard, pub(super) todo_list: TodoList, index: usize, - tree_path: Vec, changed: bool, + tree_path: Vec, pub(super) args: Args, removed_todos: Vec, tree_search_positions: Vec, @@ -49,8 +50,12 @@ struct LineMalformed; impl FromStr for IndexedLine { type Err = LineMalformed; fn from_str(input: &str) -> Result { - let (priority, message) = nth_word_parse(input, 0); - let (index, message) = nth_word_parse(message.as_str(), 0); + let (mut index, message) = nth_word_parse(input, 0); + let (mut priority, message) = nth_word_parse(message.as_str(), 0); + if priority.is_none() { + priority = index.and_then(|val| Some(val as u8)); + index = None; + } Ok(Self { index, @@ -78,7 +83,10 @@ fn nth_word_parse(input: &str, n: usize) -> (Option, String) { impl App { #[inline] pub fn new(args: Args) -> Self { - let todo_list = TodoList::read(&args.todo_path, !args.no_tree, true); + let mut todo_list = TodoList::read(&args.todo_path); + if !args.no_tree { + todo_list.read_dependencies(&args.todo_path); + } let mut app = App { x_index: 0, y_index: 0, @@ -106,7 +114,10 @@ impl App { #[inline] pub fn append_list_from_path(&mut self, path: PathBuf) { - let todo_list = TodoList::read(&path, !self.args.no_tree, true); + let todo_list = TodoList::read(&path); + if !self.args.no_tree { + self.todo_list.read_dependencies(&path); + } self.append_list(todo_list) } @@ -117,17 +128,21 @@ impl App { #[inline] pub fn open_path(&mut self, path: PathBuf) { - self.todo_list = TodoList::read(&path, !self.args.no_tree, true); + self.todo_list = TodoList::read(&path); + if !self.args.no_tree { + self.todo_list.read_dependencies(&path); + } self.args.todo_path = path; } #[inline] - pub fn output_list_to_path(&mut self, path: PathBuf) -> io::Result<()> { - let changed = self.changed; + pub fn output_list_to_path(&mut self, path: &Path) -> io::Result<()> { let list = self.current_list_mut(); - let dependency_path = list.write(&path, true)?; - list.write_dependencies(&dependency_path)?; - self.changed = changed; + let dependency_path = TodoList::append_notes_to_parent(&path); + create_dir_all(&dependency_path)?; + list.force_write(&path)?; + + list.force_write_dependencies(&dependency_path)?; Ok(()) } @@ -148,9 +163,10 @@ impl App { } pub fn do_commands_on_selected(&mut self) { + let mut changed = false; for query in self.args.search_and_select.iter() { if self.args.delete_selected { - self.changed = true; + changed = true; self.todo_list.set_todos( self.todo_list .iter() @@ -161,11 +177,12 @@ impl App { continue; } for todo in self.todo_list.iter_mut().filter(|todo| todo.matches(query)) { - self.changed = true; + changed = true; if let Some(priority) = self.args.set_selected_priority { todo.set_priority(priority as PriorityType); } - if let Some(message) = self.args.set_selected_message.clone() { + if let Some(message) = &mut self.args.set_selected_message { + let message = std::mem::take(message); todo.set_message(message); } if self.args.done_selected { @@ -173,6 +190,7 @@ impl App { } } } + self.todo_list.changed = changed; self.args.search_and_select = vec![]; } @@ -216,14 +234,14 @@ impl App { pub fn batch_editor_messages(&mut self) { let restriction = &self.restriction; - let content = self + let content = String::from("# index priority message\n") + self .current_list() .todos(restriction) .iter() .enumerate() - .map(|(i, x)| format!("{} {i} {}", x.priority(), x.message)) + .map(|(i, x)| format!("{i: <7} {: <8} {}", x.priority(), x.message)) .collect::>() - .join("\n"); + .join("\n").as_str(); let new_messages = open_temp_editor(Some(&content), temp_path("messages")).unwrap(); let new_messages = new_messages.lines(); self.batch_edit_current_list(new_messages) @@ -234,6 +252,7 @@ impl App { let mut changed = false; let mut indexed_lines: Vec = messages .into_iter() + .filter(|message| !message.starts_with('#')) .flat_map(|message| message.parse()) .collect(); @@ -262,25 +281,21 @@ impl App { } } } else { + changed = true; + delete_indices.push(i); break; } } - self.todo_list.set_todos( - self.todo_list - .iter() - .enumerate() - .filter(|(i, _)| !delete_indices.contains(i)) - .map(|(_, todo)| todo) - .cloned() - .collect(), - ); + let list_mut = self.current_list_mut(); + list_mut.filter_indices(delete_indices); for line in indexed_lines { if line.index.is_none() { - self.current_list_mut() + list_mut .push(Todo::new(line.message, line.priority)); changed = true; } } + list_mut.changed = list_mut.changed || changed; self.changed = changed; } @@ -298,7 +313,7 @@ impl App { for position in self.tree_search_positions.iter() { self.tree_path.clone_from(&position.tree_path); let list = self.current_list(); - for index in position.matching_indices.clone() { + for &index in &position.matching_indices { println!( "{}", list.index(index, &self.restriction) @@ -313,6 +328,11 @@ impl App { !self.args.no_tree } + #[inline] + pub fn is_current_changed(&self) -> bool { + self.current_list().changed + } + #[inline] pub fn is_changed(&self) -> bool { self.changed @@ -387,8 +407,8 @@ impl App { #[inline] fn set_tree_search_position(&mut self) { - let item = self.tree_search_positions[self.x_index].clone(); - self.tree_path = item.tree_path; + let item = &self.tree_search_positions[self.x_index]; + self.tree_path = item.tree_path.clone(); self.index = item.matching_indices[self.y_index]; } @@ -411,7 +431,8 @@ impl App { #[inline] pub fn read(&mut self) { self.changed = false; - self.todo_list = TodoList::read(&self.args.todo_path, true, true); + self.todo_list = TodoList::read(&self.args.todo_path); + self.todo_list.read_dependencies(&self.args.todo_path); } #[inline] @@ -427,9 +448,9 @@ impl App { pub fn parent(&mut self) -> Option<&Todo> { let mut list = &self.todo_list; let mut parent = None; - for index in self.tree_path.clone() { + for &index in &self.tree_path { parent = Some(&list.todos[index]); - if let Some(todo_list) = list.todos[index].dependency.todo_list() { + if let Some(todo_list) = list.todos[index].dependency.as_ref().map_or(None, |dep| dep.todo_list()) { list = todo_list } else { break; @@ -469,7 +490,7 @@ impl App { pub fn traverse_down(&mut self) { if self.is_tree() { match self.todo() { - Some(todo) if todo.dependency.is_list() => { + Some(todo) if todo.dependency.as_ref().map_or(false, |dep| dep.is_list()) => { let index = self.index; let restriction = self.restriction.clone(); let true_index = self @@ -484,6 +505,12 @@ impl App { } } + #[inline] + pub fn go_root(&mut self) { + self.tree_path = vec![]; + self.fix_index(); + } + #[inline] pub fn traverse_up(&mut self) -> bool { self.update_show_done_restriction(); @@ -549,12 +576,13 @@ impl App { pub fn current_list_mut(&mut self) -> &mut TodoList { self.changed = true; let is_root = self.is_root(); - let mut list = &mut self.todo_list; if is_root { - return list; + return &mut self.todo_list; } - for index in self.tree_path.clone() { - list = &mut list.todos[index].dependency.todo_list + let mut list = &mut self.todo_list; + + for &index in &self.tree_path { + list = &mut list.todos[index].dependency.as_mut().unwrap().todo_list } list } @@ -565,8 +593,8 @@ impl App { if self.is_root() { return list; } - for index in self.tree_path.clone() { - if let Some(todo_list) = &list.todos[index].dependency.todo_list() { + for &index in &self.tree_path { + if let Some(todo_list) = &list.todos[index].dependency.as_ref().map_or(None, |dep| dep.todo_list()) { list = todo_list } else { break; @@ -584,20 +612,20 @@ impl App { } #[inline] - pub fn write(&mut self) -> io::Result { - if self.changed { - self.changed = false; - let dependency_path = self.todo_list.write(&self.args.todo_path, true)?; - self.handle_removed_todo_dependency_files(&dependency_path); - self.todo_list - .delete_removed_dependent_files(&dependency_path)?; - if self.is_tree() { - self.todo_list.write_dependencies(&dependency_path)?; - } - Ok(true) - } else { - Ok(false) + pub fn write(&mut self) -> io::Result<()> { + let note_dir = TodoList::append_notes_to_parent(&self.args.todo_path); + + create_dir_all(¬e_dir)?; + let todo_path = self.args.todo_path.clone(); + self.handle_removed_todo_dependency_files(¬e_dir); + self.todo_list.write(&todo_path)?; + self.todo_list + .delete_removed_dependent_files(¬e_dir)?; + if self.is_tree() { + self.todo_list.write_dependencies(¬e_dir)?; } + self.changed = false; + Ok(()) } #[inline] @@ -781,8 +809,8 @@ impl App { #[inline] pub fn paste_todo(&mut self) { if let Ok(mut todo) = self.clipboard.get_text().parse::() { - let todo_parent = TodoList::dependency_parent(&self.args.todo_path, true); - let _ = todo.dependency.read(&todo_parent); + let todo_parent = TodoList::dependency_parent(&self.args.todo_path); + let _ = todo.dependency.as_mut().unwrap().read(&todo_parent); let list = &mut self.current_list_mut(); self.index = list.push(todo); } @@ -855,6 +883,7 @@ mod tests { app.add_dependency_traverse_down(); app.append(String::from(dependency)); } + app.todo_mut().unwrap().set_note("Heaven from hell".to_string()).unwrap(); for _ in 0..3 { app.traverse_up(); } @@ -952,6 +981,7 @@ mod tests { .collect::, io::Error>>()?; let expected_names = vec![ + "33a25a20dcf8d607bcac45120f26ab158d5dbdd2", "560b05afe5e03eae9f8ad475b0b8b73ea6911272.todo", "63c5498f09d086fca6d870345350bfb210945790.todo", "b3942ad1c555625b7f60649fe50853830b6cdb04.todo", @@ -990,10 +1020,10 @@ mod tests { app.remove_current_dependent(); app.write()?; - let names: io::Result> = match fs::read_dir(dir.join("notes")) { - Ok(value) => value.map(|res| res.map(|e| e.path())).collect(), - _ => Ok(vec![]), - }; + let names: io::Result> = fs::read_dir(dir.join("notes")) + .unwrap() + .map(|dir|dir.map(|entry|entry.path())) + .collect(); let string = fs::read_to_string(&dir.join("todo"))?; let expected_string = String::from("[0] Hello\n[0] Goodbye\n[0] Hello there\n"); remove_dir_all(dir)?; @@ -1001,4 +1031,28 @@ mod tests { assert_eq!(string, expected_string); Ok(()) } + + #[test] + fn test_remove_current_dependency_partial() -> io::Result<()> { + let dir = dir("test-remove-current-dependency-partial")?; + let mut app = write_test_todos(&dir)?; + assert_eq!(app.index, 2); + app.traverse_down(); + assert_eq!(app.index, 0); + app.remove_current_dependent(); + app.write()?; + + let names: io::Result> = fs::read_dir(dir.join("notes")) + .unwrap() + .map(|dir|dir.map(|entry|entry.path())) + .collect(); + let expected = vec![PathBuf::from("test-remove-current-dependency-partial/notes/63c5498f09d086fca6d870345350bfb210945790.todo")]; + dbg!(&names); + assert_eq!(names.unwrap(), expected); + let string = fs::read_to_string(&dir.join("todo"))?; + let expected_string = String::from("[0] Hello\n[0] Goodbye\n[0]>63c5498f09d086fca6d870345350bfb210945790.todo Hello there\n"); + remove_dir_all(dir)?; + assert_eq!(string, expected_string); + Ok(()) + } } diff --git a/src/todo_app/clipboard.rs b/src/todo_app/clipboard.rs index ab8baf6..c1fedee 100644 --- a/src/todo_app/clipboard.rs +++ b/src/todo_app/clipboard.rs @@ -10,8 +10,8 @@ impl Clipboard { } } - pub fn get_text(&self) -> String { - self.buffer.clone() + pub fn get_text(&self) -> &str { + &self.buffer } pub fn set_text(&mut self, text: String) { diff --git a/src/todo_app/todo.rs b/src/todo_app/todo.rs index c6d65a4..f0281a5 100644 --- a/src/todo_app/todo.rs +++ b/src/todo_app/todo.rs @@ -20,7 +20,7 @@ pub type PriorityType = u8; pub struct Todo { pub message: String, priority: PriorityType, - pub dependency: Dependency, + pub dependency: Option, removed_dependency: Option, done: bool, pub schedule: Schedule, @@ -41,7 +41,7 @@ impl PartialOrd for Todo { impl From<&Todo> for String { fn from(todo: &Todo) -> String { let done_str = if todo.done() { "-" } else { "" }; - let dep_str: String = (&todo.dependency).into(); + let dep_str: String = todo.dependency.as_ref().map_or(String::new(),|dep| dep.into()); let schedule_str: String = (&todo.schedule).into(); @@ -120,7 +120,7 @@ impl FromStr for Todo { } if state == State::Message && !message.is_empty() { let schedule = Schedule::from(schedule_string); - let dependency = Dependency::from(dependency_string.as_str()); + let dependency = dependency_string.parse().ok(); if schedule.should_undone() { done = false; @@ -162,13 +162,13 @@ impl Todo { } #[inline] - pub fn note_empty(&self) -> bool { - self.dependency.is_note() + pub fn is_note(&self) -> bool { + self.dependency.as_ref().map_or(false,|dep|dep.is_note()) } #[inline] pub fn dependencies(&self) -> Option<&TodoList> { - self.dependency.todo_list() + self.dependency.as_ref()?.todo_list() } #[inline] @@ -178,7 +178,7 @@ impl Todo { #[inline] pub fn remove_note(&mut self) { - if self.dependency.is_note() { + if self.is_note() { self.remove_dependency(); } } @@ -186,14 +186,16 @@ impl Todo { #[inline] pub fn add_todo_dependency(&mut self) { if self.dependency.is_none() { - self.dependency = Dependency::new_todo_list(self.hash()); + self.dependency = Some(Dependency::new_todo_list(self.hash())); } } #[inline] pub fn delete_dependency_file(&mut self, path: &Path) -> io::Result<()> { - self.dependency.todo_list.remove_dependency_files(path)?; - let _ = remove_file(path.join(self.dependency.get_name())); + if let Some(dependency) = &mut self.dependency { + dependency.todo_list.remove_dependency_files(path)?; + let _ = remove_file(path.join(dependency.get_name())); + } Ok(()) } @@ -222,7 +224,7 @@ impl Todo { } else { "" }; - let note_string = self.dependency.display(); + let note_string = self.dependency.as_ref().map_or(".", |dep| dep.display()); let daily_str = self.schedule.display(); format!( "{done_string}{}{note_string} {}{daily_str}", @@ -232,22 +234,24 @@ impl Todo { #[inline] pub fn remove_dependency(&mut self) { - if self.dependency.is_written() { - self.removed_dependency = Some(self.dependency.clone()); + if let Some(dependency) = self.dependency.as_mut() { + if dependency.is_written() { + self.removed_dependency = Some(std::mem::take(dependency)); + } } - self.dependency.remove(); + self.dependency = None; } #[inline] pub fn set_note(&mut self, note: String) -> io::Result<()> { - self.dependency = Dependency::new_note(sha1(¬e), note); + self.dependency = Some(Dependency::new_note(sha1(¬e), note)); Ok(()) } #[inline] pub fn edit_note(&mut self) -> io::Result<()> { - if !self.dependency.is_list() { - let note = open_note_temp_editor(self.dependency.note())?; + if let Some(dependency) = self.dependency.as_ref() { + let note = open_note_temp_editor(dependency.note())?; if !note.is_empty() { self.set_note(note)?; } @@ -257,7 +261,7 @@ impl Todo { #[inline] pub fn dependency_path(&self, path: &Path) -> Option { - self.dependency.path(path) + self.dependency.as_ref()?.path(path) } #[inline] @@ -400,10 +404,10 @@ mod tests { let expected = Ok(Todo { removed_dependency: None, schedule: Schedule::new(), - dependency: Dependency::new_note( + dependency: Some(Dependency::new_note( "2c924e3088204ee77ba681f72be3444357932fca".to_string(), "".to_string(), - ), + )), message: "Test".to_string(), priority: 1, done: false, @@ -432,7 +436,7 @@ mod tests { let expected = "900a80c94f076b4ee7006a9747667ccf6878a72b.todo"; todo.add_todo_dependency(); - let result = &todo.dependency.get_name(); + let result = todo.dependency.unwrap().get_name().to_string(); assert_eq!(result, expected); } @@ -441,7 +445,7 @@ mod tests { let mut todo = Todo::new("Test".to_string(), 1); todo.add_todo_dependency(); - assert!(todo.dependency.is_list()); + assert!(todo.dependency.unwrap().is_list()); } #[test] @@ -450,7 +454,7 @@ mod tests { let expected = "900a80c94f076b4ee7006a9747667ccf6878a72b.todo"; todo.add_todo_dependency(); - let result = &todo.dependency.get_name(); + let result = todo.dependency.unwrap().get_name().to_string(); assert_eq!(result, expected); } @@ -460,7 +464,7 @@ mod tests { todo.set_note("Note".to_string()) .expect("Error setting note"); - assert!(todo.dependency.is_note()); + assert!(todo.dependency.unwrap().is_note()); } #[test] @@ -470,7 +474,7 @@ mod tests { .expect("Error setting note"); assert_eq!( - todo.dependency.get_name(), + todo.dependency.unwrap().get_name(), "2c924e3088204ee77ba681f72be3444357932fca" ); } @@ -491,7 +495,7 @@ mod tests { todo.add_todo_dependency(); - assert!(todo.dependency.is_list()); + assert!(todo.dependency.unwrap().is_list()); } #[test] @@ -501,7 +505,7 @@ mod tests { todo.remove_dependency(); - assert!(!todo.dependency.is_list()); + assert!(todo.dependency.is_none()); } #[test] @@ -523,9 +527,9 @@ mod tests { let expected = Todo { removed_dependency: None, schedule: Schedule::new(), - dependency: Dependency::new_todo_list( + dependency: Some(Dependency::new_todo_list( "1BE348656D84993A6DF0DB0DECF2E95EF2CF461c".to_string(), - ), + )), message: "Read for exams".to_string(), priority: 1, done: false, @@ -540,7 +544,7 @@ mod tests { let expected = Todo { removed_dependency: None, schedule: Schedule::from("D1(2023-09-05)"), - dependency: Dependency::default(), + dependency: None, message: "this one should be daily".to_string(), priority: 2, done: false, @@ -555,7 +559,7 @@ mod tests { fn test_daily_display() { let test = Todo { removed_dependency: None, - dependency: Dependency::default(), + dependency: None, schedule: Schedule::from("D1()"), message: "this one should be daily".to_string(), priority: 2, @@ -572,7 +576,7 @@ mod tests { let todo = Todo::from_str(input).unwrap(); let expected = Todo { removed_dependency: None, - dependency: Dependency::default(), + dependency: None, schedule: Schedule::from("D7(2023-09-05)"), message: "this one should be daily".to_string(), priority: 2, diff --git a/src/todo_app/todo/dependency.rs b/src/todo_app/todo/dependency.rs index 57c6071..b0638f4 100644 --- a/src/todo_app/todo/dependency.rs +++ b/src/todo_app/todo/dependency.rs @@ -1,5 +1,6 @@ use super::Todo; use super::TodoList; +use std::str::FromStr; use std::{ fs::File, io::{self, Write}, @@ -9,7 +10,6 @@ use std::{ #[derive(Debug, Eq, PartialEq, Clone, Default)] enum DependencyMode { #[default] - None, TodoList, Note, } @@ -39,8 +39,8 @@ impl Dependency { } #[inline] - pub fn get_name(&self) -> String { - self.name.clone() + pub fn get_name(&self) -> &str { + &self.name } #[inline] @@ -86,7 +86,8 @@ impl Dependency { self.name = name_todo; self.mode = DependencyMode::TodoList; } - self.todo_list = TodoList::read(&path.join(&self.name), true, false); + self.todo_list = TodoList::read(&path.join(&self.name)); + self.todo_list.read_recursive_dependencies(&path); } _ => {} }; @@ -115,14 +116,12 @@ impl Dependency { #[inline] pub fn write(&mut self, path: &Path) -> io::Result<()> { - let name = self.name.clone(); - match self.mode.clone() { + match self.mode { DependencyMode::TodoList => { - self.todo_list.write(&path.join(&self.name), false)?; + self.todo_list.write(&path.join(&self.name))?; } - DependencyMode::Note => { - let mut file = File::create(path.join(name))?; - write!(file, "{}", self.note)?; + DependencyMode::Note if !self.written => { + self.write_note(path)?; } _ => {} }; @@ -131,14 +130,32 @@ impl Dependency { } #[inline] - pub fn path(&self, path: &Path) -> Option { - path.parent() - .map(|path| TodoList::dependency_parent(path, false)) + fn write_note(&mut self, path: &Path) -> io::Result<()> { + let mut file = File::create(path.join(&self.name))?; + write!(file, "{}", self.note)?; + Ok(()) + } + + #[inline] + pub fn force_write(&mut self, path: &Path) -> io::Result<()> { + match self.mode { + DependencyMode::TodoList => { + self.todo_list.force_write(&path.join(&self.name))?; + } + DependencyMode::Note => { + self.write_note(path)?; + } + _ => {} + }; + self.written = true; + Ok(()) } + #[inline] - pub fn is_none(&self) -> bool { - self.mode == DependencyMode::None + pub fn path(&self, path: &Path) -> Option { + path.parent() + .map(|path| TodoList::append_notes_to_parent(path)) } #[inline] @@ -154,7 +171,6 @@ impl Dependency { #[inline] pub fn display<'a>(&self) -> &'a str { match self.mode { - DependencyMode::None => ".", DependencyMode::Note => ">", DependencyMode::TodoList => "-", } @@ -169,33 +185,31 @@ impl Dependency { impl From<&Dependency> for String { #[inline] fn from(dependency: &Dependency) -> String { - match dependency.mode { - DependencyMode::None => String::new(), - _ => format!(">{}", dependency.name), - } + format!(">{}", dependency.name) } } -impl From<&str> for Dependency { +pub struct EmptyDependency; + +impl FromStr for Dependency { + type Err = EmptyDependency; #[inline] - fn from(input: &str) -> Dependency { - let mut name = String::new(); - let mode: DependencyMode; - if input.is_empty() { - mode = DependencyMode::None; - } else { - name = String::from(input); + fn from_str(input: &str) -> Result { + if !input.is_empty() { + let mode: DependencyMode; + let name = String::from(input); if input.ends_with(".todo") { mode = DependencyMode::TodoList; } else { mode = DependencyMode::Note; } - } - - Self { - name, - mode, - ..Default::default() + Ok(Self { + name, + mode, + ..Default::default() + }) + } else { + Err(EmptyDependency) } } } diff --git a/src/todo_app/todo_list.rs b/src/todo_app/todo_list.rs index c1ae8af..629553a 100644 --- a/src/todo_app/todo_list.rs +++ b/src/todo_app/todo_list.rs @@ -1,4 +1,4 @@ -use std::fs::{create_dir_all, read, File}; +use std::fs::{read, File}; use std::io; use std::io::{stdout, BufRead, BufWriter, Write}; use std::path::{Path, PathBuf}; @@ -9,12 +9,15 @@ use crate::DisplayArgs; #[derive(Debug, Eq, PartialEq, Clone, Default)] pub struct TodoList { pub todos: Vec, + pub(super) changed: bool, } type Output = Todo; + impl TodoList { pub fn new() -> Self { - TodoList { todos: Vec::new() } + TodoList { ..Default::default() } + } pub fn index(&self, index: usize, restriction: &RestrictionFunction) -> &Output { @@ -26,6 +29,7 @@ impl TodoList { } pub fn index_mut(&mut self, index: usize, restriction: &RestrictionFunction) -> &mut Output { + self.changed = true; self.todos .iter_mut() .filter(|todo| restriction(todo)) @@ -40,6 +44,9 @@ impl TodoList { #[inline] pub(super) fn delete_removed_dependent_files(&mut self, filename: &Path) -> io::Result<()> { for todo in &mut self.todos { + if let Some(dependency) = todo.dependency.as_mut() { + dependency.todo_list.delete_removed_dependent_files(filename); + } todo.delete_removed_dependent_files(filename)?; } Ok(()) @@ -47,6 +54,7 @@ impl TodoList { #[inline] pub fn prepend(&mut self, todo: Todo) { + self.changed = true; self.insert(0, todo); } @@ -66,7 +74,7 @@ impl TodoList { let prior_indices = prior_indices.unwrap_or_default(); callback(app, self, prior_indices.as_slice()); for (i, todo) in self.todos.iter().enumerate() { - if let Some(todo_list) = todo.dependency.todo_list() { + if let Some(todo_list) = todo.dependency.as_ref().map_or(None, |dep| dep.todo_list()) { let mut prior_indices = prior_indices.clone(); prior_indices.push(i); todo_list.traverse_tree(callback, Some(prior_indices), app); @@ -81,44 +89,55 @@ impl TodoList { Ok(()) } - pub fn read(filename: &Path, read_dependencies: bool, is_root: bool) -> Self { - let mut todolist = Self::new(); + pub fn read(filename: &Path) -> Self { if !filename.is_file() { - return todolist; + return Self::new(); } let file_data = read(filename).unwrap(); - - for line in file_data.lines() { - if let Ok(todo) = line.unwrap_or_default().parse::() { - todolist.push(todo); - } - } + let mut todolist = Self { + todos: file_data.lines() + .map_while(Result::ok) + .flat_map(|line| line.parse()) + .collect(), + ..Default::default() + }; todolist.sort(); - if read_dependencies { - let dependency_path = Self::dependency_parent(filename, is_root); - let _ = todolist.read_dependencies(&dependency_path); - } + todolist.changed = false; todolist } - fn read_dependencies(&mut self, path: &Path) -> io::Result<()> { + pub fn read_recursive_dependencies(&mut self, folder_name: &Path) -> io::Result<()> { for todo in &mut self.todos { - todo.dependency.read(path)?; + if let Some(dependency) = todo.dependency.as_mut() { + dependency.read(&folder_name)?; + } } Ok(()) } - pub fn dependency_parent(filename: &Path, is_root: bool) -> PathBuf { - if is_root { - filename.parent().unwrap().join("notes") - } else { - filename.parent().unwrap().to_path_buf() + pub fn read_dependencies(&mut self, filename: &Path) -> io::Result<()> { + let dependency_path = Self::append_notes_to_parent(filename); + + for todo in &mut self.todos { + if let Some(dependency) = todo.dependency.as_mut() { + dependency.read(&dependency_path)?; + } } + Ok(()) + } + + pub fn append_notes_to_parent(filename: &Path) -> PathBuf { + filename.parent().unwrap().join("notes") + } + + pub fn dependency_parent(filename: &Path) -> PathBuf { + filename.parent().unwrap().to_path_buf() } pub fn with_capacity(capacity: usize) -> Self { TodoList { todos: Vec::with_capacity(capacity), + ..Default::default() } } @@ -134,38 +153,73 @@ impl TodoList { #[inline] pub(super) fn write_dependencies(&mut self, filename: &Path) -> io::Result<()> { for todo in &mut self.todos { - todo.dependency.todo_list.write_dependencies(filename)?; - todo.dependency.write(filename)?; + if let Some(dependency) = todo.dependency.as_mut() { + dependency.todo_list.write_dependencies(filename)?; + dependency.write(filename)?; + } } Ok(()) } + #[inline] - pub fn write(&mut self, filename: &Path, is_root: bool) -> io::Result { - let dependency_path = Self::dependency_parent(filename, is_root); - create_dir_all(&dependency_path)?; + pub(super) fn force_write_dependencies(&mut self, filename: &Path) -> io::Result<()> { + for todo in &mut self.todos { + if let Some(dependency) = todo.dependency.as_mut() { + dependency.todo_list.force_write_dependencies(filename)?; + dependency.force_write(filename)?; + } + } + Ok(()) + } + + #[inline] + pub fn force_write(&mut self, filename: &Path) -> io::Result<()> { let file = File::create(filename)?; let mut writer = BufWriter::new(file); self.write_to_buf(&mut writer)?; - Ok(dependency_path) + self.changed = false; + Ok(()) + } + + #[inline] + pub fn write(&mut self, filename: &Path) -> io::Result<()> { + if self.changed { + self.force_write(filename)?; + } + Ok(()) } #[inline(always)] pub(super) fn set_todos(&mut self, todos: Vec) { + self.changed = true; self.todos = todos } + #[inline(always)] + pub(super) fn filter_indices(&mut self, indices: Vec) { + self.todos = self.todos + .iter() + .enumerate() + .filter(|(i, _)| !indices.contains(i)) + .map(|(_, todo)| todo) + .cloned() + .collect() + + } + pub fn iter(&self) -> std::slice::Iter { self.todos.iter() } pub fn iter_mut(&mut self) -> std::slice::IterMut { + self.changed = true; self.todos.iter_mut() } - pub fn messages(&self, restriction: &RestrictionFunction) -> Vec { + pub fn messages(&self, restriction: &RestrictionFunction) -> Vec<&str> { self.todos(restriction) .iter() - .map(|todo| todo.message.clone()) + .map(|todo| todo.message.as_str()) .collect() } @@ -185,6 +239,7 @@ impl TodoList { } pub fn remove(&mut self, index: usize, restriction: &RestrictionFunction) { + self.changed = true; let mut binding = self.todos(restriction); let filtered: Vec<_> = binding.iter_mut().collect(); self.todos = self @@ -205,16 +260,19 @@ impl TodoList { } pub fn cut(&mut self, index: usize, restriction: &RestrictionFunction) -> Todo { + self.changed = true; let index_in_vec = self.true_position_in_list(index, restriction); self.todos.remove(index_in_vec) } pub fn push(&mut self, item: Todo) -> usize { + self.changed = true; self.todos.push(item); self.reorder_last() } fn insert(&mut self, index: usize, item: Todo) { + self.changed = true; self.todos.insert(index, item) } @@ -229,10 +287,12 @@ impl TodoList { #[inline(always)] pub fn reorder_last(&mut self) -> usize { + self.changed = true; self.reorder(self.todos.len() - 1) } pub fn reorder(&mut self, index: usize) -> usize { + self.changed = true; if self.todos[index] < self.todos[0] { return self.move_index(index, 0, 1); } @@ -264,40 +324,39 @@ impl TodoList { } pub fn append_list(&mut self, mut todo_list: TodoList) { + self.changed = true; self.todos.append(&mut todo_list.todos); self.sort(); } #[inline(always)] pub fn sort(&mut self) { + self.changed = true; self.todos.sort() } } #[cfg(test)] mod tests { - use std::fs::{self, remove_dir_all, remove_file}; + use std::fs::{self, create_dir_all, remove_dir_all, remove_file}; use std::str::FromStr; use super::*; fn get_todo_list() -> TodoList { let path = PathBuf::from("tests/TODO_LIST"); - TodoList::read(&path, true, true) + let mut todolist = TodoList::read(&path); + todolist.read_dependencies(&path); + todolist } #[test] fn test_todolist_read_undone() { let todo_list = get_todo_list(); - let mut expected_undone = vec![ + let expected_undone = vec![ Todo::new("this todo has prio 1".to_string(), 1), Todo::new("this one has prio 2".to_string(), 2), ]; - for i in 0..expected_undone.len() { - let _ = expected_undone[i] - .dependency - .write(&PathBuf::from("/dev/null")); - } assert_eq!( expected_undone, @@ -319,9 +378,6 @@ mod tests { ]; for i in 0..expected_done.len() { expected_done[i].toggle_done(); - let _ = expected_done[i] - .dependency - .write(&PathBuf::from("/dev/null")); } assert_eq!( expected_done, @@ -344,7 +400,11 @@ mod tests { fn test_write() { let mut todo_list = get_todo_list(); let path = PathBuf::from("todo-list-test-write/tmplist"); - let _ = todo_list.write(&path, true); + let dependency_path = TodoList::append_notes_to_parent(&path); + let _ = create_dir_all(&dependency_path); + todo_list.changed = true; + + let _ = todo_list.write(&path); let contents = fs::read_to_string(&path).expect("Reading file failed :("); let expected = "[1] this todo has prio 1 @@ -362,9 +422,11 @@ mod tests { fn test_push() { let mut todo_list = get_todo_list(); let path = PathBuf::from("todo-list-test-push/tmplist"); + let dependency_path = TodoList::dependency_parent(&path); + let _ = create_dir_all(&dependency_path); todo_list.push(Todo::new("Show me your warface".to_string(), 0)); todo_list.reorder_last(); - let _ = todo_list.write(&path, true); + let _ = todo_list.write(&path); let contents = fs::read_to_string(&path).expect("Reading file failed :("); let expected = "[1] this todo has prio 1 @@ -384,6 +446,7 @@ mod tests { let todo_list = get_todo_list(); let mut sorted_list = todo_list.clone(); sorted_list.sort(); + sorted_list.changed = false; assert_eq!(todo_list, sorted_list) } @@ -392,11 +455,18 @@ mod tests { fn test_write_dependencies() -> io::Result<()> { let mut todo_list = get_todo_list(); let _ = todo_list.todos[0].add_todo_dependency(); + let path = PathBuf::from("test-write-dependency/tmplist"); + let dependency_path = TodoList::append_notes_to_parent(&path); + let _ = create_dir_all(&dependency_path); + todo_list.todos[0] .dependency + .as_mut() + .unwrap() .push(Todo::from_str("[0] Some dependency").unwrap()); - let dependency_path = todo_list.write(&path, true)?; + todo_list.write(&path)?; + let dependency_path = TodoList::append_notes_to_parent(&path); todo_list.write_dependencies(&dependency_path)?; let todo_dependency_path = PathBuf::from(format!( @@ -408,7 +478,7 @@ mod tests { assert_eq!(contents, expected); todo_list.todos[0].remove_dependency(); - todo_list.write(&path, true)?; + todo_list.write(&path)?; remove_dir_all(&path.parent().unwrap())?; Ok(()) } diff --git a/src/tui_app.rs b/src/tui_app.rs index 57f896a..ce25231 100644 --- a/src/tui_app.rs +++ b/src/tui_app.rs @@ -153,7 +153,7 @@ impl<'a> TuiApp<'a> { #[inline] pub fn title(&mut self) -> String { - let changed_str = if self.todo_app.is_changed() { "*" } else { "" }; + let changed_str = if self.todo_app.is_current_changed() { "*" } else { "" }; let size = self.todo_app.len(); let todo_string = format!("Todos ({size}){changed_str}"); @@ -233,7 +233,12 @@ impl<'a> TuiApp<'a> { #[inline] fn on_priority_delete(&mut self, new: String, old: String) { - if new.is_empty() || old.is_empty() { + if new.is_empty() { + if let Some(restriction) = self.last_restriction.clone() { + self.todo_app.set_restriction(restriction) + } + } + if old.is_empty() { self.todo_app.update_show_done_restriction() } } @@ -309,7 +314,7 @@ impl<'a> TuiApp<'a> { #[inline] pub fn nnn_output_todo(&mut self) { for path in Self::nnn_paths() { - let _ = self.todo_app.output_list_to_path(path); + let _ = self.todo_app.output_list_to_path(&path); } } @@ -359,7 +364,7 @@ impl<'a> TuiApp<'a> { #[inline] pub fn quit_save_prompt(&mut self) { - if self.todo_app.is_changed() { + if self.todo_app.is_changed() || self.todo_app.is_current_changed() { self.set_text_mode( Self::on_save_prompt, "You have done changes. You wanna save? [n: no, y: yes, c: cancel] (default: n)", @@ -520,10 +525,7 @@ impl<'a> TuiApp<'a> { #[inline] fn write(&mut self) -> io::Result<()> { - if !self.todo_app.write()? { - self.todo_app.read(); - } - Ok(()) + self.todo_app.write() } #[inline] @@ -585,9 +587,11 @@ impl<'a> TuiApp<'a> { Char('/') => self.search_prompt(), Char('?') => self.tree_search_prompt(), Char('A') => self.append_prompt(), - Char('E') | Char('e') => self.edit_prompt(key.code == Char('E')), + Char('e') | Char('E') => self.edit_prompt(key.code == Char('E')), + Char('r') if key.modifiers == KeyModifiers::CONTROL => self.edit_prompt(false), + Char('~') => self.todo_app.go_root(), Char('q') => self.quit_save_prompt(), - Char('b') => { + Char('r') => { self.todo_app.batch_editor_messages(); return Ok(Operation::Restart); } @@ -601,7 +605,7 @@ impl<'a> TuiApp<'a> { Char('c') => self.module.on_c(), Char('C') => self.module.on_capital_c(), Char('L') => self.module.on_capital_l(), - Char('r') => self.module.on_r(), + Char('f') => self.module.on_f(), Char('+') | Char('=') => self.module.on_plus(), Char('-') => self.module.on_minus(), Char('.') => self.module.on_dot(), @@ -654,13 +658,13 @@ impl<'a> TuiApp<'a> { dependency_layout: Rect, ) { if let Some(todo) = todo { - if let Some(note) = todo.dependency.note() { + if let Some(note) = todo.dependency.as_ref().map_or(None, |dep| dep.note()) { let note_widget = Paragraph::new(Text::styled(note, Style::default())) .wrap(Wrap { trim: true }) .block(default_block("Todo note")); frame.render_widget(note_widget, dependency_layout); } - if let Some(todo_list) = todo.dependency.todo_list() { + if let Some(todo_list) = todo.dependency.as_ref().map_or(None, |dep| dep.todo_list()) { Self::render_todos_widget( self.highlight_string(), frame, diff --git a/src/tui_app/modules.rs b/src/tui_app/modules.rs index 310dfd2..c0eb72b 100644 --- a/src/tui_app/modules.rs +++ b/src/tui_app/modules.rs @@ -8,7 +8,7 @@ pub trait Module<'a> { fn on_s(&mut self); fn on_capital_h(&mut self); fn on_capital_l(&mut self); - fn on_r(&mut self); + fn on_f(&mut self); fn on_minus(&mut self); fn on_plus(&mut self); fn on_dot(&mut self); diff --git a/src/tui_app/modules/potato.rs b/src/tui_app/modules/potato.rs index 930a6ab..183bf81 100644 --- a/src/tui_app/modules/potato.rs +++ b/src/tui_app/modules/potato.rs @@ -53,7 +53,7 @@ impl<'a> Module<'a> for Potato<'a> { } #[inline] - fn on_r(&mut self) { + fn on_f(&mut self) { self.restart() }