From 97df57aba5750e5ecfa8a0084d1ac46a3c3a318e Mon Sep 17 00:00:00 2001 From: Shea Frembling Date: Tue, 3 Mar 2026 14:51:37 -0600 Subject: [PATCH] add claude's changes --- Cargo.lock | 1318 +++++----------------------------------- Cargo.toml | 14 +- src/app.rs | 350 +++++++++++ src/main.rs | 147 ++++- src/{csv.rs => row.rs} | 39 +- src/ui.rs | 290 +++++++++ 6 files changed, 975 insertions(+), 1183 deletions(-) rename src/{csv.rs => row.rs} (78%) diff --git a/Cargo.lock b/Cargo.lock index 6dfba46..6ad7dd9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,15 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - [[package]] name = "allocator-api2" version = "0.2.21" @@ -32,63 +23,18 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" -[[package]] -name = "atomic" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340" -dependencies = [ - "bytemuck", -] - [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "bit-set" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" -[[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.20.2" @@ -96,10 +42,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] -name = "bytemuck" -version = "1.25.0" +name = "cassowary" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" [[package]] name = "castaway" @@ -126,23 +72,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "chacha20" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.0", - "rand_core 0.10.0", -] - [[package]] name = "chrono" version = "0.4.44" @@ -158,9 +87,9 @@ dependencies = [ [[package]] name = "compact_str" -version = "0.9.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" +checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" dependencies = [ "castaway", "cfg-if", @@ -170,49 +99,20 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "convert_case" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "cpufeatures" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" -dependencies = [ - "libc", -] - [[package]] name = "crossterm" -version = "0.29.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ - "bitflags 2.11.0", + "bitflags", "crossterm_winapi", - "derive_more", - "document-features", "mio", "parking_lot", "rustix", @@ -230,26 +130,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "crypto-common" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "csscolorparser" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2a7d3066da2de787b7f032c736763eb7ae5d355f81a68bab2675a96008b0bf" -dependencies = [ - "lab", - "phf", -] - [[package]] name = "darling" version = "0.23.0" @@ -270,7 +150,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.117", + "syn", ] [[package]] @@ -281,63 +161,7 @@ checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core", "quote", - "syn 2.0.117", -] - -[[package]] -name = "deltae" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5729f5117e208430e437df2f4843f5e5952997175992d1414f94c57d61e270b4" - -[[package]] -name = "deranged" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" -dependencies = [ - "powerfmt", -] - -[[package]] -name = "derive_more" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn 2.0.117", -] - -[[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 = "document-features" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" -dependencies = [ - "litrs", + "syn", ] [[package]] @@ -359,37 +183,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys", -] - -[[package]] -name = "euclid" -version = "0.22.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df61bf483e837f88d5c2291dcf55c67be7e676b3a51acc48db3a7b163b91ed63" -dependencies = [ - "num-traits", -] - -[[package]] -name = "fancy-regex" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" -dependencies = [ - "bit-set", - "regex", -] - -[[package]] -name = "filedescriptor" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d" -dependencies = [ - "libc", - "thiserror 1.0.69", - "winapi", + "windows-sys 0.61.2", ] [[package]] @@ -398,46 +192,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" -[[package]] -name = "finl_unicode" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9844ddc3a6e533d62bba727eb6c28b5d360921d5175e9ff0f1e621a5c590a4d5" - -[[package]] -name = "fixedbitset" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - [[package]] name = "foldhash" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" -[[package]] -name = "foldhash" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" - -[[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.3.4" @@ -446,42 +206,19 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "r-efi 5.3.0", + "r-efi", "wasip2", ] -[[package]] -name = "getrandom" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" -dependencies = [ - "cfg-if", - "libc", - "r-efi 6.0.0", - "rand_core 0.10.0", - "wasip2", - "wasip3", -] - [[package]] name = "hashbrown" version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "foldhash 0.1.5", -] - -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ "allocator-api2", "equivalent", - "foldhash 0.2.0", + "foldhash", ] [[package]] @@ -490,12 +227,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - [[package]] name = "iana-time-zone" version = "0.1.65" @@ -520,30 +251,12 @@ dependencies = [ "cc", ] -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" -[[package]] -name = "indexmap" -version = "2.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" -dependencies = [ - "equivalent", - "hashbrown 0.16.1", - "serde", - "serde_core", -] - [[package]] name = "indoc" version = "2.0.7" @@ -563,14 +276,14 @@ dependencies = [ "indoc", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] name = "itertools" -version = "0.14.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] @@ -591,61 +304,17 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "kasuari" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fe90c1150662e858c7d5f945089b7517b0a80d8bf7ba4b1b5ffc984e7230a5b" -dependencies = [ - "hashbrown 0.16.1", - "portable-atomic", - "thiserror 2.0.18", -] - -[[package]] -name = "lab" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf36173d4167ed999940f804952e6b08197cae5ad5d572eb4db150ce8ad5d58f" - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - [[package]] name = "libc" version = "0.2.182" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" -[[package]] -name = "line-clipping" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4de44e98ddbf09375cbf4d17714d18f39195f4f4894e8524501726fd9a8a4a" -dependencies = [ - "bitflags 2.11.0", -] - [[package]] name = "linux-raw-sys" -version = "0.12.1" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" - -[[package]] -name = "litrs" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "lock_api" @@ -664,50 +333,13 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "lru" -version = "0.16.3" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.16.1", + "hashbrown", ] -[[package]] -name = "mac_address" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303" -dependencies = [ - "nix", - "winapi", -] - -[[package]] -name = "memchr" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" - -[[package]] -name = "memmem" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15" - -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - [[package]] name = "mio" version = "1.1.1" @@ -717,58 +349,20 @@ dependencies = [ "libc", "log", "wasi", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] name = "mmtui" version = "0.1.0" dependencies = [ + "anyhow", "chrono", - "rand 0.10.0", + "crossterm", + "rand", "ratatui", ] -[[package]] -name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" -dependencies = [ - "bitflags 2.11.0", - "cfg-if", - "cfg_aliases", - "libc", - "memoffset", -] - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "num-conv" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" - -[[package]] -name = "num-derive" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -778,30 +372,12 @@ dependencies = [ "autocfg", ] -[[package]] -name = "num_threads" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" -dependencies = [ - "libc", -] - [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" -[[package]] -name = "ordered-float" -version = "4.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" -dependencies = [ - "num-traits", -] - [[package]] name = "parking_lot" version = "0.12.5" @@ -826,120 +402,18 @@ dependencies = [ ] [[package]] -name = "pest" -version = "2.8.6" +name = "paste" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "memchr", - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "pest_meta" -version = "2.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" -dependencies = [ - "pest", - "sha2", -] - -[[package]] -name = "phf" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" -dependencies = [ - "phf_macros", - "phf_shared", -] - -[[package]] -name = "phf_codegen" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" -dependencies = [ - "phf_generator", - "phf_shared", -] - -[[package]] -name = "phf_generator" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" -dependencies = [ - "phf_shared", - "rand 0.8.5", -] - -[[package]] -name = "phf_macros" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" -dependencies = [ - "phf_generator", - "phf_shared", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "phf_shared" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -dependencies = [ - "siphasher", -] - -[[package]] -name = "portable-atomic" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn 2.0.117", + "zerocopy", ] [[package]] @@ -966,127 +440,54 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" -[[package]] -name = "r-efi" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" - [[package]] name = "rand" -version = "0.8.5" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ - "rand_core 0.6.4", + "rand_chacha", + "rand_core", ] [[package]] -name = "rand" -version = "0.10.0" +name = "rand_chacha" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ - "chacha20", - "getrandom 0.4.2", - "rand_core 0.10.0", + "ppv-lite86", + "rand_core", ] [[package]] name = "rand_core" -version = "0.6.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" - -[[package]] -name = "rand_core" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom", +] [[package]] name = "ratatui" -version = "0.30.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1ce67fb8ba4446454d1c8dbaeda0557ff5e94d39d5e5ed7f10a65eb4c8266bc" +checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" dependencies = [ - "instability", - "ratatui-core", - "ratatui-crossterm", - "ratatui-macros", - "ratatui-termwiz", - "ratatui-widgets", -] - -[[package]] -name = "ratatui-core" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293" -dependencies = [ - "bitflags 2.11.0", + "bitflags", + "cassowary", "compact_str", - "hashbrown 0.16.1", + "crossterm", "indoc", + "instability", "itertools", - "kasuari", "lru", + "paste", "strum", - "thiserror 2.0.18", "unicode-segmentation", "unicode-truncate", - "unicode-width", -] - -[[package]] -name = "ratatui-crossterm" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "577c9b9f652b4c121fb25c6a391dd06406d3b092ba68827e6d2f09550edc54b3" -dependencies = [ - "cfg-if", - "crossterm", - "instability", - "ratatui-core", -] - -[[package]] -name = "ratatui-macros" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7f1342a13e83e4bb9d0b793d0ea762be633f9582048c892ae9041ef39c936f4" -dependencies = [ - "ratatui-core", - "ratatui-widgets", -] - -[[package]] -name = "ratatui-termwiz" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f76fe0bd0ed4295f0321b1676732e2454024c15a35d01904ddb315afd3d545c" -dependencies = [ - "ratatui-core", - "termwiz", -] - -[[package]] -name = "ratatui-widgets" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7dbfa023cd4e604c2553483820c5fe8aa9d71a42eea5aa77c6e7f35756612db" -dependencies = [ - "bitflags 2.11.0", - "hashbrown 0.16.1", - "indoc", - "instability", - "itertools", - "line-clipping", - "ratatui-core", - "strum", - "time", - "unicode-segmentation", - "unicode-width", + "unicode-width 0.2.0", ] [[package]] @@ -1095,58 +496,20 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.0", -] - -[[package]] -name = "regex" -version = "1.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", + "bitflags", ] [[package]] name = "rustix" -version = "1.1.4" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.11.0", + "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -1167,66 +530,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "semver" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "serde_json" -version = "1.0.149" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "digest", -] - [[package]] name = "shlex" version = "1.3.0" @@ -1264,12 +567,6 @@ dependencies = [ "libc", ] -[[package]] -name = "siphasher" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" - [[package]] name = "smallvec" version = "1.15.1" @@ -1290,34 +587,24 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" -version = "0.27.2" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" -version = "0.27.2" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.117", -] - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "rustversion", + "syn", ] [[package]] @@ -1331,142 +618,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "terminfo" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662" -dependencies = [ - "fnv", - "nom", - "phf", - "phf_codegen", -] - -[[package]] -name = "termios" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b" -dependencies = [ - "libc", -] - -[[package]] -name = "termwiz" -version = "0.23.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7" -dependencies = [ - "anyhow", - "base64", - "bitflags 2.11.0", - "fancy-regex", - "filedescriptor", - "finl_unicode", - "fixedbitset", - "hex", - "lazy_static", - "libc", - "log", - "memmem", - "nix", - "num-derive", - "num-traits", - "ordered-float", - "pest", - "pest_derive", - "phf", - "sha2", - "signal-hook", - "siphasher", - "terminfo", - "termios", - "thiserror 1.0.69", - "ucd-trie", - "unicode-segmentation", - "vtparse", - "wezterm-bidi", - "wezterm-blob-leases", - "wezterm-color-types", - "wezterm-dynamic", - "wezterm-input-types", - "winapi", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" -dependencies = [ - "thiserror-impl 2.0.18", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "time" -version = "0.3.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" -dependencies = [ - "deranged", - "libc", - "num-conv", - "num_threads", - "powerfmt", - "serde_core", - "time-core", -] - -[[package]] -name = "time-core" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" - -[[package]] -name = "typenum" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" - -[[package]] -name = "ucd-trie" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" - [[package]] name = "unicode-ident" version = "1.0.24" @@ -1481,59 +632,26 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-truncate" -version = "2.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b380a1238663e5f8a691f9039c73e1cdae598a30e9855f541d29b08b53e9a5" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" dependencies = [ "itertools", "unicode-segmentation", - "unicode-width", + "unicode-width 0.1.14", ] [[package]] name = "unicode-width" -version = "0.2.2" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] -name = "unicode-xid" -version = "0.2.6" +name = "unicode-width" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "uuid" -version = "1.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" -dependencies = [ - "atomic", - "getrandom 0.4.2", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "vtparse" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d9b2acfb050df409c972a37d3b8e08cdea3bddb0c09db9d53137e504cfabed0" -dependencies = [ - "utf8parse", -] +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" [[package]] name = "wasi" @@ -1550,15 +668,6 @@ dependencies = [ "wit-bindgen", ] -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen", -] - [[package]] name = "wasm-bindgen" version = "0.2.114" @@ -1591,7 +700,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.117", + "syn", "wasm-bindgen-shared", ] @@ -1604,112 +713,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags 2.11.0", - "hashbrown 0.15.5", - "indexmap", - "semver", -] - -[[package]] -name = "wezterm-bidi" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0a6e355560527dd2d1cf7890652f4f09bb3433b6aadade4c9b5ed76de5f3ec" -dependencies = [ - "log", - "wezterm-dynamic", -] - -[[package]] -name = "wezterm-blob-leases" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692daff6d93d94e29e4114544ef6d5c942a7ed998b37abdc19b17136ea428eb7" -dependencies = [ - "getrandom 0.3.4", - "mac_address", - "sha2", - "thiserror 1.0.69", - "uuid", -] - -[[package]] -name = "wezterm-color-types" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de81ef35c9010270d63772bebef2f2d6d1f2d20a983d27505ac850b8c4b4296" -dependencies = [ - "csscolorparser", - "deltae", - "lazy_static", - "wezterm-dynamic", -] - -[[package]] -name = "wezterm-dynamic" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f2ab60e120fd6eaa68d9567f3226e876684639d22a4219b313ff69ec0ccd5ac" -dependencies = [ - "log", - "ordered-float", - "strsim", - "thiserror 1.0.69", - "wezterm-dynamic-derive", -] - -[[package]] -name = "wezterm-dynamic-derive" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c0cf2d539c645b448eaffec9ec494b8b19bd5077d9e58cb1ae7efece8d575b" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "wezterm-input-types" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7012add459f951456ec9d6c7e6fc340b1ce15d6fc9629f8c42853412c029e57e" -dependencies = [ - "bitflags 1.3.2", - "euclid", - "lazy_static", - "serde", - "wezterm-dynamic", -] - [[package]] name = "winapi" version = "0.3.9" @@ -1753,7 +756,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1764,7 +767,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1791,6 +794,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -1800,96 +812,92 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" + +[[package]] +name = "zerocopy" +version = "0.8.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" dependencies = [ - "wit-bindgen-rust-macro", + "zerocopy-derive", ] [[package]] -name = "wit-bindgen-core" -version = "0.51.0" +name = "zerocopy-derive" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" dependencies = [ - "anyhow", - "heck", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap", - "prettyplease", - "syn 2.0.117", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", "proc-macro2", "quote", - "syn 2.0.117", - "wit-bindgen-core", - "wit-bindgen-rust", + "syn", ] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags 2.11.0", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] - -[[package]] -name = "zmij" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 3124877..cc85166 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,15 @@ [package] name = "mmtui" version = "0.1.0" -edition = "2024" +edition = "2021" + +[[bin]] +name = "mmtui" +path = "src/main.rs" [dependencies] -chrono = "0.4.44" -rand = "0.10.0" -ratatui = "0.30.0" +ratatui = "0.29" +crossterm = "0.28" +chrono = "0.4" +rand = "0.9" +anyhow = "1" diff --git a/src/app.rs b/src/app.rs index e69de29..c6b04ef 100644 --- a/src/app.rs +++ b/src/app.rs @@ -0,0 +1,350 @@ +use crate::row::{make_checksum, Row}; + +// ── Field indices for global inputs ────────────────────────────────────────── +pub const GLOBAL_HUT_ID: usize = 0; +pub const GLOBAL_CHECKSUM: usize = 1; +pub const GLOBAL_TARE: usize = 2; +pub const GLOBAL_LOT_STATUS: usize = 3; +pub const GLOBAL_PATH: usize = 4; +pub const GLOBAL_HUT_TYPE: usize = 5; +pub const GLOBAL_COUNT: usize = 6; + +// Field indices for per-row inputs +pub const ROW_MAT_ID: usize = 0; +pub const ROW_QUANTITY: usize = 1; +pub const ROW_UOM: usize = 2; +pub const ROW_COUNT: usize = 3; + +pub const LOT_STATUS_OPTIONS: &[&str] = &["Unrestricted", "Blocked", "Quality"]; + +/// Which section of the UI the cursor is in +#[derive(Clone, PartialEq)] +pub enum Focus { + GlobalField(usize), + LotRow(usize), // focused on the row itself (for deletion etc.) + LotField(usize, usize), // (row_index, field_index) +} + +#[derive(Clone, PartialEq)] +pub enum ModalKind { + ConfirmClose, + ConfirmQuit, +} + +pub struct LotEntry { + /// raw string buffers for each field + pub fields: [String; ROW_COUNT], +} + +impl LotEntry { + pub fn new() -> Self { + Self { + fields: [ + String::new(), // mat_id + String::new(), // quantity + String::new(), // uom + ], + } + } + + pub fn mat_id(&self) -> &str { + &self.fields[ROW_MAT_ID] + } + pub fn quantity(&self) -> f64 { + self.fields[ROW_QUANTITY].parse().unwrap_or(0.0) + } + pub fn uom(&self) -> &str { + &self.fields[ROW_UOM] + } +} + +pub struct App { + /// Global field string buffers + pub global: [String; GLOBAL_COUNT], + pub lots: Vec, + pub focus: Focus, + pub modal: Option, + /// Custom user-defined properties: (header, type, value) + pub custom_props: Vec<(String, String, String)>, +} + +impl App { + pub fn new() -> Self { + let mut global: [String; GLOBAL_COUNT] = Default::default(); + // sensible defaults + global[GLOBAL_LOT_STATUS] = LOT_STATUS_OPTIONS[0].to_string(); + Self { + global, + lots: Vec::new(), + focus: Focus::GlobalField(0), + modal: None, + custom_props: Vec::new(), + } + } + + // ── navigation ──────────────────────────────────────────────────────────── + + pub fn focus_next(&mut self) { + self.focus = match &self.focus { + Focus::GlobalField(i) => { + let next = i + 1; + if next < GLOBAL_COUNT { + Focus::GlobalField(next) + } else if !self.lots.is_empty() { + Focus::LotField(0, 0) + } else { + Focus::GlobalField(0) + } + } + Focus::LotField(row, field) => { + let next_field = field + 1; + if next_field < ROW_COUNT { + Focus::LotField(*row, next_field) + } else { + let next_row = row + 1; + if next_row < self.lots.len() { + Focus::LotField(next_row, 0) + } else { + Focus::GlobalField(0) + } + } + } + Focus::LotRow(row) => { + let next = row + 1; + if next < self.lots.len() { + Focus::LotRow(next) + } else { + Focus::LotRow(*row) + } + } + }; + } + + pub fn focus_prev(&mut self) { + self.focus = match &self.focus { + Focus::GlobalField(i) => { + if *i == 0 { + // wrap to last lot field or stay + if !self.lots.is_empty() { + let last_row = self.lots.len() - 1; + Focus::LotField(last_row, ROW_COUNT - 1) + } else { + Focus::GlobalField(GLOBAL_COUNT - 1) + } + } else { + Focus::GlobalField(i - 1) + } + } + Focus::LotField(row, field) => { + if *field == 0 { + if *row == 0 { + Focus::GlobalField(GLOBAL_COUNT - 1) + } else { + Focus::LotField(row - 1, ROW_COUNT - 1) + } + } else { + Focus::LotField(*row, field - 1) + } + } + Focus::LotRow(row) => { + if *row == 0 { + Focus::LotRow(0) + } else { + Focus::LotRow(row - 1) + } + } + }; + } + + /// Move focus to the same field in the next lot row (Down arrow) + pub fn focus_next_row(&mut self) { + match &self.focus { + Focus::LotField(row, field) => { + let next = row + 1; + if next < self.lots.len() { + let f = *field; + self.focus = Focus::LotField(next, f); + } + } + Focus::LotRow(row) => { + let next = row + 1; + if next < self.lots.len() { + self.focus = Focus::LotRow(next); + } + } + _ => {} + } + } + + /// Move focus to the same field in the previous lot row (Up arrow) + pub fn focus_prev_row(&mut self) { + match self.focus.clone() { + Focus::LotField(row, field) => { + if row == 0 { + self.focus = Focus::GlobalField(0); + } else { + self.focus = Focus::LotField(row - 1, field); + } + } + Focus::LotRow(row) => { + if row == 0 { + self.focus = Focus::GlobalField(0); + } else { + self.focus = Focus::LotRow(row - 1); + } + } + _ => {} + } + } + + // ── editing ─────────────────────────────────────────────────────────────── + + pub fn type_char(&mut self, c: char) { + match self.focus.clone() { + Focus::GlobalField(i) if i == GLOBAL_CHECKSUM => {} // toggled, not typed + Focus::GlobalField(i) => { + self.global[i].push(c); + } + Focus::LotField(row, field) => { + self.lots[row].fields[field].push(c); + } + _ => {} + } + } + + pub fn backspace(&mut self) { + match self.focus.clone() { + Focus::GlobalField(i) if i == GLOBAL_CHECKSUM => {} + Focus::GlobalField(i) => { + self.global[i].pop(); + } + Focus::LotField(row, field) => { + self.lots[row].fields[field].pop(); + } + _ => {} + } + } + + pub fn toggle_checksum(&mut self) { + let val = &self.global[GLOBAL_CHECKSUM]; + self.global[GLOBAL_CHECKSUM] = if val == "true" { + "false".into() + } else { + "true".into() + }; + } + + pub fn cycle_lot_status(&mut self) { + if let Focus::GlobalField(i) = self.focus { + if i == GLOBAL_LOT_STATUS { + let current = self.global[GLOBAL_LOT_STATUS].clone(); + let idx = LOT_STATUS_OPTIONS + .iter() + .position(|&s| s == current.as_str()) + .unwrap_or(0); + let next = (idx + 1) % LOT_STATUS_OPTIONS.len(); + self.global[GLOBAL_LOT_STATUS] = LOT_STATUS_OPTIONS[next].to_string(); + } + } + } + + // ── lot management ──────────────────────────────────────────────────────── + + pub fn add_lot(&mut self) { + self.lots.push(LotEntry::new()); + let new_row = self.lots.len() - 1; + self.focus = Focus::LotField(new_row, 0); + } + + pub fn remove_focused_lot(&mut self) { + let row = match &self.focus { + Focus::LotRow(r) | Focus::LotField(r, _) => *r, + _ => return, + }; + if row < self.lots.len() { + self.lots.remove(row); + if self.lots.is_empty() { + self.focus = Focus::GlobalField(0); + } else { + let new_row = row.min(self.lots.len() - 1); + self.focus = Focus::LotField(new_row, 0); + } + } + } + + // ── CSV generation ──────────────────────────────────────────────────────── + + pub fn generate_csv(&self) -> String { + let checksum = self.global[GLOBAL_CHECKSUM] == "true"; + let hut_id = &self.global[GLOBAL_HUT_ID]; + let tare: f64 = self.global[GLOBAL_TARE].parse().unwrap_or(0.0); + let lot_status = &self.global[GLOBAL_LOT_STATUS]; + let path = &self.global[GLOBAL_PATH]; + let hut_type = &self.global[GLOBAL_HUT_TYPE]; + + // Build header rows + let custom_headers: Vec = self + .custom_props + .iter() + .map(|(h, _, _)| h.clone()) + .collect(); + let custom_types: Vec = self + .custom_props + .iter() + .map(|(_, t, _)| t.clone()) + .collect(); + + let header_row = format!( + ",,,,,,,,,,,LOTdtCreationDate,LOTdtBornOnDate,LOTdtExpiryDate{}", + if custom_headers.is_empty() { + String::new() + } else { + format!(",{}", custom_headers.join(",")) + } + ); + let type_row = format!( + ",,,,,,,,,,,DATETIME,DATETIME,DATETIME{}", + if custom_types.is_empty() { + String::new() + } else { + format!(",{}", custom_types.join(",")) + } + ); + + let mut lines = vec![header_row, type_row]; + + for entry in &self.lots { + let name = if checksum { + make_checksum() + } else { + hut_id.to_ascii_uppercase() + }; + let row = Row::new( + &name, + hut_type, + path, + tare, + entry.mat_id(), + entry.quantity(), + entry.uom(), + lot_status, + false, // checksum already applied to name above + ); + + let custom_values: Vec = self + .custom_props + .iter() + .map(|(_, _, v)| v.clone()) + .collect(); + + let line = if custom_values.is_empty() { + format!("{}", row) + } else { + format!("{},{}", row, custom_values.join(",")) + }; + lines.push(line); + } + + lines.join("\n") + } +} diff --git a/src/main.rs b/src/main.rs index e487c7d..7e1e9c4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,148 @@ mod app; -mod csv; +mod row; mod ui; -fn main() { - println!("Hello, world!"); +use app::{App, Focus, ModalKind, GLOBAL_CHECKSUM, GLOBAL_LOT_STATUS}; +use crossterm::{ + event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyModifiers}, + execute, + terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, +}; +use ratatui::{backend::CrosstermBackend, Terminal}; +use std::io; + +fn main() -> anyhow::Result<()> { + // ── terminal setup ─────────────────────────────────────────────────────── + enable_raw_mode()?; + let mut stdout = io::stdout(); + execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?; + let backend = CrosstermBackend::new(stdout); + let mut terminal = Terminal::new(backend)?; + + let result = run(&mut terminal); + + // ── restore terminal ───────────────────────────────────────────────────── + disable_raw_mode()?; + execute!( + terminal.backend_mut(), + LeaveAlternateScreen, + DisableMouseCapture + )?; + terminal.show_cursor()?; + + // Output CSV to stdout AFTER restoring terminal so it's clean + match result { + Ok(Some(csv)) => { + print!("{}", csv); + } + Ok(None) => {} + Err(e) => { + eprintln!("Error: {}", e); + std::process::exit(1); + } + } + + Ok(()) +} + +/// Returns `Some(csv)` when the user confirms close, `None` on quit. +fn run(terminal: &mut Terminal) -> anyhow::Result> { + let mut app = App::new(); + + loop { + terminal.draw(|f| ui::draw(f, &app))?; + + if let Event::Key(key) = event::read()? { + // ── modal active ───────────────────────────────────────────────── + if app.modal.is_some() { + match key.code { + KeyCode::Char('y') | KeyCode::Char('Y') => { + let kind = app.modal.take().unwrap(); + match kind { + ModalKind::ConfirmClose => { + return Ok(Some(app.generate_csv())); + } + ModalKind::ConfirmQuit => { + return Ok(None); + } + } + } + KeyCode::Char('n') | KeyCode::Char('N') | KeyCode::Esc => { + app.modal = None; + } + _ => {} + } + continue; + } + + // ── global shortcuts ───────────────────────────────────────────── + match key.code { + KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => { + // Ctrl-C hard quit without modal + return Ok(None); + } + KeyCode::Char('c') => { + app.modal = Some(ModalKind::ConfirmClose); + continue; + } + KeyCode::Char('q') => { + app.modal = Some(ModalKind::ConfirmQuit); + continue; + } + KeyCode::Char('+') | KeyCode::Char('=') => { + app.add_lot(); + continue; + } + KeyCode::Char('-') => { + app.remove_focused_lot(); + continue; + } + KeyCode::Tab => { + app.focus_next(); + continue; + } + KeyCode::BackTab => { + app.focus_prev(); + continue; + } + KeyCode::Up => { + if matches!(app.focus, Focus::LotField(_, _) | Focus::LotRow(_)) { + app.focus_prev_row(); + } else { + app.focus_prev(); + } + continue; + } + KeyCode::Down => { + if matches!(app.focus, Focus::LotField(_, _) | Focus::LotRow(_)) { + app.focus_next_row(); + } else { + app.focus_next(); + } + continue; + } + _ => {} + } + + // ── field-specific input ───────────────────────────────────────── + match app.focus.clone() { + Focus::GlobalField(i) if i == GLOBAL_CHECKSUM => { + if key.code == KeyCode::Char(' ') { + app.toggle_checksum(); + } + } + Focus::GlobalField(i) if i == GLOBAL_LOT_STATUS => match key.code { + KeyCode::Left | KeyCode::Right | KeyCode::Char(' ') => { + app.cycle_lot_status(); + } + _ => {} + }, + _ => match key.code { + KeyCode::Backspace => app.backspace(), + KeyCode::Char(c) => app.type_char(c), + _ => {} + }, + } + } + } } diff --git a/src/csv.rs b/src/row.rs similarity index 78% rename from src/csv.rs rename to src/row.rs index c98ca67..12d9021 100644 --- a/src/csv.rs +++ b/src/row.rs @@ -1,19 +1,19 @@ pub struct Row { - name: String, - hut_type: String, - path: String, - tare: f64, - mat_id: String, - quantity: f64, - lot_id: String, - uom: String, - lot_status: String, - born: chrono::DateTime, - expire: chrono::DateTime, + pub name: String, + pub hut_type: String, + pub path: String, + pub tare: f64, + pub mat_id: String, + pub quantity: f64, + pub lot_id: String, + pub uom: String, + pub lot_status: String, + pub born: chrono::DateTime, + pub expire: chrono::DateTime, } impl Row { - const DF: &str = "%Y-%m-%d %H:%M"; + pub const DF: &'static str = "%Y-%m-%d %H:%M"; pub fn new( name: &str, @@ -30,9 +30,10 @@ impl Row { let expire = born .checked_add_days(chrono::Days::new(365)) .expect("A valid date time"); - let name = match checksum { - true => make_checksum(), - false => name.to_ascii_uppercase(), + let name = if checksum { + make_checksum() + } else { + name.to_ascii_uppercase() }; Self { name: name.clone(), @@ -50,10 +51,9 @@ impl Row { } } -fn make_checksum() -> String { - let base = rand::random_range(10000..99999).to_string(); +pub fn make_checksum() -> String { + let base = rand::random_range(10000..99999u32).to_string(); let split_expect = "a valid character in the base checksum"; - let split = format!( "{}{}{}{}{}", base.chars().next().expect(split_expect), @@ -63,14 +63,11 @@ fn make_checksum() -> String { base.chars().nth(3).expect(split_expect) ) .repeat(2); - let sum: u32 = split .chars() .map(|c| c.to_digit(10).expect("a valid integer value")) .sum(); - let last = (10 - (sum % 10)) % 10; - format!("{}{}", base, last) } diff --git a/src/ui.rs b/src/ui.rs index e69de29..b66a684 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -0,0 +1,290 @@ +use ratatui::{ + layout::{Constraint, Direction, Layout, Rect}, + style::{Color, Modifier, Style}, + text::{Line, Span}, + widgets::{Block, Borders, Cell, Clear, Paragraph, Row, Table, TableState}, + Frame, +}; + +use crate::app::{ + App, Focus, ModalKind, GLOBAL_CHECKSUM, GLOBAL_COUNT, GLOBAL_HUT_ID, GLOBAL_HUT_TYPE, + GLOBAL_LOT_STATUS, GLOBAL_PATH, GLOBAL_TARE, ROW_COUNT, ROW_MAT_ID, ROW_QUANTITY, ROW_UOM, +}; + +const GLOBAL_LABELS: &[&str] = &[ + "Hut ID", + "Checksum", + "Tare", + "Lot Status", + "Path", + "Hut Type", +]; + +const ROW_LABELS: &[&str] = &["Material ID", "Quantity", "UOM"]; + +// ── colour palette ──────────────────────────────────────────────────────────── + +const C_FOCUSED: Color = Color::Cyan; +const C_UNFOCUSED: Color = Color::DarkGray; +const C_ACCENT: Color = Color::Yellow; +const C_ROW_SEL: Color = Color::Blue; +const C_BORDER: Color = Color::Gray; +const C_HEADER: Color = Color::White; + +fn focused_style(is_focused: bool) -> Style { + if is_focused { + Style::default().fg(C_FOCUSED).add_modifier(Modifier::BOLD) + } else { + Style::default().fg(C_UNFOCUSED) + } +} + +fn focused_block<'a>(title: &'a str, is_focused: bool) -> Block<'a> { + Block::default() + .title(title) + .borders(Borders::ALL) + .border_style(if is_focused { + Style::default().fg(C_FOCUSED) + } else { + Style::default().fg(C_BORDER) + }) +} + +// ── main draw ──────────────────────────────────────────────────────────────── + +pub fn draw(f: &mut Frame, app: &App) { + let area = f.area(); + + // Overall vertical split: globals | lots | help bar + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Length(10), // global fields + Constraint::Min(6), // lot rows + Constraint::Length(3), // help bar + ]) + .split(area); + + draw_globals(f, app, chunks[0]); + draw_lots(f, app, chunks[1]); + draw_help(f, chunks[2]); + + if let Some(modal) = &app.modal { + draw_modal(f, modal, area); + } +} + +// ── globals panel ───────────────────────────────────────────────────────────── + +fn draw_globals(f: &mut Frame, app: &App, area: Rect) { + let outer = Block::default() + .title(" Global Settings ") + .borders(Borders::ALL) + .border_style(Style::default().fg(C_ACCENT)); + let inner = outer.inner(area); + f.render_widget(outer, area); + + // Two rows of three fields each + let rows = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Length(3), + Constraint::Length(3), + Constraint::Length(3), + ]) + .split(inner); + + // Row 0: Hut ID | Checksum | Tare + let row0 = Layout::default() + .direction(Direction::Horizontal) + .constraints([ + Constraint::Ratio(2, 5), + Constraint::Ratio(1, 5), + Constraint::Ratio(2, 5), + ]) + .split(rows[0]); + + // Row 1: Lot Status | Path | Hut Type + let row1 = Layout::default() + .direction(Direction::Horizontal) + .constraints([ + Constraint::Ratio(1, 3), + Constraint::Ratio(1, 3), + Constraint::Ratio(1, 3), + ]) + .split(rows[1]); + + let field_areas = [ + row0[0], // GLOBAL_HUT_ID + row0[1], // GLOBAL_CHECKSUM + row0[2], // GLOBAL_TARE + row1[0], // GLOBAL_LOT_STATUS + row1[1], // GLOBAL_PATH + row1[2], // GLOBAL_HUT_TYPE + ]; + + for i in 0..GLOBAL_COUNT { + let is_focused = app.focus == Focus::GlobalField(i); + let label = GLOBAL_LABELS[i]; + let value: String = if i == GLOBAL_CHECKSUM { + let checked = app.global[i] == "true"; + format!("[{}] (Space)", if checked { "x" } else { " " }) + } else if i == GLOBAL_LOT_STATUS { + format!("{} (←/→)", app.global[i]) + } else { + app.global[i].clone() + }; + + let display = if is_focused { + format!("{} █", value) + } else { + value + }; + + let p = Paragraph::new(display) + .block(focused_block(label, is_focused)) + .style(focused_style(is_focused)); + f.render_widget(p, field_areas[i]); + } +} + +// ── lots table ──────────────────────────────────────────────────────────────── + +fn draw_lots(f: &mut Frame, app: &App, area: Rect) { + let outer = Block::default() + .title(" Lot Rows (+) Add (-) Remove ") + .borders(Borders::ALL) + .border_style(Style::default().fg(C_ACCENT)); + let inner = outer.inner(area); + f.render_widget(outer, area); + + if app.lots.is_empty() { + let hint = Paragraph::new("No lots yet — press + to add one") + .style(Style::default().fg(C_UNFOCUSED)); + f.render_widget(hint, inner); + return; + } + + // Column widths + let col_constraints = [ + Constraint::Length(4), // # + Constraint::Min(18), // Material ID + Constraint::Length(12), // Quantity + Constraint::Length(8), // UOM + ]; + + let header_cells = ["#", "Material ID", "Quantity", "UOM"] + .iter() + .map(|h| Cell::from(*h).style(Style::default().fg(C_HEADER).add_modifier(Modifier::BOLD))); + let header = Row::new(header_cells).height(1).bottom_margin(0); + + let rows: Vec = app + .lots + .iter() + .enumerate() + .map(|(row_idx, entry)| { + let cells: Vec = (0..ROW_COUNT + 1) + .map(|col| { + if col == 0 { + // row number + Cell::from(format!("{:>2}", row_idx + 1)) + .style(Style::default().fg(C_UNFOCUSED)) + } else { + let field_idx = col - 1; + let is_focused = app.focus == Focus::LotField(row_idx, field_idx); + let text = if is_focused { + format!("{} █", entry.fields[field_idx]) + } else { + entry.fields[field_idx].clone() + }; + Cell::from(text).style(focused_style(is_focused)) + } + }) + .collect(); + + let row_selected = + matches!(&app.focus, Focus::LotRow(r) | Focus::LotField(r, _) if *r == row_idx); + let row_style = if row_selected { + Style::default().bg(Color::Rgb(20, 30, 50)) + } else { + Style::default() + }; + Row::new(cells).style(row_style).height(1) + }) + .collect(); + + let table = Table::new(rows, col_constraints) + .header(header) + .block(Block::default()) + .row_highlight_style(Style::default().bg(C_ROW_SEL)); + + let mut state = TableState::default(); + f.render_stateful_widget(table, inner, &mut state); +} + +// ── help bar ────────────────────────────────────────────────────────────────── + +fn draw_help(f: &mut Frame, area: Rect) { + let spans = vec![ + Span::styled(" Tab ", Style::default().fg(Color::Black).bg(C_ACCENT)), + Span::raw(" Next field "), + Span::styled(" + ", Style::default().fg(Color::Black).bg(Color::Green)), + Span::raw(" Add lot "), + Span::styled(" - ", Style::default().fg(Color::Black).bg(Color::Red)), + Span::raw(" Remove lot "), + Span::styled(" c ", Style::default().fg(Color::Black).bg(Color::Cyan)), + Span::raw(" Output CSV "), + Span::styled(" q ", Style::default().fg(Color::Black).bg(Color::Magenta)), + Span::raw(" Quit "), + ]; + + let help = Paragraph::new(Line::from(spans)).block( + Block::default() + .borders(Borders::ALL) + .border_style(Style::default().fg(C_BORDER)), + ); + f.render_widget(help, area); +} + +// ── modal overlay ───────────────────────────────────────────────────────────── + +fn draw_modal(f: &mut Frame, kind: &ModalKind, area: Rect) { + let (title, body) = match kind { + ModalKind::ConfirmClose => ( + " Output CSV and Close ", + "Output generated CSV to stdout and exit?\n\n [y] Yes [n] Cancel", + ), + ModalKind::ConfirmQuit => ( + " Quit Without Output ", + "Quit without outputting anything?\n\n [y] Yes [n] Cancel", + ), + }; + + let popup_width = 50u16; + let popup_height = 7u16; + let x = area.x + area.width.saturating_sub(popup_width) / 2; + let y = area.y + area.height.saturating_sub(popup_height) / 2; + let popup_area = Rect::new( + x, + y, + popup_width.min(area.width), + popup_height.min(area.height), + ); + + f.render_widget(Clear, popup_area); + + let color = match kind { + ModalKind::ConfirmClose => Color::Cyan, + ModalKind::ConfirmQuit => Color::Magenta, + }; + + let p = Paragraph::new(body) + .block( + Block::default() + .title(title) + .borders(Borders::ALL) + .border_style(Style::default().fg(color).add_modifier(Modifier::BOLD)), + ) + .style(Style::default().fg(C_HEADER)); + f.render_widget(p, popup_area); +}