From 2bb15f7cf2b0db8e09de6cb759c723d4880fda03 Mon Sep 17 00:00:00 2001 From: Dheepak Krishnamurthy Date: Sun, 3 Sep 2023 06:54:37 -0400 Subject: [PATCH 01/13] =?UTF-8?q?feat:=20Use=20color=5Feyre=20instead=20of?= =?UTF-8?q?=20anyhow=20=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 970 +++++++++++++++++++++++-------- Cargo.toml | 38 +- build.rs | 54 ++ completions/_taskwarrior-tui | 4 +- completions/_taskwarrior-tui.ps1 | 4 +- completions/taskwarrior-tui.fish | 2 +- src/app.rs | 16 +- src/cli.rs | 2 +- src/config.rs | 8 +- src/history.rs | 12 +- src/keyconfig.rs | 2 +- src/main.rs | 168 ++---- src/pane/context.rs | 2 +- src/pane/mod.rs | 2 +- src/pane/project.rs | 2 +- src/task_report.rs | 4 +- src/tui.rs | 150 +++++ src/utils.rs | 148 +++++ 18 files changed, 1178 insertions(+), 410 deletions(-) create mode 100644 src/tui.rs diff --git a/Cargo.lock b/Cargo.lock index 8981dc33..8b1f1a90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,11 +17,22 @@ 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.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" +checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" dependencies = [ "memchr", ] @@ -76,7 +87,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -86,20 +97,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] -name = "anyhow" -version = "1.0.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" - -[[package]] -name = "arc-swap" -version = "1.6.0" +name = "async-trait" +version = "0.1.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" +checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] [[package]] name = "autocfg" @@ -123,14 +133,10 @@ dependencies = [ ] [[package]] -name = "better-panic" -version = "0.3.0" +name = "base64" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fa9e1d11a268684cbd90ed36370d7577afb6c62d912ddff5c15fc34343e5036" -dependencies = [ - "backtrace", - "console", -] +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "bitflags" @@ -143,6 +149,18 @@ name = "bitflags" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +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" @@ -150,6 +168,12 @@ version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + [[package]] name = "bytes" version = "1.4.0" @@ -179,9 +203,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.26" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +checksum = "95ed24df0632f708f5f6d8082675bef2596f7084dee3dd55f632290bf35bfe0f" dependencies = [ "android-tzdata", "iana-time-zone", @@ -189,30 +213,32 @@ dependencies = [ "num-traits", "time", "wasm-bindgen", - "winapi", + "windows-targets", ] [[package]] name = "clap" -version = "4.4.1" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c8d502cbaec4595d2e7d5f61e318f05417bd2b66fdc3809498f0d3fdf0bea27" +checksum = "6a13b88d2c62ff462f88e4a121f17a82c1af05693a2f192b5c38d14de73c19f6" dependencies = [ "clap_builder", "clap_derive", - "once_cell", ] [[package]] name = "clap_builder" -version = "4.4.1" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5891c7bc0edb3e1c2204fc5e94009affabeb1821c9e5fdc3959536c5c0bb984d" +checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", + "terminal_size", + "unicase", + "unicode-width", ] [[package]] @@ -226,9 +252,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.4.0" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9fd1a5729c4548118d7d70ff234a44868d00489a4b6597b0b020918a0e91a1a" +checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" dependencies = [ "heck", "proc-macro2", @@ -253,6 +279,33 @@ dependencies = [ "winapi", ] +[[package]] +name = "color-eyre" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba75b3d9449ecdccb27ecbc479fdc0b87fa2dd43d2f8298f9bf0e59aacc8dce" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + [[package]] name = "colorchoice" version = "1.0.0" @@ -260,15 +313,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] -name = "console" -version = "0.15.7" +name = "config" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +checksum = "d379af7f68bfc21714c6c7dea883544201741d2ce8274bb12fa54f89507f52a7" dependencies = [ - "encode_unicode", + "async-trait", + "json5", "lazy_static", - "libc", - "windows-sys 0.45.0", + "nom", + "pathdiff", + "ron", + "rust-ini", + "serde", + "serde_json", + "toml 0.5.11", + "yaml-rust", ] [[package]] @@ -277,6 +337,15 @@ 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" @@ -289,6 +358,7 @@ dependencies = [ "libc", "mio", "parking_lot", + "serde", "signal-hook", "signal-hook-mio", "winapi", @@ -303,6 +373,16 @@ 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" @@ -338,17 +418,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "derive_builder" version = "0.12.0" @@ -381,10 +450,29 @@ dependencies = [ ] [[package]] -name = "destructure_traitobject" -version = "0.2.0" +name = "diff" +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" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c877555693c14d2f84191cfd3ad8582790fc52b5e2274b40b59cf5f5cea25c7" +checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" +dependencies = [ + "dirs-sys", +] [[package]] name = "dirs" @@ -404,20 +492,20 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] -name = "either" -version = "1.9.0" +name = "dlv-list" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" [[package]] -name = "encode_unicode" -version = "0.3.6" +name = "either" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "endian-type" @@ -425,6 +513,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.3.2" @@ -433,7 +527,7 @@ checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" dependencies = [ "errno-dragonfly", "libc", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -456,6 +550,16 @@ dependencies = [ "str-buf", ] +[[package]] +name = "eyre" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +dependencies = [ + "indenter", + "once_cell", +] + [[package]] name = "fd-lock" version = "3.0.13" @@ -463,8 +567,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef033ed5e9bad94e55838ca0ca906db0e043f517adda0c8b79c7a8c66c93c1b5" dependencies = [ "cfg-if", - "rustix", - "windows-sys 0.48.0", + "rustix 0.38.8", + "windows-sys", ] [[package]] @@ -562,6 +666,25 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +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" @@ -584,6 +707,15 @@ 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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" [[package]] name = "heck" @@ -603,14 +735,24 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys", ] [[package]] -name = "humantime" -version = "2.1.0" +name = "human-panic" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +checksum = "eb2df2fb4e13fa697d21d93061ebcbbd876f5ef643b48ff59cfab57a726ef140" +dependencies = [ + "anstream", + "anstyle", + "backtrace", + "os_info", + "serde", + "serde_derive", + "toml 0.7.6", + "uuid", +] [[package]] name = "iana-time-zone" @@ -641,14 +783,20 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + [[package]] name = "indexmap" -version = "1.9.3" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" dependencies = [ - "autocfg", - "hashbrown", + "equivalent", + "hashbrown 0.14.0", ] [[package]] @@ -657,6 +805,17 @@ version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c785eefb63ebd0e33416dfcb8d6da0bf27ce752843a45632a67bf10d4d4b5c4" +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys", +] + [[package]] name = "itertools" version = "0.11.0" @@ -681,6 +840,17 @@ 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" @@ -699,6 +869,12 @@ 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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + [[package]] name = "linux-raw-sys" version = "0.4.5" @@ -720,47 +896,21 @@ name = "log" version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" -dependencies = [ - "serde", -] [[package]] -name = "log-mdc" +name = "matchers" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a94d21414c1f4a51209ad204c1776a3d0765002c76c6abcb602a6f09f1e881c7" - -[[package]] -name = "log4rs" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d36ca1786d9e79b8193a68d480a0907b612f109537115c6ff655a3a1967533fd" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" dependencies = [ - "anyhow", - "arc-swap", - "chrono", - "derivative", - "fnv", - "humantime", - "libc", - "log", - "log-mdc", - "parking_lot", - "serde", - "serde-value", - "serde_json", - "serde_yaml", - "thiserror", - "thread-id", - "typemap-ors", - "winapi", + "regex-automata 0.1.10", ] [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" [[package]] name = "minimal-lexical" @@ -786,7 +936,7 @@ dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -820,6 +970,16 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-traits" version = "0.2.16" @@ -839,6 +999,12 @@ dependencies = [ "libc", ] +[[package]] +name = "numtoa" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" + [[package]] name = "object" version = "0.32.0" @@ -861,14 +1027,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] -name = "ordered-float" -version = "2.10.0" +name = "ordered-multimap" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" +checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" dependencies = [ - "num-traits", + "dlv-list", + "hashbrown 0.12.3", +] + +[[package]] +name = "os_info" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "006e42d5b888366f1880eda20371fedde764ed2213dc8496f49622fa0c99cd5e" +dependencies = [ + "log", + "serde", + "winapi", ] +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + [[package]] name = "parking_lot" version = "0.12.1" @@ -889,7 +1079,7 @@ dependencies = [ "libc", "redox_syscall 0.3.5", "smallvec", - "windows-targets 0.48.5", + "windows-targets", ] [[package]] @@ -904,6 +1094,57 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17359afc20d7ab31fdb42bb844c8b3bb1dabd7dcf7e68428492da7f16966fcef" +[[package]] +name = "pathdiff" +version = "0.2.1" +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" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bee7be22ce7918f641a33f08e3f43388c7656772244e2bbb2477f44cc9021a" +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", + "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" @@ -922,6 +1163,16 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "pretty_assertions" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "proc-macro2" version = "1.0.66" @@ -980,6 +1231,20 @@ dependencies = [ "getrandom", ] +[[package]] +name = "ratatui" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8285baa38bdc9f879d92c0e37cb562ef38aa3aeefca22b3200186bc39242d3d5" +dependencies = [ + "bitflags 2.4.0", + "cassowary", + "indoc", + "paste", + "unicode-segmentation", + "unicode-width", +] + [[package]] name = "ratatui" version = "0.23.0" @@ -1015,6 +1280,15 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_termios" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f" +dependencies = [ + "redox_syscall 0.2.16", +] + [[package]] name = "redox_users" version = "0.4.3" @@ -1028,39 +1302,89 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29" +checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.3.8", + "regex-syntax 0.7.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", ] [[package]] name = "regex-automata" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629" +checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.7.5", ] +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustix" +version = "0.37.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys", +] + [[package]] name = "rustix" version = "0.38.8" @@ -1070,8 +1394,8 @@ dependencies = [ "bitflags 2.4.0", "errno", "libc", - "linux-raw-sys", - "windows-sys 0.48.0", + "linux-raw-sys 0.4.5", + "windows-sys", ] [[package]] @@ -1136,16 +1460,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-value" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" -dependencies = [ - "ordered-float", - "serde", -] - [[package]] name = "serde_derive" version = "1.0.188" @@ -1169,15 +1483,32 @@ dependencies = [ ] [[package]] -name = "serde_yaml" -version = "0.8.26" +name = "serde_spanned" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b" +checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" dependencies = [ - "indexmap", - "ryu", "serde", - "yaml-rust", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", ] [[package]] @@ -1247,7 +1578,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -1262,6 +1593,15 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" +[[package]] +name = "strip-ansi-escapes" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ff8ef943b384c414f54aefa961dd2bd853add74ec75e7ac74cf91dba62bcfa" +dependencies = [ + "vte", +] + [[package]] name = "strsim" version = "0.10.0" @@ -1331,31 +1671,40 @@ dependencies = [ name = "taskwarrior-tui" version = "0.25.4" dependencies = [ - "anyhow", - "better-panic", "cassowary", "chrono", "clap", "clap_complete", + "color-eyre", + "config", "crossterm", - "dirs", + "directories", "futures", + "human-panic", "itertools", "lazy_static", + "libc", "log", - "log4rs", "path-clean", + "pretty_assertions", "rand", - "ratatui", + "ratatui 0.23.0", "regex", "rustyline", "serde", + "serde_derive", "serde_json", "shellexpand", "shlex", + "signal-hook", + "strip-ansi-escapes", "task-hookrs", "tokio", - "tokio-stream", + "tokio-util", + "tracing", + "tracing-error", + "tracing-subscriber", + "tui-logger", "unicode-segmentation", "unicode-truncate", "unicode-width", @@ -1363,6 +1712,28 @@ dependencies = [ "versions", ] +[[package]] +name = "terminal_size" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237" +dependencies = [ + "rustix 0.37.23", + "windows-sys", +] + +[[package]] +name = "termion" +version = "1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e" +dependencies = [ + "libc", + "numtoa", + "redox_syscall 0.2.16", + "redox_termios", +] + [[package]] name = "thiserror" version = "1.0.47" @@ -1384,14 +1755,13 @@ dependencies = [ ] [[package]] -name = "thread-id" -version = "4.2.0" +name = "thread_local" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79474f573561cdc4871a0de34a51c92f7f5a56039113fbb5b9c9f96bdb756669" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" dependencies = [ - "libc", - "redox_syscall 0.2.16", - "winapi", + "cfg-if", + "once_cell", ] [[package]] @@ -1421,7 +1791,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -1436,23 +1806,183 @@ dependencies = [ ] [[package]] -name = "tokio-stream" -version = "0.1.14" +name = "tokio-util" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" dependencies = [ + "bytes", "futures-core", + "futures-sink", "pin-project-lite", "tokio", ] [[package]] -name = "typemap-ors" -version = "1.0.0" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "tracing-core" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-error" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "tui" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68c24b707f02dd18f1e4ccceb9d49f2058c2fb86384ef9972592904d7a28867" +checksum = "ccdd26cbd674007e649a272da4475fb666d3aa0ad0531da7136db6fab0e5bad1" dependencies = [ - "unsafe-any-ors", + "bitflags 1.3.2", + "cassowary", + "termion", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "tui-logger" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9b303440de7c259d03e113a808f2f8ca3cd9097840663dc484b12f19c6f44a6" +dependencies = [ + "chrono", + "fxhash", + "lazy_static", + "log", + "parking_lot", + "ratatui 0.22.0", + "termion", + "tracing", + "tracing-subscriber", + "tui", +] + +[[package]] +name = "typenum" +version = "1.16.0" +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" + +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", ] [[package]] @@ -1482,15 +2012,6 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" -[[package]] -name = "unsafe-any-ors" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a303d30665362d9680d7d91d78b23f5f899504d4f08b3c4cf08d055d87c0ad" -dependencies = [ - "destructure_traitobject", -] - [[package]] name = "utf8parse" version = "0.2.1" @@ -1507,6 +2028,18 @@ dependencies = [ "serde", ] +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "versions" version = "5.0.1" @@ -1517,6 +2050,26 @@ dependencies = [ "nom", ] +[[package]] +name = "vte" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197" +dependencies = [ + "utf8parse", + "vte_generate_state_changes", +] + +[[package]] +name = "vte_generate_state_changes" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" @@ -1611,16 +2164,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", + "windows-targets", ] [[package]] @@ -1629,22 +2173,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows-targets", ] [[package]] @@ -1653,81 +2182,45 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -1736,15 +2229,18 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" +name = "winnow" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +dependencies = [ + "memchr", +] [[package]] name = "yaml-rust" @@ -1754,3 +2250,9 @@ 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" diff --git a/Cargo.toml b/Cargo.toml index d4ef53bc..317924b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,38 +7,45 @@ repository = "https://github.com/kdheepak/taskwarrior-tui/" homepage = "https://kdheepak.com/taskwarrior-tui" readme = "README.md" authors = ["Dheepak Krishnamurthy "] -edition = "2018" +edition = "2021" keywords = ["taskwarrior", "tui"] categories = ["command-line-utilities"] [dependencies] -anyhow = "1.0.75" -better-panic = "0.3.0" cassowary = "0.3.0" -chrono = "0.4.26" -clap = { version = "4.4.1", features = ["derive"] } -crossterm = { version = "0.27.0", features = [ - "event-stream", -] } -dirs = "5.0.1" +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" futures = "0.3.28" +human-panic = "1.2.0" itertools = "0.11.0" lazy_static = "1.4.0" +libc = "0.2.147" log = "0.4.20" -log4rs = "1.2.0" path-clean = "1.0.1" +pretty_assertions = "1.4.0" rand = "0.8.5" -regex = "1.9.4" +ratatui = "0.23.0" +regex = "1.9.5" rustyline = { version = "12.0.0", features = ["with-file-history", "derive"] } serde = { version = "1.0.188", features = ["derive"] } +serde_derive = "1.0.188" serde_json = "1.0.105" shellexpand = "3.1.0" shlex = "1.1.0" +signal-hook = "0.3.17" +strip-ansi-escapes = "0.2.0" task-hookrs = "0.9.0" tokio = { version = "1.32.0", features = ["full"] } -tokio-stream = "0.1.14" -ratatui = "0.23.0" +tokio-util = "0.7.8" +tracing = "0.1.37" +tracing-error = "0.2.0" +tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } +tui-logger = { version = "0.9.5", features = ["ratatui-support", "tracing-support"] } unicode-segmentation = "1.10.1" unicode-truncate = "0.2.0" unicode-width = "0.1.10" @@ -55,11 +62,10 @@ buildflags = ["--release"] taskwarrior-tui = { path = "/usr/bin/taskwarrior-tui" } [profile.release] -debug = 1 incremental = true -lto = "off" +lto = true [build-dependencies] -clap = { version = "4.4.1", features = ["derive"] } +clap = { version = "4.4.2", features = ["derive"] } clap_complete = "4.4.0" shlex = "1.1.0" diff --git a/build.rs b/build.rs index 1031588e..fdba145a 100644 --- a/build.rs +++ b/build.rs @@ -18,7 +18,61 @@ fn run_pandoc() -> Result { cmd.output() } +fn get_commit_hash() { + let git_output = std::process::Command::new("git").args(["rev-parse", "--git-dir"]).output().ok(); + let git_dir = git_output.as_ref().and_then(|output| { + std::str::from_utf8(&output.stdout) + .ok() + .and_then(|s| s.strip_suffix('\n').or_else(|| s.strip_suffix("\r\n"))) + }); + + // Tell cargo to rebuild if the head or any relevant refs change. + if let Some(git_dir) = git_dir { + let git_path = std::path::Path::new(git_dir); + let refs_path = git_path.join("refs"); + if git_path.join("HEAD").exists() { + println!("cargo:rerun-if-changed={}/HEAD", git_dir); + } + if git_path.join("packed-refs").exists() { + println!("cargo:rerun-if-changed={}/packed-refs", git_dir); + } + if refs_path.join("heads").exists() { + println!("cargo:rerun-if-changed={}/refs/heads", git_dir); + } + if refs_path.join("tags").exists() { + println!("cargo:rerun-if-changed={}/refs/tags", git_dir); + } + } + + let git_output = std::process::Command::new("git") + .args(["describe", "--always", "--tags", "--long", "--dirty"]) + .output() + .ok(); + let git_info = git_output + .as_ref() + .and_then(|output| std::str::from_utf8(&output.stdout).ok().map(str::trim)); + let cargo_pkg_version = env!("CARGO_PKG_VERSION"); + + // Default git_describe to cargo_pkg_version + let mut git_describe = String::from(cargo_pkg_version); + + if let Some(git_info) = git_info { + // If the `git_info` contains `CARGO_PKG_VERSION`, we simply use `git_info` as it is. + // Otherwise, prepend `CARGO_PKG_VERSION` to `git_info`. + if git_info.contains(cargo_pkg_version) { + // Remove the 'g' before the commit sha + let git_info = &git_info.replace('g', ""); + git_describe = git_info.to_string(); + } else { + git_describe = format!("v{}-{}", cargo_pkg_version, git_info); + } + } + + println!("cargo:rustc-env=TASKWARRIOR_TUI_GIT_INFO={}", git_describe); +} + fn main() { + get_commit_hash(); let mut app = generate_cli_app(); let name = app.get_name().to_string(); let outdir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("completions/"); diff --git a/completions/_taskwarrior-tui b/completions/_taskwarrior-tui index b8bd2fa5..d93eeed0 100644 --- a/completions/_taskwarrior-tui +++ b/completions/_taskwarrior-tui @@ -17,8 +17,8 @@ _taskwarrior-tui() { _arguments "${_arguments_options[@]}" \ '-d+[Sets the data folder for taskwarrior-tui]:FOLDER: ' \ '--data=[Sets the data folder for taskwarrior-tui]:FOLDER: ' \ -'-c+[Sets the config folder for taskwarrior-tui (currently not used)]:FOLDER: ' \ -'--config=[Sets the config folder for taskwarrior-tui (currently not used)]:FOLDER: ' \ +'-c+[Sets the config folder for taskwarrior-tui]:FOLDER: ' \ +'--config=[Sets the config folder for taskwarrior-tui]:FOLDER: ' \ '--taskdata=[Sets the .task folder using the TASKDATA environment variable for taskwarrior]:FOLDER: ' \ '--taskrc=[Sets the .taskrc file using the TASKRC environment variable for taskwarrior]:FILE: ' \ '-r+[Sets default report]:STRING: ' \ diff --git a/completions/_taskwarrior-tui.ps1 b/completions/_taskwarrior-tui.ps1 index c80f1937..f1305960 100644 --- a/completions/_taskwarrior-tui.ps1 +++ b/completions/_taskwarrior-tui.ps1 @@ -23,8 +23,8 @@ Register-ArgumentCompleter -Native -CommandName 'taskwarrior-tui' -ScriptBlock { 'taskwarrior-tui' { [CompletionResult]::new('-d', 'd', [CompletionResultType]::ParameterName, 'Sets the data folder for taskwarrior-tui') [CompletionResult]::new('--data', 'data', [CompletionResultType]::ParameterName, 'Sets the data folder for taskwarrior-tui') - [CompletionResult]::new('-c', 'c', [CompletionResultType]::ParameterName, 'Sets the config folder for taskwarrior-tui (currently not used)') - [CompletionResult]::new('--config', 'config', [CompletionResultType]::ParameterName, 'Sets the config folder for taskwarrior-tui (currently not used)') + [CompletionResult]::new('-c', 'c', [CompletionResultType]::ParameterName, 'Sets the config folder for taskwarrior-tui') + [CompletionResult]::new('--config', 'config', [CompletionResultType]::ParameterName, 'Sets the config folder for taskwarrior-tui') [CompletionResult]::new('--taskdata', 'taskdata', [CompletionResultType]::ParameterName, 'Sets the .task folder using the TASKDATA environment variable for taskwarrior') [CompletionResult]::new('--taskrc', 'taskrc', [CompletionResultType]::ParameterName, 'Sets the .taskrc file using the TASKRC environment variable for taskwarrior') [CompletionResult]::new('-r', 'r', [CompletionResultType]::ParameterName, 'Sets default report') diff --git a/completions/taskwarrior-tui.fish b/completions/taskwarrior-tui.fish index 7a1ba99e..89e1bdea 100644 --- a/completions/taskwarrior-tui.fish +++ b/completions/taskwarrior-tui.fish @@ -1,5 +1,5 @@ complete -c taskwarrior-tui -s d -l data -d 'Sets the data folder for taskwarrior-tui' -r -complete -c taskwarrior-tui -s c -l config -d 'Sets the config folder for taskwarrior-tui (currently not used)' -r +complete -c taskwarrior-tui -s c -l config -d 'Sets the config folder for taskwarrior-tui' -r complete -c taskwarrior-tui -l taskdata -d 'Sets the .task folder using the TASKDATA environment variable for taskwarrior' -r complete -c taskwarrior-tui -l taskrc -d 'Sets the .taskrc file using the TASKRC environment variable for taskwarrior' -r complete -c taskwarrior-tui -s r -l report -d 'Sets default report' -r diff --git a/src/app.rs b/src/app.rs index ba4c9791..57a02dce 100644 --- a/src/app.rs +++ b/src/app.rs @@ -5,13 +5,13 @@ use std::{ convert::TryInto, fs, io, io::{Read, Write}, - path::Path, + path::{Path, PathBuf}, sync::{mpsc, Arc, Mutex}, time::{Duration, Instant, SystemTime}, }; -use anyhow::{anyhow, Context as AnyhowContext, Result}; use chrono::{DateTime, Datelike, FixedOffset, Local, NaiveDate, NaiveDateTime, TimeZone, Timelike}; +use color_eyre::eyre::{anyhow, Context as AnyhowContext, Result}; use crossterm::{ event::{DisableMouseCapture, EnableMouseCapture}, execute, @@ -57,7 +57,8 @@ use crate::{ scrollbar::Scrollbar, table::{Row, Table, TableMode, TableState}, task_report::TaskReportTable, - ui, utils, + ui, + utils::{self, get_data_dir}, }; const MAX_LINE: usize = 4096; @@ -177,7 +178,6 @@ pub struct TaskwarriorTui { pub all_tasks: Vec, pub task_details: HashMap, pub marked: HashSet, - // stores index of current task that is highlighted pub current_selection: usize, pub current_selection_uuid: Option, pub current_selection_id: Option, @@ -240,7 +240,7 @@ impl TaskwarriorTui { .output() .context("Unable to run `task --version`")?; - let task_version = Versioning::new(String::from_utf8_lossy(&output.stdout).trim()).context("Unable to get version string")?; + let task_version = Versioning::new(String::from_utf8_lossy(&output.stdout).trim()).ok_or(anyhow!("Unable to get version string"))?; let (w, h) = crossterm::terminal::size().unwrap_or((50, 15)); @@ -251,6 +251,8 @@ impl TaskwarriorTui { }; let event_loop = crate::event::EventLoop::new(tick_rate, init_event_loop); + let data_dir = get_data_dir(); + let mut app = Self { should_quit: false, dirty: true, @@ -280,8 +282,8 @@ impl TaskwarriorTui { keyconfig: kc, terminal_width: w, terminal_height: h, - filter_history: HistoryContext::new("filter.history"), - command_history: HistoryContext::new("command.history"), + filter_history: HistoryContext::new("filter.history", data_dir.clone()), + command_history: HistoryContext::new("command.history", data_dir.clone()), history_status: None, completion_list: CompletionList::with_items(vec![]), show_completion_pane: false, diff --git a/src/cli.rs b/src/cli.rs index 3482cebe..dbf0a92c 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -21,7 +21,7 @@ pub fn generate_cli_app() -> clap::Command { .short('c') .long("config") .value_name("FOLDER") - .help("Sets the config folder for taskwarrior-tui (currently not used)") + .help("Sets the config folder for taskwarrior-tui") .action(clap::ArgAction::Set), ) .arg( diff --git a/src/config.rs b/src/config.rs index fa7afa1c..26811d14 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, error::Error, str}; -use anyhow::{Context, Result}; +use color_eyre::eyre::{eyre, Context, Result}; use ratatui::{ style::{Color, Modifier, Style}, symbols::{bar::FULL, line::DOUBLE_VERTICAL}, @@ -465,14 +465,14 @@ impl Config { fn get_rule_precedence_color(data: &str) -> Vec { let data = Self::get_config("rule.precedence.color", data) - .context("Unable to parse `task show rule.precedence.color`.") + .ok_or_else(|| eyre!("Unable to parse `task show rule.precedence.color`.")) .unwrap(); data.split(',').map(ToString::to_string).collect::>() } fn get_uda_priority_values(data: &str) -> Vec { let data = Self::get_config("uda.priority.values", data) - .context("Unable to parse `task show uda.priority.values`.") + .ok_or_else(|| eyre!("Unable to parse `task show uda.priority.values`.")) .unwrap(); data.split(',').map(ToString::to_string).collect::>() } @@ -489,7 +489,7 @@ impl Config { fn get_data_location(data: &str) -> String { Self::get_config("data.location", data) - .context("Unable to parse `task show data.location`.") + .ok_or_else(|| eyre!("Unable to parse `task show data.location`.")) .unwrap() } diff --git a/src/history.rs b/src/history.rs index 1b9aa52d..01b3cd74 100644 --- a/src/history.rs +++ b/src/history.rs @@ -3,7 +3,7 @@ use std::{ path::{Path, PathBuf}, }; -use anyhow::{anyhow, Result}; +use color_eyre::eyre::{anyhow, Result}; use rustyline::{ error::ReadlineError, history::{DefaultHistory, History, SearchDirection}, @@ -16,17 +16,9 @@ pub struct HistoryContext { } impl HistoryContext { - pub fn new(filename: &str) -> Self { + pub fn new(filename: &str, data_path: PathBuf) -> Self { let history = DefaultHistory::new(); - let data_path = if let Ok(s) = std::env::var("TASKWARRIOR_TUI_DATA") { - PathBuf::from(s) - } else { - dirs::data_local_dir() - .map(|d| d.join("taskwarrior-tui")) - .expect("Unable to create configuration directory for taskwarrior-tui") - }; - std::fs::create_dir_all(&data_path).unwrap_or_else(|_| panic!("Unable to create configuration directory in {:?}", &data_path)); let data_path = data_path.join(filename); diff --git a/src/keyconfig.rs b/src/keyconfig.rs index 8db43afa..3bb45d57 100644 --- a/src/keyconfig.rs +++ b/src/keyconfig.rs @@ -1,6 +1,6 @@ use std::{collections::HashSet, error::Error, hash::Hash}; -use anyhow::{anyhow, Result}; +use color_eyre::eyre::{anyhow, Result}; use log::{error, info, warn}; use serde::{Deserialize, Serialize}; diff --git a/src/main.rs b/src/main.rs index f47d328d..396234e4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,6 +17,7 @@ mod pane; mod scrollbar; mod table; mod task_report; +mod tui; mod ui; mod utils; @@ -29,8 +30,8 @@ use std::{ time::Duration, }; -use anyhow::Result; use app::{Mode, TaskwarriorTui}; +use color_eyre::eyre::Result; use crossterm::{ cursor, event::{DisableMouseCapture, EnableMouseCapture, EventStream}, @@ -39,91 +40,20 @@ use crossterm::{ }; use futures::stream::{FuturesUnordered, StreamExt}; use log::{debug, error, info, log_enabled, trace, warn, Level, LevelFilter}; -use log4rs::{ - append::file::FileAppender, - config::{Appender, Config, Logger, Root}, - encode::pattern::PatternEncoder, -}; -use path_clean::PathClean; use ratatui::{backend::CrosstermBackend, Terminal}; +use utils::{get_config_dir, get_data_dir}; -use crate::{action::Action, event::Event, keyconfig::KeyConfig}; +use crate::{ + action::Action, + event::Event, + keyconfig::KeyConfig, + utils::{initialize_logging, initialize_panic_handler}, +}; const LOG_PATTERN: &str = "{d(%Y-%m-%d %H:%M:%S)} | {l} | {f}:{L} | {m}{n}"; -pub fn destruct_terminal() { - disable_raw_mode().unwrap(); - execute!(io::stdout(), LeaveAlternateScreen, DisableMouseCapture).unwrap(); - execute!(io::stdout(), cursor::Show).unwrap(); -} - -pub fn initialize_logging() { - let data_local_dir = if let Ok(s) = std::env::var("TASKWARRIOR_TUI_DATA") { - PathBuf::from(s) - } else { - dirs::data_local_dir() - .expect("Unable to find data directory for taskwarrior-tui") - .join("taskwarrior-tui") - }; - - std::fs::create_dir_all(&data_local_dir).unwrap_or_else(|_| panic!("Unable to create {:?}", data_local_dir)); - - let logfile = FileAppender::builder() - .encoder(Box::new(PatternEncoder::new(LOG_PATTERN))) - .append(false) - .build(data_local_dir.join("taskwarrior-tui.log")) - .expect("Failed to build log file appender."); - - let levelfilter = match std::env::var("TASKWARRIOR_TUI_LOG_LEVEL").unwrap_or_else(|_| "info".to_string()).as_str() { - "off" => LevelFilter::Off, - "warn" => LevelFilter::Warn, - "info" => LevelFilter::Info, - "debug" => LevelFilter::Debug, - "trace" => LevelFilter::Trace, - _ => LevelFilter::Info, - }; - let config = Config::builder() - .appender(Appender::builder().build("logfile", Box::new(logfile))) - .logger(Logger::builder().build("taskwarrior_tui", levelfilter)) - .build(Root::builder().appender("logfile").build(LevelFilter::Info)) - .expect("Failed to build logging config."); - - log4rs::init_config(config).expect("Failed to initialize logging."); -} - -pub fn absolute_path(path: impl AsRef) -> io::Result { - let path = path.as_ref(); - - let absolute_path = if path.is_absolute() { - path.to_path_buf() - } else { - env::current_dir()?.join(path) - } - .clean(); - - Ok(absolute_path) -} - -async fn tui_main(report: &str) -> Result<()> { - panic::set_hook(Box::new(|panic_info| { - destruct_terminal(); - better_panic::Settings::auto().create_panic_handler()(panic_info); - })); - - let mut app = app::TaskwarriorTui::new(report, true).await?; - - let mut terminal = app.start_tui()?; - - let r = app.run(&mut terminal).await; - - app.pause_tui().await?; - - r -} - -fn main() -> Result<()> { - better_panic::install(); - +#[tokio::main] +async fn main() -> Result<()> { let matches = cli::generate_cli_app().get_matches(); let config = matches.get_one::("config"); @@ -133,58 +63,42 @@ fn main() -> Result<()> { let binding = String::from("next"); let report = matches.get_one::("report").unwrap_or(&binding); - if let Some(e) = config { - if env::var("TASKWARRIOR_TUI_CONFIG").is_err() { - // if environment variable is not set, this env::var returns an error - env::set_var( - "TASKWARRIOR_TUI_CONFIG", - absolute_path(PathBuf::from(e)).expect("Unable to get path for config"), - ) - } else { - warn!("TASKWARRIOR_TUI_CONFIG environment variable cannot be set.") - } - } + let config_dir = config.map(PathBuf::from).unwrap_or_else(get_config_dir); + let data_dir = data.map(PathBuf::from).unwrap_or_else(get_data_dir); + + // if let Some(e) = taskrc { + // if env::var("TASKRC").is_err() { + // // if environment variable is not set, this env::var returns an error + // env::set_var("TASKRC", absolute_path(PathBuf::from(e)).expect("Unable to get path for taskrc")) + // } else { + // warn!("TASKRC environment variable cannot be set.") + // } + // } + // + // if let Some(e) = taskdata { + // if env::var("TASKDATA").is_err() { + // // if environment variable is not set, this env::var returns an error + // env::set_var("TASKDATA", absolute_path(PathBuf::from(e)).expect("Unable to get path for taskdata")) + // } else { + // warn!("TASKDATA environment variable cannot be set.") + // } + // } + + initialize_logging(data_dir)?; + initialize_panic_handler()?; - if let Some(e) = data { - if env::var("TASKWARRIOR_TUI_DATA").is_err() { - // if environment variable is not set, this env::var returns an error - env::set_var( - "TASKWARRIOR_TUI_DATA", - absolute_path(PathBuf::from(e)).expect("Unable to get path for data"), - ) - } else { - warn!("TASKWARRIOR_TUI_DATA environment variable cannot be set.") - } - } + debug!("getting matches from clap..."); + debug!("report = {:?}", &report); + debug!("config = {:?}", &config); - if let Some(e) = taskrc { - if env::var("TASKRC").is_err() { - // if environment variable is not set, this env::var returns an error - env::set_var("TASKRC", absolute_path(PathBuf::from(e)).expect("Unable to get path for taskrc")) - } else { - warn!("TASKRC environment variable cannot be set.") - } - } + let mut app = app::TaskwarriorTui::new(report, true).await?; - if let Some(e) = taskdata { - if env::var("TASKDATA").is_err() { - // if environment variable is not set, this env::var returns an error - env::set_var("TASKDATA", absolute_path(PathBuf::from(e)).expect("Unable to get path for taskdata")) - } else { - warn!("TASKDATA environment variable cannot be set.") - } - } + let mut terminal = app.start_tui()?; - initialize_logging(); + let r = app.run(&mut terminal).await; - debug!("getting matches from clap..."); - debug!("report = {:?}", &report); - debug!("config = {:?}", &config); + app.pause_tui().await?; - let r = tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build()? - .block_on(async { tui_main(report).await }); if let Err(err) = r { eprintln!("\x1b[0;31m[taskwarrior-tui error]\x1b[0m: {}\n\nIf you need additional help, please report as a github issue on https://github.com/kdheepak/taskwarrior-tui", err); std::process::exit(1); diff --git a/src/pane/context.rs b/src/pane/context.rs index 07a5daf7..3df22b9f 100644 --- a/src/pane/context.rs +++ b/src/pane/context.rs @@ -1,6 +1,6 @@ use std::fmt; -use anyhow::{anyhow, Context as AnyhowContext, Result}; +use color_eyre::eyre::{anyhow, Context as AnyhowContext, Result}; const NAME: &str = "Name"; const TYPE: &str = "Remaining"; diff --git a/src/pane/mod.rs b/src/pane/mod.rs index ee1f1b30..afd7b7d9 100644 --- a/src/pane/mod.rs +++ b/src/pane/mod.rs @@ -1,6 +1,6 @@ use std::ops::Index; -use anyhow::Result; +use color_eyre::eyre::Result; use crate::{ action::Action, diff --git a/src/pane/project.rs b/src/pane/project.rs index 5e379067..813036af 100644 --- a/src/pane/project.rs +++ b/src/pane/project.rs @@ -1,6 +1,6 @@ use std::fmt; -use anyhow::{anyhow, Context as AnyhowContext, Result}; +use color_eyre::eyre::{anyhow, Context as AnyhowContext, Result}; const COL_WIDTH: usize = 21; const PROJECT_HEADER: &str = "Name"; diff --git a/src/task_report.rs b/src/task_report.rs index 33572956..ec8a48bc 100644 --- a/src/task_report.rs +++ b/src/task_report.rs @@ -1,7 +1,7 @@ use std::{error::Error, process::Command}; -use anyhow::Result; use chrono::{DateTime, Datelike, Local, NaiveDate, NaiveDateTime, TimeZone}; +use color_eyre::eyre::Result; use itertools::join; use task_hookrs::{task::Task, uda::UDAValue}; use unicode_truncate::UnicodeTruncateStr; @@ -14,7 +14,7 @@ pub fn format_date_time(dt: NaiveDateTime) -> String { pub fn format_date(dt: NaiveDateTime) -> String { let offset = Local.offset_from_utc_datetime(&dt); - let dt = DateTime::::from_utc(dt, offset); + let dt = DateTime::::from_naive_utc_and_offset(dt, offset); dt.format("%Y-%m-%d").to_string() } diff --git a/src/tui.rs b/src/tui.rs new file mode 100644 index 00000000..1e263036 --- /dev/null +++ b/src/tui.rs @@ -0,0 +1,150 @@ +use std::ops::{Deref, DerefMut}; + +use color_eyre::eyre::Result; +use crossterm::{ + cursor, + event::{Event as CrosstermEvent, KeyEvent, KeyEventKind, MouseEvent}, + terminal::{EnterAlternateScreen, LeaveAlternateScreen}, +}; +use futures::{FutureExt, StreamExt}; +use ratatui::backend::CrosstermBackend as Backend; +use serde_derive::{Deserialize, Serialize}; +use tokio::{ + sync::mpsc::{self, UnboundedReceiver, UnboundedSender}, + task::JoinHandle, +}; +use tokio_util::sync::CancellationToken; + +pub type CrosstermFrame<'a> = ratatui::Frame<'a, Backend>; + +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub enum Event { + Quit, + Error, + Closed, + Tick, + Key(KeyEvent), + Mouse(MouseEvent), + Resize(u16, u16), +} + +pub struct Tui { + pub terminal: ratatui::Terminal>, + pub task: JoinHandle<()>, + pub cancellation_token: CancellationToken, + pub event_rx: UnboundedReceiver, + pub event_tx: UnboundedSender, + pub tick_rate: usize, +} + +impl Tui { + pub fn new(tick_rate: usize) -> Result { + let terminal = ratatui::Terminal::new(Backend::new(std::io::stderr()))?; + let (event_tx, event_rx) = mpsc::unbounded_channel(); + let cancellation_token = CancellationToken::new(); + let task = tokio::spawn(async {}); + Ok(Self { + terminal, + task, + cancellation_token, + event_rx, + event_tx, + tick_rate, + }) + } + + pub fn start(&mut self) { + let tick_rate = std::time::Duration::from_millis(self.tick_rate as u64); + self.cancellation_token.cancel(); + self.cancellation_token = CancellationToken::new(); + let _cancellation_token = self.cancellation_token.clone(); + let _event_tx = self.event_tx.clone(); + self.task = tokio::spawn(async move { + let mut reader = crossterm::event::EventStream::new(); + let mut interval = tokio::time::interval(tick_rate); + loop { + let delay = interval.tick(); + let crossterm_event = reader.next().fuse(); + tokio::select! { + _ = _cancellation_token.cancelled() => { + break; + } + maybe_event = crossterm_event => { + match maybe_event { + Some(Ok(evt)) => { + match evt { + CrosstermEvent::Key(key) => { + if key.kind == KeyEventKind::Press { + _event_tx.send(Event::Key(key)).unwrap(); + } + }, + CrosstermEvent::Resize(x, y) => { + _event_tx.send(Event::Resize(x, y)).unwrap(); + }, + _ => {}, + } + } + Some(Err(_)) => { + _event_tx.send(Event::Error).unwrap(); + } + None => {}, + } + }, + _ = delay => { + _event_tx.send(Event::Tick).unwrap(); + }, + } + } + }); + } + + pub fn enter(&mut self) -> Result<()> { + crossterm::terminal::enable_raw_mode()?; + crossterm::execute!(std::io::stderr(), EnterAlternateScreen, cursor::Hide)?; + self.start(); + Ok(()) + } + + pub fn exit(&self) -> Result<()> { + crossterm::execute!(std::io::stderr(), LeaveAlternateScreen, cursor::Show)?; + crossterm::terminal::disable_raw_mode()?; + self.cancellation_token.cancel(); + Ok(()) + } + + pub fn suspend(&self) -> Result<()> { + self.exit()?; + #[cfg(not(windows))] + signal_hook::low_level::raise(signal_hook::consts::signal::SIGTSTP)?; + Ok(()) + } + + pub fn resume(&mut self) -> Result<()> { + self.enter()?; + Ok(()) + } + + pub async fn next(&mut self) -> Option { + self.event_rx.recv().await + } +} + +impl Deref for Tui { + type Target = ratatui::Terminal>; + + fn deref(&self) -> &Self::Target { + &self.terminal + } +} + +impl DerefMut for Tui { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.terminal + } +} + +impl Drop for Tui { + fn drop(&mut self) { + self.exit().unwrap(); + } +} diff --git a/src/utils.rs b/src/utils.rs index 57f228ab..2ffa161b 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -15,3 +15,151 @@ impl ChangeListener for Changeset { fn replace(&mut self, idx: usize, old: &str, new: &str) {} } + +use std::path::PathBuf; + +use color_eyre::eyre::{anyhow, Context, Result}; +use directories::ProjectDirs; +use lazy_static::lazy_static; +use tracing::error; +use tracing_error::ErrorLayer; +use tracing_subscriber::{self, filter::EnvFilter, prelude::__tracing_subscriber_SubscriberExt, util::SubscriberInitExt, Layer}; + +use crate::tui::Tui; + +lazy_static! { + pub static ref CRATE_NAME: String = env!("CARGO_CRATE_NAME").to_uppercase().to_string(); + pub static ref DATA_FOLDER: Option = std::env::var(format!("{}_DATA", CRATE_NAME.clone())).ok().map(PathBuf::from); + pub static ref CONFIG_FOLDER: Option = std::env::var(format!("{}_CONFIG", CRATE_NAME.clone())).ok().map(PathBuf::from); + pub static ref GIT_COMMIT_HASH: String = std::env::var(format!("{}_GIT_INFO", CRATE_NAME.clone())).unwrap_or_else(|_| String::from("Unknown")); + pub static ref LOG_FILE: String = format!("{}.log", CRATE_NAME.to_lowercase()); +} + +fn project_directory() -> Option { + ProjectDirs::from("com", "kdheepak", CRATE_NAME.clone().to_lowercase().as_str()) +} + +pub fn initialize_panic_handler() -> Result<()> { + let (panic_hook, eyre_hook) = color_eyre::config::HookBuilder::default().into_hooks(); + eyre_hook.install()?; + std::panic::set_hook(Box::new(move |panic_info| { + if let Ok(t) = Tui::new(0) { + if let Err(r) = t.exit() { + error!("Unable to exit Terminal: {:?}", r); + } + } + let msg = format!("{}", panic_hook.panic_report(panic_info)); + tracing::error!("{}", strip_ansi_escapes::strip_str(&msg)); + use human_panic::{handle_dump, print_msg, Metadata}; + let meta = Metadata { + version: env!("CARGO_PKG_VERSION").into(), + name: env!("CARGO_PKG_NAME").into(), + authors: env!("CARGO_PKG_AUTHORS").replace(':', ", ").into(), + homepage: env!("CARGO_PKG_HOMEPAGE").into(), + }; + let file_path = handle_dump(&meta, panic_info); + print_msg(file_path, &meta).expect("human-panic: printing error message to console failed"); + eprintln!("{}", msg); + std::process::exit(libc::EXIT_FAILURE); + })); + Ok(()) +} + +pub fn get_data_dir() -> PathBuf { + let directory = if let Some(s) = DATA_FOLDER.clone() { + s + } else if let Some(proj_dirs) = project_directory() { + proj_dirs.data_local_dir().to_path_buf() + } else { + PathBuf::from(".").join(".data") + }; + directory +} + +pub fn get_config_dir() -> PathBuf { + let directory = if let Some(s) = CONFIG_FOLDER.clone() { + s + } else if let Some(proj_dirs) = project_directory() { + proj_dirs.config_local_dir().to_path_buf() + } else { + PathBuf::from(".").join(".config") + }; + directory +} + +pub fn initialize_logging(directory: PathBuf) -> Result<()> { + std::fs::create_dir_all(directory.clone())?; + let log_path = directory.join(LOG_FILE.clone()); + let log_file = std::fs::File::create(log_path)?; + let file_subscriber = tracing_subscriber::fmt::layer() + .with_file(true) + .with_line_number(true) + .with_writer(log_file) + .with_target(false) + .with_ansi(false) + .with_filter(EnvFilter::from_default_env()); + + tracing_subscriber::registry() + .with(file_subscriber) + .with(tui_logger::tracing_subscriber_layer()) + .with(ErrorLayer::default()) + .init(); + let default_level = std::env::var("RUST_LOG").map_or(log::LevelFilter::Info, |val| match val.to_lowercase().as_str() { + "off" => log::LevelFilter::Off, + "error" => log::LevelFilter::Error, + "warn" => log::LevelFilter::Warn, + "info" => log::LevelFilter::Info, + "debug" => log::LevelFilter::Debug, + "trace" => log::LevelFilter::Trace, + _ => log::LevelFilter::Info, + }); + tui_logger::set_default_level(default_level); + + Ok(()) +} + +/// Similar to the `std::dbg!` macro, but generates `tracing` events rather +/// than printing to stdout. +/// +/// By default, the verbosity level for the generated events is `DEBUG`, but +/// this can be customized. +#[macro_export] +macro_rules! trace_dbg { + (target: $target:expr, level: $level:expr, $ex:expr) => {{ + match $ex { + value => { + tracing::event!(target: $target, $level, ?value, stringify!($ex)); + value + } + } + }}; + (level: $level:expr, $ex:expr) => { + trace_dbg!(target: module_path!(), level: $level, $ex) + }; + (target: $target:expr, $ex:expr) => { + trace_dbg!(target: $target, level: tracing::Level::DEBUG, $ex) + }; + ($ex:expr) => { + trace_dbg!(level: tracing::Level::DEBUG, $ex) + }; +} + +pub fn version() -> String { + let author = clap::crate_authors!(); + + let commit_hash = GIT_COMMIT_HASH.clone(); + + // let current_exe_path = PathBuf::from(clap::crate_name!()).display().to_string(); + let config_dir_path = get_config_dir().display().to_string(); + let data_dir_path = get_data_dir().display().to_string(); + + format!( + "\ +{commit_hash} + +Authors: {author} + +Config directory: {config_dir_path} +Data directory: {data_dir_path}" + ) +} From 265ecd85c7bd6b0f5429ab14ed0e1bb765b17598 Mon Sep 17 00:00:00 2001 From: Dheepak Krishnamurthy Date: Sun, 3 Sep 2023 15:46:29 -0400 Subject: [PATCH 02/13] =?UTF-8?q?feat:=20Use=20tui-input=20and=20tui.rs=20?= =?UTF-8?q?instead=20of=20event.rs=20=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 11 + Cargo.toml | 1 + src/app.rs | 529 ++++++++++++++++++++++---------------------- src/event.rs | 128 ----------- src/keyconfig.rs | 3 +- src/main.rs | 8 +- src/pane/context.rs | 1 - src/pane/mod.rs | 5 +- src/pane/project.rs | 21 +- 9 files changed, 295 insertions(+), 412 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8b1f1a90..4a23efc9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1704,6 +1704,7 @@ dependencies = [ "tracing", "tracing-error", "tracing-subscriber", + "tui-input", "tui-logger", "unicode-segmentation", "unicode-truncate", @@ -1946,6 +1947,16 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "tui-input" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3e785f863a3af4c800a2a669d0b64c879b538738e352607e2624d03f868dc01" +dependencies = [ + "crossterm", + "unicode-width", +] + [[package]] name = "tui-logger" version = "0.9.5" diff --git a/Cargo.toml b/Cargo.toml index 317924b7..bcc7d72a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,7 @@ tokio-util = "0.7.8" tracing = "0.1.37" tracing-error = "0.2.0" tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } +tui-input = "0.8.0" tui-logger = { version = "0.9.5", features = ["ratatui-support", "tracing-support"] } unicode-segmentation = "1.10.1" unicode-truncate = "0.2.0" diff --git a/src/app.rs b/src/app.rs index 57a02dce..f0990dea 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}, + event::{DisableMouseCapture, EnableMouseCapture, KeyCode, KeyModifiers}, execute, style::style, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, @@ -32,8 +32,10 @@ use ratatui::{ Terminal, }; use regex::Regex; -use rustyline::{history::SearchDirection as HistoryDirection, line_buffer::LineBuffer, At, Editor, Word}; +use rustyline::{history::SearchDirection as HistoryDirection, At, Editor, Word}; use task_hookrs::{date::Date, import::import, project::Project, status::TaskStatus, task::Task}; +use tui_input::backend::crossterm::EventHandler; +use tui_input::Input; use unicode_segmentation::{Graphemes, UnicodeSegmentation}; use unicode_width::UnicodeWidthStr; use uuid::Uuid; @@ -45,7 +47,6 @@ use crate::{ completion::{get_start_word_under_cursor, CompletionList}, config, config::Config, - event::{Event, KeyCode}, help::Help, history::HistoryContext, keyconfig::KeyConfig, @@ -57,6 +58,7 @@ use crate::{ scrollbar::Scrollbar, table::{Row, Table, TableMode, TableState}, task_report::TaskReportTable, + tui::{self, Event}, ui, utils::{self, get_data_dir}, }; @@ -166,14 +168,15 @@ pub enum Mode { } pub struct TaskwarriorTui { + pub tick_rate: u64, pub should_quit: bool, pub dirty: bool, pub task_table_state: TableState, pub current_context_filter: String, pub current_context: String, - pub command: LineBuffer, - pub filter: LineBuffer, - pub modify: LineBuffer, + pub command: Input, + pub filter: Input, + pub modify: Input, pub tasks: Vec, pub all_tasks: Vec, pub task_details: HashMap, @@ -204,7 +207,6 @@ pub struct TaskwarriorTui { pub contexts: ContextsState, pub task_version: Versioning, pub error: Option, - pub event_loop: crate::event::EventLoop, pub requires_redraw: bool, pub changes: utils::Changeset, } @@ -244,16 +246,10 @@ impl TaskwarriorTui { let (w, h) = crossterm::terminal::size().unwrap_or((50, 15)); - let tick_rate = if c.uda_tick_rate > 0 { - Some(std::time::Duration::from_millis(c.uda_tick_rate)) - } else { - None - }; - let event_loop = crate::event::EventLoop::new(tick_rate, init_event_loop); - let data_dir = get_data_dir(); let mut app = Self { + tick_rate: c.uda_tick_rate, should_quit: false, dirty: true, task_table_state: TableState::default(), @@ -266,9 +262,9 @@ impl TaskwarriorTui { current_selection_id: None, current_context_filter: "".to_string(), current_context: "".to_string(), - command: LineBuffer::with_capacity(MAX_LINE), - filter: LineBuffer::with_capacity(MAX_LINE), - modify: LineBuffer::with_capacity(MAX_LINE), + command: Input::default(), + filter: Input::default(), + modify: Input::default(), mode: Mode::Tasks(Action::Report), previous_mode: None, task_report_height: 0, @@ -292,21 +288,18 @@ impl TaskwarriorTui { contexts: ContextsState::new(), task_version, error: None, - event_loop, requires_redraw: false, changes: utils::Changeset::default(), }; - for c in app.config.filter.chars() { - app.filter.insert(c, 1, &mut app.changes); - } + app.filter = app.filter.with_value(app.config.filter.clone()); app.task_report_table.date_time_vague_precise = app.config.uda_task_report_date_time_vague_more_precise; app.update(true).await?; app.filter_history.load()?; - app.filter_history.add(app.filter.as_str()); + app.filter_history.add(app.filter.value()); app.command_history.load()?; app.task_background(); @@ -342,60 +335,52 @@ impl TaskwarriorTui { Ok(()) } - pub async fn abort_event_loop(&mut self) -> Result<()> { - self.event_loop.abort.send(())?; - while let Some(event) = self.next().await { - if let Event::Closed = event { - break; - } - } - Ok(()) - } - pub async fn resume_event_loop(&mut self) -> Result<()> { let tick_rate = if self.config.uda_tick_rate > 0 { Some(std::time::Duration::from_millis(self.config.uda_tick_rate)) } else { None }; - self.event_loop = crate::event::EventLoop::new(tick_rate, true); + // self.event_loop = crate::event::EventLoop::new(tick_rate, true); Ok(()) } - pub async fn pause_tui(&mut self) -> Result<()> { - self.abort_event_loop().await?; - let backend = CrosstermBackend::new(io::stdout()); - let mut terminal = Terminal::new(backend)?; - disable_raw_mode()?; - execute!(io::stdout(), LeaveAlternateScreen, DisableMouseCapture)?; - terminal.show_cursor()?; - Ok(()) - } - - pub async fn next(&mut self) -> Option> { - self.event_loop.rx.recv().await - } + pub async fn run(&mut self) -> Result<()> { + let mut tui = tui::Tui::new(self.tick_rate as usize)?; + tui.enter()?; - pub async fn run(&mut self, terminal: &mut Terminal) -> Result<()> { loop { if self.requires_redraw { - terminal.resize(terminal.size()?)?; + let s = tui.size()?; + tui.resize(s)?; self.requires_redraw = false; } - terminal.draw(|f| self.draw(f))?; + tui.draw(|f| self.draw(f))?; // Handle input - if let Some(event) = self.next().await { + if let Some(event) = tui.next().await { match event { - Event::Input(input) => { - debug!("Received input = {:?}", input); - self.handle_input(input).await?; + Event::Key(keyevent) => { + debug!("Received input = {:?}", keyevent); + self.handle_input(keyevent.code).await?; + } + Event::Mouse(mouseevent) => { + debug!("tui mouseevent") } Event::Tick => { debug!("Tick event"); self.update(false).await?; } Event::Closed => { - debug!("Event loop closed"); + debug!("tui closed"); + } + Event::Error => { + debug!("tui errored"); + } + Event::Resize(x, y) => { + debug!("tui resized"); + } + Event::Quit => { + debug!("tui quit") } } } @@ -404,11 +389,12 @@ impl TaskwarriorTui { break; } } + tui.exit()?; Ok(()) } pub fn reset_command(&mut self) { - self.command.update("", 0, &mut self.changes) + self.command.reset() } pub fn get_context(&mut self) -> Result<()> { @@ -608,23 +594,23 @@ impl TaskwarriorTui { // reset error when entering Action::Report self.previous_mode = None; self.error = None; - let position = Self::get_position(&self.command); + let position = self.command.visual_cursor(); self.draw_command( f, rects[1], - self.filter.as_str(), + self.filter.value(), (Span::raw("Filter Tasks"), self.history_status.as_ref().map(Span::raw)), - Self::get_position(&self.filter), + self.filter.visual_cursor(), false, self.error.clone(), ); } Action::Jump => { - let position = Self::get_position(&self.command); + let position = self.command.visual_cursor(); self.draw_command( f, rects[1], - self.command.as_str(), + self.command.value(), (Span::styled("Jump to Task", Style::default().add_modifier(Modifier::BOLD)), None), position, true, @@ -632,14 +618,14 @@ impl TaskwarriorTui { ); } Action::Filter => { - let position = Self::get_position(&self.filter); + let position = self.filter.visual_cursor(); if self.show_completion_pane { self.draw_completion_pop_up(f, rects[1], position); } self.draw_command( f, rects[1], - self.filter.as_str(), + self.filter.value(), ( Span::styled("Filter Tasks", Style::default().add_modifier(Modifier::BOLD)), self @@ -653,17 +639,17 @@ impl TaskwarriorTui { ); } Action::Log => { - if self.config.uda_auto_insert_double_quotes_on_log && self.command.is_empty() { - self.command.update(r#""""#, 1, &mut self.changes); + if self.config.uda_auto_insert_double_quotes_on_log && self.command.value().is_empty() { + self.command = self.command.clone().with_value(r#""""#.to_string()); }; - let position = Self::get_position(&self.command); + let position = self.command.visual_cursor(); if self.show_completion_pane { self.draw_completion_pop_up(f, rects[1], position); } self.draw_command( f, rects[1], - self.command.as_str(), + self.command.value(), ( Span::styled("Log Task", Style::default().add_modifier(Modifier::BOLD)), self @@ -677,11 +663,11 @@ impl TaskwarriorTui { ); } Action::Subprocess => { - let position = Self::get_position(&self.command); + let position = self.command.visual_cursor(); self.draw_command( f, rects[1], - self.command.as_str(), + self.command.value(), (Span::styled("Shell Command", Style::default().add_modifier(Modifier::BOLD)), None), position, true, @@ -689,7 +675,7 @@ impl TaskwarriorTui { ); } Action::Modify => { - let position = Self::get_position(&self.modify); + let position = self.modify.visual_cursor(); if self.show_completion_pane { self.draw_completion_pop_up(f, rects[1], position); } @@ -701,7 +687,7 @@ impl TaskwarriorTui { self.draw_command( f, rects[1], - self.modify.as_str(), + self.modify.value(), ( Span::styled(label, Style::default().add_modifier(Modifier::BOLD)), self @@ -715,10 +701,10 @@ impl TaskwarriorTui { ); } Action::Annotate => { - if self.config.uda_auto_insert_double_quotes_on_annotate && self.command.is_empty() { - self.command.update(r#""""#, 1, &mut self.changes); + if self.config.uda_auto_insert_double_quotes_on_annotate && self.command.value().is_empty() { + self.command = self.command.clone().with_value(r#""""#.to_string()); }; - let position = Self::get_position(&self.command); + let position = self.command.visual_cursor(); if self.show_completion_pane { self.draw_completion_pop_up(f, rects[1], position); } @@ -730,7 +716,7 @@ impl TaskwarriorTui { self.draw_command( f, rects[1], - self.command.as_str(), + self.command.value(), ( Span::styled(label, Style::default().add_modifier(Modifier::BOLD)), self @@ -744,17 +730,17 @@ impl TaskwarriorTui { ); } Action::Add => { - if self.config.uda_auto_insert_double_quotes_on_add && self.command.is_empty() { - self.command.update(r#""""#, 1, &mut self.changes); + if self.config.uda_auto_insert_double_quotes_on_add && self.command.value().is_empty() { + self.command = self.command.clone().with_value(r#""""#.to_string()); }; - let position = Self::get_position(&self.command); + let position = self.command.visual_cursor(); if self.show_completion_pane { self.draw_completion_pop_up(f, rects[1], position); } self.draw_command( f, rects[1], - self.command.as_str(), + self.command.value(), ( Span::styled("Add Task", Style::default().add_modifier(Modifier::BOLD)), self @@ -771,9 +757,9 @@ impl TaskwarriorTui { self.draw_command( f, rects[1], - self.filter.as_str(), + self.filter.value(), ("Filter Tasks".into(), None), - Self::get_position(&self.filter), + self.filter.visual_cursor(), false, self.error.clone(), ); @@ -783,9 +769,9 @@ impl TaskwarriorTui { self.draw_command( f, rects[1], - self.filter.as_str(), + self.filter.value(), ("Filter Tasks".into(), None), - Self::get_position(&self.filter), + self.filter.visual_cursor(), false, self.error.clone(), ); @@ -879,17 +865,6 @@ impl TaskwarriorTui { } } - pub fn get_position(lb: &LineBuffer) -> usize { - let mut position = 0; - for (i, (j, g)) in lb.as_str().grapheme_indices(true).enumerate() { - if j == lb.pos() { - break; - } - position += g.width(); - } - position - } - fn draw_help_popup(&mut self, f: &mut Frame, percent_x: u16, percent_y: u16) { let area = centered_rect(percent_x, percent_y, f.size()); f.render_widget(Clear, area); @@ -1598,7 +1573,7 @@ impl TaskwarriorTui { if self.tasks.is_empty() { return Ok(()); } - let i = self.command.as_str().parse::()?; + let i = self.command.value().parse::()?; if let Some(task) = self.task_by_id(i as u64) { let j = self.task_index_by_uuid(*task.uuid()).unwrap_or_default(); self.current_selection = j; @@ -1691,7 +1666,7 @@ impl TaskwarriorTui { .arg("rc._forcecolor=off"); // .arg("rc.verbose:override=false"); - if let Some(args) = shlex::split(format!(r#"rc.report.{}.filter='{}'"#, self.report, self.filter.trim()).trim()) { + if let Some(args) = shlex::split(format!(r#"rc.report.{}.filter='{}'"#, self.report, self.filter.value().trim()).trim()) { for arg in args { task.arg(arg); } @@ -1762,7 +1737,7 @@ impl TaskwarriorTui { pub fn task_subprocess(&mut self) -> Result<(), String> { let task_uuids = if self.tasks.is_empty() { vec![] } else { self.selected_task_uuids() }; - let shell = self.command.as_str(); + let shell = self.command.value(); let r = match shlex::split(shell) { Some(cmd) => { @@ -1809,7 +1784,7 @@ impl TaskwarriorTui { command.arg("log"); - let shell = self.command.as_str(); + let shell = self.command.value(); match shlex::split(shell) { Some(cmd) => { @@ -1855,7 +1830,7 @@ impl TaskwarriorTui { } pub async fn task_shortcut(&mut self, s: usize) -> Result<(), String> { - self.pause_tui().await.unwrap(); + // self.pause_tui().await.unwrap(); let task_uuids = if self.tasks.is_empty() { vec![] } else { self.selected_task_uuids() }; @@ -1933,7 +1908,7 @@ impl TaskwarriorTui { } command.arg("modify"); - let shell = self.modify.as_str(); + let shell = self.modify.value(); let r = match shlex::split(shell) { Some(cmd) => { @@ -1984,7 +1959,7 @@ impl TaskwarriorTui { } command.arg("annotate"); - let shell = self.command.as_str(); + let shell = self.command.value(); let r = match shlex::split(shell) { Some(cmd) => { @@ -2022,7 +1997,7 @@ impl TaskwarriorTui { let mut command = std::process::Command::new("task"); command.arg("add"); - let shell = self.command.as_str(); + let shell = self.command.value(); match shlex::split(shell) { Some(cmd) => { @@ -2227,7 +2202,7 @@ impl TaskwarriorTui { return Ok(()); } - self.pause_tui().await.unwrap(); + // self.pause_tui().await.unwrap(); let selected = self.current_selection; let task_id = self.tasks[selected].id().unwrap_or_default(); @@ -2437,11 +2412,11 @@ impl TaskwarriorTui { self.handle_input_by_task_mode(input).await?; } Mode::Projects => { - ProjectsState::handle_input(self, input)?; + ProjectsState::handle_input(self, input.into())?; self.update(false).await?; } Mode::Calendar => { - if input == self.keyconfig.quit || input == KeyCode::Ctrl('c') { + if input == self.keyconfig.quit { self.should_quit = true; } else if input == self.keyconfig.next_tab { if self.config.uda_change_focus_rotate { @@ -2459,10 +2434,10 @@ impl TaskwarriorTui { self.task_report_previous_page(); } else if input == KeyCode::PageDown || input == self.keyconfig.page_down { self.calendar_year += 10; - } else if input == KeyCode::Ctrl('e') { - self.task_details_scroll_down(); - } else if input == KeyCode::Ctrl('y') { - self.task_details_scroll_up(); + // } else if input == KeyCode::Ctrl('e') { + // self.task_details_scroll_down(); + // } else if input == KeyCode::Ctrl('y') { + // self.task_details_scroll_up(); } else if input == self.keyconfig.done { if self.config.uda_task_report_prompt_on_done { self.mode = Mode::Tasks(Action::DonePrompt); @@ -2494,7 +2469,8 @@ impl TaskwarriorTui { Action::Report => { if input == KeyCode::Esc { self.marked.clear(); - } else if input == self.keyconfig.quit || input == KeyCode::Ctrl('c') { + } else if input == self.keyconfig.quit { + // || input == KeyCode::Ctrl('c') { self.should_quit = true; } else if input == self.keyconfig.select { self.task_table_state.multiple_selection(); @@ -2516,10 +2492,10 @@ impl TaskwarriorTui { self.task_report_next_page(); } else if input == KeyCode::PageUp || input == self.keyconfig.page_up { self.task_report_previous_page(); - } else if input == KeyCode::Ctrl('e') { - self.task_details_scroll_down(); - } else if input == KeyCode::Ctrl('y') { - self.task_details_scroll_up(); + // } else if input == KeyCode::Ctrl('e') { + // self.task_details_scroll_down(); + // } else if input == KeyCode::Ctrl('y') { + // self.task_details_scroll_up(); } else if input == self.keyconfig.done { if self.config.uda_task_report_prompt_on_done { self.mode = Mode::Tasks(Action::DonePrompt); @@ -2626,11 +2602,11 @@ impl TaskwarriorTui { s = format!("{}due:{} ", s, get_formatted_datetime(date)); } } - self.modify.update(&s, s.as_str().len(), &mut self.changes); + self.modify = self.modify.clone().with_value(s); } - None => self.modify.update("", 0, &mut self.changes), + None => self.modify.reset(), }, - TableMode::MultipleSelection => self.modify.update("", 0, &mut self.changes), + TableMode::MultipleSelection => self.modify.reset(), } } else if input == self.keyconfig.shell { self.mode = Mode::Tasks(Action::Subprocess); @@ -2852,7 +2828,7 @@ impl TaskwarriorTui { self.show_completion_pane = false; self.completion_list.unselect(); } else { - self.modify.update("", 0, &mut self.changes); + self.modify.reset(); self.mode = Mode::Tasks(Action::Report); } } @@ -2860,9 +2836,11 @@ impl TaskwarriorTui { if self.show_completion_pane { self.show_completion_pane = false; if let Some((i, (r, m, o, _, _))) = self.completion_list.selected() { - let (before, after) = self.modify.as_str().split_at(self.modify.pos()); + let (before, after) = self.modify.value().split_at(self.modify.cursor()); let fs = format!("{}{}{}", before.trim_end_matches(&o), r, after); - self.modify.update(&fs, self.modify.pos() + r.len() - o.len(), &mut self.changes); + let cursor = self.modify.cursor() + r.len() - o.len(); + self.modify = self.modify.clone().with_value(fs); + self.modify = self.modify.clone().with_cursor(cursor); } self.completion_list.unselect(); } else if self.error.is_some() { @@ -2872,8 +2850,8 @@ impl TaskwarriorTui { match self.task_modify() { Ok(_) => { self.mode = Mode::Tasks(Action::Report); - self.command_history.add(self.modify.as_str()); - self.modify.update("", 0, &mut self.changes); + self.command_history.add(self.modify.value()); + self.modify.reset(); self.update(true).await?; } Err(e) => { @@ -2883,7 +2861,8 @@ impl TaskwarriorTui { } } } - KeyCode::Tab | KeyCode::Ctrl('n') => { + KeyCode::Tab => { + // | KeyCode::Ctrl('n') if !self.completion_list.is_empty() { self.update_input_for_completion(); if !self.show_completion_pane { @@ -2892,7 +2871,8 @@ impl TaskwarriorTui { self.completion_list.next(); } } - KeyCode::BackTab | KeyCode::Ctrl('p') => { + KeyCode::BackTab => { + // | KeyCode::Ctrl('p') if self.show_completion_pane && !self.completion_list.is_empty() { self.completion_list.previous(); } @@ -2903,11 +2883,12 @@ impl TaskwarriorTui { self.completion_list.previous(); } else if let Some(s) = self .command_history - .history_search(&self.modify.as_str()[..self.modify.pos()], HistoryDirection::Reverse) + .history_search(&self.modify.value()[..self.modify.cursor()], HistoryDirection::Reverse) { - let p = self.modify.pos(); - self.modify.update("", 0, &mut self.changes); - self.modify.update(&s, std::cmp::min(s.len(), p), &mut self.changes); + self.modify.reset(); + let p = std::cmp::min(s.len(), self.modify.cursor()); + self.modify = self.modify.clone().with_value(s); + self.modify = self.modify.clone().with_cursor(p); self.history_status = Some(format!( "{} / {}", self @@ -2924,11 +2905,12 @@ impl TaskwarriorTui { self.completion_list.next(); } else if let Some(s) = self .command_history - .history_search(&self.modify.as_str()[..self.modify.pos()], HistoryDirection::Forward) + .history_search(&self.modify.value()[..self.modify.cursor()], HistoryDirection::Forward) { - let p = self.modify.pos(); - self.modify.update("", 0, &mut self.changes); - self.modify.update(&s, std::cmp::min(s.len(), p), &mut self.changes); + self.modify.reset(); + let p = std::cmp::min(s.len(), self.modify.cursor()); + self.modify = self.modify.clone().with_value(s); + self.modify = self.modify.clone().with_cursor(p); self.history_status = Some(format!( "{} / {}", self @@ -2986,9 +2968,11 @@ impl TaskwarriorTui { if self.show_completion_pane { self.show_completion_pane = false; if let Some((i, (r, m, o, _, _))) = self.completion_list.selected() { - let (before, after) = self.command.as_str().split_at(self.command.pos()); + let (before, after) = self.command.value().split_at(self.command.cursor()); let fs = format!("{}{}{}", before.trim_end_matches(&o), r, after); - self.command.update(&fs, self.command.pos() + r.len() - o.len(), &mut self.changes); + let cursor = self.command.cursor() + r.len() - o.len(); + self.command.clone().with_value(fs); + self.command.clone().with_cursor(cursor); } self.completion_list.unselect(); } else if self.error.is_some() { @@ -2998,7 +2982,7 @@ impl TaskwarriorTui { match self.task_log() { Ok(_) => { self.mode = Mode::Tasks(Action::Report); - self.command_history.add(self.command.as_str()); + self.command_history.add(self.command.value()); self.reset_command(); self.history_status = None; self.update(true).await?; @@ -3010,7 +2994,8 @@ impl TaskwarriorTui { } } } - KeyCode::Tab | KeyCode::Ctrl('n') => { + KeyCode::Tab => { + // | KeyCode::Ctrl('n') if !self.completion_list.is_empty() { self.update_input_for_completion(); if !self.show_completion_pane { @@ -3019,7 +3004,8 @@ impl TaskwarriorTui { self.completion_list.next(); } } - KeyCode::BackTab | KeyCode::Ctrl('p') => { + KeyCode::BackTab => { + // | KeyCode::Ctrl('p') if self.show_completion_pane && !self.completion_list.is_empty() { self.completion_list.previous(); } @@ -3030,11 +3016,12 @@ impl TaskwarriorTui { self.completion_list.previous(); } else if let Some(s) = self .command_history - .history_search(&self.command.as_str()[..self.command.pos()], HistoryDirection::Reverse) + .history_search(&self.command.value()[..self.command.cursor()], HistoryDirection::Reverse) { - let p = self.command.pos(); - self.command.update("", 0, &mut self.changes); - self.command.update(&s, std::cmp::min(s.len(), p), &mut self.changes); + let p = std::cmp::min(s.len(), self.command.cursor()); + self.command.reset(); + self.command = self.command.clone().with_value(s); + self.command = self.command.clone().with_cursor(p); self.history_status = Some(format!( "{} / {}", self @@ -3051,11 +3038,12 @@ impl TaskwarriorTui { self.completion_list.next(); } else if let Some(s) = self .command_history - .history_search(&self.command.as_str()[..self.command.pos()], HistoryDirection::Forward) + .history_search(&self.command.value()[..self.command.cursor()], HistoryDirection::Forward) { - let p = self.command.pos(); - self.command.update("", 0, &mut self.changes); - self.command.update(&s, std::cmp::min(s.len(), p), &mut self.changes); + let p = std::cmp::min(s.len(), self.command.cursor()); + self.command.reset(); + self.command = self.command.clone().with_value(s); + self.command = self.command.clone().with_cursor(p); self.history_status = Some(format!( "{} / {}", self @@ -3088,9 +3076,11 @@ impl TaskwarriorTui { if self.show_completion_pane { self.show_completion_pane = false; if let Some((i, (r, m, o, _, _))) = self.completion_list.selected() { - let (before, after) = self.command.as_str().split_at(self.command.pos()); + let (before, after) = self.command.value().split_at(self.command.cursor()); let fs = format!("{}{}{}", before.trim_end_matches(&o), r, after); - self.command.update(&fs, self.command.pos() + r.len() - o.len(), &mut self.changes); + let cursor = self.command.cursor() + r.len() - o.len(); + self.command = self.command.clone().with_value(fs); + self.command = self.command.clone().with_cursor(cursor); } self.completion_list.unselect(); } else if self.error.is_some() { @@ -3100,7 +3090,7 @@ impl TaskwarriorTui { match self.task_annotate() { Ok(_) => { self.mode = Mode::Tasks(Action::Report); - self.command_history.add(self.command.as_str()); + self.command_history.add(self.command.value()); self.reset_command(); self.history_status = None; self.update(true).await?; @@ -3112,7 +3102,8 @@ impl TaskwarriorTui { } } } - KeyCode::Tab | KeyCode::Ctrl('n') => { + KeyCode::Tab => { + // | KeyCode::Ctrl('n') if !self.completion_list.is_empty() { self.update_input_for_completion(); if !self.show_completion_pane { @@ -3121,7 +3112,8 @@ impl TaskwarriorTui { self.completion_list.next(); } } - KeyCode::BackTab | KeyCode::Ctrl('p') => { + KeyCode::BackTab => { + // | KeyCode::Ctrl('p') if self.show_completion_pane && !self.completion_list.is_empty() { self.completion_list.previous(); } @@ -3131,11 +3123,12 @@ impl TaskwarriorTui { self.completion_list.previous(); } else if let Some(s) = self .command_history - .history_search(&self.command.as_str()[..self.command.pos()], HistoryDirection::Reverse) + .history_search(&self.command.value()[..self.command.cursor()], HistoryDirection::Reverse) { - let p = self.command.pos(); - self.command.update("", 0, &mut self.changes); - self.command.update(&s, std::cmp::min(s.len(), p), &mut self.changes); + let p = std::cmp::min(self.command.cursor(), s.len()); + self.command.reset(); + self.command = self.command.clone().with_value(s); + self.command = self.command.clone().with_cursor(p); self.history_status = Some(format!( "{} / {}", self @@ -3152,11 +3145,12 @@ impl TaskwarriorTui { self.completion_list.next(); } else if let Some(s) = self .command_history - .history_search(&self.command.as_str()[..self.command.pos()], HistoryDirection::Forward) + .history_search(&self.command.value()[..self.command.cursor()], HistoryDirection::Forward) { - let p = self.command.pos(); - self.command.update("", 0, &mut self.changes); - self.command.update(&s, std::cmp::min(s.len(), p), &mut self.changes); + let p = std::cmp::min(self.command.cursor(), s.len()); + self.command.reset(); + self.command = self.command.clone().with_value(s); + self.command = self.command.clone().with_cursor(p); self.history_status = Some(format!( "{} / {}", self @@ -3216,9 +3210,11 @@ impl TaskwarriorTui { if self.show_completion_pane { self.show_completion_pane = false; if let Some((i, (r, m, o, _, _))) = self.completion_list.selected() { - let (before, after) = self.command.as_str().split_at(self.command.pos()); + let (before, after) = self.command.value().split_at(self.command.cursor()); let fs = format!("{}{}{}", before.trim_end_matches(&o), r, after); - self.command.update(&fs, self.command.pos() + r.len() - o.len(), &mut self.changes); + let cursor = self.command.cursor() + r.len() - o.len(); + self.command = self.command.clone().with_value(fs); + self.command = self.command.clone().with_cursor(cursor); } self.completion_list.unselect(); } else if self.error.is_some() { @@ -3228,7 +3224,7 @@ impl TaskwarriorTui { match self.task_add() { Ok(_) => { self.mode = Mode::Tasks(Action::Report); - self.command_history.add(self.command.as_str()); + self.command_history.add(self.command.value()); self.reset_command(); self.history_status = None; self.update(true).await?; @@ -3240,7 +3236,8 @@ impl TaskwarriorTui { } } } - KeyCode::Tab | KeyCode::Ctrl('n') => { + KeyCode::Tab => { + // KeyCode::Ctrl('n') if !self.completion_list.is_empty() { self.update_input_for_completion(); if !self.show_completion_pane { @@ -3249,7 +3246,8 @@ impl TaskwarriorTui { self.completion_list.next(); } } - KeyCode::BackTab | KeyCode::Ctrl('p') => { + KeyCode::BackTab => { + // | KeyCode::Ctrl('p') if self.show_completion_pane && !self.completion_list.is_empty() { self.completion_list.previous(); } @@ -3259,11 +3257,12 @@ impl TaskwarriorTui { self.completion_list.previous(); } else if let Some(s) = self .command_history - .history_search(&self.command.as_str()[..self.command.pos()], HistoryDirection::Reverse) + .history_search(&self.command.value()[..self.command.cursor()], HistoryDirection::Reverse) { - let p = self.command.pos(); - self.command.update("", 0, &mut self.changes); - self.command.update(&s, std::cmp::min(s.len(), p), &mut self.changes); + let p = std::cmp::min(self.command.cursor(), s.len()); + self.command.reset(); + self.command = self.command.clone().with_value(s); + self.command = self.command.clone().with_cursor(p); self.history_status = Some(format!( "{} / {}", self @@ -3281,11 +3280,12 @@ impl TaskwarriorTui { self.completion_list.next(); } else if let Some(s) = self .command_history - .history_search(&self.command.as_str()[..self.command.pos()], HistoryDirection::Forward) + .history_search(&self.command.value()[..self.command.cursor()], HistoryDirection::Forward) { - let p = self.command.pos(); - self.command.update("", 0, &mut self.changes); - self.command.update(&s, std::cmp::min(s.len(), p), &mut self.changes); + let p = std::cmp::min(self.command.cursor(), s.len()); + self.command.reset(); + self.command = self.command.clone().with_value(s); + self.command = self.command.clone().with_cursor(p); self.history_status = Some(format!( "{} / {}", self @@ -3310,12 +3310,10 @@ impl TaskwarriorTui { self.completion_list.unselect(); } else { self.mode = Mode::Tasks(Action::Report); - self.filter_history.add(self.filter.as_str()); + self.filter_history.add(self.filter.value()); if self.config.uda_reset_filter_on_esc { - self.filter.update("", 0, &mut self.changes); - for c in self.config.filter.chars() { - self.filter.insert(c, 1, &mut self.changes); - } + self.filter.reset(); + self.filter = self.filter.clone().with_value(self.config.filter.clone()); self.update_input_for_completion(); self.dirty = true; } @@ -3327,9 +3325,11 @@ impl TaskwarriorTui { if self.show_completion_pane { self.show_completion_pane = false; if let Some((i, (r, m, o, _, _))) = self.completion_list.selected() { - let (before, after) = self.filter.as_str().split_at(self.filter.pos()); + let (before, after) = self.filter.value().split_at(self.filter.cursor()); let fs = format!("{}{}{}", before.trim_end_matches(&o), r, after); - self.filter.update(&fs, self.filter.pos() + r.len() - o.len(), &mut self.changes); + let cursor = self.filter.cursor() + r.len() - o.len(); + self.filter = self.filter.clone().with_value(fs); + self.filter = self.filter.clone().with_cursor(cursor); } self.completion_list.unselect(); self.dirty = true; @@ -3338,7 +3338,7 @@ impl TaskwarriorTui { self.mode = Mode::Tasks(Action::Error); } else { self.mode = Mode::Tasks(Action::Report); - self.filter_history.add(self.filter.as_str()); + self.filter_history.add(self.filter.value()); self.history_status = None; self.update(true).await?; } @@ -3348,11 +3348,12 @@ impl TaskwarriorTui { self.completion_list.previous(); } else if let Some(s) = self .filter_history - .history_search(&self.filter.as_str()[..self.filter.pos()], HistoryDirection::Reverse) + .history_search(&self.filter.value()[..self.filter.cursor()], HistoryDirection::Reverse) { - let p = self.filter.pos(); - self.filter.update("", 0, &mut self.changes); - self.filter.update(&s, std::cmp::min(p, s.len()), &mut self.changes); + let p = std::cmp::min(self.filter.cursor(), s.len()); + self.filter.reset(); + self.filter = self.filter.clone().with_value(s); + self.filter = self.filter.clone().with_cursor(p); self.history_status = Some(format!( "{} / {}", self @@ -3370,11 +3371,12 @@ impl TaskwarriorTui { self.completion_list.next(); } else if let Some(s) = self .filter_history - .history_search(&self.filter.as_str()[..self.filter.pos()], HistoryDirection::Forward) + .history_search(&self.filter.value()[..self.filter.cursor()], HistoryDirection::Forward) { - let p = self.filter.pos(); - self.filter.update("", 0, &mut self.changes); - self.filter.update(&s, std::cmp::min(p, s.len()), &mut self.changes); + let p = std::cmp::min(self.filter.cursor(), s.len()); + self.filter.reset(); + self.filter = self.filter.clone().with_value(s); + self.filter = self.filter.clone().with_cursor(p); self.history_status = Some(format!( "{} / {}", self @@ -3387,7 +3389,8 @@ impl TaskwarriorTui { self.dirty = true; } } - KeyCode::Tab | KeyCode::Ctrl('n') => { + KeyCode::Tab => { + // | KeyCode::Ctrl('n') if !self.completion_list.is_empty() { self.update_input_for_completion(); if !self.show_completion_pane { @@ -3396,20 +3399,19 @@ impl TaskwarriorTui { self.completion_list.next(); } } - KeyCode::BackTab | KeyCode::Ctrl('p') => { + KeyCode::BackTab => { + // | KeyCode::Ctrl('p') if self.show_completion_pane && !self.completion_list.is_empty() { self.completion_list.previous(); } } - KeyCode::Ctrl('r') => { - self.filter.update("", 0, &mut self.changes); - for c in self.config.filter.chars() { - self.filter.insert(c, 1, &mut self.changes); - } - self.history_status = None; - self.update_input_for_completion(); - self.dirty = true; - } + // KeyCode::Ctrl('r') => { + // self.filter.reset(); + // self.filter = self.filter.with_value(self.config.filter.clone()); + // self.history_status = None; + // self.update_input_for_completion(); + // self.dirty = true; + // } _ => { handle_movement(&mut self.filter, input, &mut self.changes); self.update_input_for_completion(); @@ -3619,18 +3621,18 @@ impl TaskwarriorTui { pub fn update_input_for_completion(&mut self) { match self.mode { Mode::Tasks(Action::Add | Action::Annotate | Action::Log) => { - let i = get_start_word_under_cursor(self.command.as_str(), self.command.pos()); - let input = self.command.as_str()[i..self.command.pos()].to_string(); + let i = get_start_word_under_cursor(self.command.value(), self.command.cursor()); + let input = self.command.value()[i..self.command.cursor()].to_string(); self.completion_list.input(input, "".to_string()); } Mode::Tasks(Action::Modify) => { - let i = get_start_word_under_cursor(self.modify.as_str(), self.modify.pos()); - let input = self.modify.as_str()[i..self.modify.pos()].to_string(); + let i = get_start_word_under_cursor(self.modify.value(), self.modify.cursor()); + let input = self.modify.value()[i..self.modify.cursor()].to_string(); self.completion_list.input(input, "".to_string()); } Mode::Tasks(Action::Filter) => { - let i = get_start_word_under_cursor(self.filter.as_str(), self.filter.pos()); - let input = self.filter.as_str()[i..self.filter.pos()].to_string(); + let i = get_start_word_under_cursor(self.filter.value(), self.filter.cursor()); + let input = self.filter.value()[i..self.filter.cursor()].to_string(); self.completion_list.input(input, "".to_string()); } _ => {} @@ -3638,49 +3640,52 @@ impl TaskwarriorTui { } } -pub fn handle_movement(linebuffer: &mut LineBuffer, input: KeyCode, changes: &mut utils::Changeset) { +pub fn handle_movement(linebuffer: &mut Input, input: KeyCode, changes: &mut utils::Changeset) { match input { - KeyCode::Ctrl('f') | KeyCode::Right => { - linebuffer.move_forward(1); - } - KeyCode::Ctrl('b') | KeyCode::Left => { - linebuffer.move_backward(1); - } - KeyCode::Ctrl('h') | KeyCode::Backspace => { - linebuffer.backspace(1, changes); - } - KeyCode::Ctrl('d') | KeyCode::Delete => { - linebuffer.delete(1, changes); - } - KeyCode::Ctrl('a') | KeyCode::Home => { - linebuffer.move_home(); - } - KeyCode::Ctrl('e') | KeyCode::End => { - linebuffer.move_end(); - } - KeyCode::Ctrl('k') => { - linebuffer.kill_line(changes); - } - KeyCode::Ctrl('u') => { - linebuffer.discard_line(changes); - } - KeyCode::Ctrl('w') | KeyCode::AltBackspace | KeyCode::CtrlBackspace => { - linebuffer.delete_prev_word(Word::Emacs, 1, changes); - } - KeyCode::Alt('d') | KeyCode::AltDelete | KeyCode::CtrlDelete => { - linebuffer.delete_word(At::AfterEnd, Word::Emacs, 1, changes); - } - KeyCode::Alt('f') => { - linebuffer.move_to_next_word(At::AfterEnd, Word::Emacs, 1); - } - KeyCode::Alt('b') => { - linebuffer.move_to_prev_word(Word::Emacs, 1); - } - KeyCode::Alt('t') => { - linebuffer.transpose_words(1, changes); - } + // KeyCode::Ctrl('f') | KeyCode::Right => { + // linebuffer.move_forward(1); + // } + // KeyCode::Ctrl('b') | KeyCode::Left => { + // linebuffer.move_backward(1); + // } + // KeyCode::Ctrl('h') | KeyCode::Backspace => { + // linebuffer.backspace(1, changes); + // } + // KeyCode::Ctrl('d') | KeyCode::Delete => { + // linebuffer.delete(1, changes); + // } + // KeyCode::Ctrl('a') | KeyCode::Home => { + // linebuffer.move_home(); + // } + // KeyCode::Ctrl('e') | KeyCode::End => { + // linebuffer.move_end(); + // } + // KeyCode::Ctrl('k') => { + // linebuffer.kill_line(changes); + // } + // KeyCode::Ctrl('u') => { + // linebuffer.discard_line(changes); + // } + // KeyCode::Ctrl('w') | KeyCode::AltBackspace | KeyCode::CtrlBackspace => { + // linebuffer.delete_prev_word(Word::Emacs, 1, changes); + // } + // KeyCode::Alt('d') | KeyCode::AltDelete | KeyCode::CtrlDelete => { + // linebuffer.delete_word(At::AfterEnd, Word::Emacs, 1, changes); + // } + // KeyCode::Alt('f') => { + // linebuffer.move_to_next_word(At::AfterEnd, Word::Emacs, 1); + // } + // KeyCode::Alt('b') => { + // linebuffer.move_to_prev_word(Word::Emacs, 1); + // } + // KeyCode::Alt('t') => { + // linebuffer.transpose_words(1, changes); + // } KeyCode::Char(c) => { - linebuffer.insert(c, 1, changes); + linebuffer.handle_event(&crossterm::event::Event::Key(crossterm::event::KeyEvent::new( + input, + KeyModifiers::empty(), + ))); } _ => {} } @@ -3796,7 +3801,7 @@ mod tests { app.update(true).await.unwrap(); app.handle_input(KeyCode::Down).await.unwrap(); - assert_eq!("\"Buy groceries\" +test", app.command.as_str()); + assert_eq!("\"Buy groceries\" +test", app.command.value()); app.handle_input(KeyCode::Char('\n')).await.unwrap(); @@ -3821,13 +3826,13 @@ mod tests { app.update(true).await.unwrap(); app.handle_input(KeyCode::Down).await.unwrap(); - assert_eq!("\"Buy groceries", app.command.as_str()); + assert_eq!("\"Buy groceries", app.command.value()); app.update(true).await.unwrap(); app.handle_input(KeyCode::Up).await.unwrap(); - assert_eq!("\"Buy groceries\" +test", app.command.as_str()); + assert_eq!("\"Buy groceries\" +test", app.command.value()); // teardown(); } @@ -4275,11 +4280,11 @@ mod tests { TableMode::SingleSelection => match app.task_current() { Some(t) => { let s = format!("{} ", t.description()); - app.modify.update(&s, s.as_str().len(), &mut app.changes) + app.modify = app.modify.with_value(s); } - None => app.modify.update("", 0, &mut app.changes), + None => app.modify.reset(), }, - TableMode::MultipleSelection => app.modify.update("", 0, &mut app.changes), + TableMode::MultipleSelection => app.modify.reset(), } app.update(true).await.unwrap(); @@ -4293,7 +4298,7 @@ mod tests { .constraints([Constraint::Min(0), Constraint::Length(3)].as_ref()) .split(f.size()); - let position = TaskwarriorTui::get_position(&app.modify); + let position = &app.modify.visual_cursor(); f.set_cursor( std::cmp::min(rects[1].x + position as u16, rects[1].x + rects[1].width.saturating_sub(2)), rects[1].y + 1, @@ -4326,7 +4331,7 @@ mod tests { app.draw_command( f, rects[1], - app.modify.as_str(), + app.modify.value(), (Span::styled(label, Style::default().add_modifier(Modifier::BOLD)), None), position, true, @@ -4347,7 +4352,7 @@ mod tests { .constraints([Constraint::Min(0), Constraint::Length(3)].as_ref()) .split(f.size()); - let position = TaskwarriorTui::get_position(&app.modify); + let position = &app.modify.visual_cursor(); f.set_cursor( std::cmp::min(rects[1].x + position as u16, rects[1].x + rects[1].width.saturating_sub(2)), rects[1].y + 1, @@ -4380,7 +4385,7 @@ mod tests { app.draw_command( f, rects[1], - app.modify.as_str(), + app.modify.value(), (Span::styled(label, Style::default().add_modifier(Modifier::BOLD)), None), position, true, @@ -4734,17 +4739,17 @@ mod tests { match app.task_current() { Some(t) => { let s = format!("{} ", t.description()); - app.modify.update(&s, s.as_str().len(), &mut app.changes) + app.modify = app.modify.with_value(s); } - None => app.modify.update("", 0, &mut app.changes), + None => app.modify.reset(), } app.update(true).await.unwrap(); - dbg!(app.modify.as_str()); - dbg!(app.modify.as_str().len()); - dbg!(app.modify.graphemes(true).count()); - dbg!(app.modify.pos()); - let position = TaskwarriorTui::get_position(&app.modify); + dbg!(app.modify.value()); + dbg!(app.modify.value().len()); + dbg!(app.modify.value().graphemes(true).count()); + dbg!(app.modify.cursor()); + let position = app.modify.visual_cursor(); dbg!(position); } @@ -4758,7 +4763,7 @@ mod tests { for c in input.chars() { app.handle_input(KeyCode::Char(c)).await.unwrap(); } - app.handle_input(KeyCode::Ctrl('e')).await.unwrap(); + // app.handle_input(KeyCode::Ctrl('e')).await.unwrap(); let input = " project:CO"; for c in input.chars() { diff --git a/src/event.rs b/src/event.rs index 87047f54..139597f9 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1,130 +1,2 @@ -use crossterm::event::{ - KeyCode::{BackTab, Backspace, Char, Delete, Down, End, Enter, Esc, Home, Insert, Left, Null, PageDown, PageUp, Right, Tab, Up, F}, - KeyEvent, KeyModifiers, -}; -use futures::StreamExt; -use log::{debug, error, info, log_enabled, trace, warn, Level, LevelFilter}; -use serde::{Deserialize, Serialize}; -use tokio::{ - sync::{mpsc, oneshot}, - task::JoinHandle, -}; -#[derive(Debug, Clone, Copy)] -pub enum Event { - Input(I), - Tick, - Closed, -} -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, PartialOrd, Eq)] -pub enum KeyCode { - CtrlBackspace, - CtrlDelete, - AltBackspace, - AltDelete, - Backspace, - Left, - Right, - Up, - Down, - Home, - End, - PageUp, - PageDown, - BackTab, - Delete, - Insert, - F(u8), - Char(char), - Alt(char), - Ctrl(char), - Null, - Esc, - Tab, -} - -pub struct EventLoop { - pub rx: mpsc::UnboundedReceiver>, - pub tx: mpsc::UnboundedSender>, - pub abort: mpsc::UnboundedSender<()>, - pub tick_rate: std::time::Duration, -} - -impl EventLoop { - pub fn new(tick_rate: Option, init: bool) -> Self { - let (tx, rx) = mpsc::unbounded_channel(); - let _tx = tx.clone(); - let should_tick = tick_rate.is_some(); - let tick_rate = tick_rate.unwrap_or(std::time::Duration::from_millis(250)); - - let (abort, mut abort_recv) = mpsc::unbounded_channel(); - - if init { - let mut reader = crossterm::event::EventStream::new(); - tokio::spawn(async move { - loop { - let delay = tokio::time::sleep(tick_rate); - let event = reader.next(); - - tokio::select! { - _ = abort_recv.recv() => { - _tx.send(Event::Closed).unwrap_or_else(|_| warn!("Unable to send Closed event")); - _tx.send(Event::Tick).unwrap_or_else(|_| warn!("Unable to send Tick event")); - break; - }, - _ = delay, if should_tick => { - _tx.send(Event::Tick).unwrap_or_else(|_| warn!("Unable to send Tick event")); - }, - _ = _tx.closed() => break, - maybe_event = event => { - if let Some(Ok(crossterm::event::Event::Key(key))) = maybe_event { - let key = match key.code { - Backspace => { - match key.modifiers { - KeyModifiers::CONTROL => KeyCode::CtrlBackspace, - KeyModifiers::ALT => KeyCode::AltBackspace, - _ => KeyCode::Backspace, - } - }, - Delete => { - match key.modifiers { - KeyModifiers::CONTROL => KeyCode::CtrlDelete, - KeyModifiers::ALT => KeyCode::AltDelete, - _ => KeyCode::Delete, - } - }, - Enter => KeyCode::Char('\n'), - Left => KeyCode::Left, - Right => KeyCode::Right, - Up => KeyCode::Up, - Down => KeyCode::Down, - Home => KeyCode::Home, - End => KeyCode::End, - PageUp => KeyCode::PageUp, - PageDown => KeyCode::PageDown, - Tab => KeyCode::Tab, - BackTab => KeyCode::BackTab, - Insert => KeyCode::Insert, - F(k) => KeyCode::F(k), - Null => KeyCode::Null, - Esc => KeyCode::Esc, - Char(c) => match key.modifiers { - KeyModifiers::NONE | KeyModifiers::SHIFT => KeyCode::Char(c), - KeyModifiers::CONTROL => KeyCode::Ctrl(c), - KeyModifiers::ALT => KeyCode::Alt(c), - _ => KeyCode::Null, - }, - _ => KeyCode::Null, - }; - _tx.send(Event::Input(key)).unwrap_or_else(|_| warn!("Unable to send {:?} event", key)); - } - } - } - } - }); - } - - Self { tx, rx, tick_rate, abort } - } -} diff --git a/src/keyconfig.rs b/src/keyconfig.rs index 3bb45d57..f90b1376 100644 --- a/src/keyconfig.rs +++ b/src/keyconfig.rs @@ -1,11 +1,10 @@ use std::{collections::HashSet, error::Error, hash::Hash}; use color_eyre::eyre::{anyhow, Result}; +use crossterm::event::KeyCode; use log::{error, info, warn}; use serde::{Deserialize, Serialize}; -use crate::event::KeyCode; - #[derive(Serialize, Deserialize, Debug)] pub struct KeyConfig { pub quit: KeyCode, diff --git a/src/main.rs b/src/main.rs index 396234e4..686fcacc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,6 @@ mod calendar; mod cli; mod completion; mod config; -mod event; mod help; mod history; mod keyconfig; @@ -45,7 +44,6 @@ use utils::{get_config_dir, get_data_dir}; use crate::{ action::Action, - event::Event, keyconfig::KeyConfig, utils::{initialize_logging, initialize_panic_handler}, }; @@ -93,11 +91,7 @@ async fn main() -> Result<()> { let mut app = app::TaskwarriorTui::new(report, true).await?; - let mut terminal = app.start_tui()?; - - let r = app.run(&mut terminal).await; - - app.pause_tui().await?; + let r = app.run().await; if let Err(err) = r { eprintln!("\x1b[0;31m[taskwarrior-tui error]\x1b[0m: {}\n\nIf you need additional help, please report as a github issue on https://github.com/kdheepak/taskwarrior-tui", err); diff --git a/src/pane/context.rs b/src/pane/context.rs index 3df22b9f..3a190f73 100644 --- a/src/pane/context.rs +++ b/src/pane/context.rs @@ -30,7 +30,6 @@ use uuid::Uuid; use crate::{ action::Action, app::{Mode, TaskwarriorTui}, - event::KeyCode, pane::Pane, table::TableState, }; diff --git a/src/pane/mod.rs b/src/pane/mod.rs index afd7b7d9..7ca1c98d 100644 --- a/src/pane/mod.rs +++ b/src/pane/mod.rs @@ -1,18 +1,19 @@ use std::ops::Index; use color_eyre::eyre::Result; +use crossterm::event::KeyEvent; use crate::{ action::Action, app::{Mode, TaskwarriorTui}, - event::KeyCode, + tui::Event, }; pub mod context; pub mod project; pub trait Pane { - fn handle_input(app: &mut TaskwarriorTui, input: KeyCode) -> Result<()>; + fn handle_input(app: &mut TaskwarriorTui, input: KeyEvent) -> Result<()>; fn change_focus_to_left_pane(app: &mut TaskwarriorTui) { match app.mode { Mode::Tasks(_) => { diff --git a/src/pane/project.rs b/src/pane/project.rs index 813036af..0b7252a3 100644 --- a/src/pane/project.rs +++ b/src/pane/project.rs @@ -1,6 +1,7 @@ use std::fmt; use color_eyre::eyre::{anyhow, Context as AnyhowContext, Result}; +use crossterm::event::{KeyCode, KeyEvent}; const COL_WIDTH: usize = 21; const PROJECT_HEADER: &str = "Name"; @@ -30,7 +31,6 @@ use uuid::Uuid; use crate::{ action::Action, app::{Mode, TaskwarriorTui}, - event::KeyCode, pane::Pane, table::TableState, utils::Changeset, @@ -144,18 +144,19 @@ impl ProjectsState { } impl Pane for ProjectsState { - fn handle_input(app: &mut TaskwarriorTui, input: KeyCode) -> Result<()> { - if input == app.keyconfig.quit || input == KeyCode::Ctrl('c') { + fn handle_input(app: &mut TaskwarriorTui, input: KeyEvent) -> Result<()> { + if input.code == app.keyconfig.quit { + // || input == KeyCode::Ctrl('c') { app.should_quit = true; - } else if input == app.keyconfig.next_tab { + } else if input.code == app.keyconfig.next_tab { Self::change_focus_to_right_pane(app); - } else if input == app.keyconfig.previous_tab { + } else if input.code == app.keyconfig.previous_tab { Self::change_focus_to_left_pane(app); - } else if input == KeyCode::Down || input == app.keyconfig.down { + } else if input.code == KeyCode::Down || input.code == app.keyconfig.down { self::focus_on_next_project(app); - } else if input == KeyCode::Up || input == app.keyconfig.up { + } else if input.code == KeyCode::Up || input.code == app.keyconfig.up { self::focus_on_previous_project(app); - } else if input == app.keyconfig.select { + } else if input.code == app.keyconfig.select { self::update_task_filter_by_selection(app)?; } app.projects.update_table_state(); @@ -182,11 +183,11 @@ fn update_task_filter_by_selection(app: &mut TaskwarriorTui) -> Result<()> { let last_project_pattern = ProjectsState::pattern_by_marked(app); app.projects.toggle_mark(); let new_project_pattern = ProjectsState::pattern_by_marked(app); - let current_filter = app.filter.as_str(); + let current_filter = app.filter.value(); app.filter_history.add(current_filter); let mut filter = current_filter.replace(&last_project_pattern, ""); filter = format!("{}{}", filter, new_project_pattern); - app.filter.update(filter.as_str(), filter.len(), &mut Changeset::default()); + app.filter = app.filter.clone().with_value(filter); Ok(()) } From 0be6170939562fc6cb377b2da203329f29a2dc13 Mon Sep 17 00:00:00 2001 From: Dheepak Krishnamurthy Date: Sun, 3 Sep 2023 17:08:28 -0400 Subject: [PATCH 03/13] =?UTF-8?q?feat:=20Make=20tui-logger=20use=20default?= =?UTF-8?q?-feature=3Dfalse=20=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 42 ----------------------------------------- Cargo.toml | 2 +- src/app.rs | 55 +++++------------------------------------------------- 3 files changed, 6 insertions(+), 93 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4a23efc9..9a29df0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -999,12 +999,6 @@ dependencies = [ "libc", ] -[[package]] -name = "numtoa" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" - [[package]] name = "object" version = "0.32.0" @@ -1280,15 +1274,6 @@ dependencies = [ "bitflags 1.3.2", ] -[[package]] -name = "redox_termios" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f" -dependencies = [ - "redox_syscall 0.2.16", -] - [[package]] name = "redox_users" version = "0.4.3" @@ -1723,18 +1708,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "termion" -version = "1.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e" -dependencies = [ - "libc", - "numtoa", - "redox_syscall 0.2.16", - "redox_termios", -] - [[package]] name = "thiserror" version = "1.0.47" @@ -1934,19 +1907,6 @@ dependencies = [ "tracing-log", ] -[[package]] -name = "tui" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccdd26cbd674007e649a272da4475fb666d3aa0ad0531da7136db6fab0e5bad1" -dependencies = [ - "bitflags 1.3.2", - "cassowary", - "termion", - "unicode-segmentation", - "unicode-width", -] - [[package]] name = "tui-input" version = "0.8.0" @@ -1969,10 +1929,8 @@ dependencies = [ "log", "parking_lot", "ratatui 0.22.0", - "termion", "tracing", "tracing-subscriber", - "tui", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index bcc7d72a..5ef8efef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,7 @@ tracing = "0.1.37" tracing-error = "0.2.0" tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } tui-input = "0.8.0" -tui-logger = { version = "0.9.5", features = ["ratatui-support", "tracing-support"] } +tui-logger = { version = "0.9.5", default-features = false, features = ["ratatui-support", "tracing-support"] } unicode-segmentation = "1.10.1" unicode-truncate = "0.2.0" unicode-width = "0.1.10" diff --git a/src/app.rs b/src/app.rs index f0990dea..1cac750a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -34,8 +34,7 @@ use ratatui::{ use regex::Regex; use rustyline::{history::SearchDirection as HistoryDirection, At, Editor, Word}; use task_hookrs::{date::Date, import::import, project::Project, status::TaskStatus, task::Task}; -use tui_input::backend::crossterm::EventHandler; -use tui_input::Input; +use tui_input::{backend::crossterm::EventHandler, Input}; use unicode_segmentation::{Graphemes, UnicodeSegmentation}; use unicode_width::UnicodeWidthStr; use uuid::Uuid; @@ -3641,54 +3640,10 @@ impl TaskwarriorTui { } pub fn handle_movement(linebuffer: &mut Input, input: KeyCode, changes: &mut utils::Changeset) { - match input { - // KeyCode::Ctrl('f') | KeyCode::Right => { - // linebuffer.move_forward(1); - // } - // KeyCode::Ctrl('b') | KeyCode::Left => { - // linebuffer.move_backward(1); - // } - // KeyCode::Ctrl('h') | KeyCode::Backspace => { - // linebuffer.backspace(1, changes); - // } - // KeyCode::Ctrl('d') | KeyCode::Delete => { - // linebuffer.delete(1, changes); - // } - // KeyCode::Ctrl('a') | KeyCode::Home => { - // linebuffer.move_home(); - // } - // KeyCode::Ctrl('e') | KeyCode::End => { - // linebuffer.move_end(); - // } - // KeyCode::Ctrl('k') => { - // linebuffer.kill_line(changes); - // } - // KeyCode::Ctrl('u') => { - // linebuffer.discard_line(changes); - // } - // KeyCode::Ctrl('w') | KeyCode::AltBackspace | KeyCode::CtrlBackspace => { - // linebuffer.delete_prev_word(Word::Emacs, 1, changes); - // } - // KeyCode::Alt('d') | KeyCode::AltDelete | KeyCode::CtrlDelete => { - // linebuffer.delete_word(At::AfterEnd, Word::Emacs, 1, changes); - // } - // KeyCode::Alt('f') => { - // linebuffer.move_to_next_word(At::AfterEnd, Word::Emacs, 1); - // } - // KeyCode::Alt('b') => { - // linebuffer.move_to_prev_word(Word::Emacs, 1); - // } - // KeyCode::Alt('t') => { - // linebuffer.transpose_words(1, changes); - // } - KeyCode::Char(c) => { - linebuffer.handle_event(&crossterm::event::Event::Key(crossterm::event::KeyEvent::new( - input, - KeyModifiers::empty(), - ))); - } - _ => {} - } + linebuffer.handle_event(&crossterm::event::Event::Key(crossterm::event::KeyEvent::new( + input, + KeyModifiers::empty(), + ))); } pub fn add_tag(task: &mut Task, tag: String) { From 0d89f657d5d690fff43e8eea34b780ef77f75519 Mon Sep 17 00:00:00 2001 From: Dheepak Krishnamurthy Date: Sun, 3 Sep 2023 17:21:10 -0400 Subject: [PATCH 04/13] =?UTF-8?q?wip:=20Remove=20tests=20=F0=9F=9A=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.rs | 1079 +--------------------------------------------------- 1 file changed, 1 insertion(+), 1078 deletions(-) diff --git a/src/app.rs b/src/app.rs index 1cac750a..520966d3 100644 --- a/src/app.rs +++ b/src/app.rs @@ -3662,1081 +3662,4 @@ pub fn remove_tag(task: &mut Task, tag: &str) { } #[cfg(test)] -mod tests { - use std::{ffi::OsStr, fmt::Write, fs::File, io, path::Path}; - - use ratatui::{backend::TestBackend, buffer::Buffer}; - - use super::*; - - /// Returns a string representation of the given buffer for debugging purpose. - fn buffer_view(buffer: &Buffer) -> String { - let mut view = String::with_capacity(buffer.content.len() + buffer.area.height as usize * 3); - for cells in buffer.content.chunks(buffer.area.width as usize) { - let mut overwritten = vec![]; - let mut skip: usize = 0; - view.push('"'); - for (x, c) in cells.iter().enumerate() { - if skip == 0 { - view.push_str(&c.symbol); - } else { - overwritten.push((x, &c.symbol)) - } - skip = std::cmp::max(skip, c.symbol.width()).saturating_sub(1); - } - view.push('"'); - if !overwritten.is_empty() { - write!(&mut view, " Hidden by multi-width symbols: {:?}", overwritten).unwrap(); - } - view.push('\n'); - } - view - } - - #[test] - fn test_centered_rect() { - assert_eq!(centered_rect(50, 50, Rect::new(0, 0, 100, 100)), Rect::new(25, 25, 50, 50)); - } - - fn setup() { - use std::process::Stdio; - let mut f = File::open(Path::new(env!("TASKDATA")).parent().unwrap().join("export.json")).unwrap(); - let mut s = String::new(); - f.read_to_string(&mut s).unwrap(); - let tasks = task_hookrs::import::import(s.as_bytes()).unwrap(); - // tasks.iter_mut().find(| t | t.id().unwrap() == 1).unwrap().priority_mut().replace(&mut "H".to_string()); - // tasks.iter_mut().find(| t | t.id().unwrap() == 2).unwrap().priority_mut().replace(&mut "H".to_string()); - // tasks.iter_mut().find(| t | t.id().unwrap() == 4).unwrap().tags_mut().replace(&mut vec!["test".to_string(), "another tag".to_string()]); - assert!(task_hookrs::tw::save(&tasks).is_ok()); - } - - fn teardown() { - let cd = Path::new(env!("TASKDATA")); - std::fs::remove_dir_all(cd).unwrap(); - } - - async fn test_taskwarrior_tui_history() { - let mut app = TaskwarriorTui::new("next", false).await.unwrap(); - // setup(); - app.mode = Mode::Tasks(Action::Add); - app.update_completion_list(); - let input = "Wash car"; - for c in input.chars() { - app.handle_input(KeyCode::Char(c)).await.unwrap(); - } - app.handle_input(KeyCode::Right).await.unwrap(); - let input = " +test"; - for c in input.chars() { - app.handle_input(KeyCode::Char(c)).await.unwrap(); - } - app.handle_input(KeyCode::Char('\n')).await.unwrap(); - - app.mode = Mode::Tasks(Action::Add); - - app.update_completion_list(); - - let backend = TestBackend::new(50, 15); - let mut terminal = Terminal::new(backend).unwrap(); - terminal - .draw(|f| { - app.draw(f); - app.draw(f); - }) - .unwrap(); - - let input = "Buy groceries"; - for c in input.chars() { - app.handle_input(KeyCode::Char(c)).await.unwrap(); - } - app.handle_input(KeyCode::Right).await.unwrap(); - let input = " +test"; - for c in input.chars() { - app.handle_input(KeyCode::Char(c)).await.unwrap(); - } - app.update(true).await.unwrap(); - app.handle_input(KeyCode::Down).await.unwrap(); - - assert_eq!("\"Buy groceries\" +test", app.command.value()); - - app.handle_input(KeyCode::Char('\n')).await.unwrap(); - - app.mode = Mode::Tasks(Action::Add); - app.update_completion_list(); - - let backend = TestBackend::new(50, 15); - let mut terminal = Terminal::new(backend).unwrap(); - terminal - .draw(|f| { - app.draw(f); - app.draw(f); - }) - .unwrap(); - - let input = "Buy groceries"; - for c in input.chars() { - app.handle_input(KeyCode::Char(c)).await.unwrap(); - } - app.handle_input(KeyCode::Right).await.unwrap(); - app.handle_input(KeyCode::Backspace).await.unwrap(); - app.update(true).await.unwrap(); - app.handle_input(KeyCode::Down).await.unwrap(); - - assert_eq!("\"Buy groceries", app.command.value()); - - app.update(true).await.unwrap(); - - app.handle_input(KeyCode::Up).await.unwrap(); - - assert_eq!("\"Buy groceries\" +test", app.command.value()); - // teardown(); - } - - #[test] - fn test_taskwarrior_tui() { - let r = tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .unwrap() - .block_on(async { _test_taskwarrior_tui().await }); - } - - async fn _test_taskwarrior_tui() { - let app = TaskwarriorTui::new("next", false).await.unwrap(); - - assert!( - app.task_by_index(0).is_none(), - "Expected task data to be empty but found {} tasks. Delete contents of {:?} and {:?} and run the tests again.", - app.tasks.len(), - Path::new(env!("TASKDATA")), - Path::new(env!("TASKDATA")).parent().unwrap().join(".config") - ); - - let app = TaskwarriorTui::new("next", false).await.unwrap(); - assert!(app - .task_by_uuid(Uuid::parse_str("3f43831b-88dc-45e2-bf0d-4aea6db634cc").unwrap()) - .is_none()); - - test_draw_empty_task_report().await; - - test_draw_calendar().await; - test_draw_help_popup().await; - - setup(); - - let app = TaskwarriorTui::new("next", false).await.unwrap(); - assert!(app.task_by_index(0).is_some()); - - let app = TaskwarriorTui::new("next", false).await.unwrap(); - assert!(app - .task_by_uuid(Uuid::parse_str("3f43831b-88dc-45e2-bf0d-4aea6db634cc").unwrap()) - .is_some()); - - test_draw_task_report_with_extended_modify_command().await; - // test_draw_task_report(); - test_task_tags().await; - test_task_style().await; - test_task_context().await; - test_task_tomorrow().await; - test_task_earlier_today().await; - test_task_later_today().await; - test_taskwarrior_tui_history().await; - - teardown(); - } - - async fn test_task_tags() { - // testing tags - let app = TaskwarriorTui::new("next", false).await.unwrap(); - let task = app.task_by_id(1).unwrap(); - - let tags = vec!["PENDING".to_string(), "PRIORITY".to_string()]; - - for tag in tags { - assert!(task.tags().unwrap().contains(&tag)); - } - - let mut app = TaskwarriorTui::new("next", false).await.unwrap(); - let task = app.task_by_id(11).unwrap(); - let tags = vec!["finance", "UNBLOCKED", "PENDING", "TAGGED", "UDA"] - .iter() - .map(ToString::to_string) - .collect::>(); - for tag in tags { - assert!(task.tags().unwrap().contains(&tag)); - } - - if let Some(task) = app.task_by_id(11) { - let i = app.task_index_by_uuid(*task.uuid()).unwrap_or_default(); - app.current_selection = i; - app.current_selection_id = None; - app.current_selection_uuid = None; - } - - app.task_quick_tag().unwrap(); - app.update(true).await.unwrap(); - - let task = app.task_by_id(11).unwrap(); - let tags = vec!["next", "finance", "UNBLOCKED", "PENDING", "TAGGED", "UDA"] - .iter() - .map(ToString::to_string) - .collect::>(); - for tag in tags { - assert!(task.tags().unwrap().contains(&tag)); - } - - app.task_quick_tag().unwrap(); - app.update(true).await.unwrap(); - - let task = app.task_by_id(11).unwrap(); - let tags = vec!["finance", "UNBLOCKED", "PENDING", "TAGGED", "UDA"] - .iter() - .map(ToString::to_string) - .collect::>(); - for tag in tags { - assert!(task.tags().unwrap().contains(&tag)); - } - } - - async fn test_task_style() { - let app = TaskwarriorTui::new("next", false).await.unwrap(); - let task = app.task_by_id(1).unwrap(); - for r in vec![ - "active", - "blocked", - "blocking", - "completed", - "deleted", - "due", - "due.today", - "keyword.", - "overdue", - "project.", - "recurring", - "scheduled", - "tag.", - "tagged", - "uda.", - ] { - assert!(app.config.rule_precedence_color.contains(&r.to_string())); - } - let style = app.style_for_task(&task); - - assert_eq!(style, Style::default().fg(Color::Indexed(2))); - - let task = app.task_by_id(11).unwrap(); - let style = app.style_for_task(&task); - } - - async fn test_task_context() { - let mut app = TaskwarriorTui::new("next", false).await.unwrap(); - - assert!(app.update(true).await.is_ok()); - - app.context_select().unwrap(); - - assert_eq!(app.tasks.len(), 26); - assert_eq!(app.current_context_filter, ""); - - assert_eq!(app.contexts.table_state.current_selection(), Some(0)); - app.context_next(); - app.context_next(); - app.context_select().unwrap(); - assert_eq!(app.contexts.table_state.current_selection(), Some(2)); - - assert!(app.update(true).await.is_ok()); - - assert_eq!(app.tasks.len(), 1); - assert_eq!(app.current_context_filter, "+finance -private"); - - assert_eq!(app.contexts.table_state.current_selection(), Some(2)); - app.context_previous(); - app.context_previous(); - app.context_select().unwrap(); - assert_eq!(app.contexts.table_state.current_selection(), Some(0)); - - assert!(app.update(true).await.is_ok()); - - assert_eq!(app.tasks.len(), 26); - assert_eq!(app.current_context_filter, ""); - } - - async fn test_task_tomorrow() { - let total_tasks: u64 = 26; - - let mut app = TaskwarriorTui::new("next", false).await.unwrap(); - assert!(app.update(true).await.is_ok()); - assert_eq!(app.tasks.len(), total_tasks as usize); - assert_eq!(app.current_context_filter, ""); - - let now = Local::now(); - let now = TimeZone::from_utc_datetime(now.offset(), &now.naive_utc()); - - let mut command = std::process::Command::new("task"); - command.arg("add"); - let tomorrow = now + chrono::Duration::days(1); - let message = format!( - "'new task for testing tomorrow' due:{:04}-{:02}-{:02}", - tomorrow.year(), - tomorrow.month(), - tomorrow.day(), - ); - - let shell = message.as_str().replace("'", "\\'"); - let cmd = shlex::split(&shell).unwrap(); - for s in cmd { - command.arg(&s); - } - let output = command.output().unwrap(); - let s = String::from_utf8_lossy(&output.stdout); - let re = Regex::new(r"^Created task (?P\d+).\n$").unwrap(); - let caps = re.captures(&s).unwrap(); - - let task_id = caps["task_id"].parse::().unwrap(); - assert_eq!(task_id, total_tasks + 1); - - assert!(app.update(true).await.is_ok()); - assert_eq!(app.tasks.len(), (total_tasks + 1) as usize); - assert_eq!(app.current_context_filter, ""); - - let task = app.task_by_id(task_id).unwrap(); - - for s in &["DUE", "MONTH", "PENDING", "QUARTER", "TOMORROW", "UDA", "UNBLOCKED", "YEAR"] { - if !(task.tags().unwrap().contains(&s.to_string())) { - println!("Expected {} to be in tags", s); - } - } - - let output = std::process::Command::new("task") - .arg("rc.confirmation=off") - .arg("undo") - .output() - .unwrap(); - - let mut app = TaskwarriorTui::new("next", false).await.unwrap(); - assert!(app.update(true).await.is_ok()); - assert_eq!(app.tasks.len(), total_tasks as usize); - assert_eq!(app.current_context_filter, ""); - } - - async fn test_task_earlier_today() { - let total_tasks: u64 = 26; - - let mut app = TaskwarriorTui::new("next", false).await.unwrap(); - assert!(app.update(true).await.is_ok()); - assert_eq!(app.tasks.len(), total_tasks as usize); - assert_eq!(app.current_context_filter, ""); - - let now = Local::now(); - let now = TimeZone::from_utc_datetime(now.offset(), &now.naive_utc()); - - let mut command = std::process::Command::new("task"); - command.arg("add"); - let message = "'new task for testing earlier today' due:now"; - - let shell = message.replace("'", "\\'"); - let cmd = shlex::split(&shell).unwrap(); - for s in cmd { - command.arg(&s); - } - let output = command.output().unwrap(); - let s = String::from_utf8_lossy(&output.stdout); - let re = Regex::new(r"^Created task (?P\d+).\n$").unwrap(); - let caps = re.captures(&s).unwrap(); - let task_id = caps["task_id"].parse::().unwrap(); - assert_eq!(task_id, total_tasks + 1); - - assert!(app.update(true).await.is_ok()); - assert_eq!(app.tasks.len(), (total_tasks + 1) as usize); - assert_eq!(app.current_context_filter, ""); - - let task = app.task_by_id(task_id).unwrap(); - for s in &[ - "DUE", - "DUETODAY", - "MONTH", - "OVERDUE", - "PENDING", - "QUARTER", - "TODAY", - "UDA", - "UNBLOCKED", - "YEAR", - ] { - assert!(task.tags().unwrap().contains(&s.to_string())); - } - - let output = std::process::Command::new("task") - .arg("rc.confirmation=off") - .arg("undo") - .output() - .unwrap(); - - let mut app = TaskwarriorTui::new("next", false).await.unwrap(); - assert!(app.update(true).await.is_ok()); - assert_eq!(app.tasks.len(), total_tasks as usize); - assert_eq!(app.current_context_filter, ""); - } - - async fn test_task_later_today() { - let total_tasks: u64 = 26; - - let mut app = TaskwarriorTui::new("next", false).await.unwrap(); - assert!(app.update(true).await.is_ok()); - assert_eq!(app.tasks.len(), total_tasks as usize); - assert_eq!(app.current_context_filter, ""); - - let now = Local::now(); - let now = TimeZone::from_utc_datetime(now.offset(), &now.naive_utc()); - - let mut command = std::process::Command::new("task"); - command.arg("add"); - let message = format!( - "'new task for testing later today' due:'{:04}-{:02}-{:02}T{:02}:{:02}:{:02}'", - now.year(), - now.month(), - now.day(), - now.hour(), - now.minute() + 1, - now.second(), - ); - - let shell = message.as_str().replace("'", "\\'"); - let cmd = shlex::split(&shell).unwrap(); - for s in cmd { - command.arg(&s); - } - let output = command.output().unwrap(); - let s = String::from_utf8_lossy(&output.stdout); - let re = Regex::new(r"^Created task (?P\d+).\n$").unwrap(); - let caps = re.captures(&s).unwrap(); - let task_id = caps["task_id"].parse::().unwrap(); - assert_eq!(task_id, total_tasks + 1); - - assert!(app.update(true).await.is_ok()); - assert_eq!(app.tasks.len(), (total_tasks + 1) as usize); - assert_eq!(app.current_context_filter, ""); - - let task = app.task_by_id(task_id).unwrap(); - for s in &["DUE", "DUETODAY", "MONTH", "PENDING", "QUARTER", "TODAY", "UDA", "UNBLOCKED", "YEAR"] { - assert!(task.tags().unwrap().contains(&s.to_string())); - } - - let output = std::process::Command::new("task") - .arg("rc.confirmation=off") - .arg("undo") - .output() - .unwrap(); - - let mut app = TaskwarriorTui::new("next", false).await.unwrap(); - assert!(app.update(true).await.is_ok()); - assert_eq!(app.tasks.len(), total_tasks as usize); - assert_eq!(app.current_context_filter, ""); - } - - async fn test_draw_empty_task_report() { - let mut expected = Buffer::with_lines(vec![ - " Tasks Projects Calendar [none]", - " ", - " ", - " ", - " ", - " ", - " ", - "──────────────────────────────────────────────────", - "Task not found ", - " ", - " ", - " ", - " ", - "Filter Tasks ", - "(status:pending or status:waiting) ", - ]); - - for i in 0..=49 { - // First line - expected.get_mut(i, 0).set_style(Style::default().add_modifier(Modifier::REVERSED)); - } - for i in 1..=5 { - // Tasks - expected - .get_mut(i, 0) - .set_style(Style::default().add_modifier(Modifier::BOLD).add_modifier(Modifier::REVERSED)); - } - for i in 0..=49 { - // Command line - expected.get_mut(i, 13).set_style(Style::default().add_modifier(Modifier::REVERSED)); - } - - let mut app = TaskwarriorTui::new("next", false).await.unwrap(); - - app.task_report_next(); - app.context_next(); - - let total_tasks: u64 = 0; - - assert!(app.update(true).await.is_ok()); - assert_eq!(app.tasks.len(), total_tasks as usize); - assert_eq!(app.current_context_filter, ""); - - let now = Local::now(); - let now = TimeZone::from_utc_datetime(now.offset(), &now.naive_utc()); - - app.update(true).await.unwrap(); - - let backend = TestBackend::new(50, 15); - let mut terminal = Terminal::new(backend).unwrap(); - terminal - .draw(|f| { - app.draw(f); - }) - .unwrap(); - - assert_eq!(terminal.backend().size().unwrap(), expected.area); - terminal.backend().assert_buffer(&expected); - } - - async fn test_draw_task_report_with_extended_modify_command() { - let mut expected1 = Buffer::with_lines(vec![ - "Modify Task 10 ", - " based on your .taskrc ", - " ", - ]); - - let mut expected2 = Buffer::with_lines(vec![ - "Modify Task 10 ", - "Support color for tasks b", - " ", - ]); - - for i in 0..=13 { - // Task - expected1.get_mut(i, 0).set_style(Style::default().add_modifier(Modifier::BOLD)); - expected2.get_mut(i, 0).set_style(Style::default().add_modifier(Modifier::BOLD)); - } - for i in 0..=24 { - // Command line - expected1.get_mut(i, 0).set_style(Style::default().add_modifier(Modifier::REVERSED)); - expected2.get_mut(i, 0).set_style(Style::default().add_modifier(Modifier::REVERSED)); - } - - let mut app = TaskwarriorTui::new("next", false).await.unwrap(); - - let total_tasks: u64 = 26; - - assert!(app.update(true).await.is_ok()); - assert_eq!(app.tasks.len(), total_tasks as usize); - assert_eq!(app.current_context_filter, ""); - - let now = Local::now(); - let now = TimeZone::from_utc_datetime(now.offset(), &now.naive_utc()); - - app.mode = Mode::Tasks(Action::Modify); - match app.task_table_state.mode() { - TableMode::SingleSelection => match app.task_current() { - Some(t) => { - let s = format!("{} ", t.description()); - app.modify = app.modify.with_value(s); - } - None => app.modify.reset(), - }, - TableMode::MultipleSelection => app.modify.reset(), - } - - app.update(true).await.unwrap(); - - let backend = TestBackend::new(25, 3); - let mut terminal = Terminal::new(backend).unwrap(); - terminal - .draw(|f| { - let rects = Layout::default() - .direction(Direction::Vertical) - .constraints([Constraint::Min(0), Constraint::Length(3)].as_ref()) - .split(f.size()); - - let position = &app.modify.visual_cursor(); - f.set_cursor( - std::cmp::min(rects[1].x + position as u16, rects[1].x + rects[1].width.saturating_sub(2)), - rects[1].y + 1, - ); - f.render_widget(Clear, rects[1]); - let selected = app.current_selection; - let task_ids = if app.tasks.is_empty() { - vec!["0".to_string()] - } else { - match app.task_table_state.mode() { - TableMode::SingleSelection => { - vec![app.tasks[selected].id().unwrap_or_default().to_string()] - } - TableMode::MultipleSelection => { - let mut tids = vec![]; - for uuid in app.marked.iter() { - if let Some(t) = app.task_by_uuid(*uuid) { - tids.push(t.id().unwrap_or_default().to_string()); - } - } - tids - } - } - }; - let label = if task_ids.len() > 1 { - format!("Modify Tasks {}", task_ids.join(",")) - } else { - format!("Modify Task {}", task_ids.join(",")) - }; - app.draw_command( - f, - rects[1], - app.modify.value(), - (Span::styled(label, Style::default().add_modifier(Modifier::BOLD)), None), - position, - true, - app.error.clone(), - ); - }) - .unwrap(); - - assert_eq!(terminal.backend().size().unwrap(), expected1.area); - terminal.backend().assert_buffer(&expected1); - - app.modify.move_home(); - - terminal - .draw(|f| { - let rects = Layout::default() - .direction(Direction::Vertical) - .constraints([Constraint::Min(0), Constraint::Length(3)].as_ref()) - .split(f.size()); - - let position = &app.modify.visual_cursor(); - f.set_cursor( - std::cmp::min(rects[1].x + position as u16, rects[1].x + rects[1].width.saturating_sub(2)), - rects[1].y + 1, - ); - f.render_widget(Clear, rects[1]); - let selected = app.current_selection; - let task_ids = if app.tasks.is_empty() { - vec!["0".to_string()] - } else { - match app.task_table_state.mode() { - TableMode::SingleSelection => { - vec![app.tasks[selected].id().unwrap_or_default().to_string()] - } - TableMode::MultipleSelection => { - let mut tids = vec![]; - for uuid in app.marked.iter() { - if let Some(t) = app.task_by_uuid(*uuid) { - tids.push(t.id().unwrap_or_default().to_string()); - } - } - tids - } - } - }; - let label = if task_ids.len() > 1 { - format!("Modify Tasks {}", task_ids.join(",")) - } else { - format!("Modify Task {}", task_ids.join(",")) - }; - app.draw_command( - f, - rects[1], - app.modify.value(), - (Span::styled(label, Style::default().add_modifier(Modifier::BOLD)), None), - position, - true, - app.error.clone(), - ); - }) - .unwrap(); - - assert_eq!(terminal.backend().size().unwrap(), expected2.area); - terminal.backend().assert_buffer(&expected2); - } - - async fn test_draw_task_report() { - let mut expected = Buffer::with_lines(vec![ - "╭Task|Calendar───────────────────────────────────╮", - "│ ID Age Deps P Projec Tag Due Descrip Urg │", - "│ │", - "│• 27 0s U new ta… 15.00│", - "│ 28 0s U none new ta… 15.00│", - "╰────────────────────────────────────────────────╯", - "╭Task 27─────────────────────────────────────────╮", - "│ │", - "│Name Value │", - "│------------- ----------------------------------│", - "│ID 27 │", - "╰────────────────────────────────────────────────╯", - "╭Filter Tasks────────────────────────────────────╮", - "│(status:pending or status:waiting) │", - "╰────────────────────────────────────────────────╯", - ]); - - for i in 1..=4 { - // Task - expected.get_mut(i, 0).set_style(Style::default().add_modifier(Modifier::BOLD)); - } - for i in 6..=13 { - // Calendar - expected.get_mut(i, 0).set_style(Style::default().add_modifier(Modifier::DIM)); - } - - for r in &[ - 1..=4, // ID - 6..=8, // Age - 10..=13, // Deps - 15..=15, // P - 17..=22, // Projec - 24..=30, // Tag - 32..=34, // Due - 36..=42, // Descr - 44..=48, // Urg - ] { - for i in r.clone() { - expected.get_mut(i, 1).set_style(Style::default().add_modifier(Modifier::UNDERLINED)); - } - } - - for i in 1..expected.area().width - 1 { - expected - .get_mut(i, 3) - .set_style(Style::default().fg(Color::Indexed(1)).bg(Color::Reset).add_modifier(Modifier::BOLD)); - } - - for i in 1..expected.area().width - 1 { - expected - .get_mut(i, 4) - .set_style(Style::default().fg(Color::Indexed(1)).bg(Color::Indexed(4))); - } - - let mut app = TaskwarriorTui::new("next", false).await.unwrap(); - - app.task_report_next(); - app.context_next(); - - let total_tasks: u64 = 26; - - assert!(app.update(true).await.is_ok()); - assert_eq!(app.tasks.len(), total_tasks as usize); - assert_eq!(app.current_context_filter, ""); - - let now = Local::now(); - let now = TimeZone::from_utc_datetime(now.offset(), &now.naive_utc()); - - let mut command = std::process::Command::new("task"); - command.arg("add"); - let message = "'new task 1 for testing draw' priority:U"; - - let shell = message.replace("'", "\\'"); - let cmd = shlex::split(&shell).unwrap(); - for s in cmd { - command.arg(&s); - } - let output = command.output().unwrap(); - let s = String::from_utf8_lossy(&output.stdout); - let re = Regex::new(r"^Created task (?P\d+).\n$").unwrap(); - let caps = re.captures(&s).unwrap(); - let task_id = caps["task_id"].parse::().unwrap(); - assert_eq!(task_id, total_tasks + 1); - - let mut command = std::process::Command::new("task"); - command.arg("add"); - let message = "'new task 2 for testing draw' priority:U +none"; - - let shell = message.replace("'", "\\'"); - let cmd = shlex::split(&shell).unwrap(); - for s in cmd { - command.arg(&s); - } - let output = command.output().unwrap(); - let s = String::from_utf8_lossy(&output.stdout); - let re = Regex::new(r"^Created task (?P\d+).\n$").unwrap(); - let caps = re.captures(&s).unwrap(); - let task_id = caps["task_id"].parse::().unwrap(); - assert_eq!(task_id, total_tasks + 2); - - app.task_report_next(); - app.task_report_previous(); - app.task_report_next_page(); - app.task_report_previous_page(); - app.task_report_bottom(); - app.task_report_top(); - app.update(true).await.unwrap(); - - let backend = TestBackend::new(50, 15); - let mut terminal = Terminal::new(backend).unwrap(); - app.task_report_show_info = !app.task_report_show_info; - terminal - .draw(|f| { - app.draw(f); - app.draw(f); - }) - .unwrap(); - app.task_report_show_info = !app.task_report_show_info; - terminal - .draw(|f| { - app.draw(f); - app.draw(f); - }) - .unwrap(); - - let output = std::process::Command::new("task") - .arg("rc.confirmation=off") - .arg("undo") - .output() - .unwrap(); - let output = std::process::Command::new("task") - .arg("rc.confirmation=off") - .arg("undo") - .output() - .unwrap(); - - assert_eq!(terminal.backend().size().unwrap(), expected.area); - terminal.backend().assert_buffer(&expected); - } - - async fn test_draw_calendar() { - let mut expected = Buffer::with_lines(vec![ - " Tasks Projects Calendar [none]", - " ", - " 2020 ", - " ", - " January February ", - " Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa ", - " 1 2 3 4 1 ", - " 5 6 7 8 9 10 11 2 3 4 5 6 7 8 ", - " 12 13 14 15 16 17 18 9 10 11 12 13 14 15 ", - " 19 20 21 22 23 24 25 16 17 18 19 20 21 22 ", - " 26 27 28 29 30 31 23 24 25 26 27 28 29 ", - " ", - " ", - " ", - " ", - ]); - - for i in 0..=49 { - // First line - expected.get_mut(i, 0).set_style(Style::default().add_modifier(Modifier::REVERSED)); - } - for i in 20..=27 { - // Calendar - expected - .get_mut(i, 0) - .set_style(Style::default().add_modifier(Modifier::BOLD).add_modifier(Modifier::REVERSED)); - } - - for i in 0..=49 { - expected.get_mut(i, 2).set_style(Style::default().add_modifier(Modifier::UNDERLINED)); - } - - for i in 3..=22 { - expected.get_mut(i, 4).set_style(Style::default().bg(Color::Reset)); - } - - for i in 25..=44 { - expected.get_mut(i, 4).set_style(Style::default().bg(Color::Reset)); - } - - for i in 3..=22 { - expected - .get_mut(i, 5) - .set_style(Style::default().bg(Color::Reset).add_modifier(Modifier::UNDERLINED)); - } - - for i in 25..=44 { - expected - .get_mut(i, 5) - .set_style(Style::default().bg(Color::Reset).add_modifier(Modifier::UNDERLINED)); - } - - let mut app = TaskwarriorTui::new("next", false).await.unwrap(); - - app.task_report_next(); - app.context_next(); - app.update(true).await.unwrap(); - - app.calendar_year = 2020; - app.mode = Mode::Calendar; - - app.update(true).await.unwrap(); - - let backend = TestBackend::new(50, 15); - let mut terminal = Terminal::new(backend).unwrap(); - terminal - .draw(|f| { - app.draw(f); - app.draw(f); - }) - .unwrap(); - - assert_eq!(terminal.backend().size().unwrap(), expected.area); - terminal.backend().assert_buffer(&expected); - } - - async fn test_draw_help_popup() { - let mut expected = Buffer::with_lines(vec![ - "╭Help──────────────────────────────────╮", - "│# Default Keybindings │", - "│ │", - "│Keybindings: │", - "│ │", - "│ Esc: │", - "│ │", - "│ ]: Next view │", - "│ │", - "│ [: Previous view │", - "╰──────────────────────────────────────╯", - "9% ─────────────────────────────────────", - ]); - - for i in 1..=4 { - // Calendar - expected.get_mut(i, 0).set_style(Style::default().add_modifier(Modifier::BOLD)); - } - expected.get_mut(3, 11).set_style(Style::default().fg(Color::Gray)); - expected.get_mut(4, 11).set_style(Style::default().fg(Color::Gray)); - expected.get_mut(5, 11).set_style(Style::default().fg(Color::Gray)); - - let mut app = TaskwarriorTui::new("next", false).await.unwrap(); - - app.mode = Mode::Tasks(Action::HelpPopup); - app.task_report_next(); - app.context_next(); - app.update(true).await.unwrap(); - - let backend = TestBackend::new(40, 12); - let mut terminal = Terminal::new(backend).unwrap(); - terminal - .draw(|f| { - app.draw_help_popup(f, 100, 100); - }) - .unwrap(); - - assert_eq!(terminal.backend().size().unwrap(), expected.area); - terminal.backend().assert_buffer(&expected); - } - - // #[test] - async fn test_draw_context_menu() { - let mut expected = Buffer::with_lines(vec![ - "╭Context───────────────────────────────────────────────────────────────────────╮", - "│Name Description Active│", - "│ │", - "│• none yes │", - "│ finance +finance -private no │", - "│ personal +personal -private no │", - "│ work -personal -private no │", - "│ │", - "│ │", - "╰──────────────────────────────────────────────────────────────────────────────╯", - ]); - - for i in 1..=7 { - // Task - expected.get_mut(i, 0).set_style(Style::default().add_modifier(Modifier::BOLD)); - } - - for i in 1..=10 { - // Task - expected.get_mut(i, 1).set_style(Style::default().add_modifier(Modifier::UNDERLINED)); - } - - for i in 12..=71 { - // Task - expected.get_mut(i, 1).set_style(Style::default().add_modifier(Modifier::UNDERLINED)); - } - - for i in 73..=78 { - // Task - expected.get_mut(i, 1).set_style(Style::default().add_modifier(Modifier::UNDERLINED)); - } - - for i in 1..=78 { - // Task - expected.get_mut(i, 3).set_style(Style::default().add_modifier(Modifier::BOLD)); - } - - let mut app = TaskwarriorTui::new("next", false).await.unwrap(); - - app.mode = Mode::Tasks(Action::ContextMenu); - app.task_report_next(); - app.update(true).await.unwrap(); - - let backend = TestBackend::new(80, 10); - let mut terminal = Terminal::new(backend).unwrap(); - terminal - .draw(|f| { - app.draw_context_menu(f, 100, 100); - app.draw_context_menu(f, 100, 100); - }) - .unwrap(); - - assert_eq!(terminal.backend().size().unwrap(), expected.area); - terminal.backend().assert_buffer(&expected); - } - - // #[test] - async fn test_graphemes() { - dbg!("写作业".graphemes(true).count()); - dbg!(UnicodeWidthStr::width("写作业")); - dbg!(UnicodeWidthStr::width("abc")); - - let mut app = TaskwarriorTui::new("next", false).await.unwrap(); - - if let Some(task) = app.task_by_id(27) { - let i = app.task_index_by_uuid(*task.uuid()).unwrap_or_default(); - app.current_selection = i; - app.current_selection_id = None; - app.current_selection_uuid = None; - } - app.update(true).await.unwrap(); - app.mode = Mode::Tasks(Action::Modify); - match app.task_current() { - Some(t) => { - let s = format!("{} ", t.description()); - app.modify = app.modify.with_value(s); - } - None => app.modify.reset(), - } - app.update(true).await.unwrap(); - - dbg!(app.modify.value()); - dbg!(app.modify.value().len()); - dbg!(app.modify.value().graphemes(true).count()); - dbg!(app.modify.cursor()); - let position = app.modify.visual_cursor(); - dbg!(position); - } - - // #[test] - async fn test_taskwarrior_tui_completion() { - let mut app = TaskwarriorTui::new("next", false).await.unwrap(); - app.handle_input(KeyCode::Char('z')).await.unwrap(); - app.mode = Mode::Tasks(Action::Add); - app.update_completion_list(); - let input = "Wash car"; - for c in input.chars() { - app.handle_input(KeyCode::Char(c)).await.unwrap(); - } - // app.handle_input(KeyCode::Ctrl('e')).await.unwrap(); - - let input = " project:CO"; - for c in input.chars() { - app.handle_input(KeyCode::Char(c)).await.unwrap(); - } - - app.mode = Mode::Tasks(Action::Add); - app.update_completion_list(); - app.handle_input(KeyCode::Tab).await.unwrap(); - app.handle_input(KeyCode::Char('\n')).await.unwrap(); - let backend = TestBackend::new(80, 50); - let mut terminal = Terminal::new(backend).unwrap(); - terminal - .draw(|f| { - app.draw(f); - app.draw(f); - }) - .unwrap(); - println!("{}", buffer_view(terminal.backend().buffer())); - } -} +mod tests {} From 951939efb8008ef46ab0b71cae58727aaea83ba3 Mon Sep 17 00:00:00 2001 From: Dheepak Krishnamurthy Date: Mon, 4 Sep 2023 14:17:38 -0400 Subject: [PATCH 05/13] WIP --- Cargo.lock | 296 +++---------- Cargo.toml | 3 +- src/action.rs | 29 +- src/app.rs | 37 +- src/config.rs | 1121 +++++++++++++++-------------------------------- src/keyevent.rs | 279 ++++++++++++ src/keymap.rs | 196 +++++++++ src/main.rs | 149 ++++--- 8 files changed, 1023 insertions(+), 1087 deletions(-) create mode 100644 src/keyevent.rs create mode 100644 src/keymap.rs diff --git a/Cargo.lock b/Cargo.lock index 9a29df0e..fd09daaa 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" -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" -dependencies = [ - "memchr", - "thiserror", - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.7.3" +name = "pear" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bee7be22ce7918f641a33f08e3f43388c7656772244e2bbb2477f44cc9021a" +checksum = "61a386cd715229d399604b50d1361683fe687066f42d56f54be995bc6868f71c" dependencies = [ - "pest", - "pest_generator", + "inlinable_string", + "pear_codegen", + "yansi 1.0.0-rc.1", ] [[package]] -name = "pest_generator" -version = "2.7.3" +name = "pear_codegen" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1511785c5e98d79a05e8a6bc34b4ac2168a0e3e92161862030ad84daa223141" +checksum = "da9f0f13dac8069c139e8300a6510e3f4143ecf5259c60b116a9b271b4ca0d54" 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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" - -[[package]] -name = "ucd-trie" -version = "0.1.6" +name = "uncased" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +checksum = "9b9bc53168a4be7402ab86c3aad243a84dd7381d09be0eddc81280c1da95ca68" +dependencies = [ + "version_check", +] [[package]] name = "unicase" @@ -2212,16 +2043,13 @@ dependencies = [ ] [[package]] -name = "yaml-rust" -version = "0.4.5" +name = "yansi" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" -dependencies = [ - "linked-hash-map", -] +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "yansi" -version = "0.5.1" +version = "1.0.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +checksum = "1367295b8f788d371ce2dbc842c7b709c73ee1364d30351dd300ec2203b12377" diff --git a/Cargo.toml b/Cargo.toml index 5ef8efef..584246d4 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 7d8ddae6..0180ab4d 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 520966d3..dbfafc76 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 26811d14..85bf8c7e 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}; + +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}; -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 +#[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) + } +} + +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 } } -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 +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 + } +} + +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) } } -#[derive(Debug)] -pub struct Uda { - label: String, - kind: String, - values: Option>, - default: Option, - urgency: Option, +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(Debug)] +#[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 = 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 { + 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(); + + 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