diff --git a/Cargo.lock b/Cargo.lock index 2504263..eb99d9c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,21 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "addr2line" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - [[package]] name = "aho-corasick" version = "1.1.4" @@ -96,7 +81,16 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", +] + +[[package]] +name = "atomic" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340" +dependencies = [ + "bytemuck", ] [[package]] @@ -154,51 +148,66 @@ dependencies = [ "tower-service", ] -[[package]] -name = "backtrace" -version = "0.3.76" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-link", -] - [[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.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +[[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.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +[[package]] +name = "bytemuck" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" + [[package]] name = "bytes" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" -[[package]] -name = "cassowary" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" - [[package]] name = "castaway" version = "0.2.4" @@ -214,6 +223,12 @@ 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 = "clap" version = "4.5.55" @@ -245,7 +260,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -254,33 +269,6 @@ version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" -[[package]] -name = "color-eyre" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d" -dependencies = [ - "backtrace", - "color-spantrace", - "eyre", - "indenter", - "once_cell", - "owo-colors", - "tracing-error", -] - -[[package]] -name = "color-spantrace" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427" -dependencies = [ - "once_cell", - "owo-colors", - "tracing-core", - "tracing-error", -] - [[package]] name = "colorchoice" version = "1.0.4" @@ -298,9 +286,9 @@ dependencies = [ [[package]] name = "compact_str" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" +checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" dependencies = [ "castaway", "cfg-if", @@ -320,19 +308,12 @@ dependencies = [ ] [[package]] -name = "crossterm" -version = "0.28.1" +name = "cpufeatures" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ - "bitflags", - "crossterm_winapi", - "mio", - "parking_lot", - "rustix 0.38.44", - "signal-hook", - "signal-hook-mio", - "winapi", + "libc", ] [[package]] @@ -341,13 +322,13 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags", + "bitflags 2.10.0", "crossterm_winapi", "derive_more", "document-features", "mio", "parking_lot", - "rustix 1.1.3", + "rustix", "signal-hook", "signal-hook-mio", "winapi", @@ -362,6 +343,26 @@ 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" @@ -382,7 +383,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.114", ] [[package]] @@ -393,7 +394,22 @@ checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core", "quote", - "syn", + "syn 2.0.114", +] + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +dependencies = [ + "powerfmt", ] [[package]] @@ -415,28 +431,17 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn", + "syn 2.0.114", ] [[package]] -name = "directories" -version = "5.0.1" +name = "digest" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.48.0", + "block-buffer", + "crypto-common", ] [[package]] @@ -483,13 +488,22 @@ dependencies = [ ] [[package]] -name = "eyre" -version = "0.6.12" +name = "euclid" +version = "0.22.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +checksum = "df61bf483e837f88d5c2291dcf55c67be7e676b3a51acc48db3a7b163b91ed63" dependencies = [ - "indenter", - "once_cell", + "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]] @@ -498,6 +512,29 @@ 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 = "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 = "fixedbitset" version = "0.5.7" @@ -516,6 +553,12 @@ 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 = "futures-channel" version = "0.3.31" @@ -539,7 +582,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -569,14 +612,13 @@ dependencies = [ ] [[package]] -name = "getrandom" -version = "0.2.17" +name = "generic-array" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ - "cfg-if", - "libc", - "wasi", + "typenum", + "version_check", ] [[package]] @@ -591,12 +633,6 @@ dependencies = [ "wasip2", ] -[[package]] -name = "gimli" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" - [[package]] name = "granc" version = "0.7.0" @@ -613,18 +649,14 @@ name = "granc-tui" version = "0.1.0" dependencies = [ "anyhow", - "color-eyre", - "crossterm 0.29.0", - "directories", + "colored", + "crossterm", "granc_core 0.6.0", - "once_cell", "ratatui", - "serde", "serde_json", "teatui", + "thiserror 2.0.18", "tokio", - "tui-textarea", - "uuid", ] [[package]] @@ -691,9 +723,7 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "allocator-api2", - "equivalent", - "foldhash", + "foldhash 0.1.5", ] [[package]] @@ -701,6 +731,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" @@ -708,6 +743,12 @@ 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 = "http" version = "1.4.0" @@ -816,12 +857,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" -[[package]] -name = "indenter" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" - [[package]] name = "indexmap" version = "2.13.0" @@ -851,7 +886,7 @@ dependencies = [ "indoc", "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -860,15 +895,6 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.14.0" @@ -894,6 +920,23 @@ 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" @@ -907,21 +950,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] -name = "libredox" -version = "0.1.12" +name = "line-clipping" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +checksum = "5f4de44e98ddbf09375cbf4d17714d18f39195f4f4894e8524501726fd9a8a4a" dependencies = [ - "bitflags", - "libc", + "bitflags 2.10.0", ] -[[package]] -name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - [[package]] name = "linux-raw-sys" version = "0.11.0" @@ -951,11 +987,21 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "lru" -version = "0.12.5" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" dependencies = [ - "hashbrown 0.15.5", + "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]] @@ -970,6 +1016,21 @@ version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[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 = "mime" version = "0.3.17" @@ -977,13 +1038,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] -name = "miniz_oxide" -version = "0.8.9" +name = "minimal-lexical" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", -] +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" @@ -1003,6 +1061,46 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.10.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.114", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1013,12 +1111,12 @@ dependencies = [ ] [[package]] -name = "object" -version = "0.37.3" +name = "num_threads" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" dependencies = [ - "memchr", + "libc", ] [[package]] @@ -1033,12 +1131,6 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - [[package]] name = "ordered-float" version = "2.10.1" @@ -1049,10 +1141,13 @@ dependencies = [ ] [[package]] -name = "owo-colors" -version = "4.2.3" +name = "ordered-float" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" +checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" +dependencies = [ + "num-traits", +] [[package]] name = "parking_lot" @@ -1077,29 +1172,118 @@ dependencies = [ "windows-link", ] -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - [[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f9dbced329c441fa79d80472764b1a2c7e57123553b8519b36663a2fb234ed" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bb96d5051a78f44f43c8f712d8e810adb0ebf923fc9ed2655a7f66f63ba8ee5" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "pest_meta" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "602113b5b5e8621770cfd490cfd90b9f84ab29bd2b0e49ad83eb6d186cef2365" +dependencies = [ + "pest", + "sha2", +] + [[package]] name = "petgraph" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" dependencies = [ - "fixedbitset", + "fixedbitset 0.5.7", "hashbrown 0.15.5", "indexmap", ] +[[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.114", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" version = "1.1.10" @@ -1117,7 +1301,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -1132,6 +1316,18 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "portable-atomic" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" + +[[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" @@ -1139,7 +1335,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.114", ] [[package]] @@ -1168,7 +1364,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" dependencies = [ "heck", - "itertools 0.14.0", + "itertools", "log", "multimap", "petgraph", @@ -1178,7 +1374,7 @@ dependencies = [ "pulldown-cmark", "pulldown-cmark-to-cmark", "regex", - "syn", + "syn 2.0.114", "tempfile", ] @@ -1189,10 +1385,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", - "itertools 0.14.0", + "itertools", "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -1223,7 +1419,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e8bbe1a966bd2f362681a44f6edce3c2310ac21e4d5067a6e7ec396297a6ea0" dependencies = [ - "bitflags", + "bitflags 2.10.0", "memchr", "unicase", ] @@ -1253,24 +1449,103 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] -name = "ratatui" -version = "0.29.0" +name = "rand" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" +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 = [ - "bitflags", - "cassowary", - "compact_str", - "crossterm 0.28.1", - "indoc", "instability", - "itertools 0.13.0", + "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.10.0", + "compact_str", + "hashbrown 0.16.1", + "indoc", + "itertools", + "kasuari", "lru", - "paste", "strum", + "thiserror 2.0.18", "unicode-segmentation", "unicode-truncate", - "unicode-width 0.2.0", + "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.10.0", + "hashbrown 0.16.1", + "indoc", + "instability", + "itertools", + "line-clipping", + "ratatui-core", + "strum", + "time", + "unicode-segmentation", + "unicode-width", ] [[package]] @@ -1279,18 +1554,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", -] - -[[package]] -name = "redox_users" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" -dependencies = [ - "getrandom 0.2.17", - "libredox", - "thiserror 1.0.69", + "bitflags 2.10.0", ] [[package]] @@ -1322,12 +1586,6 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" -[[package]] -name = "rustc-demangle" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" - [[package]] name = "rustc_version" version = "0.4.1" @@ -1337,29 +1595,16 @@ dependencies = [ "semver", ] -[[package]] -name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", -] - [[package]] name = "rustix" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ - "bitflags", + "bitflags 2.10.0", "errno", "libc", - "linux-raw-sys 0.11.0", + "linux-raw-sys", "windows-sys 0.61.2", ] @@ -1403,7 +1648,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" dependencies = [ - "ordered-float", + "ordered-float 2.10.1", "serde", ] @@ -1424,7 +1669,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -1441,12 +1686,14 @@ dependencies = [ ] [[package]] -name = "sharded-slab" -version = "0.1.7" +name = "sha2" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ - "lazy_static", + "cfg-if", + "cpufeatures", + "digest", ] [[package]] @@ -1480,6 +1727,12 @@ dependencies = [ "libc", ] +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" + [[package]] name = "slab" version = "0.4.11" @@ -1516,24 +1769,34 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" -version = "0.26.3" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" -version = "0.26.4" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" dependencies = [ "heck", "proc-macro2", "quote", - "rustversion", - "syn", + "syn 2.0.114", +] + +[[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]] @@ -1555,13 +1818,14 @@ checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" [[package]] name = "teatui" -version = "0.1.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9a13b47a097b60254a881d30d701665c8fd83556e3c682e039e0650585790ba" +checksum = "3bf729327e4d649bd0ec48bdee903ef4edf149c3c7b198eb6353a471a3254aab" dependencies = [ - "color-eyre", - "crossterm 0.29.0", + "crossterm", "ratatui", + "thiserror 2.0.18", + "tokio", ] [[package]] @@ -1571,12 +1835,75 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom", "once_cell", - "rustix 1.1.3", + "rustix", "windows-sys 0.61.2", ] +[[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.10.0", + "fancy-regex", + "filedescriptor", + "finl_unicode", + "fixedbitset 0.4.2", + "hex", + "lazy_static", + "libc", + "log", + "memmem", + "nix", + "num-derive", + "num-traits", + "ordered-float 4.6.0", + "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" @@ -1603,7 +1930,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -1614,18 +1941,30 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] -name = "thread_local" -version = "1.1.9" +name = "time" +version = "0.3.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +checksum = "9da98b7d9b7dad93488a84b8248efc35352b0b2657397d4167e7ad67e5d535e5" dependencies = [ - "cfg-if", + "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 = "tokio" version = "1.49.0" @@ -1651,7 +1990,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -1716,7 +2055,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -1741,7 +2080,7 @@ dependencies = [ "prost-build", "prost-types", "quote", - "syn", + "syn 2.0.114", "tempfile", "tonic-build", ] @@ -1810,7 +2149,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -1820,28 +2159,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", - "valuable", -] - -[[package]] -name = "tracing-error" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" -dependencies = [ - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" -dependencies = [ - "sharded-slab", - "thread_local", - "tracing-core", ] [[package]] @@ -1851,15 +2168,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] -name = "tui-textarea" -version = "0.7.0" +name = "typenum" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a5318dd619ed73c52a9417ad19046724effc1287fb75cdcc4eca1d6ac1acbae" -dependencies = [ - "crossterm 0.28.1", - "ratatui", - "unicode-width 0.2.0", -] +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "unicase" @@ -1881,21 +2199,15 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-truncate" -version = "1.1.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +checksum = "16b380a1238663e5f8a691f9039c73e1cdae598a30e9855f541d29b08b53e9a5" dependencies = [ - "itertools 0.13.0", + "itertools", "unicode-segmentation", - "unicode-width 0.1.14", + "unicode-width", ] -[[package]] -name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - [[package]] name = "unicode-width" version = "0.2.0" @@ -1914,17 +2226,26 @@ version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" dependencies = [ - "getrandom 0.3.4", + "atomic", + "getrandom", "js-sys", - "serde_core", "wasm-bindgen", ] [[package]] -name = "valuable" -version = "0.1.1" +name = "version_check" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +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 = "want" @@ -1982,7 +2303,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.114", "wasm-bindgen-shared", ] @@ -1995,6 +2316,78 @@ dependencies = [ "unicode-ident", ] +[[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", + "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 4.6.0", + "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" @@ -2023,31 +2416,13 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-sys" version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.5", + "windows-targets", ] [[package]] @@ -2059,37 +2434,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - [[package]] name = "windows-targets" version = "0.53.5" @@ -2097,148 +2441,58 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", + "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.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[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_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - [[package]] name = "windows_aarch64_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - [[package]] name = "windows_i686_gnu" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" -[[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_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - [[package]] name = "windows_i686_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - [[package]] name = "windows_x86_64_gnu" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[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_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - [[package]] name = "windows_x86_64_msvc" version = "0.53.1" diff --git a/granc-tui/Cargo.toml b/granc-tui/Cargo.toml index ea7b537..ba4f498 100644 --- a/granc-tui/Cargo.toml +++ b/granc-tui/Cargo.toml @@ -4,25 +4,12 @@ version = "0.1.0" edition = "2024" [dependencies] -# Core Logic granc_core = { path = "../granc-core" } - -# Architecture -teatui = "0.1" -ratatui = "0.29.0" -crossterm = "0.29.0" -tui-textarea = "0.7.0" - -# Async Runtime -tokio = { version = "1.43", features = ["full"] } - -# Data & Config -serde = { version = "1.0", features = ["derive"] } +teatui = { version = "0.4.0", features = ["tokio"] } +ratatui = "0.30" +crossterm = "0.29" serde_json = "1.0" -directories = "5.0" -uuid = { version = "1.10", features = ["v4", "serde"] } -once_cell = "1.19" - -# Error Handling -color-eyre = "0.6" +thiserror = "2.0" anyhow = "1.0" +colored = "3.1" +tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } diff --git a/granc-tui/src/config.rs b/granc-tui/src/config.rs deleted file mode 100644 index 5d8c1f0..0000000 --- a/granc-tui/src/config.rs +++ /dev/null @@ -1,68 +0,0 @@ -use anyhow::{Context, Result}; -use directories::ProjectDirs; -use serde::{Deserialize, Serialize}; -use std::fs; -use std::path::PathBuf; -use uuid::Uuid; - -#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq)] -pub struct AppConfig { - pub projects: Vec, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -pub struct Project { - pub id: Uuid, - pub name: String, - pub connection: ConnectionConfig, - pub saved_requests: Vec, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -#[serde(tag = "type", content = "value")] -pub enum ConnectionConfig { - Reflection { url: String }, - File { url: String, path: PathBuf }, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -pub struct SavedRequest { - pub id: Uuid, - pub name: String, - pub service: String, - pub method: String, - pub body: String, - pub headers: Vec<(String, String)>, -} - -pub struct ConfigManager { - config_path: PathBuf, -} - -impl ConfigManager { - pub fn new() -> Result { - let proj_dirs = ProjectDirs::from("com", "granc", "granc-tui") - .context("Could not determine config directory")?; - let config_dir = proj_dirs.config_dir(); - fs::create_dir_all(config_dir)?; - - Ok(Self { - config_path: config_dir.join("config.json"), - }) - } - - pub fn load(&self) -> Result { - if !self.config_path.exists() { - return Ok(AppConfig::default()); - } - let content = fs::read_to_string(&self.config_path)?; - let config = serde_json::from_str(&content).unwrap_or_default(); - Ok(config) - } - - pub fn save(&self, config: &AppConfig) -> Result<()> { - let content = serde_json::to_string_pretty(config)?; - fs::write(&self.config_path, content)?; - Ok(()) - } -} diff --git a/granc-tui/src/effects.rs b/granc-tui/src/effects.rs deleted file mode 100644 index 099e04b..0000000 --- a/granc-tui/src/effects.rs +++ /dev/null @@ -1,243 +0,0 @@ -use crate::config::{ConfigManager, ConnectionConfig, Project}; -use crate::globals; -use crate::model::{MethodData, Model}; -use crate::msg::Msg; -use color_eyre::Result; -use granc_core::client::{Descriptor, DynamicRequest, DynamicResponse, GrancClient}; - -#[derive(Debug, Clone)] -pub enum Effect { - LoadConfigFromDisk, - SaveConfigToDisk(crate::config::AppConfig), - FetchServices(Project), - FetchMethods { - project: Project, - service: String, - }, - ExecuteCall { - project: Project, - service: String, - method: String, - body: String, - headers: Vec<(String, String)>, - }, -} - -pub fn handle_effect(_model: &Model, effect: Effect) -> Result> { - match effect { - Effect::LoadConfigFromDisk => { - let manager = ConfigManager::new().map_err(|e| color_eyre::eyre::eyre!(e))?; - match manager.load() { - Ok(cfg) => Ok(Some(Msg::ConfigLoaded(Ok(cfg)))), - Err(e) => Ok(Some(Msg::ConfigLoaded(Err(e.to_string())))), - } - } - - Effect::SaveConfigToDisk(cfg) => { - if let Ok(manager) = ConfigManager::new() { - let _ = manager.save(&cfg); - } - Ok(None) - } - - Effect::FetchServices(proj) => { - let handle = globals::get_handle(); - let project_id = proj.id; - let result = handle.block_on(async move { fetch_services_async(&proj).await }); - - match result { - Ok(services) => Ok(Some(Msg::ServicesFetched { - project_id, - services, - })), - Err(e) => Ok(Some(Msg::CallResponse(Err(format!("Fetch failed: {}", e))))), - } - } - - Effect::FetchMethods { project, service } => { - let handle = globals::get_handle(); - let service_name = service.clone(); - let result = - handle.block_on(async move { fetch_methods_async(&project, &service_name).await }); - - match result { - Ok(methods) => Ok(Some(Msg::MethodsFetched { service, methods })), - Err(e) => Ok(Some(Msg::CallResponse(Err(format!( - "Fetch methods failed: {}", - e - ))))), - } - } - - Effect::ExecuteCall { - project, - service, - method, - body, - headers, - } => { - let handle = globals::get_handle(); - let result = handle.block_on(async move { - execute_call_async(&project, service, method, body, headers).await - }); - - Ok(Some(Msg::CallResponse(result))) - } - } -} - -// --- Async Handlers --- - -async fn fetch_services_async(proj: &Project) -> std::result::Result, String> { - match &proj.connection { - ConnectionConfig::Reflection { url } => { - let mut client = GrancClient::connect(url).await.map_err(|e| e.to_string())?; - client.list_services().await.map_err(|e| e.to_string()) - } - ConnectionConfig::File { url, path } => { - let bytes = std::fs::read(path).map_err(|e| e.to_string())?; - if let Ok(c) = GrancClient::connect(url).await { - if let Ok(fc) = c.with_file_descriptor(bytes.clone()) { - return Ok(fc.list_services()); - } - } - let client = GrancClient::offline(bytes).map_err(|e| e.to_string())?; - Ok(client.list_services()) - } - } -} - -async fn fetch_methods_async( - proj: &Project, - service: &str, -) -> std::result::Result, String> { - fn extract(descriptor: Descriptor) -> std::result::Result, String> { - match descriptor { - Descriptor::ServiceDescriptor(sd) => { - let methods = sd - .methods() - .map(|m| { - let input_desc = m.input(); - let input = input_desc.name(); - let output_desc = m.output(); - let output = output_desc.name(); - let client_stream = if m.is_client_streaming() { - "stream " - } else { - "" - }; - let server_stream = if m.is_server_streaming() { - "stream " - } else { - "" - }; - - MethodData { - name: m.name().to_string(), - signature: format!( - "rpc {}({}{}) returns ({}{})", - m.name(), - client_stream, - input, - server_stream, - output - ), - } - }) - .collect(); - Ok(methods) - } - _ => Err("Symbol is not a service".to_string()), - } - } - - match &proj.connection { - ConnectionConfig::Reflection { url } => { - let mut client = GrancClient::connect(url).await.map_err(|e| e.to_string())?; - let descriptor = client - .get_descriptor_by_symbol(service) - .await - .map_err(|e| e.to_string())?; - extract(descriptor) - } - ConnectionConfig::File { url, path } => { - let bytes = std::fs::read(path).map_err(|e| e.to_string())?; - if let Ok(c) = GrancClient::connect(url).await { - // Fixed: Removed 'mut' from fc - if let Ok(fc) = c.with_file_descriptor(bytes.clone()) { - if let Some(d) = fc.get_descriptor_by_symbol(service) { - return extract(d); - } - } - } - let client = GrancClient::offline(bytes).map_err(|e| e.to_string())?; - if let Some(d) = client.get_descriptor_by_symbol(service) { - return extract(d); - } - Err("Service not found".to_string()) - } - } -} - -async fn execute_call_async( - proj: &Project, - service: String, - method: String, - body: String, - headers: Vec<(String, String)>, -) -> std::result::Result { - let json_body: serde_json::Value = - serde_json::from_str(&body).map_err(|e| format!("Invalid JSON: {}", e))?; - - let req = DynamicRequest { - service, - method, - body: json_body, - headers, - }; - - match &proj.connection { - ConnectionConfig::Reflection { url } => { - let mut client = GrancClient::connect(url).await.map_err(|e| e.to_string())?; - let resp = client.dynamic(req).await.map_err(|e| e.to_string())?; - format_response(resp) - } - ConnectionConfig::File { url, path } => { - let bytes = std::fs::read(path).map_err(|e| e.to_string())?; - let client = GrancClient::connect(url).await.map_err(|e| e.to_string())?; - let mut client = client - .with_file_descriptor(bytes) - .map_err(|e| e.to_string())?; - let resp = client.dynamic(req).await.map_err(|e| e.to_string())?; - format_response(resp) - } - } -} - -fn format_response(resp: DynamicResponse) -> std::result::Result { - match resp { - DynamicResponse::Unary(Ok(v)) => Ok(serde_json::to_string_pretty(&v).unwrap_or_default()), - DynamicResponse::Unary(Err(s)) => { - Err(format!("gRPC Error: {} (Code: {})", s.message(), s.code())) - } - DynamicResponse::Streaming(r) => { - let mut out = String::new(); - match r { - Ok(msgs) => { - for (i, msg) in msgs.into_iter().enumerate() { - match msg { - Ok(v) => out.push_str(&format!( - "Msg {}:\n{}\n", - i, - serde_json::to_string_pretty(&v).unwrap_or_default() - )), - Err(s) => out.push_str(&format!("Msg {} Error: {}\n", i, s.message())), - } - } - Ok(out) - } - Err(s) => Err(format!("Stream Error: {}", s.message())), - } - } - } -} diff --git a/granc-tui/src/globals.rs b/granc-tui/src/globals.rs deleted file mode 100644 index 5dc7c4c..0000000 --- a/granc-tui/src/globals.rs +++ /dev/null @@ -1,15 +0,0 @@ -use once_cell::sync::OnceCell; -use tokio::runtime::Handle; - -pub static TOKIO_HANDLE: OnceCell = OnceCell::new(); - -pub fn init_handle() { - let handle = Handle::current(); - TOKIO_HANDLE - .set(handle) - .expect("Failed to set global Tokio handle"); -} - -pub fn get_handle() -> &'static Handle { - TOKIO_HANDLE.get().expect("Tokio handle not initialized") -} diff --git a/granc-tui/src/main.rs b/granc-tui/src/main.rs index 7a71bdd..6048cf4 100644 --- a/granc-tui/src/main.rs +++ b/granc-tui/src/main.rs @@ -1,28 +1,459 @@ -mod config; -mod effects; -mod globals; -mod model; -mod msg; -mod update; -mod view; - -use model::Model; - -#[tokio::main] -async fn main() -> color_eyre::Result<()> { - color_eyre::install()?; - - // Initialize global Tokio handle for effects thread - globals::init_handle(); - - let initial_model = Model::default(); +use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind}; +use granc_core::client::{Descriptor, DynamicRequest, DynamicResponse, GrancClient}; +use ratatui::{ + buffer::Buffer, + layout::{Constraint, Direction, Layout, Rect}, + style::{Color, Modifier, Style, Stylize}, + text::{Line, Span}, + widgets::{Block, Borders, List, ListItem, Paragraph, Widget, Wrap}, +}; +use serde_json::json; +use teatui::{ProgramError, update::Update}; +fn main() -> Result<(), ProgramError> { teatui::start( - initial_model, - update::update, - view::view, - effects::handle_effect, - )?; - - Ok(()) + || { + ( + Model::default(), + Some(Effect::Connect("http://localhost:50051".to_string())), + ) + }, + update, + view, + run_effects, + ) +} + +// --- Model: Functional Application State --- + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Pane { + Services, + Methods, + Payload, +} + +#[derive(Clone, Debug)] +pub struct Model { + pub uri: String, + pub services: Vec, + pub selected_service_idx: usize, + pub methods: Vec, + pub selected_method_idx: usize, + pub method_definition: String, + pub json_payload: String, + pub response_log: String, + pub active_pane: Pane, + pub error: Option, +} + +impl Default for Model { + fn default() -> Self { + Self { + uri: "http://localhost:50051".into(), + services: vec![], + selected_service_idx: 0, + methods: vec![], + selected_method_idx: 0, + method_definition: "Select a method to see schema...".into(), + json_payload: json!({ "name": "Granc" }).to_string(), + response_log: "Ready to inspect server.".into(), + active_pane: Pane::Services, + error: None, + } + } +} + +// --- Messages & Effects --- + +#[derive(Debug)] +pub enum Message { + SetServices(Vec), + SetMethods(Vec), + SetMethodDefinition(String), + SetResponse(String), + SetError(String), + MoveDown, + MoveUp, + SwitchPane, + ExecuteCall, + Tick, + Exit, +} + +#[derive(Debug, Clone)] +pub enum Effect { + Connect(String), + FetchMethods(String), + DescribeSymbol(String), + Call(String, String, String), +} + +impl From for Message { + fn from(value: Event) -> Self { + match value { + Event::Key(KeyEvent { + code: KeyCode::Char('q') | KeyCode::Esc, + kind: KeyEventKind::Press, + .. + }) => Self::Exit, + Event::Key(KeyEvent { + code: KeyCode::Tab, + kind: KeyEventKind::Press, + .. + }) => Self::SwitchPane, + Event::Key(KeyEvent { + code: KeyCode::Down | KeyCode::Char('j'), + kind: KeyEventKind::Press, + .. + }) => Self::MoveDown, + Event::Key(KeyEvent { + code: KeyCode::Up | KeyCode::Char('k'), + kind: KeyEventKind::Press, + .. + }) => Self::MoveUp, + Event::Key(KeyEvent { + code: KeyCode::Enter, + kind: KeyEventKind::Press, + .. + }) => Self::ExecuteCall, + _ => Self::Tick, + } + } +} + +// --- Update: Pure Logic --- + +pub fn update(mut model: Model, msg: Message) -> Update { + match msg { + Message::Exit => Update::Exit, + Message::SwitchPane => { + model.active_pane = match model.active_pane { + Pane::Services => Pane::Methods, + Pane::Methods => Pane::Payload, + Pane::Payload => Pane::Services, + }; + Update::Next(model, None) + } + Message::MoveDown => match model.active_pane { + Pane::Services if !model.services.is_empty() => { + model.selected_service_idx = + (model.selected_service_idx + 1) % model.services.len(); + Update::Next( + model.clone(), + Some(Effect::FetchMethods( + model.services[model.selected_service_idx].clone(), + )), + ) + } + Pane::Methods if !model.methods.is_empty() => { + model.selected_method_idx = (model.selected_method_idx + 1) % model.methods.len(); + let symbol = format!( + "{}.{}", + model.services[model.selected_service_idx], + model.methods[model.selected_method_idx] + ); + Update::Next(model, Some(Effect::DescribeSymbol(symbol))) + } + _ => Update::Next(model, None), + }, + Message::MoveUp => match model.active_pane { + Pane::Services if !model.services.is_empty() => { + model.selected_service_idx = if model.selected_service_idx == 0 { + model.services.len() - 1 + } else { + model.selected_service_idx - 1 + }; + Update::Next( + model.clone(), + Some(Effect::FetchMethods( + model.services[model.selected_service_idx].clone(), + )), + ) + } + Pane::Methods if !model.methods.is_empty() => { + model.selected_method_idx = if model.selected_method_idx == 0 { + model.methods.len() - 1 + } else { + model.selected_method_idx - 1 + }; + let symbol = format!( + "{}.{}", + model.services[model.selected_service_idx], + model.methods[model.selected_method_idx] + ); + Update::Next(model, Some(Effect::DescribeSymbol(symbol))) + } + _ => Update::Next(model, None), + }, + Message::SetServices(svcs) => { + model.services = svcs; + Update::Next(model, None) + } + Message::SetMethods(meths) => { + model.methods = meths; + Update::Next(model, None) + } + Message::SetMethodDefinition(def) => { + model.method_definition = def; + Update::Next(model, None) + } + Message::ExecuteCall => { + if model.services.is_empty() || model.methods.is_empty() { + return Update::Next(model, None); + } + let svc = model.services[model.selected_service_idx].clone(); + let meth = model.methods[model.selected_method_idx].clone(); + Update::Next( + model.clone(), + Some(Effect::Call(svc, meth, model.json_payload.clone())), + ) + } + Message::SetResponse(res) => { + model.response_log = res; + Update::Next(model, None) + } + Message::SetError(err) => { + model.error = Some(err); + Update::Next(model, None) + } + _ => Update::Next(model, None), + } +} + +// --- Effects: Async Isolation with local Tokio Reactor --- + +pub async fn run_effects(model: Model, effect: Effect) -> Option { + let uri = model.uri.clone(); + + match effect { + Effect::Connect(url) => match GrancClient::connect(&url).await { + Ok(mut client) => Some(Message::SetServices( + client.list_services().await.unwrap_or_default(), + )), + Err(e) => Some(Message::SetError(e.to_string())), + }, + Effect::FetchMethods(svc_name) => { + let mut client = GrancClient::connect(&uri).await.ok()?; + if let Ok(Descriptor::ServiceDescriptor(sd)) = + client.get_descriptor_by_symbol(&svc_name).await + { + return Some(Message::SetMethods( + sd.methods().map(|m| m.name().to_string()).collect(), + )); + } + None + } + Effect::DescribeSymbol(symbol) => { + let mut client = GrancClient::connect(&uri).await.ok()?; + if let Ok(descriptor) = client.get_descriptor_by_symbol(&symbol).await { + let def = match descriptor { + Descriptor::MessageDescriptor(m) => { + format!("message {} {{ // ... }}", m.name()) + } + Descriptor::ServiceDescriptor(s) => { + format!("service {} {{ // ... }}", s.name()) + } + Descriptor::EnumDescriptor(e) => format!("enum {} {{ // ... }}", e.name()), + }; + return Some(Message::SetMethodDefinition(def)); + } + None + } + Effect::Call(svc, meth, payload) => { + let mut client = GrancClient::connect(&uri).await.ok()?; + let body = serde_json::from_str(&payload).unwrap_or(json!({})); + let req = DynamicRequest { + service: svc, + method: meth, + body, + headers: vec![], + }; + match client.dynamic(req).await { + Ok(DynamicResponse::Unary(Ok(v))) => Some(Message::SetResponse(v.to_string())), + Ok(DynamicResponse::Unary(Err(s))) => { + Some(Message::SetError(s.message().to_string())) + } + _ => Some(Message::SetError("Call failed".into())), + } + } + } +} + +// --- View: Fully Featured Multi-Pane Widget --- + +pub fn view(model: Model) -> AppWidget { + AppWidget { model } +} + +pub struct AppWidget { + model: Model, +} + +impl Widget for AppWidget { + fn render(self, area: Rect, buf: &mut Buffer) { + let active_style = Style::default() + .fg(Color::Cyan) + .add_modifier(Modifier::BOLD); + let normal_style = Style::default().fg(Color::DarkGray); + + // Layout: [Header] (3) -> [Content] (rest) -> [Footer] (1) + let root = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Length(3), + Constraint::Min(10), + Constraint::Length(1), + ]) + .split(area); + + // 1. Header + let header_content = vec![ + Line::from(vec![ + Span::styled( + " 🦀 GRANC WORKSPACE ", + Style::default().bg(Color::Blue).fg(Color::White).bold(), + ), + Span::raw(" Server: "), + Span::styled(self.model.uri.clone(), Color::Green).underlined(), + ]), + Line::from( + " (TAB: Switch Pane | Arrows/JK: Select | ENTER: Call) " + .italic() + .dark_gray(), + ), + ]; + Paragraph::new(header_content).render(root[0], buf); + + // 2. Content Split: Sidebar (30%) and Main (70%) + let body = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Percentage(30), Constraint::Percentage(70)]) + .split(root[1]); + + // 2a. Sidebar Vertical: Services (50%) and Methods (50%) + let sidebar = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) + .split(body[0]); + + // Services List + let svc_items: Vec = self + .model + .services + .iter() + .enumerate() + .map(|(i, s)| { + let style = if i == self.model.selected_service_idx { + Style::default().fg(Color::Yellow).bold() + } else { + Style::default() + }; + ListItem::new(format!(" • {}", s)).style(style) + }) + .collect(); + List::new(svc_items) + .block( + Block::default() + .borders(Borders::ALL) + .title(" 1. SERVICES ") + .border_style(if self.model.active_pane == Pane::Services { + active_style + } else { + normal_style + }), + ) + .render(sidebar[0], buf); + + // Methods List + let meth_items: Vec = self + .model + .methods + .iter() + .enumerate() + .map(|(i, m)| { + let style = if i == self.model.selected_method_idx { + Style::default().fg(Color::Cyan).bold() + } else { + Style::default() + }; + ListItem::new(format!(" ƒ {}", m)).style(style) + }) + .collect(); + List::new(meth_items) + .block( + Block::default() + .borders(Borders::ALL) + .title(" 2. METHODS ") + .border_style(if self.model.active_pane == Pane::Methods { + active_style + } else { + normal_style + }), + ) + .render(sidebar[1], buf); + + // 2b. Main Vertical: Definition (40%) and Payload/Response (60%) + let main = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Percentage(40), Constraint::Percentage(60)]) + .split(body[1]); + + Paragraph::new(self.model.method_definition.clone()) + .block( + Block::default() + .borders(Borders::ALL) + .title(" PROTO DEFINITION ") + .italic() + .cyan(), + ) + .render(main[0], buf); + + // Payload & Response Horizontal Split + let execution = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) + .split(main[1]); + + Paragraph::new(self.model.json_payload.clone()) + .block( + Block::default() + .borders(Borders::ALL) + .title(" 3. PAYLOAD ") + .border_style(if self.model.active_pane == Pane::Payload { + active_style + } else { + normal_style + }), + ) + .render(execution[0], buf); + + let resp_style = if self.model.error.is_some() { + Color::Red + } else { + Color::LightGreen + }; + Paragraph::new( + self.model + .error + .clone() + .unwrap_or(self.model.response_log.clone()), + ) + .block( + Block::default() + .borders(Borders::ALL) + .title(" RESPONSE LOG "), + ) + .style(Style::default().fg(resp_style)) + .wrap(Wrap { trim: true }) + .render(execution[1], buf); + + // 3. Footer + Paragraph::new( + " Press 'q' to Quit | Granc Workspace v0.1.0 " + .on_dark_gray() + .white(), + ) + .render(root[2], buf); + } } diff --git a/granc-tui/src/model.rs b/granc-tui/src/model.rs deleted file mode 100644 index 3f0ba7c..0000000 --- a/granc-tui/src/model.rs +++ /dev/null @@ -1,100 +0,0 @@ -use crate::config::{AppConfig, Project}; -use tui_textarea::TextArea; -use uuid::Uuid; - -#[derive(Debug, Clone, PartialEq)] -pub enum Screen { - Dashboard, - NewProject, - ServiceBrowser, - MethodBrowser, - MethodView, - ResponseView, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum Focus { - Body, - HeaderKey(usize), - HeaderValue(usize), -} - -#[derive(Debug, Clone)] -pub struct MethodData { - pub name: String, - pub signature: String, -} - -#[derive(Debug, Clone)] -pub struct HeaderPair { - pub key: String, - pub value: String, -} - -#[derive(Debug, Clone)] -pub struct Model { - pub screen: Screen, - pub config: AppConfig, - - // Global Input Buffer (New Project / URL) - pub input_buffer: String, - - // Navigation Indices - pub project_list_idx: usize, - pub service_list_idx: usize, - pub method_list_idx: usize, - - // Data - pub selected_project_id: Option, - pub services: Vec, - pub methods: Vec, // Updated to hold signature - pub selected_service: Option, - pub selected_method: Option, - - // Request Editor State - pub body_editor: TextArea<'static>, - pub headers: Vec, - pub focus: Focus, - - // Results - pub response_output: String, - pub status_message: Option, -} - -impl Default for Model { - fn default() -> Self { - let mut editor = TextArea::default(); - editor.set_block( - ratatui::widgets::Block::default() - .borders(ratatui::widgets::Borders::ALL) - .title("Body (JSON)"), - ); - editor.insert_str("{}"); - - Self { - screen: Screen::Dashboard, - config: AppConfig::default(), - input_buffer: String::new(), - project_list_idx: 0, - service_list_idx: 0, - method_list_idx: 0, - selected_project_id: None, - services: vec![], - methods: vec![], - selected_service: None, - selected_method: None, - body_editor: editor, - headers: vec![], - focus: Focus::Body, - response_output: String::new(), - status_message: None, - } - } -} - -impl Model { - pub fn current_project(&self) -> Option<&Project> { - self.selected_project_id - .and_then(|id| self.config.projects.iter().find(|p| p.id == id)) - } -} diff --git a/granc-tui/src/msg.rs b/granc-tui/src/msg.rs deleted file mode 100644 index 73906dd..0000000 --- a/granc-tui/src/msg.rs +++ /dev/null @@ -1,46 +0,0 @@ -use crate::config::AppConfig; -use crate::model::MethodData; -use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind}; -use uuid::Uuid; - -#[derive(Debug, Clone)] -pub enum Msg { - // --- User Input --- - Key(KeyEvent), - - // --- Config Lifecycle --- - ConfigLoaded(Result), - - // --- Async Results --- - ServicesFetched { - project_id: Uuid, - services: Vec, - }, - MethodsFetched { - service: String, - methods: Vec, - }, - CallResponse(Result), - - // --- System --- - NoOp, - Exit, -} - -impl From for Msg { - fn from(event: Event) -> Self { - match event { - Event::Key(key) if key.kind == KeyEventKind::Press => match key.code { - KeyCode::Char('c') - if key - .modifiers - .contains(crossterm::event::KeyModifiers::CONTROL) => - { - Msg::Exit - } - _ => Msg::Key(key), - }, - _ => Msg::NoOp, - } - } -} diff --git a/granc-tui/src/update.rs b/granc-tui/src/update.rs deleted file mode 100644 index b448cb4..0000000 --- a/granc-tui/src/update.rs +++ /dev/null @@ -1,365 +0,0 @@ -use color_eyre::Result; -use crossterm::event::{KeyCode, KeyModifiers}; -use teatui::Update; -use uuid::Uuid; - -use crate::config::{ConnectionConfig, Project}; -use crate::effects::Effect; -use crate::model::{Focus, HeaderPair, Model, Screen}; -use crate::msg::Msg; - -pub fn update(mut model: Model, msg: Msg) -> Result> { - match msg { - Msg::Exit => Ok(Update::Exit), - Msg::NoOp => Ok(Update::Next(model)), - - Msg::ConfigLoaded(res) => { - match res { - Ok(cfg) => { - model.config = cfg; - model.status_message = Some("Config loaded".into()); - } - Err(e) => model.status_message = Some(format!("Config Error: {}", e)), - } - Ok(Update::Next(model)) - } - - // Handle Editor Specific Messages (Method View) - Msg::Key(key) if model.screen == Screen::MethodView => match key.code { - KeyCode::Esc => { - model.screen = Screen::MethodBrowser; - Ok(Update::Next(model)) - } - KeyCode::Tab => { - match model.focus { - Focus::Body => { - if !model.headers.is_empty() { - model.focus = Focus::HeaderKey(0); - } - } - Focus::HeaderKey(i) => model.focus = Focus::HeaderValue(i), - Focus::HeaderValue(i) => { - if i + 1 < model.headers.len() { - model.focus = Focus::HeaderKey(i + 1); - } else { - model.focus = Focus::Body; - } - } - } - Ok(Update::Next(model)) - } - KeyCode::Char('h') if key.modifiers.contains(KeyModifiers::CONTROL) => { - model.headers.push(HeaderPair { - key: "".into(), - value: "".into(), - }); - model.focus = Focus::HeaderKey(model.headers.len() - 1); - Ok(Update::Next(model)) - } - KeyCode::Char('d') if key.modifiers.contains(KeyModifiers::CONTROL) => { - match model.focus { - Focus::HeaderKey(i) | Focus::HeaderValue(i) => { - model.headers.remove(i); - model.focus = Focus::Body; - } - _ => {} - } - Ok(Update::Next(model)) - } - KeyCode::Enter | KeyCode::Char('s') - if key.modifiers.contains(KeyModifiers::CONTROL) => - { - execute_request(model) - } - _ => { - match model.focus { - Focus::Body => { - model.body_editor.input(to_ratatui_key(key)); - } - Focus::HeaderKey(i) => { - handle_text_input(&mut model.headers[i].key, key); - } - Focus::HeaderValue(i) => { - handle_text_input(&mut model.headers[i].value, key); - } - } - Ok(Update::Next(model)) - } - }, - - Msg::Key(key) => match (model.screen.clone(), key.code) { - // Global - (_, KeyCode::Char('q')) => Ok(Update::Exit), - - // --- Dashboard --- - (Screen::Dashboard, KeyCode::Char('l')) => { - Ok(Update::NextWithEffect(model, Effect::LoadConfigFromDisk)) - } - (Screen::Dashboard, KeyCode::Char('n')) => { - model.screen = Screen::NewProject; - model.input_buffer.clear(); - Ok(Update::Next(model)) - } - (Screen::Dashboard, KeyCode::Down) => { - if !model.config.projects.is_empty() { - model.project_list_idx = - (model.project_list_idx + 1) % model.config.projects.len(); - } - Ok(Update::Next(model)) - } - (Screen::Dashboard, KeyCode::Up) => { - if !model.config.projects.is_empty() { - model.project_list_idx = if model.project_list_idx == 0 { - model.config.projects.len() - 1 - } else { - model.project_list_idx - 1 - }; - } - Ok(Update::Next(model)) - } - (Screen::Dashboard, KeyCode::Enter) => { - let project_opt = model.config.projects.get(model.project_list_idx).cloned(); - if let Some(proj) = project_opt { - model.selected_project_id = Some(proj.id); - model.screen = Screen::ServiceBrowser; - model.service_list_idx = 0; - model.status_message = Some("Fetching services...".into()); - Ok(Update::NextWithEffect(model, Effect::FetchServices(proj))) - } else { - Ok(Update::Next(model)) - } - } - - // --- New Project --- - (Screen::NewProject, KeyCode::Enter) => { - let new_proj = Project { - id: Uuid::new_v4(), - name: if model.input_buffer.is_empty() { - "Untitled".to_string() - } else { - model.input_buffer.clone() - }, - connection: ConnectionConfig::Reflection { - url: model.input_buffer.clone(), - }, - saved_requests: vec![], - }; - model.config.projects.push(new_proj); - model.screen = Screen::Dashboard; - model.project_list_idx = model.config.projects.len() - 1; - let effect = Effect::SaveConfigToDisk(model.config.clone()); - Ok(Update::NextWithEffect(model, effect)) - } - (Screen::NewProject, KeyCode::Char(c)) => { - model.input_buffer.push(c); - Ok(Update::Next(model)) - } - (Screen::NewProject, KeyCode::Backspace) => { - model.input_buffer.pop(); - Ok(Update::Next(model)) - } - (Screen::NewProject, KeyCode::Esc) => { - model.screen = Screen::Dashboard; - Ok(Update::Next(model)) - } - - // --- Service Browser --- - (Screen::ServiceBrowser, KeyCode::Down) => { - if !model.services.is_empty() { - model.service_list_idx = (model.service_list_idx + 1) % model.services.len(); - } - Ok(Update::Next(model)) - } - (Screen::ServiceBrowser, KeyCode::Up) => { - if !model.services.is_empty() { - model.service_list_idx = if model.service_list_idx == 0 { - model.services.len() - 1 - } else { - model.service_list_idx - 1 - }; - } - Ok(Update::Next(model)) - } - (Screen::ServiceBrowser, KeyCode::Enter) => { - if let (Some(svc), Some(proj)) = ( - model.services.get(model.service_list_idx).cloned(), - model.current_project().cloned(), - ) { - model.selected_service = Some(svc.clone()); - model.status_message = Some("Fetching methods...".into()); - Ok(Update::NextWithEffect( - model, - Effect::FetchMethods { - project: proj, - service: svc, - }, - )) - } else { - Ok(Update::Next(model)) - } - } - (Screen::ServiceBrowser, KeyCode::Esc) => { - model.screen = Screen::Dashboard; - Ok(Update::Next(model)) - } - - // --- Method Browser --- - (Screen::MethodBrowser, KeyCode::Down) => { - if !model.methods.is_empty() { - model.method_list_idx = (model.method_list_idx + 1) % model.methods.len(); - } - Ok(Update::Next(model)) - } - (Screen::MethodBrowser, KeyCode::Up) => { - if !model.methods.is_empty() { - model.method_list_idx = if model.method_list_idx == 0 { - model.methods.len() - 1 - } else { - model.method_list_idx - 1 - }; - } - Ok(Update::Next(model)) - } - (Screen::MethodBrowser, KeyCode::Enter) => { - if let Some(m) = model.methods.get(model.method_list_idx).cloned() { - model.selected_method = Some(m.name); - model.headers.clear(); - model.focus = Focus::Body; - model.screen = Screen::MethodView; - } - Ok(Update::Next(model)) - } - (Screen::MethodBrowser, KeyCode::Esc) => { - model.screen = Screen::ServiceBrowser; - Ok(Update::Next(model)) - } - - // --- Response View --- - (Screen::ResponseView, KeyCode::Esc) => { - model.screen = Screen::MethodView; - Ok(Update::Next(model)) - } - - _ => Ok(Update::Next(model)), - }, - - Msg::ServicesFetched { - project_id, - services, - } => { - if model.selected_project_id == Some(project_id) { - model.services = services; - model.service_list_idx = 0; - model.status_message = Some("Services loaded.".into()); - } - Ok(Update::Next(model)) - } - - Msg::MethodsFetched { service, methods } => { - if model.selected_service.as_deref() == Some(&service) { - model.methods = methods; - model.method_list_idx = 0; - model.screen = Screen::MethodBrowser; - model.status_message = Some("Methods loaded.".into()); - } - Ok(Update::Next(model)) - } - - Msg::CallResponse(res) => { - model.screen = Screen::ResponseView; - match res { - Ok(s) => { - model.response_output = s; - model.status_message = Some("Call success".into()); - } - Err(e) => { - model.response_output = format!("Error: {}", e); - model.status_message = Some("Call failed".into()); - } - } - Ok(Update::Next(model)) - } - } -} - -fn execute_request(mut model: Model) -> Result> { - let execution_data = if let (Some(p), Some(s), Some(m)) = ( - model.current_project(), - &model.selected_service, - &model.selected_method, - ) { - Some((p.clone(), s.clone(), m.clone())) - } else { - None - }; - - if let Some((p, s, m)) = execution_data { - let headers: Vec<(String, String)> = model - .headers - .iter() - .filter(|h| !h.key.is_empty()) - .map(|h| (h.key.clone(), h.value.clone())) - .collect(); - - let body_lines = model.body_editor.lines().to_vec(); - let body = body_lines.join("\n"); - - let effect = Effect::ExecuteCall { - project: p, - service: s, - method: m, - body, - headers, - }; - model.status_message = Some("Executing request...".into()); - Ok(Update::NextWithEffect(model, effect)) - } else { - model.status_message = - Some("Error: Missing execution context (project/service/method)".into()); - Ok(Update::Next(model)) - } -} - -fn handle_text_input(target: &mut String, key: crossterm::event::KeyEvent) { - match key.code { - KeyCode::Char(c) => target.push(c), - KeyCode::Backspace => { - target.pop(); - } - _ => {} - } -} - -fn to_ratatui_key(key: crossterm::event::KeyEvent) -> ratatui::crossterm::event::KeyEvent { - let code = match key.code { - crossterm::event::KeyCode::Backspace => ratatui::crossterm::event::KeyCode::Backspace, - crossterm::event::KeyCode::Enter => ratatui::crossterm::event::KeyCode::Enter, - crossterm::event::KeyCode::Left => ratatui::crossterm::event::KeyCode::Left, - crossterm::event::KeyCode::Right => ratatui::crossterm::event::KeyCode::Right, - crossterm::event::KeyCode::Up => ratatui::crossterm::event::KeyCode::Up, - crossterm::event::KeyCode::Down => ratatui::crossterm::event::KeyCode::Down, - crossterm::event::KeyCode::Home => ratatui::crossterm::event::KeyCode::Home, - crossterm::event::KeyCode::End => ratatui::crossterm::event::KeyCode::End, - crossterm::event::KeyCode::PageUp => ratatui::crossterm::event::KeyCode::PageUp, - crossterm::event::KeyCode::PageDown => ratatui::crossterm::event::KeyCode::PageDown, - crossterm::event::KeyCode::Tab => ratatui::crossterm::event::KeyCode::Tab, - crossterm::event::KeyCode::BackTab => ratatui::crossterm::event::KeyCode::BackTab, - crossterm::event::KeyCode::Delete => ratatui::crossterm::event::KeyCode::Delete, - crossterm::event::KeyCode::Insert => ratatui::crossterm::event::KeyCode::Insert, - crossterm::event::KeyCode::F(n) => ratatui::crossterm::event::KeyCode::F(n), - crossterm::event::KeyCode::Char(c) => ratatui::crossterm::event::KeyCode::Char(c), - crossterm::event::KeyCode::Null => ratatui::crossterm::event::KeyCode::Null, - crossterm::event::KeyCode::Esc => ratatui::crossterm::event::KeyCode::Esc, - _ => ratatui::crossterm::event::KeyCode::Null, - }; - - let modifiers = - ratatui::crossterm::event::KeyModifiers::from_bits_truncate(key.modifiers.bits()); - - ratatui::crossterm::event::KeyEvent { - code, - modifiers, - kind: ratatui::crossterm::event::KeyEventKind::Press, - state: ratatui::crossterm::event::KeyEventState::empty(), - } -} diff --git a/granc-tui/src/view.rs b/granc-tui/src/view.rs deleted file mode 100644 index fa7c43c..0000000 --- a/granc-tui/src/view.rs +++ /dev/null @@ -1,217 +0,0 @@ -use crate::model::{Focus, Model, Screen}; -use color_eyre::Result; -use ratatui::{ - layout::{Constraint, Direction, Layout, Rect}, - style::{Color, Style, Stylize}, - widgets::ListState, - widgets::{Block, Borders, List, ListItem, Paragraph, StatefulWidget, Widget, WidgetRef}, -}; -use teatui::View; - -struct RootWidget { - model: Model, -} - -impl WidgetRef for RootWidget { - fn render_ref(&self, area: Rect, buf: &mut ratatui::prelude::Buffer) { - let chunks = Layout::default() - .direction(Direction::Vertical) - .constraints([Constraint::Min(0), Constraint::Length(1)]) - .split(area); - - let main_area = chunks[0]; - let status_bar = chunks[1]; - - match self.model.screen { - Screen::Dashboard => draw_dashboard(&self.model, main_area, buf), - Screen::NewProject => draw_new_project(&self.model, main_area, buf), - Screen::ServiceBrowser => draw_services(&self.model, main_area, buf), - Screen::MethodBrowser => draw_method_browser(&self.model, main_area, buf), - Screen::MethodView => draw_method_execution(&self.model, main_area, buf), - Screen::ResponseView => draw_response(&self.model, main_area, buf), - } - - let msg = self.model.status_message.as_deref().unwrap_or("Ready"); - let status_text = format!( - " {} | Screen: {:?} | [Q] Quit | [L] Load", - msg, self.model.screen - ); - Paragraph::new(status_text) - .style(Style::default().bg(Color::Blue).fg(Color::White)) - .render_ref(status_bar, buf); - } -} - -pub fn view(model: &Model) -> Result { - Ok(View::new(RootWidget { - model: model.clone(), - })) -} - -// --- Helpers --- - -fn draw_dashboard(model: &Model, area: Rect, buf: &mut ratatui::prelude::Buffer) { - let items: Vec = model - .config - .projects - .iter() - .map(|p| ListItem::new(p.name.as_str())) - .collect(); - - let list = List::new(items) - .block( - Block::default() - .title("Projects (Press 'n' for new)") - .borders(Borders::ALL), - ) - .highlight_style(Style::default().bg(Color::DarkGray).bold()); - - let mut state = ListState::default().with_selected(Some(model.project_list_idx)); - StatefulWidget::render(list, area, buf, &mut state); -} - -fn draw_new_project(model: &Model, area: Rect, buf: &mut ratatui::prelude::Buffer) { - let text = Paragraph::new(format!("Server URL: {}", model.input_buffer)).block( - Block::default() - .title("New Project (Enter URL)") - .borders(Borders::ALL), - ); - text.render_ref(area, buf); -} - -fn draw_services(model: &Model, area: Rect, buf: &mut ratatui::prelude::Buffer) { - let items: Vec = model - .services - .iter() - .map(|s| ListItem::new(s.as_str())) - .collect(); - - let list = List::new(items) - .block(Block::default().title("Services").borders(Borders::ALL)) - .highlight_style(Style::default().bg(Color::DarkGray).bold()); - - let mut state = ListState::default().with_selected(Some(model.service_list_idx)); - StatefulWidget::render(list, area, buf, &mut state); -} - -fn draw_method_browser(model: &Model, area: Rect, buf: &mut ratatui::prelude::Buffer) { - let items: Vec = model - .methods - .iter() - .map(|m| ListItem::new(m.signature.as_str())) - .collect(); - - let list = List::new(items) - .block( - Block::default() - .title(format!( - "Methods of {}", - model.selected_service.as_deref().unwrap_or("?") - )) - .borders(Borders::ALL), - ) - .highlight_style(Style::default().bg(Color::DarkGray).bold()); - - let mut state = ListState::default().with_selected(Some(model.method_list_idx)); - StatefulWidget::render(list, area, buf, &mut state); -} - -fn draw_method_execution(model: &Model, area: Rect, buf: &mut ratatui::prelude::Buffer) { - let chunks = Layout::default() - .direction(Direction::Vertical) - .constraints([ - Constraint::Length(1), // Title - Constraint::Percentage(60), // Body - Constraint::Percentage(30), // Headers - Constraint::Length(1), // Hint - ]) - .split(area); - - let title = format!( - "Executing: {}", - model.selected_method.as_deref().unwrap_or("?") - ); - Paragraph::new(title).bold().render_ref(chunks[0], buf); - - // Body Editor - let mut editor = model.body_editor.clone(); - let body_block = Block::default() - .borders(Borders::ALL) - .title("Request Body (JSON)"); - - if model.focus == Focus::Body { - editor.set_style(Style::default()); - editor.set_block(body_block.border_style(Style::default().fg(Color::Yellow))); - } else { - editor.set_style(Style::default().fg(Color::DarkGray)); - editor.set_block(body_block); - } - - editor.render(chunks[1], buf); - - // Headers - let header_block = Block::default() - .borders(Borders::ALL) - .title("Headers (Ctrl+H to add)"); - header_block.render_ref(chunks[2], buf); - - let header_area = chunks[2].inner(ratatui::layout::Margin { - horizontal: 1, - vertical: 1, - }); - - let header_rows = Layout::default() - .direction(Direction::Vertical) - .constraints(vec![Constraint::Length(1); model.headers.len()]) - .split(header_area); - - for (i, header) in model.headers.iter().enumerate() { - if i >= header_rows.len() { - break; - } - - let row_chunks = Layout::default() - .direction(Direction::Horizontal) - .constraints([ - Constraint::Percentage(45), - Constraint::Length(1), - Constraint::Percentage(45), - ]) - .split(header_rows[i]); - - let k_style = if model.focus == Focus::HeaderKey(i) { - Style::default().fg(Color::Yellow) - } else { - Style::default() - }; - Paragraph::new(header.key.as_str()) - .style(k_style) - .render_ref(row_chunks[0], buf); - - Paragraph::new(":").render_ref(row_chunks[1], buf); - - let v_style = if model.focus == Focus::HeaderValue(i) { - Style::default().fg(Color::Yellow) - } else { - Style::default() - }; - Paragraph::new(header.value.as_str()) - .style(v_style) - .render_ref(row_chunks[2], buf); - } - - Paragraph::new( - "[Tab] Cycle Focus | [Ctrl+Enter/S] Send | [Ctrl+H] Add Header | [Ctrl+D] Remove Header", - ) - .style(Style::default().fg(Color::Gray)) - .render_ref(chunks[3], buf); -} - -fn draw_response(model: &Model, area: Rect, buf: &mut ratatui::prelude::Buffer) { - let p = Paragraph::new(model.response_output.as_str()).block( - Block::default() - .title("Response (Esc to back)") - .borders(Borders::ALL), - ); - p.render_ref(area, buf); -}