feacc59b
Add TUI patch review dashboard
a73x 2026-03-20 19:32
Interactive terminal dashboard (`git collab dashboard`) built with ratatui/crossterm. Two-pane layout: patch list on the left, details or colored diff view on the right. Supports keyboard navigation, pane switching, diff toggle, show-all filter, and live refresh. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
diff --git a/Cargo.lock b/Cargo.lock index c850add..537c260 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,6 +3,21 @@ 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -68,24 +83,84 @@ 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "bytemuck" version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" [[package]] name = "castaway" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" dependencies = [ "rustversion", ] [[package]] name = "cc" version = "1.2.57" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -104,6 +179,12 @@ 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 = "chrono" version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -148,7 +229,7 @@ dependencies = [ "heck", "proc-macro2", "quote", "syn", "syn 2.0.117", ] [[package]] @@ -164,12 +245,172 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "compact_str" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" dependencies = [ "castaway", "cfg-if", "itoa", "rustversion", "ryu", "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 = "crossterm" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ "bitflags 2.11.0", "crossterm_winapi", "derive_more", "document-features", "mio", "parking_lot", "rustix", "signal-hook", "signal-hook-mio", "winapi", ] [[package]] name = "crossterm_winapi" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" dependencies = [ "darling_core", "darling_macro", ] [[package]] name = "darling_core" version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" dependencies = [ "ident_case", "proc-macro2", "quote", "strsim", "syn 2.0.117", ] [[package]] name = "darling_macro" version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" 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 = "displaydoc" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -177,10 +418,25 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", "syn", "syn 2.0.117", ] [[package]] name = "document-features" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" dependencies = [ "litrs", ] [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -197,24 +453,78 @@ dependencies = [ ] [[package]] name = "euclid" version = "0.22.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" 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 = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[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", ] [[package]] name = "find-msvc-tools" 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 = "form_urlencoded" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -224,6 +534,16 @@ dependencies = [ ] [[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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -254,11 +574,13 @@ version = "0.1.0" dependencies = [ "chrono", "clap", "crossterm", "git2", "ratatui", "serde", "serde_json", "tempfile", "thiserror", "thiserror 2.0.18", ] [[package]] @@ -267,7 +589,7 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724" dependencies = [ "bitflags", "bitflags 2.11.0", "libc", "libgit2-sys", "log", @@ -282,7 +604,7 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "foldhash", "foldhash 0.1.5", ] [[package]] @@ -290,6 +612,11 @@ 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", ] [[package]] name = "heck" @@ -298,6 +625,12 @@ 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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -409,6 +742,12 @@ 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 = "idna" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -442,12 +781,43 @@ dependencies = [ ] [[package]] name = "indoc" version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" dependencies = [ "rustversion", ] [[package]] name = "instability" version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5eb2d60ef19920a3a9193c3e371f726ec1dafc045dac788d0fb3704272458971" dependencies = [ "darling", "indoc", "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "is_terminal_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -474,6 +844,29 @@ dependencies = [ ] [[package]] name = "kasuari" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bde5057d6143cc94e861d90f591b9303d6716c6b9602309150bd068853c10899" 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" @@ -526,6 +919,15 @@ dependencies = [ ] [[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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -538,35 +940,151 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" [[package]] name = "litrs" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" [[package]] name = "lock_api" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ "scopeguard", ] [[package]] name = "log" version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "lru" version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" dependencies = [ "hashbrown 0.16.1", ] [[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 = "num-traits" version = "0.2.19" name = "memmem" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 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 = "once_cell" version = "1.21.4" name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "once_cell_polyfill" version = "1.70.2" name = "mio" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", "log", "wasi", "windows-sys", ] [[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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 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.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" @@ -589,18 +1107,151 @@ dependencies = [ ] [[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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-link", ] [[package]] name = "percent-encoding" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" 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", ] [[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 = "pkg-config" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "portable-atomic" version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "potential_utf" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -610,13 +1261,19 @@ dependencies = [ ] [[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", "syn 2.0.117", ] [[package]] @@ -650,12 +1307,159 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" [[package]] name = "ratatui" version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1ce67fb8ba4446454d1c8dbaeda0557ff5e94d39d5e5ed7f10a65eb4c8266bc" 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", "compact_str", "hashbrown 0.16.1", "indoc", "itertools", "kasuari", "lru", "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", ] [[package]] name = "redox_syscall" 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", ] [[package]] name = "rustix" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ "bitflags", "bitflags 2.11.0", "errno", "libc", "linux-raw-sys", @@ -669,6 +1473,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "scopeguard" 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" @@ -701,7 +1517,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", "syn", "syn 2.0.117", ] [[package]] @@ -718,12 +1534,60 @@ dependencies = [ ] [[package]] name = "sha2" version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook" version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" dependencies = [ "libc", "signal-hook-registry", ] [[package]] name = "signal-hook-mio" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" dependencies = [ "libc", "mio", "signal-hook", ] [[package]] name = "signal-hook-registry" version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ "errno", "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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -736,12 +1600,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" 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", ] [[package]] name = "syn" version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -760,7 +1662,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", "syn", "syn 2.0.117", ] [[package]] @@ -777,12 +1679,95 @@ dependencies = [ ] [[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", "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]] @@ -793,10 +1778,31 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", "syn", "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 = "tinystr" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -807,12 +1813,47 @@ dependencies = [ ] [[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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-truncate" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16b380a1238663e5f8a691f9039c73e1cdae598a30e9855f541d29b08b53e9a5" dependencies = [ "itertools", "unicode-segmentation", "unicode-width", ] [[package]] name = "unicode-width" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "unicode-xid" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -843,12 +1884,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" dependencies = [ "atomic", "getrandom 0.4.2", "js-sys", "wasm-bindgen", ] [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[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", ] [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -898,7 +1972,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", "syn", "syn 2.0.117", "wasm-bindgen-shared", ] @@ -939,13 +2013,107 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ "bitflags", "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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -966,7 +2134,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", "syn", "syn 2.0.117", ] [[package]] @@ -977,7 +2145,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", "syn", "syn 2.0.117", ] [[package]] @@ -1043,7 +2211,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", "syn", "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -1059,7 +2227,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", "syn", "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -1071,7 +2239,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", "bitflags", "bitflags 2.11.0", "indexmap", "log", "serde", @@ -1126,7 +2294,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", "syn", "syn 2.0.117", "synstructure", ] @@ -1147,7 +2315,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", "syn", "syn 2.0.117", "synstructure", ] @@ -1181,7 +2349,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", "syn", "syn 2.0.117", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 6c23c40..3f520d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,8 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" chrono = { version = "0.4", features = ["serde"] } thiserror = "2" ratatui = "0.30.0" crossterm = "0.29.0" [dev-dependencies] tempfile = "3" diff --git a/Makefile b/Makefile index 9713f6a..11fc2dc 100644 --- a/Makefile +++ b/Makefile @@ -40,10 +40,13 @@ clean: $(CARGO) clean # --- Dev helpers --- .PHONY: run dev .PHONY: run dev dashboard run: $(CARGO) run -- $(ARGS) dev: fmt $(CARGO) run -- $(ARGS) dashboard: fmt $(CARGO) run -- dashboard diff --git a/src/cli.rs b/src/cli.rs index e097e87..11fac50 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -23,6 +23,9 @@ pub enum Commands { #[command(subcommand)] Patch(PatchCmd), /// Interactive patch review dashboard Dashboard, /// Sync with a remote (fetch, reconcile, push) Sync { /// Remote name (default: origin) diff --git a/src/error.rs b/src/error.rs index 0f65c88..61b936c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -10,4 +10,7 @@ pub enum Error { #[error("{0}")] Cmd(String), #[error(transparent)] Io(#[from] std::io::Error), } diff --git a/src/lib.rs b/src/lib.rs index 3dd124d..46cbe62 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,3 +7,4 @@ pub mod issue; pub mod patch; pub mod state; pub mod sync; pub mod tui; diff --git a/src/main.rs b/src/main.rs index 29d73c6..2b9c91f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ mod issue; mod patch; mod state; mod sync; mod tui; use clap::Parser; use cli::{Cli, Commands, IssueCmd, PatchCmd}; @@ -73,6 +74,7 @@ fn run(cli: Cli) -> Result<(), error::Error> { PatchCmd::Merge { id } => patch::merge(&repo, &id), PatchCmd::Close { id, reason } => patch::close(&repo, &id, reason.as_deref()), }, Commands::Dashboard => tui::run(&repo), Commands::Sync { remote } => sync::sync(&repo, &remote), } } diff --git a/src/tui.rs b/src/tui.rs new file mode 100644 index 0000000..9963a7e --- /dev/null +++ b/src/tui.rs @@ -0,0 +1,512 @@ use std::collections::HashMap; use std::io::{self, stdout}; use std::time::Duration; use crossterm::event::{self, Event, KeyCode, KeyModifiers}; use crossterm::terminal::{self, EnterAlternateScreen, LeaveAlternateScreen}; use crossterm::ExecutableCommand; use git2::{DiffFormat, Oid, Repository}; use ratatui::prelude::*; use ratatui::widgets::{Block, Borders, List, ListItem, ListState, Paragraph, Wrap}; use crate::error::Error; use crate::state::{self, PatchState, PatchStatus}; #[derive(PartialEq)] enum Pane { PatchList, Detail, } #[derive(PartialEq)] enum ViewMode { Details, Diff, } struct App { patches: Vec<PatchState>, list_state: ListState, diff_cache: HashMap<String, String>, scroll: u16, pane: Pane, mode: ViewMode, show_all: bool, } impl App { fn new(patches: Vec<PatchState>) -> Self { let mut list_state = ListState::default(); if !patches.is_empty() { list_state.select(Some(0)); } Self { patches, list_state, diff_cache: HashMap::new(), scroll: 0, pane: Pane::PatchList, mode: ViewMode::Details, show_all: false, } } fn visible_patches(&self) -> Vec<&PatchState> { if self.show_all { self.patches.iter().collect() } else { self.patches .iter() .filter(|p| p.status == PatchStatus::Open) .collect() } } fn move_selection(&mut self, delta: i32) { let visible = self.visible_patches(); let len = visible.len(); if len == 0 { return; } let current = self.list_state.selected().unwrap_or(0); let new = if delta > 0 { (current + delta as usize).min(len - 1) } else { current.saturating_sub((-delta) as usize) }; self.list_state.select(Some(new)); self.scroll = 0; } fn reload(&mut self, repo: &Repository) { if let Ok(patches) = state::list_patches(repo) { self.patches = patches; let visible_len = self.visible_patches().len(); if let Some(sel) = self.list_state.selected() { if sel >= visible_len { self.list_state.select(if visible_len > 0 { Some(visible_len - 1) } else { None }); } } } } } fn generate_diff(repo: &Repository, patch: &PatchState) -> String { let result = (|| -> Result<String, Error> { let head_oid = Oid::from_str(&patch.head_commit) .map_err(|e| Error::Cmd(format!("bad head OID: {}", e)))?; let head_commit = repo.find_commit(head_oid)?; let head_tree = head_commit.tree()?; let base_ref = format!("refs/heads/{}", patch.base_ref); let base_tree = if let Ok(base_oid) = repo.refname_to_id(&base_ref) { let base_commit = repo.find_commit(base_oid)?; Some(base_commit.tree()?) } else { None }; let diff = repo.diff_tree_to_tree(base_tree.as_ref(), Some(&head_tree), None)?; let mut output = String::new(); let mut lines = 0usize; diff.print(DiffFormat::Patch, |_delta, _hunk, line| { if lines >= 5000 { return false; } let prefix = match line.origin() { '+' => "+", '-' => "-", ' ' => " ", 'H' => "", 'F' => "", _ => "", }; if !prefix.is_empty() || matches!(line.origin(), 'H' | 'F') { output.push_str(prefix); } if let Ok(content) = std::str::from_utf8(line.content()) { output.push_str(content); } lines += 1; true })?; if lines >= 5000 { output.push_str("\n[truncated at 5000 lines]"); } Ok(output) })(); match result { Ok(diff) => { if diff.is_empty() { "No diff available (commits may be identical)".to_string() } else { diff } } Err(e) => format!("Diff unavailable: {}", e), } } pub fn run(repo: &Repository) -> Result<(), Error> { let patches = state::list_patches(repo)?; let mut app = App::new(patches); terminal::enable_raw_mode()?; stdout().execute(EnterAlternateScreen)?; let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?; let result = run_loop(&mut terminal, &mut app, repo); terminal::disable_raw_mode()?; stdout().execute(LeaveAlternateScreen)?; result } fn run_loop( terminal: &mut Terminal<CrosstermBackend<io::Stdout>>, app: &mut App, repo: &Repository, ) -> Result<(), Error> { loop { // Cache diff for selected patch if needed if app.mode == ViewMode::Diff { if let Some(idx) = app.list_state.selected() { if let Some(patch) = app.patches.get(idx) { if !app.diff_cache.contains_key(&patch.id) { let id = patch.id.clone(); let diff = generate_diff(repo, patch); app.diff_cache.insert(id, diff); } } } } terminal.draw(|frame| ui(frame, app))?; if event::poll(Duration::from_millis(100))? { if let Event::Key(key) = event::read()? { match key.code { KeyCode::Char('q') | KeyCode::Esc => return Ok(()), KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => { return Ok(()) } KeyCode::Char('j') | KeyCode::Down => { if app.pane == Pane::PatchList { app.move_selection(1); } else { app.scroll = app.scroll.saturating_add(1); } } KeyCode::Char('k') | KeyCode::Up => { if app.pane == Pane::PatchList { app.move_selection(-1); } else { app.scroll = app.scroll.saturating_sub(1); } } KeyCode::PageDown => app.scroll = app.scroll.saturating_add(20), KeyCode::PageUp => app.scroll = app.scroll.saturating_sub(20), KeyCode::Tab | KeyCode::Enter => { app.pane = match app.pane { Pane::PatchList => Pane::Detail, Pane::Detail => Pane::PatchList, }; } KeyCode::Char('d') => { app.mode = match app.mode { ViewMode::Details => ViewMode::Diff, ViewMode::Diff => ViewMode::Details, }; app.scroll = 0; } KeyCode::Char('a') => { app.show_all = !app.show_all; app.list_state.select(Some(0)); } KeyCode::Char('r') => { app.reload(repo); } _ => {} } } } } } fn ui(frame: &mut Frame, app: &mut App) { let chunks = Layout::default() .direction(Direction::Vertical) .constraints([Constraint::Min(1), Constraint::Length(1)]) .split(frame.area()); let main_area = chunks[0]; let footer_area = chunks[1]; let panes = Layout::default() .direction(Direction::Horizontal) .constraints([Constraint::Percentage(30), Constraint::Percentage(70)]) .split(main_area); render_patch_list(frame, app, panes[0]); render_detail(frame, app, panes[1]); render_footer(frame, app, footer_area); } fn render_patch_list(frame: &mut Frame, app: &mut App, area: Rect) { let visible = app.visible_patches(); let items: Vec<ListItem> = visible .iter() .map(|p| { let status = match p.status { PatchStatus::Open => "open", PatchStatus::Closed => "closed", PatchStatus::Merged => "merged", }; let style = match p.status { PatchStatus::Open => Style::default().fg(Color::Green), PatchStatus::Closed => Style::default().fg(Color::Red), PatchStatus::Merged => Style::default().fg(Color::Cyan), }; ListItem::new(format!("{:.8} {:6} {}", p.id, status, p.title)).style(style) }) .collect(); let border_style = if app.pane == Pane::PatchList { Style::default().fg(Color::Yellow) } else { Style::default().fg(Color::DarkGray) }; let title = if app.show_all { "Patches (all)" } else { "Patches (open)" }; let list = List::new(items) .block( Block::default() .borders(Borders::ALL) .title(title) .border_style(border_style), ) .highlight_style( Style::default() .bg(Color::DarkGray) .add_modifier(Modifier::BOLD), ) .highlight_symbol("> "); frame.render_stateful_widget(list, area, &mut app.list_state); } fn render_detail(frame: &mut Frame, app: &App, area: Rect) { let border_style = if app.pane == Pane::Detail { Style::default().fg(Color::Yellow) } else { Style::default().fg(Color::DarkGray) }; let visible = app.visible_patches(); let selected_idx = app.list_state.selected().unwrap_or(0); let patch = visible.get(selected_idx); let title = match app.mode { ViewMode::Details => "Details", ViewMode::Diff => "Diff", }; if patch.is_none() { let block = Block::default() .borders(Borders::ALL) .title(title) .border_style(border_style); let para = Paragraph::new("No patches to display.").block(block); frame.render_widget(para, area); return; } let patch = patch.unwrap(); let content: Text = match app.mode { ViewMode::Details => build_detail_text(patch), ViewMode::Diff => { let diff_text = app .diff_cache .get(&patch.id) .map(|s| s.as_str()) .unwrap_or("Loading..."); colorize_diff(diff_text) } }; let block = Block::default() .borders(Borders::ALL) .title(title) .border_style(border_style); let para = Paragraph::new(content) .block(block) .wrap(Wrap { trim: false }) .scroll((app.scroll, 0)); frame.render_widget(para, area); } fn build_detail_text(patch: &PatchState) -> Text<'static> { let status = match patch.status { PatchStatus::Open => "open", PatchStatus::Closed => "closed", PatchStatus::Merged => "merged", }; let mut lines: Vec<Line> = vec![ Line::from(vec![ Span::styled("Patch ", Style::default().add_modifier(Modifier::BOLD)), Span::styled( format!("{:.8}", patch.id), Style::default() .fg(Color::Yellow) .add_modifier(Modifier::BOLD), ), Span::raw(format!(" [{}]", status)), ]), Line::from(vec![ Span::styled("Title: ", Style::default().fg(Color::DarkGray)), Span::raw(patch.title.clone()), ]), Line::from(vec![ Span::styled("Author: ", Style::default().fg(Color::DarkGray)), Span::raw(format!("{} <{}>", patch.author.name, patch.author.email)), ]), Line::from(vec![ Span::styled("Base: ", Style::default().fg(Color::DarkGray)), Span::raw(patch.base_ref.clone()), Span::raw(" "), Span::styled("Head: ", Style::default().fg(Color::DarkGray)), Span::styled( format!("{:.8}", patch.head_commit), Style::default().fg(Color::Cyan), ), ]), Line::from(vec![ Span::styled("Created: ", Style::default().fg(Color::DarkGray)), Span::raw(patch.created_at.clone()), ]), ]; if !patch.body.is_empty() { lines.push(Line::raw("")); for l in patch.body.lines() { lines.push(Line::raw(l.to_string())); } } if !patch.reviews.is_empty() { lines.push(Line::raw("")); lines.push(Line::styled( "--- Reviews ---", Style::default() .fg(Color::Magenta) .add_modifier(Modifier::BOLD), )); for r in &patch.reviews { lines.push(Line::raw("")); let verdict_style = match r.verdict { crate::event::ReviewVerdict::Approve => Style::default().fg(Color::Green), crate::event::ReviewVerdict::RequestChanges => Style::default().fg(Color::Red), crate::event::ReviewVerdict::Comment => Style::default().fg(Color::Yellow), }; lines.push(Line::from(vec![ Span::raw(format!("{} ", r.author.name)), Span::styled(format!("({:?})", r.verdict), verdict_style), Span::styled( format!(" - {}", r.timestamp), Style::default().fg(Color::DarkGray), ), ])); for l in r.body.lines() { lines.push(Line::raw(format!(" {}", l))); } } } if !patch.comments.is_empty() { lines.push(Line::raw("")); lines.push(Line::styled( "--- Comments ---", Style::default() .fg(Color::Blue) .add_modifier(Modifier::BOLD), )); for c in &patch.comments { lines.push(Line::raw("")); lines.push(Line::from(vec![ Span::styled( c.author.name.clone(), Style::default().add_modifier(Modifier::BOLD), ), Span::styled( format!(" ({})", c.timestamp), Style::default().fg(Color::DarkGray), ), ])); for l in c.body.lines() { lines.push(Line::raw(format!(" {}", l))); } } } Text::from(lines) } fn colorize_diff(diff: &str) -> Text<'static> { let lines: Vec<Line> = diff .lines() .map(|line| { let style = if line.starts_with('+') && !line.starts_with("+++") { Style::default().fg(Color::Green) } else if line.starts_with('-') && !line.starts_with("---") { Style::default().fg(Color::Red) } else if line.starts_with("@@") { Style::default().fg(Color::Cyan) } else if line.starts_with("diff ") || line.starts_with("index ") { Style::default().fg(Color::DarkGray) } else if line.starts_with("+++") || line.starts_with("---") { Style::default().fg(Color::Yellow) } else { Style::default() }; Line::styled(line.to_string(), style) }) .collect(); Text::from(lines) } fn render_footer(frame: &mut Frame, app: &App, area: Rect) { let mode_hint = match app.mode { ViewMode::Details => "d:diff", ViewMode::Diff => "d:details", }; let filter_hint = if app.show_all { "a:open only" } else { "a:show all" }; let text = format!( " j/k:navigate Tab:switch pane {} {} r:refresh q:quit", mode_hint, filter_hint ); let para = Paragraph::new(text).style(Style::default().bg(Color::DarkGray).fg(Color::White)); frame.render_widget(para, area); }