From 146e4d78120f58ce2f1eb50abab1441b94e053ce Mon Sep 17 00:00:00 2001 From: Felipe Contreras Salinas Date: Sun, 22 Jun 2025 20:11:48 -0400 Subject: [PATCH] feat: add edit mode to TUI (#57) Reviewed-on: https://oolong.ludwig.dog/pitbuster/huellas/pulls/57 Co-authored-by: Felipe Contreras Salinas Co-committed-by: Felipe Contreras Salinas --- Cargo.lock | 655 ++++++++++++++++++++++++----------------------- Cargo.toml | 9 +- src/tui/keys.rs | 52 ++-- src/tui/mod.rs | 6 +- src/tui/state.rs | 79 +++++- src/tui/ui.rs | 523 ++++++++++++++++++++++++++++--------- 6 files changed, 839 insertions(+), 485 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7ba2ad5..519a8ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aho-corasick" @@ -121,9 +121,9 @@ checksum = "3c1e7e457ea78e524f48639f551fd79703ac3f2237f5ecccdf4708f8a75ad373" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "axum" @@ -225,9 +225,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", @@ -246,9 +246,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] name = "bitflags" @@ -276,9 +276,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "bytesize" @@ -303,18 +303,18 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.15" +version = "1.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c736e259eea577f443d5c86c304f9f4ae0295c43f3ba05c21f1d66b5f06001af" +checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" dependencies = [ "shlex", ] [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "clap" @@ -412,9 +412,9 @@ dependencies = [ [[package]] name = "crc" -version = "3.2.1" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" dependencies = [ "crc-catalog", ] @@ -448,29 +448,14 @@ checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ "bitflags", "crossterm_winapi", - "mio", - "parking_lot", - "rustix 0.38.44", - "signal-hook", - "signal-hook-mio", - "winapi", -] - -[[package]] -name = "crossterm" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" -dependencies = [ - "bitflags", - "document-features", "futures-core", "mio", "parking_lot", - "rustix 1.0.7", + "rustix", "serde", "signal-hook", "signal-hook-mio", + "winapi", ] [[package]] @@ -529,9 +514,9 @@ dependencies = [ [[package]] name = "der" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid", "pem-rfc7468", @@ -540,9 +525,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.11" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", ] @@ -576,15 +561,6 @@ dependencies = [ "syn", ] -[[package]] -name = "document-features" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" -dependencies = [ - "litrs", -] - [[package]] name = "dotenvy" version = "0.15.7" @@ -593,9 +569,9 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "either" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" dependencies = [ "serde", ] @@ -608,12 +584,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -657,9 +633,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "form_urlencoded" @@ -768,25 +744,25 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", ] [[package]] name = "getrandom" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", - "wasi 0.13.3+wasi-0.2.2", - "windows-targets 0.52.6", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", ] [[package]] @@ -797,9 +773,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "h2" -version = "0.4.8" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2" +checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5" dependencies = [ "atomic-waker", "bytes", @@ -816,9 +792,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" dependencies = [ "allocator-api2", "equivalent", @@ -896,12 +872,12 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", - "futures-util", + "futures-core", "http", "http-body", "pin-project-lite", @@ -915,9 +891,9 @@ checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" [[package]] name = "httparse" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "httpdate" @@ -934,7 +910,7 @@ dependencies = [ "axum-msgpack", "axum-test", "clap", - "crossterm 0.29.0", + "crossterm", "dotenvy", "futures", "itertools 0.14.0", @@ -947,6 +923,7 @@ dependencies = [ "tower-http", "tracing", "tracing-subscriber", + "tui-textarea", ] [[package]] @@ -972,16 +949,18 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.10" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" dependencies = [ "bytes", "futures-channel", + "futures-core", "futures-util", "http", "http-body", "hyper", + "libc", "pin-project-lite", "socket2", "tokio", @@ -991,21 +970,22 @@ dependencies = [ [[package]] name = "icu_collections" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", + "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] -name = "icu_locid" -version = "1.5.0" +name = "icu_locale_core" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" dependencies = [ "displaydoc", "litemap", @@ -1014,31 +994,11 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - [[package]] name = "icu_normalizer" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ "displaydoc", "icu_collections", @@ -1046,67 +1006,54 @@ dependencies = [ "icu_properties", "icu_provider", "smallvec", - "utf16_iter", - "utf8_iter", - "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" -version = "1.5.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ "displaydoc", "icu_collections", - "icu_locid_transform", + "icu_locale_core", "icu_properties_data", "icu_provider", - "tinystr", + "potential_utf", + "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "1.5.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] name = "icu_provider" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ "displaydoc", - "icu_locid", - "icu_provider_macros", + "icu_locale_core", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", + "zerotrie", "zerovec", ] -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "ident_case" version = "1.0.1" @@ -1126,9 +1073,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", @@ -1136,9 +1083,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", "hashbrown", @@ -1189,9 +1136,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "lazy_static" @@ -1204,15 +1151,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.170" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libm" -version = "0.2.11" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libsqlite3-sys" @@ -1231,29 +1178,17 @@ version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" -[[package]] -name = "linux-raw-sys" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" - [[package]] name = "litemap" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" - -[[package]] -name = "litrs" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", @@ -1261,9 +1196,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.26" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "lru" @@ -1301,9 +1236,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "mime" @@ -1323,23 +1258,23 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.8.5" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] [[package]] name = "mio" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", ] [[package]] @@ -1406,9 +1341,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.3" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "once_cell_polyfill" @@ -1424,9 +1359,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -1434,9 +1369,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", @@ -1501,9 +1436,18 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] [[package]] name = "powerfmt" @@ -1513,11 +1457,11 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy 0.7.35", + "zerocopy", ] [[package]] @@ -1532,22 +1476,28 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.93" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "rand" version = "0.8.5" @@ -1561,13 +1511,12 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.2", - "zerocopy 0.8.20", + "rand_core 0.9.3", ] [[package]] @@ -1587,7 +1536,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.2", + "rand_core 0.9.3", ] [[package]] @@ -1596,17 +1545,16 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", ] [[package]] name = "rand_core" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a509b1a2ffbe92afab0e55c8fd99dea1c280e8171bd2d88682bb20bc41cbc2c" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.1", - "zerocopy 0.8.20", + "getrandom 0.3.3", ] [[package]] @@ -1618,7 +1566,7 @@ dependencies = [ "bitflags", "cassowary", "compact_str", - "crossterm 0.28.1", + "crossterm", "indoc", "instability", "itertools 0.13.0", @@ -1632,9 +1580,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.9" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b568323e98e49e2a0899dcee453dd679fae22d69adf9b11dd508d1549b7e2f" +checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" dependencies = [ "bitflags", ] @@ -1694,13 +1642,13 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.11" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da5349ae27d3887ca812fb375b45a4fbb36d8d12d2df394968cd86e35683fe73" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.15", + "getrandom 0.2.16", "libc", "untrusted", "windows-sys 0.52.0", @@ -1730,9 +1678,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519" +checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" dependencies = [ "const-oid", "digest", @@ -1759,15 +1707,15 @@ dependencies = [ "futures-util", "http", "mime", - "rand 0.9.0", + "rand 0.9.1", "thiserror", ] [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" [[package]] name = "rustix" @@ -1778,28 +1726,15 @@ dependencies = [ "bitflags", "errno", "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", -] - -[[package]] -name = "rustix" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys 0.9.4", + "linux-raw-sys", "windows-sys 0.59.0", ] [[package]] name = "rustls" -version = "0.23.23" +version = "0.23.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" +checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643" dependencies = [ "once_cell", "ring", @@ -1811,15 +1746,18 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] [[package]] name = "rustls-webpki" -version = "0.102.8" +version = "0.103.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" dependencies = [ "ring", "rustls-pki-types", @@ -1828,15 +1766,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.19" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "ryu" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "scopeguard" @@ -1866,9 +1804,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.139" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", @@ -1878,9 +1816,9 @@ dependencies = [ [[package]] name = "serde_path_to_error" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" dependencies = [ "itoa", "serde", @@ -1911,9 +1849,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", @@ -1958,9 +1896,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.2" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" dependencies = [ "libc", ] @@ -1977,27 +1915,24 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" [[package]] name = "smallvec" -version = "1.14.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" dependencies = [ "serde", ] [[package]] name = "socket2" -version = "0.5.8" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", "windows-sys 0.52.0", @@ -2068,7 +2003,7 @@ dependencies = [ "tokio-stream", "tracing", "url", - "webpki-roots", + "webpki-roots 0.26.11", ] [[package]] @@ -2268,9 +2203,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.98" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -2285,9 +2220,9 @@ checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" [[package]] name = "synstructure" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", @@ -2316,19 +2251,18 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ "cfg-if", - "once_cell", ] [[package]] name = "time" -version = "0.3.37" +version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "itoa", @@ -2341,15 +2275,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" -version = "0.2.19" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" dependencies = [ "num-conv", "time-core", @@ -2357,9 +2291,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.7.6" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", "zerovec", @@ -2367,9 +2301,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" dependencies = [ "tinyvec_macros", ] @@ -2500,9 +2434,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.28" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", @@ -2511,9 +2445,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", "valuable", @@ -2552,6 +2486,17 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tui-textarea" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a5318dd619ed73c52a9417ad19046724effc1287fb75cdcc4eca1d6ac1acbae" +dependencies = [ + "crossterm", + "ratatui", + "unicode-width 0.2.0", +] + [[package]] name = "typenum" version = "1.18.0" @@ -2572,9 +2517,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-normalization" @@ -2637,12 +2582,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -2684,15 +2623,15 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" -version = "0.13.3+wasi-0.2.2" +version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] @@ -2705,18 +2644,27 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "webpki-roots" -version = "0.26.8" +version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.1", +] + +[[package]] +name = "webpki-roots" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8782dd5a41a24eed3a4f40b606249b3e236ca61adf1f25ea4d45c73de122b502" dependencies = [ "rustls-pki-types", ] [[package]] name = "whoami" -version = "1.5.2" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" +checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7" dependencies = [ "redox_syscall", "wasite", @@ -2771,6 +2719,15 @@ 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.2", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -2795,13 +2752,29 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -2814,6 +2787,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -2826,6 +2805,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -2838,12 +2823,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -2856,6 +2853,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -2868,6 +2871,12 @@ 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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -2880,6 +2889,12 @@ 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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -2892,26 +2907,26 @@ 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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "wit-bindgen-rt" -version = "0.33.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ "bitflags", ] -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - [[package]] name = "writeable" -version = "0.5.5" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "yansi" @@ -2921,9 +2936,9 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yoke" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ "serde", "stable_deref_trait", @@ -2933,9 +2948,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", @@ -2945,39 +2960,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.35" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ - "byteorder", - "zerocopy-derive 0.7.35", -] - -[[package]] -name = "zerocopy" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde3bb8c68a8f3f1ed4ac9221aad6b10cece3e60a8e2ea54a6a2dec806d0084c" -dependencies = [ - "zerocopy-derive 0.8.20", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.20" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eea57037071898bf96a6da35fd626f4f27e9cee3ead2a6c703cf09d472b2e700" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", @@ -2986,18 +2980,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", @@ -3011,11 +3005,22 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + [[package]] name = "zerovec" -version = "0.10.4" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" dependencies = [ "yoke", "zerofrom", @@ -3024,9 +3029,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.10.3" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index dcda983..f06b934 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,8 @@ axum = { version = "0.8.4", default-features = false, features = [ ] } axum-msgpack = "0.5.0" clap = { version = "4.5.40", features = ["derive"] } -crossterm = { version = "0.29.0", default-features = false, features = [ +# This must be the same version that ratatui depends on :( +crossterm = { version = "0.28.1", default-features = false, features = [ "bracketed-paste", "event-stream", "serde", @@ -33,13 +34,13 @@ sqlx = { version = "0.8.6", default-features = false, features = [ "sqlite", "tls-rustls", ] } +thiserror = "2.0.12" tokio = { version = "1.45.1", default-features = false, features = [ "macros", "rt-multi-thread", "signal", ] } tokio-util = "0.7.15" -thiserror = "2.0.12" tower-http = { version = "0.6.6", default-features = false, features = ["fs"] } tracing = "0.1.41" tracing-subscriber = { version = "0.3.19", default-features = false, features = [ @@ -48,6 +49,10 @@ tracing-subscriber = { version = "0.3.19", default-features = false, features = "tracing", "tracing-log", ] } +tui-textarea = { version = "0.7.0", default-features = false, features = [ + "ratatui", + "crossterm", +] } [dev-dependencies] axum-test = { version = "17.3.0", features = ["msgpack"] } diff --git a/src/tui/keys.rs b/src/tui/keys.rs index 217cd64..a434e8e 100644 --- a/src/tui/keys.rs +++ b/src/tui/keys.rs @@ -6,34 +6,36 @@ use super::state::{Mode, State}; /// Event handling pub async fn handle_key(state: &mut State, key_event: KeyEvent) { + if state.confirmation.is_some() { + match key_event.code { + KeyCode::Char('y') => state.proceed_confirmation().await, + KeyCode::Char('n') | KeyCode::Esc => state.cancel_confirmation(), + _ => {} + }; + return; + } match state.mode { - Mode::List => { - if state.confirmation.is_some() { - match key_event.code { - KeyCode::Char('y') => state.proceed_confirmation().await, - KeyCode::Char('n') | KeyCode::Esc => state.cancel_confirmation(), - _ => {} - } - } else { - match key_event.code { - KeyCode::Char('d') => state.confirm_deletion(), - KeyCode::Char('e') => state.mode = Mode::Edit, - KeyCode::Home => state.selected_place.select_first(), - KeyCode::End => state.selected_place.select_last(), - KeyCode::PageUp => state.prev_page(), - KeyCode::PageDown => state.next_page(), - KeyCode::Up | KeyCode::Char('k') => state.selected_place.select_previous(), - KeyCode::Down | KeyCode::Char('j') => state.selected_place.select_next(), - KeyCode::Esc | KeyCode::Char('q') => state.quit = true, - _ => {} - } + Mode::List => match key_event.code { + KeyCode::Char('d') => state.confirm_deletion(), + KeyCode::Char('e') => { + state.set_edit_mode(); } - } + KeyCode::Home => state.selected_place.select_first(), + KeyCode::End => state.selected_place.select_last(), + KeyCode::PageUp => state.prev_page(), + KeyCode::PageDown => state.next_page(), + KeyCode::Up | KeyCode::Char('k') => state.selected_place.select_previous(), + KeyCode::Down | KeyCode::Char('j') => state.selected_place.select_next(), + KeyCode::Esc | KeyCode::Char('q') => state.quit = true, + _ => {} + }, Mode::Edit => match (key_event.modifiers, key_event.code) { - (KeyModifiers::NONE, KeyCode::Esc) => state.mode = Mode::List, - (KeyModifiers::NONE, KeyCode::Tab) => {} - (KeyModifiers::SHIFT, KeyCode::Tab) => {} - _ => {} + (_, KeyCode::Esc) => state.set_list_mode(), + (_, KeyCode::Tab) => state.edit_next(), + + (_, KeyCode::BackTab) => state.edit_prev(), + (KeyModifiers::CONTROL, KeyCode::Char('s')) => state.start_save(), + _ => state.edit_input(key_event), }, } } diff --git a/src/tui/mod.rs b/src/tui/mod.rs index 3ba35a8..a2b4173 100644 --- a/src/tui/mod.rs +++ b/src/tui/mod.rs @@ -9,6 +9,7 @@ use anyhow::Result; use state::State; use terminal::Event; +use ui::UI; use crate::places::db_repository::DbPlacesRepository; @@ -16,13 +17,16 @@ use crate::places::db_repository::DbPlacesRepository; pub async fn tui(places_repository: DbPlacesRepository) -> Result<()> { let (_, terminal_height) = crossterm::terminal::size()?; let mut state = State::new(places_repository, terminal_height); + let mut ui = UI::new(&state); let mut tui = terminal::Tui::new()?; let result = loop { match tui.next().await? { Event::Key(key_event) => keys::handle_key(&mut state, key_event).await, Event::Render => { - tui.draw(|frame| ui::ui_draw(&mut state, frame))?; + let messages = state.ui_messages.drain(0..).collect::>(); + ui.handle_messages(&mut state, messages); + tui.draw(|frame| ui.draw(&mut state, frame))?; } Event::Tick => { state.fetch_places().await; diff --git a/src/tui/state.rs b/src/tui/state.rs index 96732be..836f986 100644 --- a/src/tui/state.rs +++ b/src/tui/state.rs @@ -1,10 +1,13 @@ //! TUI state +use ratatui::crossterm::event::KeyEvent; use ratatui::widgets::TableState; -use crate::places::{ - db_repository::DbPlacesRepository, models::Place, repository::PlacesRepository, -}; +use crate::places::db_repository::DbPlacesRepository; +use crate::places::models::Place; +use crate::places::repository::PlacesRepository; + +use super::ui::Message; pub struct State { pub height: u16, @@ -15,9 +18,11 @@ pub struct State { places_status: DataStatus, pub confirmation: Option, pub selected_place: TableState, + pub ui_messages: Vec, pub quit: bool, } +#[derive(Copy, Clone)] pub enum Mode { List, Edit, @@ -30,8 +35,7 @@ enum DataStatus { pub enum ConfirmationStatus { Deletion(i64), - #[expect(dead_code)] - Save(i64), + Save(Place), } impl State { @@ -45,18 +49,59 @@ impl State { places: vec![], selected_place: TableState::default(), confirmation: None, + ui_messages: Vec::new(), quit: false, } } + pub fn set_list_mode(&mut self) { + self.mode = Mode::List; + self.push_mode_change(); + } + + pub fn set_edit_mode(&mut self) { + self.mode = Mode::Edit; + self.push_mode_change(); + + let Some(selection) = self.selected_place.selected() else { + return; + }; + let Some(place) = self.places.get(selection) else { + return; + }; + self.ui_messages.push(Message::EditPlace(place.clone())) + } + + fn push_mode_change(&mut self) { + self.ui_messages.push(Message::UpdateAppMode(self.mode)); + } + pub fn next_page(&mut self) { self.page += 1; self.places_status = DataStatus::Old; + self.push_page_change(); } pub fn prev_page(&mut self) { self.page = self.page.saturating_sub(1); self.places_status = DataStatus::Old; + self.push_page_change(); + } + + fn push_page_change(&mut self) { + self.ui_messages.push(Message::UpdatePage(self.page)); + } + + pub fn edit_next(&mut self) { + self.ui_messages.push(Message::EditNext); + } + + pub fn edit_prev(&mut self) { + self.ui_messages.push(Message::EditPrev); + } + + pub fn edit_input(&mut self, key_event: KeyEvent) { + self.ui_messages.push(Message::Input(key_event)); } pub async fn fetch_places(&mut self) { @@ -81,6 +126,8 @@ impl State { } } self.places_status = DataStatus::Fresh; + self.ui_messages + .push(Message::UpdatePlaces(self.places.clone())) } pub fn confirm_deletion(&mut self) { @@ -93,6 +140,20 @@ impl State { } } + pub fn start_save(&mut self) { + if let Some(Some(id)) = self + .selected_place + .selected() + .map(|index| self.places.get(index).map(|p| p.id)) + { + self.ui_messages.push(Message::SavePlace(id)); + } + } + + pub fn confirm_save(&mut self, place: Place) { + self.confirmation = Some(ConfirmationStatus::Save(place)); + } + pub fn cancel_confirmation(&mut self) { self.confirmation = None; } @@ -108,7 +169,13 @@ impl State { tracing::error!("{err}"); } } - ConfirmationStatus::Save(_) => todo!(), + ConfirmationStatus::Save(place) => { + if let Err(err) = self.places_repository.update_place(place.clone()).await { + tracing::error!("{err}"); + } + self.mode = Mode::List; + self.push_mode_change(); + } } self.confirmation = None; diff --git a/src/tui/ui.rs b/src/tui/ui.rs index 33a0782..ef41bd8 100644 --- a/src/tui/ui.rs +++ b/src/tui/ui.rs @@ -2,59 +2,347 @@ use itertools::Itertools; use ratatui::Frame; +use ratatui::crossterm::event::{KeyCode, KeyEvent}; use ratatui::layout::{Constraint, Direction, Flex, Layout, Rect}; -use ratatui::style::{Color, Style, Stylize}; +use ratatui::style::{Color, Modifier, Style, Stylize}; use ratatui::text::{Line, Span, Text, ToSpan}; -use ratatui::widgets::{Block, Clear, Padding, Paragraph, Row, Table}; +use ratatui::widgets::{Block, Borders, Clear, Padding, Paragraph, Row, Table}; +use tui_textarea::TextArea; + +use crate::places::models::Place; use super::state::{ConfirmationStatus, Mode, State}; -/// UI drawing -pub fn ui_draw(state: &mut State, f: &mut Frame<'_>) { - let main_split = Layout::default() - .direction(Direction::Vertical) - .constraints([ - Constraint::Length(1), - Constraint::Fill(1), - Constraint::Length(1), - ]) - .split(f.area()); +pub enum Message { + UpdateAppMode(Mode), + UpdatePage(u32), + UpdatePlaces(Vec), + EditPlace(Place), + EditNext, + EditPrev, + SavePlace(i64), + Input(KeyEvent), +} - header_draw(state, f, main_split[0]); - main_draw(state, f, main_split[1]); - footer_draw(state, f, main_split[2]); +pub struct UI { + header: Header, + main: Main, + footer: Footer, } -fn header_draw(state: &mut State, f: &mut Frame<'_>, area: Rect) { - let split = Layout::default() - .direction(Direction::Horizontal) - .constraints([ - Constraint::Length(9), - Constraint::Fill(1), - Constraint::Length(6), +impl UI { + pub fn new(state: &State) -> Self { + Self { + header: Header::new(state), + main: Main::new(state), + footer: Footer::new(state), + } + } + + pub fn handle_messages>( + &mut self, + state: &mut State, + messages: M, + ) { + for m in messages { + match m { + Message::UpdateAppMode(mode) => { + self.header.update_app_mode(mode); + self.footer.update_keybindings(mode); + } + Message::UpdatePage(page) => self.header.update_page(page), + Message::UpdatePlaces(places) => self.main.update_places_table(places), + Message::EditPlace(place) => self.main.set_edit_textareas(place), + Message::EditNext => self.main.next_textarea(), + Message::EditPrev => self.main.prev_textarea(), + Message::SavePlace(id) => self.main.save_place(state, id), + Message::Input(key_event) => self.main.pass_input(key_event), + } + } + } + + /// UI drawing + pub fn draw(&mut self, state: &mut State, f: &mut Frame<'_>) { + let main_split = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Length(1), + Constraint::Fill(1), + Constraint::Length(1), + ]) + .split(f.area()); + + self.header.draw(f, main_split[0]); + self.main.draw(state, f, main_split[1]); + self.footer.draw(f, main_split[2]); + } +} + +struct Header { + app_name: Span<'static>, + app_mode: Span<'static>, + page: Line<'static>, +} + +impl Header { + fn new(state: &State) -> Self { + let app_name = Span::styled( + " huellas ", + Style::new().bg(Color::Gray).fg(Color::Black).bold(), + ); + let app_mode = Self::get_app_mode(state.mode); + let page = Self::get_page(state.page); + Self { + app_name, + app_mode, + page, + } + } + + fn update_app_mode(&mut self, new_mode: Mode) { + self.app_mode = Self::get_app_mode(new_mode) + } + + fn get_app_mode(mode: Mode) -> Span<'static> { + match mode { + Mode::List => " LIST ".to_span().black().on_light_green().bold(), + Mode::Edit => " EDIT ".to_span().black().on_light_blue().bold(), + } + } + + fn update_page(&mut self, new_page: u32) { + self.page = Self::get_page(new_page) + } + + fn get_page(page: u32) -> Line<'static> { + Line::from_iter([ + "Page: ".to_span(), + Span::raw(page.to_string()), + " ".to_span(), ]) - .split(area); - let app_name = Span::styled( - " huellas ", - Style::new().bg(Color::Gray).fg(Color::Black).bold(), - ); - let page = - Line::from_iter(["Page: ".to_span(), state.page.to_span(), " ".to_span()]).right_aligned(); - let app_mode = match state.mode { - Mode::List => Span::styled(" LIST ", Style::new().black().on_light_green().bold()), - Mode::Edit => Span::styled(" EDIT ", Style::new().black().on_light_blue().bold()), - }; - f.render_widget(app_name, split[0]); - f.render_widget(page, split[1]); - f.render_widget(app_mode, split[2]); + .right_aligned() + } + + fn draw(&self, f: &mut Frame<'_>, area: Rect) { + let split = Layout::default() + .direction(Direction::Horizontal) + .constraints([ + Constraint::Length(9), + Constraint::Fill(1), + Constraint::Length(6), + ]) + .split(area); + f.render_widget(&self.app_name, split[0]); + f.render_widget(&self.page, split[1]); + f.render_widget(&self.app_mode, split[2]); + } } -fn main_draw(state: &mut State, f: &mut Frame<'_>, area: Rect) { - match state.mode { - Mode::List => list_draw(state, f, area), - Mode::Edit => edit_draw(state, f, area), +struct Main { + places_table: Table<'static>, + edit_textareas: Vec>, + selected_textarea: usize, +} + +impl Main { + fn new(state: &State) -> Self { + let places_table = Self::get_places_table(state.places.clone()); + let edit_textareas = Vec::new(); + let selected_textarea = 0; + Self { + places_table, + edit_textareas, + selected_textarea, + } + } + + fn update_places_table(&mut self, new_places: Vec) { + self.places_table = Self::get_places_table(new_places); + } + + fn get_places_table(places: Vec) -> Table<'static> { + let places = places.into_iter().map(|p| { + Row::new([ + p.id.to_string(), + p.name, + p.latitude.to_string(), + p.longitude.to_string(), + p.icon, + p.address, + p.open_hours, + p.description, + url(p.url), + ]) + }); + + let widths = [ + Constraint::Length(3), + Constraint::Fill(1), + Constraint::Length(7), + Constraint::Length(7), + Constraint::Length(8), + Constraint::Fill(1), + Constraint::Fill(1), + Constraint::Fill(1), + Constraint::Fill(1), + ]; + + Table::new(places, widths) + .header( + Row::new([ + "Id", + "Name", + "Lat", + "Long", + "Icon", + "Address", + "Open Hours", + "Description", + "URL", + ]) + .style(Style::new().white().on_dark_gray().bold()), + ) + .row_highlight_style(Style::new().reversed()) + } + + fn set_edit_textareas(&mut self, place: Place) { + let mut name = TextArea::new(vec![place.name]); + name.set_block(Block::default().title("Name")); + let mut latitude = TextArea::new(vec![place.latitude.to_string()]); + latitude.set_block(Block::default().title("Latitude")); + let mut longitude = TextArea::new(vec![place.longitude.to_string()]); + longitude.set_block(Block::default().title("Longitude")); + let mut icon = TextArea::new(vec![place.icon]); + icon.set_block(Block::default().title("Icon")); + let mut address = TextArea::new(vec![place.address]); + address.set_block(Block::default().title("Address")); + let mut url = TextArea::new(vec![place.url.unwrap_or_default()]); + url.set_block(Block::default().title("URL")); + let mut open_hours = TextArea::new(vec![place.open_hours]); + open_hours.set_block(Block::default().title("Open Hours")); + let mut description = TextArea::new(vec![place.description]); + description.set_block(Block::default().title("Description")); + + self.edit_textareas = vec![ + name, + latitude, + longitude, + icon, + address, + url, + open_hours, + description, + ]; + + for textarea in &mut self.edit_textareas { + inactive_textarea(textarea); + } + + active_textarea(&mut self.edit_textareas[0]); + self.selected_textarea = 0; + } + + fn next_textarea(&mut self) { + let n = self.edit_textareas.len(); + if n != 0 { + if let Some(prev_textarea) = self.edit_textareas.get_mut(self.selected_textarea) { + inactive_textarea(prev_textarea); + } + self.selected_textarea = (self.selected_textarea + 1) % n; + if let Some(next_textarea) = self.edit_textareas.get_mut(self.selected_textarea) { + active_textarea(next_textarea); + } + } + } + + fn prev_textarea(&mut self) { + let n = self.edit_textareas.len(); + if n != 0 { + if let Some(prev_textarea) = self.edit_textareas.get_mut(self.selected_textarea) { + inactive_textarea(prev_textarea); + } + self.selected_textarea = (self.selected_textarea + n - 1) % n; + if let Some(next_textarea) = self.edit_textareas.get_mut(self.selected_textarea) { + active_textarea(next_textarea); + } + } + } + + fn save_place(&self, state: &mut State, id: i64) { + let name = self.edit_textareas[0].lines().concat(); + let Ok(latitude) = self.edit_textareas[1].lines().concat().parse::() else { + return; + }; + let Ok(longitude) = self.edit_textareas[2].lines().concat().parse::() else { + return; + }; + let icon = self.edit_textareas[3].lines().concat(); + let address = self.edit_textareas[4].lines().concat(); + let url = self.edit_textareas[5].lines().concat(); + let url = if url.is_empty() { None } else { Some(url) }; + let open_hours = self.edit_textareas[6].lines().concat(); + let description = self.edit_textareas[7].lines().concat(); + let place = Place { + id, + name, + address, + open_hours, + icon, + description, + longitude, + latitude, + url, + }; + state.confirm_save(place); + } + + fn pass_input(&mut self, key_event: KeyEvent) { + let Some(active_textarea) = self.edit_textareas.get_mut(self.selected_textarea) else { + return; + }; + match key_event.code { + KeyCode::Enter => { + // Only allow line breaking on open hours and description fields + if self.selected_textarea == 6 || self.selected_textarea == 7 { + active_textarea.input(key_event); + } + } + _ => { + active_textarea.input(key_event); + } + } + } + + fn draw(&self, state: &mut State, f: &mut Frame<'_>, area: Rect) { + match state.mode { + Mode::List => self.list_draw(state, f, area), + Mode::Edit => self.edit_draw(state, f, area), + } + confirmation_dialog_draw(state, f, area); + } + + fn list_draw(&self, state: &mut State, f: &mut Frame<'_>, area: Rect) { + f.render_stateful_widget(&self.places_table, area, &mut state.selected_place); + } + + fn edit_draw(&self, _state: &mut State, f: &mut Frame<'_>, area: Rect) { + let areas: [_; 8] = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Length(3), + Constraint::Length(3), + Constraint::Length(3), + Constraint::Length(3), + Constraint::Length(3), + Constraint::Length(3), + Constraint::Min(5), + Constraint::Min(5), + ]) + .areas(area); + for (textarea, area) in self.edit_textareas.iter().zip(areas.into_iter()) { + f.render_widget(textarea, area); + } } - confirmation_dialog_draw(state, f, area); } fn confirmation_dialog_draw(state: &mut State, f: &mut Frame<'_>, area: Rect) { @@ -65,7 +353,7 @@ fn confirmation_dialog_draw(state: &mut State, f: &mut Frame<'_>, area: Rect) { let dialog_area = center(area, Constraint::Percentage(80), Constraint::Percentage(60)); let (action, id) = match confirmation { ConfirmationStatus::Deletion(id) => ("delete", id), - ConfirmationStatus::Save(id) => ("save", id), + ConfirmationStatus::Save(place) => ("save", &place.id), }; let confirmation_dialog = Paragraph::new(Text::from_iter([ Line::from_iter([ @@ -84,100 +372,60 @@ fn confirmation_dialog_draw(state: &mut State, f: &mut Frame<'_>, area: Rect) { f.render_widget(confirmation_dialog, dialog_area); } -fn list_draw(state: &mut State, f: &mut Frame<'_>, area: Rect) { - let places = state.places.iter().map(|p| { - Row::new([ - p.id.to_string(), - p.name.clone(), - p.latitude.to_string(), - p.longitude.to_string(), - p.icon.clone(), - p.address.clone(), - p.open_hours.clone(), - p.description.clone(), - url(p.url.as_ref().map(|u| u.as_ref())), - ]) - }); - - let widths = [ - Constraint::Length(3), - Constraint::Fill(1), - Constraint::Length(7), - Constraint::Length(7), - Constraint::Length(8), - Constraint::Fill(1), - Constraint::Fill(1), - Constraint::Fill(1), - Constraint::Fill(1), - ]; - - let places_table = Table::new(places, widths) - .header( - Row::new([ - "Id", - "Name", - "Lat", - "Long", - "Icon", - "Address", - "Open Hours", - "Description", - "URL", - ]) - .style(Style::new().white().on_dark_gray().bold()), - ) - .row_highlight_style(Style::new().reversed()); - - f.render_stateful_widget(places_table, area, &mut state.selected_place); +struct Footer { + keybindings: Paragraph<'static>, } -fn edit_draw(_state: &mut State, f: &mut Frame<'_>, area: Rect) { - let dialog_area = center(area, Constraint::Percentage(80), Constraint::Percentage(60)); - let confirmation_dialog = - Paragraph::new(Text::from_iter([ - Line::from("Not implemented yet :(").italic() - ])) - .centered() - .block(Block::bordered().padding(Padding::uniform(1))); +impl Footer { + fn new(state: &State) -> Self { + let keybindings = Self::get_keybindings(state.mode); + Self { keybindings } + } - f.render_widget(Clear, dialog_area); - f.render_widget(confirmation_dialog, dialog_area); -} + fn update_keybindings(&mut self, new_mode: Mode) { + self.keybindings = Self::get_keybindings(new_mode) + } -#[expect(unstable_name_collisions)] -fn footer_draw(state: &mut State, f: &mut Frame<'_>, area: Rect) { - let separator = Span::styled(" ", Style::new().black().on_black()); - let keybindings = match state.mode { - Mode::List => { - let keybindings = [ - ("j", "Next"), - ("k", "Previous"), - ("Home", "First"), - ("End", "Last"), - ("PgUp", "Prev Page"), - ("PgDown", "Next Page"), - ("e", "Edit"), - ("d", "Delete"), - ] - .map(|(key, action)| keybinding(key, action).to_vec()) - .into_iter() - .intersperse(vec![separator]) - .flatten(); - Paragraph::new(Line::from_iter(keybindings)) - } - Mode::Edit => { - let keybindings = [("Esc", "Close w/o saving")] + #[expect(unstable_name_collisions)] + fn get_keybindings(mode: Mode) -> Paragraph<'static> { + let separator = Span::styled(" ", Style::new().black().on_black()); + match mode { + Mode::List => { + let keybindings = [ + ("j/k", "Next/Previous"), + ("Home/End", "First/Last"), + ("PgUp", "Prev Page"), + ("PgDown", "Next Page"), + ("e", "Edit"), + ("d", "Delete"), + ] .map(|(key, action)| keybinding(key, action).to_vec()) .into_iter() .intersperse(vec![separator]) .flatten(); - Paragraph::new(Line::from_iter(keybindings)) + Paragraph::new(Line::from_iter(keybindings)) + } + Mode::Edit => { + let keybindings = [ + ("Esc", "Close w/o saving"), + ("Tab/S-Tab", "Next/prev field"), + ("C-s", "Save"), + ] + .map(|(key, action)| keybinding(key, action).to_vec()) + .into_iter() + .intersperse(vec![separator]) + .flatten(); + Paragraph::new(Line::from_iter(keybindings)) + } } - }; - f.render_widget(keybindings, area); + } + + fn draw(&self, f: &mut Frame<'_>, area: Rect) { + f.render_widget(&self.keybindings, area); + } } -fn url(url: Option<&str>) -> String { +fn url(url: Option) -> String { match url { Some(url) => { if url.starts_with("https://instagram.com/") { @@ -187,7 +435,7 @@ fn url(url: Option<&str>) -> String { .trim_end_matches("/") ) } else { - url.to_owned() + url } } None => String::new(), @@ -214,3 +462,26 @@ fn center(area: Rect, horizontal: Constraint, vertical: Constraint) -> Rect { let [area] = Layout::vertical([vertical]).flex(Flex::Center).areas(area); area } + +fn inactive_textarea(textarea: &mut TextArea<'_>) { + textarea.set_cursor_line_style(Style::default()); + textarea.set_cursor_style(Style::default()); + if let Some(block) = textarea.block().map(|block| { + block + .clone() + .borders(Borders::ALL) + .style(Style::default().fg(Color::Gray)) + }) { + textarea.set_block(block); + }; +} + +fn active_textarea(textarea: &mut TextArea<'_>) { + textarea.set_cursor_style(Style::default().add_modifier(Modifier::REVERSED)); + if let Some(block) = textarea + .block() + .map(|block| block.clone().style(Style::default().bold())) + { + textarea.set_block(block); + }; +}