diff --git a/Cargo.lock b/Cargo.lock index b472894..38629b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -58,19 +58,13 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] -[[package]] -name = "allocator-api2" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" - [[package]] name = "android-activity" version = "0.4.3" @@ -95,20 +89,11 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" -[[package]] -name = "ansi_term" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" -dependencies = [ - "winapi", -] - [[package]] name = "anstream" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" dependencies = [ "anstyle", "anstyle-parse", @@ -126,30 +111,30 @@ checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anstyle-parse" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -192,9 +177,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.21.4" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" [[package]] name = "bitflags" @@ -204,9 +189,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" dependencies = [ "serde", ] @@ -259,7 +244,7 @@ checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.43", ] [[package]] @@ -288,12 +273,6 @@ dependencies = [ "vec_map", ] -[[package]] -name = "cassowary" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" - [[package]] name = "cc" version = "1.0.83" @@ -333,9 +312,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.6" +version = "4.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956" +checksum = "dcfab8ba68f3668e89f6ff60f5b205cea56aa7b769451a59f34b8682f51c056d" dependencies = [ "clap_builder", "clap_derive", @@ -343,9 +322,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.6" +version = "4.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45" +checksum = "fb7fb5e4e979aec3be7791562fcba452f94ad85e954da024396433e0e25a79e9" dependencies = [ "anstream", "anstyle", @@ -355,21 +334,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.4.2" +version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.43", ] [[package]] name = "clap_lex" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] name = "clipboard-win" @@ -412,33 +391,6 @@ dependencies = [ "objc", ] -[[package]] -name = "color-eyre" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" -dependencies = [ - "backtrace", - "color-spantrace", - "eyre", - "indenter", - "once_cell", - "owo-colors", - "tracing-error", -] - -[[package]] -name = "color-spantrace" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" -dependencies = [ - "once_cell", - "owo-colors", - "tracing-core", - "tracing-error", -] - [[package]] name = "color_quant" version = "1.1.0" @@ -461,18 +413,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "comfy-table" -version = "7.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ab77dbd8adecaf3f0db40581631b995f312a8a5ae3aa9993188bb8f23d83a5b" -dependencies = [ - "crossterm 0.26.1", - "strum 0.24.1", - "strum_macros 0.24.3", - "unicode-width", -] - [[package]] name = "core-foundation" version = "0.9.4" @@ -524,9 +464,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +checksum = "fca89a0e215bab21874660c67903c5f143333cab1da83d041c7ded6053774751" dependencies = [ "cfg-if", "crossbeam-epoch", @@ -535,69 +475,24 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.15" +version = "0.9.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +checksum = "0e3681d554572a651dda4186cd47240627c3d0114d45a95f6ad27f2f22e7548d" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", - "memoffset 0.9.0", - "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.16" +version = "0.8.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c" dependencies = [ "cfg-if", ] -[[package]] -name = "crossterm" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13" -dependencies = [ - "bitflags 1.3.2", - "crossterm_winapi", - "libc", - "mio", - "parking_lot", - "signal-hook", - "signal-hook-mio", - "winapi", -] - -[[package]] -name = "crossterm" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" -dependencies = [ - "bitflags 2.4.0", - "crossterm_winapi", - "futures-core", - "libc", - "mio", - "parking_lot", - "serde", - "signal-hook", - "signal-hook-mio", - "winapi", -] - -[[package]] -name = "crossterm_winapi" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" -dependencies = [ - "winapi", -] - [[package]] name = "csv" version = "1.3.0" @@ -619,15 +514,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "directories" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" -dependencies = [ - "dirs-sys", -] - [[package]] name = "directories-next" version = "2.0.0" @@ -638,18 +524,6 @@ dependencies = [ "dirs-sys-next", ] -[[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", -] - [[package]] name = "dirs-sys-next" version = "0.1.2" @@ -686,37 +560,24 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" name = "dust-lang" version = "0.4.0" dependencies = [ - "ansi_term", "cc", "clap", - "color-eyre", - "comfy-table", - "crossterm 0.27.0", "csv", - "directories", "eframe", "egui", "env_logger", - "futures", - "git2", - "lazy_static", + "libc", "log", "rand", - "ratatui", "rayon", "reqwest", "rustyline", "serde", "serde_json", - "signal-hook", - "tokio", - "tokio-util", + "strip-ansi-escapes", "toml", "tracing", - "tracing-error", - "tracing-subscriber", "tree-sitter", - "tui-textarea", "wasm-bindgen-futures", ] @@ -849,7 +710,7 @@ checksum = "c2ad8cef1d801a4686bfd8919f0b30eac4c8e48968c437a6405ded4fb5272d2b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.43", ] [[package]] @@ -890,23 +751,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.4" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add4f07d43996f76ef320709726a556a9d4f965d9410d8d0271132d2f8293480" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", "libc", + "windows-sys 0.52.0", ] [[package]] @@ -919,16 +769,6 @@ dependencies = [ "str-buf", ] -[[package]] -name = "eyre" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6267a1fa6f59179ea4afc8e50fd8612a3cc60bc858f786ff877a4a8cb042799" -dependencies = [ - "indenter", - "once_cell", -] - [[package]] name = "fastrand" version = "2.0.1" @@ -988,28 +828,13 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] -[[package]] -name = "futures" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - [[package]] name = "futures-channel" version = "0.3.30" @@ -1017,7 +842,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", - "futures-sink", ] [[package]] @@ -1026,34 +850,12 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" -[[package]] -name = "futures-executor" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - [[package]] name = "futures-io" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" -[[package]] -name = "futures-macro" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.37", -] - [[package]] name = "futures-sink" version = "0.3.30" @@ -1072,11 +874,8 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ - "futures-channel", "futures-core", "futures-io", - "futures-macro", - "futures-sink", "futures-task", "memchr", "pin-project-lite", @@ -1096,9 +895,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if", "libc", @@ -1107,24 +906,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" - -[[package]] -name = "git2" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf97ba92db08df386e10c8ede66a2a0369bd277090afd8710e19e38de9ec0cd" -dependencies = [ - "bitflags 2.4.0", - "libc", - "libgit2-sys", - "log", - "openssl-probe", - "openssl-sys", - "url", -] +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "gl_generator" @@ -1215,9 +999,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.21" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" +checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" dependencies = [ "bytes", "fnv", @@ -1225,7 +1009,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 1.9.3", + "indexmap", "slab", "tokio", "tokio-util", @@ -1234,19 +1018,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.12.3" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hashbrown" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" -dependencies = [ - "ahash", - "allocator-api2", -] +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] name = "heck" @@ -1262,18 +1036,18 @@ checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] name = "home" -version = "0.5.5" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "http" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" dependencies = [ "bytes", "fnv", @@ -1282,9 +1056,9 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", "http", @@ -1311,9 +1085,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.27" +version = "0.14.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" dependencies = [ "bytes", "futures-channel", @@ -1326,7 +1100,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.9", + "socket2", "tokio", "tower-service", "tracing", @@ -1348,9 +1122,9 @@ dependencies = [ [[package]] name = "idna" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -1370,38 +1144,16 @@ dependencies = [ "png", ] -[[package]] -name = "indenter" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" - [[package]] name = "indexmap" -version = "1.9.3" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - -[[package]] -name = "indexmap" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", - "hashbrown 0.14.1", + "hashbrown", ] -[[package]] -name = "indoc" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" - [[package]] name = "instant" version = "0.1.12" @@ -1416,9 +1168,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "is-terminal" @@ -1431,20 +1183,11 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "itertools" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" -dependencies = [ - "either", -] - [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "jni" @@ -1470,18 +1213,18 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" dependencies = [ "wasm-bindgen", ] @@ -1504,20 +1247,6 @@ version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" -[[package]] -name = "libgit2-sys" -version = "0.16.1+1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2a2bb3680b094add03bb3732ec520ece34da31a8cd2d633d1389d0f0fb60d0c" -dependencies = [ - "cc", - "libc", - "libssh2-sys", - "libz-sys", - "openssl-sys", - "pkg-config", -] - [[package]] name = "libloading" version = "0.7.4" @@ -1544,7 +1273,7 @@ version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "libc", "redox_syscall 0.4.1", ] @@ -1555,48 +1284,22 @@ version = "0.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "libc", "redox_syscall 0.4.1", ] -[[package]] -name = "libssh2-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dc8a030b787e2119a731f1951d6a773e2280c660f8ec4b0f5e1505a386e71ee" -dependencies = [ - "cc", - "libc", - "libz-sys", - "openssl-sys", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "libz-sys" -version = "1.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "linux-raw-sys" -version = "0.4.8" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3852614a3bd9ca9804678ba6be5e3b8ce76dfc902cae004e3e0c44051b6e88db" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "lock_api" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", @@ -1608,15 +1311,6 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" -[[package]] -name = "lru" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2994eeba8ed550fd9b47a0b38f0242bc3344e496483c6180b69139cc2fa5d1d7" -dependencies = [ - "hashbrown 0.14.1", -] - [[package]] name = "malloc_buf" version = "0.0.6" @@ -1626,20 +1320,11 @@ dependencies = [ "libc", ] -[[package]] -name = "matchers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -dependencies = [ - "regex-automata 0.1.10", -] - [[package]] name = "memchr" -version = "2.6.4" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "memmap2" @@ -1668,15 +1353,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "memoffset" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] - [[package]] name = "mime" version = "0.3.17" @@ -1804,16 +1480,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" -[[package]] -name = "nu-ansi-term" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" -dependencies = [ - "overload", - "winapi", -] - [[package]] name = "num-integer" version = "0.1.45" @@ -1893,7 +1559,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.43", ] [[package]] @@ -1953,26 +1619,26 @@ dependencies = [ [[package]] name = "object" -version = "0.32.1" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "openssl" -version = "0.10.57" +version = "0.10.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" +checksum = "8cde4d2d9200ad5909f8dac647e29482e07c3a35de8a13fce7c9c7747ad9f671" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "cfg-if", "foreign-types", "libc", @@ -1989,7 +1655,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.43", ] [[package]] @@ -2000,9 +1666,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.93" +version = "0.9.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" +checksum = "c1665caf8ab2dc9aef43d1c0023bd904633a6a05cb30b0ad59bec2ae986e57a7" dependencies = [ "cc", "libc", @@ -2010,12 +1676,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - [[package]] name = "orbclient" version = "0.3.47" @@ -2025,12 +1685,6 @@ dependencies = [ "libredox 0.0.2", ] -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - [[package]] name = "owned_ttf_parser" version = "0.20.0" @@ -2040,12 +1694,6 @@ dependencies = [ "ttf-parser", ] -[[package]] -name = "owo-colors" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" - [[package]] name = "parking_lot" version = "0.12.1" @@ -2058,28 +1706,22 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.3.5", + "redox_syscall 0.4.1", "smallvec", "windows-targets 0.48.5", ] -[[package]] -name = "paste" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" - [[package]] name = "percent-encoding" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" @@ -2095,9 +1737,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" [[package]] name = "png" @@ -2130,9 +1772,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.67" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8" dependencies = [ "unicode-ident", ] @@ -2186,25 +1828,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "ratatui" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5659e52e4ba6e07b2dad9f1158f578ef84a73762625ddb51536019f34d180eb" -dependencies = [ - "bitflags 2.4.0", - "cassowary", - "crossterm 0.27.0", - "indoc", - "itertools", - "lru", - "paste", - "stability", - "strum 0.25.0", - "unicode-segmentation", - "unicode-width", -] - [[package]] name = "raw-window-handle" version = "0.5.2" @@ -2262,53 +1885,38 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.6" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebee201405406dbf528b8b672104ae6d6d63e6d118cb10e4d51abbc7b58044ff" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.3.9", - "regex-syntax 0.7.5", + "regex-automata", + "regex-syntax", ] [[package]] name = "regex-automata" -version = "0.1.10" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", -] - -[[package]] -name = "regex-automata" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.5", + "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.6.29" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - -[[package]] -name = "regex-syntax" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "reqwest" -version = "0.11.22" +version = "0.11.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" +checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" dependencies = [ "base64", "bytes", @@ -2349,7 +1957,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ "base64", - "bitflags 2.4.0", + "bitflags 2.4.1", "serde", "serde_derive", ] @@ -2362,30 +1970,24 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.38.17" +version = "0.38.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f25469e9ae0f3d0047ca8b93fc56843f38e6774f0914a107ff8b41be8be8e0b7" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "errno", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] -[[package]] -name = "rustversion" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" - [[package]] name = "rustyline" version = "12.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "994eca4bca05c87e86e15d90fc7a91d1be64b4482b38cb2d27474568fe7c9db9" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "cfg-if", "clipboard-win", "fd-lock", @@ -2411,14 +2013,14 @@ checksum = "5a32af5427251d2e4be14fc151eabe18abb4a7aad5efee7044da9f096c906a43" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.43", ] [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] name = "same-file" @@ -2431,11 +2033,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -2475,29 +2077,29 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.188" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.43", ] [[package]] name = "serde_json" -version = "1.0.107" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", @@ -2506,9 +2108,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" dependencies = [ "serde", ] @@ -2525,45 +2127,6 @@ dependencies = [ "serde", ] -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "signal-hook" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[package]] -name = "signal-hook-mio" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" -dependencies = [ - "libc", - "mio", - "signal-hook", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" -dependencies = [ - "libc", -] - [[package]] name = "simd-adler32" version = "0.3.7" @@ -2590,9 +2153,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.1" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" [[package]] name = "smithay-client-toolkit" @@ -2623,16 +2186,6 @@ dependencies = [ "wayland-client", ] -[[package]] -name = "socket2" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "socket2" version = "0.5.5" @@ -2643,16 +2196,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "stability" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebd1b177894da2a2d9120208c3386066af06a488255caabc5de8ddca22dbc3ce" -dependencies = [ - "quote", - "syn 1.0.109", -] - [[package]] name = "static_assertions" version = "1.1.0" @@ -2665,53 +2208,21 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" +[[package]] +name = "strip-ansi-escapes" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ff8ef943b384c414f54aefa961dd2bd853add74ec75e7ac74cf91dba62bcfa" +dependencies = [ + "vte", +] + [[package]] name = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" -[[package]] -name = "strum" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" - -[[package]] -name = "strum" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" -dependencies = [ - "strum_macros 0.25.3", -] - -[[package]] -name = "strum_macros" -version = "0.24.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn 1.0.109", -] - -[[package]] -name = "strum_macros" -version = "0.25.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.37", -] - [[package]] name = "syn" version = "1.0.109" @@ -2725,9 +2236,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.37" +version = "2.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" +checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53" dependencies = [ "proc-macro2", "quote", @@ -2757,15 +2268,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.8.0" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" dependencies = [ "cfg-if", "fastrand", - "redox_syscall 0.3.5", + "redox_syscall 0.4.1", "rustix", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -2794,17 +2305,7 @@ checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", -] - -[[package]] -name = "thread_local" -version = "1.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" -dependencies = [ - "cfg-if", - "once_cell", + "syn 2.0.43", ] [[package]] @@ -2834,23 +2335,10 @@ dependencies = [ "mio", "num_cpus", "pin-project-lite", - "signal-hook-registry", - "socket2 0.5.5", - "tokio-macros", + "socket2", "windows-sys 0.48.0", ] -[[package]] -name = "tokio-macros" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.37", -] - [[package]] name = "tokio-native-tls" version = "0.3.1" @@ -2877,21 +2365,21 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.2" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.20.2", + "toml_edit 0.21.0", ] [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" dependencies = [ "serde", ] @@ -2902,18 +2390,18 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.0.2", + "indexmap", "toml_datetime", "winnow", ] [[package]] name = "toml_edit" -version = "0.20.2" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" dependencies = [ - "indexmap 2.0.2", + "indexmap", "serde", "serde_spanned", "toml_datetime", @@ -2945,7 +2433,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.43", ] [[package]] @@ -2955,46 +2443,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", - "valuable", -] - -[[package]] -name = "tracing-error" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" -dependencies = [ - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", ] [[package]] @@ -3009,9 +2457,9 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "ttf-parser" @@ -3019,23 +2467,11 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" -[[package]] -name = "tui-textarea" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e38ced1f941a9cfc923fbf2fe6858443c42cc5220bfd35bdd3648371e7bd8e" -dependencies = [ - "crossterm 0.27.0", - "ratatui", - "regex", - "unicode-width", -] - [[package]] name = "unicode-bidi" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" [[package]] name = "unicode-ident" @@ -3066,9 +2502,9 @@ checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "url" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", "idna", @@ -3081,12 +2517,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" -[[package]] -name = "valuable" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" - [[package]] name = "vcpkg" version = "0.2.15" @@ -3105,6 +2535,26 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "vte" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197" +dependencies = [ + "utf8parse", + "vte_generate_state_changes", +] + +[[package]] +name = "vte_generate_state_changes" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "walkdir" version = "2.4.0" @@ -3132,9 +2582,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -3142,24 +2592,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.43", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.37" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" dependencies = [ "cfg-if", "js-sys", @@ -3169,9 +2619,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3179,22 +2629,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.43", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" [[package]] name = "wayland-client" @@ -3283,9 +2733,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.64" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" dependencies = [ "js-sys", "wasm-bindgen", @@ -3592,9 +3042,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.5.15" +version = "0.5.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +checksum = "97a4882e6b134d6c28953a387571f1acdd3496830d5e36c5e3a1075580ea641c" dependencies = [ "memchr", ] @@ -3671,5 +3121,5 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.43", ] diff --git a/Cargo.toml b/Cargo.toml index 3216c99..e8b8195 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,53 +5,38 @@ version = "0.4.0" repository = "https://git.jeffa.io/jeff/dust.git" edition = "2021" license = "MIT" +authors = ["Jeff Anderson"] [[bin]] name = "dust" path = "src/main.rs" -[[bin]] -name = "tui" - [profile.dev] opt-level = 1 [profile.dev.package."*"] opt-level = 3 [dependencies] -ansi_term = "0.12.1" clap = { version = "4.4.4", features = ["derive"] } -color-eyre = "0.6.2" -comfy-table = "7.0.1" -crossterm = { version = "0.27.0", features = ["serde", "event-stream"] } csv = "1.2.2" -directories = "5.0.1" -futures = "0.3.30" -git2 = "0.18.1" -lazy_static = "1.4.0" +libc = "0.2.148" +log = "0.4.20" rand = "0.8.5" -ratatui = "0.25.0" rayon = "1.8.0" reqwest = { version = "0.11.20", features = ["blocking", "json"] } rustyline = { version = "12.0.0", features = ["derive", "with-file-history"] } serde = { version = "1.0.188", features = ["derive"] } serde_json = "1.0.107" -signal-hook = "0.3.17" -tokio = { version = "1.35.1", features = ["signal-hook-registry", "macros", "signal", "rt", "time", "rt-multi-thread"] } -tokio-util = "0.7.10" +strip-ansi-escapes = "0.2.0" toml = "0.8.1" tracing = "0.1.40" -tracing-error = "0.2.0" -tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } tree-sitter = "0.20.10" -tui-textarea = { version = "0.4.0", features = ["search"] } egui = "0.24.1" eframe = { version = "0.24.1", default-features = false, features = [ "default_fonts", # Embed the default egui fonts. "glow", # Use the glow rendering backend. Alternative: "wgpu". "persistence", # Enable restoring app state when restarting the app. ] } -log = "0.4" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] env_logger = "0.10" diff --git a/assets/config.json5 b/assets/config.json5 new file mode 100644 index 0000000..2157fba --- /dev/null +++ b/assets/config.json5 @@ -0,0 +1,14 @@ +{ + "keybindings": { + "Home": { + "": "Quit", // Quit the application + "": "Quit", // Another way to quit + "": "Quit", // Yet another way to quit + "": "Suspend", // Suspend the application + "": "Up", + "": "Down", + "": "Left", + "": "Right", + }, + } +} diff --git a/src/bin/tui/action.rs b/src/bin/tui/action.rs new file mode 100644 index 0000000..051af6a --- /dev/null +++ b/src/bin/tui/action.rs @@ -0,0 +1,25 @@ +use std::{fmt, string::ToString}; + +use serde::{ + de::{self, Deserializer, Visitor}, + Deserialize, Serialize, +}; +use strum::Display; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Display, Deserialize)] +pub enum Action { + Tick, + Render, + ReadFile, + Resize(u16, u16), + Suspend, + Resume, + Quit, + Refresh, + Error(String), + Help, + Up, + Down, + Left, + Right, +} diff --git a/src/bin/tui/app.rs b/src/bin/tui/app.rs index d614ce1..36c3058 100644 --- a/src/bin/tui/app.rs +++ b/src/bin/tui/app.rs @@ -1,88 +1,152 @@ use std::path::PathBuf; -use crossterm::event::KeyCode; -use dust_lang::Map; -use ratatui::Frame; -use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; +use color_eyre::eyre::Result; +use crossterm::event::KeyEvent; +use ratatui::prelude::Rect; +use serde::{Deserialize, Serialize}; +use tokio::sync::mpsc; -use crate::{interpreter_display::InterpreterDisplay, terminal::Terminal, Action, Elm, Result}; +use crate::{ + action::Action, + components::{fps::FpsCounter, home::Home, Component}, + config::Config, + mode::Mode, + tui, +}; pub struct App { - action_rx: UnboundedReceiver, - action_tx: UnboundedSender, - interpreter_display: InterpreterDisplay, - should_quit: bool, + pub config: Config, + pub components: Vec>, + pub should_quit: bool, + pub should_suspend: bool, + pub mode: Mode, + pub last_tick_key_events: Vec, } impl App { - pub fn new( - action_rx: UnboundedReceiver, - action_tx: UnboundedSender, - path: PathBuf, - ) -> Result { - let interpreter_display = InterpreterDisplay::new(Map::new(), path)?; - - Ok(App { - action_rx, - action_tx, - interpreter_display, + pub fn new(path: PathBuf) -> Result { + let home = Home::new(path)?; + let config = Config::new()?; + let mode = Mode::Home; + Ok(Self { + components: vec![Box::new(home)], should_quit: false, + should_suspend: false, + config, + mode, + last_tick_key_events: Vec::new(), }) } pub async fn run(&mut self) -> Result<()> { - let mut terminal = Terminal::new()?.tick_rate(4.0).frame_rate(30.0); + let (action_tx, mut action_rx) = mpsc::unbounded_channel(); - terminal.enter()?; + let mut tui = tui::Tui::new()?.mouse(true); - loop { - if self.should_quit { - break; - } + tui.enter()?; - if let Some(action) = terminal.next().await { - self.action_tx.send(action)?; - } else { - continue; - }; - - while let Ok(action) = self.action_rx.try_recv() { - if let Action::Render = action { - terminal.draw(|frame| { - self.view(frame); - })?; - } - - let next_action = self.update(action)?; - - if let Some(action) = next_action { - self.action_tx.send(action)?; - } - } + for component in self.components.iter_mut() { + component.register_action_handler(action_tx.clone())?; } - terminal.exit()?; + for component in self.components.iter_mut() { + component.register_config_handler(self.config.clone())?; + } + for component in self.components.iter_mut() { + component.init(tui.size()?)?; + } + + loop { + if let Some(e) = tui.next().await { + match e { + tui::Event::Quit => action_tx.send(Action::Quit)?, + tui::Event::Tick => action_tx.send(Action::Tick)?, + tui::Event::Render => action_tx.send(Action::Render)?, + tui::Event::Resize(x, y) => action_tx.send(Action::Resize(x, y))?, + tui::Event::Key(key) => { + if let Some(keymap) = self.config.keybindings.get(&self.mode) { + if let Some(action) = keymap.get(&vec![key]) { + log::info!("Got action: {action:?}"); + action_tx.send(action.clone())?; + } else { + // If the key was not handled as a single key action, + // then consider it for multi-key combinations. + self.last_tick_key_events.push(key); + + // Check for multi-key combinations + if let Some(action) = keymap.get(&self.last_tick_key_events) { + log::info!("Got action: {action:?}"); + action_tx.send(action.clone())?; + } + } + }; + } + _ => {} + } + for component in self.components.iter_mut() { + if let Some(action) = component.handle_events(Some(e.clone()))? { + action_tx.send(action)?; + } + } + } + + while let Ok(action) = action_rx.try_recv() { + if action != Action::Tick && action != Action::Render { + log::debug!("{action:?}"); + } + match action { + Action::Tick => { + self.last_tick_key_events.drain(..); + } + Action::Quit => self.should_quit = true, + Action::Suspend => self.should_suspend = true, + Action::Resume => self.should_suspend = false, + Action::Resize(w, h) => { + tui.resize(Rect::new(0, 0, w, h))?; + tui.draw(|f| { + for component in self.components.iter_mut() { + let r = component.draw(f, f.size()); + if let Err(e) = r { + action_tx + .send(Action::Error(format!("Failed to draw: {:?}", e))) + .unwrap(); + } + } + })?; + } + Action::Render => { + tui.draw(|f| { + for component in self.components.iter_mut() { + let r = component.draw(f, f.size()); + if let Err(e) = r { + action_tx + .send(Action::Error(format!("Failed to draw: {:?}", e))) + .unwrap(); + } + } + })?; + } + _ => {} + } + for component in self.components.iter_mut() { + if let Some(action) = component.update(action.clone())? { + action_tx.send(action)? + }; + } + } + if self.should_suspend { + tui.suspend()?; + action_tx.send(Action::Resume)?; + tui = tui::Tui::new()?; + // tui.mouse(true); + tui.enter()?; + } else if self.should_quit { + tui.stop()?; + break; + } + } + tui.exit()?; Ok(()) } } - -impl Elm for App { - fn update(&mut self, message: Action) -> Result> { - match message { - Action::Quit => self.should_quit = true, - Action::Key(key_event) => { - if let KeyCode::Esc = key_event.code { - return Ok(Some(Action::Quit)); - } - } - _ => {} - } - - self.interpreter_display.update(message) - } - - fn view(&self, frame: &mut Frame) { - self.interpreter_display.view(frame) - } -} diff --git a/src/bin/tui/cli.rs b/src/bin/tui/cli.rs new file mode 100644 index 0000000..1a54c29 --- /dev/null +++ b/src/bin/tui/cli.rs @@ -0,0 +1,12 @@ +use std::path::PathBuf; + +use clap::Parser; + +use crate::utils::version; + +#[derive(Parser, Debug)] +#[command(author, version = version(), about)] +pub struct Cli { + /// File that will be run and watched for changes. + pub path: PathBuf, +} diff --git a/src/bin/tui/components.rs b/src/bin/tui/components.rs new file mode 100644 index 0000000..2fbb868 --- /dev/null +++ b/src/bin/tui/components.rs @@ -0,0 +1,126 @@ +use color_eyre::eyre::Result; +use crossterm::event::{KeyEvent, MouseEvent}; +use ratatui::layout::Rect; +use tokio::sync::mpsc::UnboundedSender; + +use crate::{ + action::Action, + config::Config, + tui::{Event, Frame}, +}; + +pub mod fps; +pub mod home; +mod list_display; +mod map_display; + +/// `Component` is a trait that represents a visual and interactive element of the user interface. +/// Implementors of this trait can be registered with the main application loop and will be able to receive events, +/// update state, and be rendered on the screen. +pub trait Component { + /// Register an action handler that can send actions for processing if necessary. + /// + /// # Arguments + /// + /// * `tx` - An unbounded sender that can send actions. + /// + /// # Returns + /// + /// * `Result<()>` - An Ok result or an error. + #[allow(unused_variables)] + fn register_action_handler(&mut self, tx: UnboundedSender) -> Result<()> { + Ok(()) + } + /// Register a configuration handler that provides configuration settings if necessary. + /// + /// # Arguments + /// + /// * `config` - Configuration settings. + /// + /// # Returns + /// + /// * `Result<()>` - An Ok result or an error. + #[allow(unused_variables)] + fn register_config_handler(&mut self, config: Config) -> Result<()> { + Ok(()) + } + /// Initialize the component with a specified area if necessary. + /// + /// # Arguments + /// + /// * `area` - Rectangular area to initialize the component within. + /// + /// # Returns + /// + /// * `Result<()>` - An Ok result or an error. + fn init(&mut self, area: Rect) -> Result<()> { + Ok(()) + } + /// Handle incoming events and produce actions if necessary. + /// + /// # Arguments + /// + /// * `event` - An optional event to be processed. + /// + /// # Returns + /// + /// * `Result>` - An action to be processed or none. + fn handle_events(&mut self, event: Option) -> Result> { + let r = match event { + Some(Event::Key(key_event)) => self.handle_key_events(key_event)?, + Some(Event::Mouse(mouse_event)) => self.handle_mouse_events(mouse_event)?, + _ => None, + }; + Ok(r) + } + /// Handle key events and produce actions if necessary. + /// + /// # Arguments + /// + /// * `key` - A key event to be processed. + /// + /// # Returns + /// + /// * `Result>` - An action to be processed or none. + #[allow(unused_variables)] + fn handle_key_events(&mut self, key: KeyEvent) -> Result> { + Ok(None) + } + /// Handle mouse events and produce actions if necessary. + /// + /// # Arguments + /// + /// * `mouse` - A mouse event to be processed. + /// + /// # Returns + /// + /// * `Result>` - An action to be processed or none. + #[allow(unused_variables)] + fn handle_mouse_events(&mut self, mouse: MouseEvent) -> Result> { + Ok(None) + } + /// Update the state of the component based on a received action. (REQUIRED) + /// + /// # Arguments + /// + /// * `action` - An action that may modify the state of the component. + /// + /// # Returns + /// + /// * `Result>` - An action to be processed or none. + #[allow(unused_variables)] + fn update(&mut self, action: Action) -> Result> { + Ok(None) + } + /// Render the component on the screen. (REQUIRED) + /// + /// # Arguments + /// + /// * `f` - A frame used for rendering. + /// * `area` - The area in which the component should be drawn. + /// + /// # Returns + /// + /// * `Result<()>` - An Ok result or an error. + fn draw(&mut self, f: &mut Frame<'_>, area: Rect) -> Result<()>; +} diff --git a/src/bin/tui/components/fps.rs b/src/bin/tui/components/fps.rs new file mode 100644 index 0000000..7c79805 --- /dev/null +++ b/src/bin/tui/components/fps.rs @@ -0,0 +1,90 @@ +use std::time::Instant; + +use color_eyre::eyre::Result; +use ratatui::{prelude::*, widgets::*}; + +use super::Component; +use crate::{action::Action, tui::Frame}; + +#[derive(Debug, Clone, PartialEq)] +pub struct FpsCounter { + app_start_time: Instant, + app_frames: u32, + app_fps: f64, + + render_start_time: Instant, + render_frames: u32, + render_fps: f64, +} + +impl Default for FpsCounter { + fn default() -> Self { + Self::new() + } +} + +impl FpsCounter { + pub fn new() -> Self { + Self { + app_start_time: Instant::now(), + app_frames: 0, + app_fps: 0.0, + render_start_time: Instant::now(), + render_frames: 0, + render_fps: 0.0, + } + } + + fn app_tick(&mut self) -> Result<()> { + self.app_frames += 1; + let now = Instant::now(); + let elapsed = (now - self.app_start_time).as_secs_f64(); + if elapsed >= 1.0 { + self.app_fps = self.app_frames as f64 / elapsed; + self.app_start_time = now; + self.app_frames = 0; + } + Ok(()) + } + + fn render_tick(&mut self) -> Result<()> { + self.render_frames += 1; + let now = Instant::now(); + let elapsed = (now - self.render_start_time).as_secs_f64(); + if elapsed >= 1.0 { + self.render_fps = self.render_frames as f64 / elapsed; + self.render_start_time = now; + self.render_frames = 0; + } + Ok(()) + } +} + +impl Component for FpsCounter { + fn update(&mut self, action: Action) -> Result> { + if let Action::Tick = action { + self.app_tick()? + }; + if let Action::Render = action { + self.render_tick()? + }; + Ok(None) + } + + fn draw(&mut self, f: &mut Frame<'_>, rect: Rect) -> Result<()> { + let rects = Layout::default() + .direction(Direction::Vertical) + .constraints(vec![ + Constraint::Length(1), // first row + Constraint::Min(0), + ]) + .split(rect); + + let rect = rects[0]; + + let s = format!("{:.2} ticks per sec (app) {:.2} frames per sec (render)", self.app_fps, self.render_fps); + let block = Block::default().title(block::Title::from(s.dim()).alignment(Alignment::Right)); + f.render_widget(block, rect); + Ok(()) + } +} diff --git a/src/bin/tui/components/home.rs b/src/bin/tui/components/home.rs new file mode 100644 index 0000000..082fee7 --- /dev/null +++ b/src/bin/tui/components/home.rs @@ -0,0 +1,99 @@ +use std::{ + collections::HashMap, + fs::read_to_string, + path::PathBuf, + time::{Duration, SystemTime}, +}; + +use color_eyre::eyre::Result; +use crossterm::event::{KeyCode, KeyEvent}; +use dust_lang::{interpret, interpret_with_context, Map, Value}; +use ratatui::{prelude::*, widgets::*}; +use serde::{Deserialize, Serialize}; +use tokio::sync::mpsc::UnboundedSender; +use tui_tree_widget::{Tree, TreeItem, TreeState}; + +use super::{map_display::MapDisplay, Component, Frame}; +use crate::{ + action::Action, + config::{Config, KeyBindings}, +}; + +pub struct Home<'a> { + command_tx: Option>, + config: Config, + path: PathBuf, + source: String, + context: Map, + context_tree_state: TreeState, + context_display: MapDisplay<'a>, + output: dust_lang::Result, + last_modified: SystemTime, +} + +impl<'a> Home<'a> { + pub fn new(path: PathBuf) -> Result { + let context = Map::new(); + let context_display = MapDisplay::new(context.clone())?; + + Ok(Home { + command_tx: None, + config: Config::default(), + path, + source: String::new(), + context, + context_display, + context_tree_state: TreeState::default(), + output: Ok(Value::default()), + last_modified: SystemTime::now(), + }) + } +} + +impl<'a> Component for Home<'a> { + fn register_action_handler(&mut self, tx: UnboundedSender) -> Result<()> { + self.command_tx = Some(tx); + Ok(()) + } + + fn register_config_handler(&mut self, config: Config) -> Result<()> { + self.config = config; + Ok(()) + } + + fn update(&mut self, action: Action) -> Result> { + match action { + Action::Tick => { + let modified = self.path.metadata()?.modified()?; + + if modified != self.last_modified { + self.source = read_to_string(&self.path)?; + self.last_modified = modified; + self.output = interpret_with_context(&self.source, self.context.clone()); + self.context_display = MapDisplay::new(self.context.clone())?; + } + } + _ => {} + } + + self.context_display.update(action) + } + + fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { + let layout = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) + .split(area); + + self.context_display.draw(frame, layout[0])?; + + let output_text = match &self.output { + Ok(value) => value.to_string(), + Err(error) => error.to_string(), + }; + + frame.render_widget(Paragraph::new(output_text), layout[1]); + + Ok(()) + } +} diff --git a/src/bin/tui/components/list_display.rs b/src/bin/tui/components/list_display.rs new file mode 100644 index 0000000..29c3592 --- /dev/null +++ b/src/bin/tui/components/list_display.rs @@ -0,0 +1,5 @@ +use dust_lang::List; + +pub struct ListDisplay { + list: List, +} diff --git a/src/bin/tui/components/map_display.rs b/src/bin/tui/components/map_display.rs new file mode 100644 index 0000000..8024438 --- /dev/null +++ b/src/bin/tui/components/map_display.rs @@ -0,0 +1,106 @@ +use color_eyre::Result; +use dust_lang::{Map, Type, Value}; +use lazy_static::lazy_static; +use ratatui::{ + prelude::Rect, + style::{Color, Modifier, Style, Stylize}, + widgets::{Block, Borders}, + Frame, +}; +use serial_int::SerialGenerator; +use std::{hash::Hash, sync::Mutex}; +use tui_tree_widget::{Tree, TreeItem, TreeState}; + +use crate::{action::Action, components::Component}; + +lazy_static! { + static ref ID_GENERATOR: Mutex> = Mutex::new(SerialGenerator::new()); +} + +fn create_tree_item<'a>(key: String, value: &Value) -> Result> { + let tree_item = match value { + Value::List(list) => { + let mut items = Vec::new(); + + for (index, value) in list.items().iter().enumerate() { + let item = create_tree_item(index.to_string(), value)?; + + items.push(item); + } + + TreeItem::new(ID_GENERATOR.lock().unwrap().generate(), key, items)? + } + Value::Map(map) => { + let mut items = Vec::new(); + + for (key, (value, _)) in map.variables()?.iter() { + let item = create_tree_item(key.to_string(), value)?; + + items.push(item); + } + + TreeItem::new(ID_GENERATOR.lock().unwrap().generate(), key, items)? + } + Value::Function(_) => todo!(), + Value::String(string) => TreeItem::new_leaf( + ID_GENERATOR.lock().unwrap().generate(), + format!("{key} = {value}"), + ), + Value::Float(float) => TreeItem::new_leaf( + ID_GENERATOR.lock().unwrap().generate(), + format!("{key} = {value}"), + ), + Value::Integer(integer) => TreeItem::new_leaf( + ID_GENERATOR.lock().unwrap().generate(), + format!("{key} = {value}"), + ), + Value::Boolean(_) => todo!(), + Value::Option(_) => todo!(), + }; + + Ok(tree_item) +} + +pub struct MapDisplay<'a> { + tree_state: TreeState, + items: Vec>, +} + +impl<'a> MapDisplay<'a> { + pub fn new(map: Map) -> Result { + let tree_state = TreeState::default(); + let mut items = Vec::new(); + + for (key, (value, _)) in map.variables()?.iter() { + let item = create_tree_item(key.to_string(), value)?; + + items.push(item); + } + + Ok(MapDisplay { tree_state, items }) + } +} + +impl<'a> Component for MapDisplay<'a> { + fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { + let tree = Tree::new(self.items.clone())? + .block(Block::new().title("context").borders(Borders::ALL)) + .highlight_style(Style::new().add_modifier(Modifier::BOLD)); + + frame.render_stateful_widget(tree, area, &mut self.tree_state); + + Ok(()) + } + + fn update(&mut self, action: Action) -> Result> { + match action { + Action::Up => self.tree_state.key_up(self.items.as_slice()), + Action::Down => self.tree_state.key_down(&self.items), + Action::Left => self.tree_state.key_left(), + Action::Right => self.tree_state.key_right(), + _ => {} + } + + Ok(None) + } +} diff --git a/src/bin/tui/config.rs b/src/bin/tui/config.rs new file mode 100644 index 0000000..44d91d9 --- /dev/null +++ b/src/bin/tui/config.rs @@ -0,0 +1,563 @@ +use std::{collections::HashMap, fmt, path::PathBuf}; + +use color_eyre::eyre::Result; +use config::Value; +use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; +use derive_deref::{Deref, DerefMut}; +use ratatui::style::{Color, Modifier, Style}; +use serde::{ + de::{self, Deserializer, MapAccess, Visitor}, + Deserialize, Serialize, +}; +use serde_json::Value as JsonValue; + +use crate::{action::Action, mode::Mode}; + +const CONFIG: &str = include_str!("../../../assets/config.json5"); + +#[derive(Clone, Debug, Deserialize, Default)] +pub struct AppConfig { + #[serde(default)] + pub _data_dir: PathBuf, + #[serde(default)] + pub _config_dir: PathBuf, +} + +#[derive(Clone, Debug, Default, Deserialize)] +pub struct Config { + #[serde(default, flatten)] + pub config: AppConfig, + #[serde(default)] + pub keybindings: KeyBindings, + #[serde(default)] + pub styles: Styles, +} + +impl Config { + pub fn new() -> Result { + let default_config: Config = json5::from_str(CONFIG).unwrap(); + let data_dir = crate::utils::get_data_dir(); + let config_dir = crate::utils::get_config_dir(); + let mut builder = config::Config::builder() + .set_default("_data_dir", data_dir.to_str().unwrap())? + .set_default("_config_dir", config_dir.to_str().unwrap())?; + + let config_files = [ + ("config.json5", config::FileFormat::Json5), + ("config.json", config::FileFormat::Json), + ("config.yaml", config::FileFormat::Yaml), + ("config.toml", config::FileFormat::Toml), + ("config.ini", config::FileFormat::Ini), + ]; + let mut found_config = false; + for (file, format) in &config_files { + builder = builder.add_source( + config::File::from(config_dir.join(file)) + .format(*format) + .required(false), + ); + if config_dir.join(file).exists() { + found_config = true + } + } + if !found_config { + log::error!("No configuration file found. Application may not behave as expected"); + } + + let mut cfg: Self = builder.build()?.try_deserialize()?; + + for (mode, default_bindings) in default_config.keybindings.iter() { + let user_bindings = cfg.keybindings.entry(*mode).or_default(); + for (key, cmd) in default_bindings.iter() { + user_bindings + .entry(key.clone()) + .or_insert_with(|| cmd.clone()); + } + } + for (mode, default_styles) in default_config.styles.iter() { + let user_styles = cfg.styles.entry(*mode).or_default(); + for (style_key, style) in default_styles.iter() { + user_styles + .entry(style_key.clone()) + .or_insert_with(|| style.clone()); + } + } + + Ok(cfg) + } +} + +#[derive(Clone, Debug, Default, Deref, DerefMut)] +pub struct KeyBindings(pub HashMap, Action>>); + +impl<'de> Deserialize<'de> for KeyBindings { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let parsed_map = HashMap::>::deserialize(deserializer)?; + + let keybindings = parsed_map + .into_iter() + .map(|(mode, inner_map)| { + let converted_inner_map = inner_map + .into_iter() + .map(|(key_str, cmd)| (parse_key_sequence(&key_str).unwrap(), cmd)) + .collect(); + (mode, converted_inner_map) + }) + .collect(); + + Ok(KeyBindings(keybindings)) + } +} + +fn parse_key_event(raw: &str) -> Result { + let raw_lower = raw.to_ascii_lowercase(); + let (remaining, modifiers) = extract_modifiers(&raw_lower); + parse_key_code_with_modifiers(remaining, modifiers) +} + +fn extract_modifiers(raw: &str) -> (&str, KeyModifiers) { + let mut modifiers = KeyModifiers::empty(); + let mut current = raw; + + loop { + match current { + rest if rest.starts_with("ctrl-") => { + modifiers.insert(KeyModifiers::CONTROL); + current = &rest[5..]; + } + rest if rest.starts_with("alt-") => { + modifiers.insert(KeyModifiers::ALT); + current = &rest[4..]; + } + rest if rest.starts_with("shift-") => { + modifiers.insert(KeyModifiers::SHIFT); + current = &rest[6..]; + } + _ => break, // break out of the loop if no known prefix is detected + }; + } + + (current, modifiers) +} + +fn parse_key_code_with_modifiers( + raw: &str, + mut modifiers: KeyModifiers, +) -> Result { + let c = match raw { + "esc" => KeyCode::Esc, + "enter" => KeyCode::Enter, + "left" => KeyCode::Left, + "right" => KeyCode::Right, + "up" => KeyCode::Up, + "down" => KeyCode::Down, + "home" => KeyCode::Home, + "end" => KeyCode::End, + "pageup" => KeyCode::PageUp, + "pagedown" => KeyCode::PageDown, + "backtab" => { + modifiers.insert(KeyModifiers::SHIFT); + KeyCode::BackTab + } + "backspace" => KeyCode::Backspace, + "delete" => KeyCode::Delete, + "insert" => KeyCode::Insert, + "f1" => KeyCode::F(1), + "f2" => KeyCode::F(2), + "f3" => KeyCode::F(3), + "f4" => KeyCode::F(4), + "f5" => KeyCode::F(5), + "f6" => KeyCode::F(6), + "f7" => KeyCode::F(7), + "f8" => KeyCode::F(8), + "f9" => KeyCode::F(9), + "f10" => KeyCode::F(10), + "f11" => KeyCode::F(11), + "f12" => KeyCode::F(12), + "space" => KeyCode::Char(' '), + "hyphen" => KeyCode::Char('-'), + "minus" => KeyCode::Char('-'), + "tab" => KeyCode::Tab, + c if c.len() == 1 => { + let mut c = c.chars().next().unwrap(); + if modifiers.contains(KeyModifiers::SHIFT) { + c = c.to_ascii_uppercase(); + } + KeyCode::Char(c) + } + _ => return Err(format!("Unable to parse {raw}")), + }; + Ok(KeyEvent::new(c, modifiers)) +} + +pub fn key_event_to_string(key_event: &KeyEvent) -> String { + let char; + let key_code = match key_event.code { + KeyCode::Backspace => "backspace", + KeyCode::Enter => "enter", + KeyCode::Left => "left", + KeyCode::Right => "right", + KeyCode::Up => "up", + KeyCode::Down => "down", + KeyCode::Home => "home", + KeyCode::End => "end", + KeyCode::PageUp => "pageup", + KeyCode::PageDown => "pagedown", + KeyCode::Tab => "tab", + KeyCode::BackTab => "backtab", + KeyCode::Delete => "delete", + KeyCode::Insert => "insert", + KeyCode::F(c) => { + char = format!("f({c})"); + &char + } + KeyCode::Char(c) if c == ' ' => "space", + KeyCode::Char(c) => { + char = c.to_string(); + &char + } + KeyCode::Esc => "esc", + KeyCode::Null => "", + KeyCode::CapsLock => "", + KeyCode::Menu => "", + KeyCode::ScrollLock => "", + KeyCode::Media(_) => "", + KeyCode::NumLock => "", + KeyCode::PrintScreen => "", + KeyCode::Pause => "", + KeyCode::KeypadBegin => "", + KeyCode::Modifier(_) => "", + }; + + let mut modifiers = Vec::with_capacity(3); + + if key_event.modifiers.intersects(KeyModifiers::CONTROL) { + modifiers.push("ctrl"); + } + + if key_event.modifiers.intersects(KeyModifiers::SHIFT) { + modifiers.push("shift"); + } + + if key_event.modifiers.intersects(KeyModifiers::ALT) { + modifiers.push("alt"); + } + + let mut key = modifiers.join("-"); + + if !key.is_empty() { + key.push('-'); + } + key.push_str(key_code); + + key +} + +pub fn parse_key_sequence(raw: &str) -> Result, String> { + if raw.chars().filter(|c| *c == '>').count() != raw.chars().filter(|c| *c == '<').count() { + return Err(format!("Unable to parse `{}`", raw)); + } + let raw = if !raw.contains("><") { + let raw = raw.strip_prefix('<').unwrap_or(raw); + let raw = raw.strip_prefix('>').unwrap_or(raw); + raw + } else { + raw + }; + let sequences = raw + .split("><") + .map(|seq| { + if let Some(s) = seq.strip_prefix('<') { + s + } else if let Some(s) = seq.strip_suffix('>') { + s + } else { + seq + } + }) + .collect::>(); + + sequences.into_iter().map(parse_key_event).collect() +} + +#[derive(Clone, Debug, Default, Deref, DerefMut)] +pub struct Styles(pub HashMap>); + +impl<'de> Deserialize<'de> for Styles { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let parsed_map = HashMap::>::deserialize(deserializer)?; + + let styles = parsed_map + .into_iter() + .map(|(mode, inner_map)| { + let converted_inner_map = inner_map + .into_iter() + .map(|(str, style)| (str, parse_style(&style))) + .collect(); + (mode, converted_inner_map) + }) + .collect(); + + Ok(Styles(styles)) + } +} + +pub fn parse_style(line: &str) -> Style { + let (foreground, background) = + line.split_at(line.to_lowercase().find("on ").unwrap_or(line.len())); + let foreground = process_color_string(foreground); + let background = process_color_string(&background.replace("on ", "")); + + let mut style = Style::default(); + if let Some(fg) = parse_color(&foreground.0) { + style = style.fg(fg); + } + if let Some(bg) = parse_color(&background.0) { + style = style.bg(bg); + } + style = style.add_modifier(foreground.1 | background.1); + style +} + +fn process_color_string(color_str: &str) -> (String, Modifier) { + let color = color_str + .replace("grey", "gray") + .replace("bright ", "") + .replace("bold ", "") + .replace("underline ", "") + .replace("inverse ", ""); + + let mut modifiers = Modifier::empty(); + if color_str.contains("underline") { + modifiers |= Modifier::UNDERLINED; + } + if color_str.contains("bold") { + modifiers |= Modifier::BOLD; + } + if color_str.contains("inverse") { + modifiers |= Modifier::REVERSED; + } + + (color, modifiers) +} + +fn parse_color(s: &str) -> Option { + let s = s.trim_start(); + let s = s.trim_end(); + if s.contains("bright color") { + let s = s.trim_start_matches("bright "); + let c = s + .trim_start_matches("color") + .parse::() + .unwrap_or_default(); + Some(Color::Indexed(c.wrapping_shl(8))) + } else if s.contains("color") { + let c = s + .trim_start_matches("color") + .parse::() + .unwrap_or_default(); + Some(Color::Indexed(c)) + } else if s.contains("gray") { + let c = 232 + + s.trim_start_matches("gray") + .parse::() + .unwrap_or_default(); + Some(Color::Indexed(c)) + } else if s.contains("rgb") { + let red = (s.as_bytes()[3] as char).to_digit(10).unwrap_or_default() as u8; + let green = (s.as_bytes()[4] as char).to_digit(10).unwrap_or_default() as u8; + let blue = (s.as_bytes()[5] as char).to_digit(10).unwrap_or_default() as u8; + let c = 16 + red * 36 + green * 6 + blue; + Some(Color::Indexed(c)) + } else if s == "bold black" { + Some(Color::Indexed(8)) + } else if s == "bold red" { + Some(Color::Indexed(9)) + } else if s == "bold green" { + Some(Color::Indexed(10)) + } else if s == "bold yellow" { + Some(Color::Indexed(11)) + } else if s == "bold blue" { + Some(Color::Indexed(12)) + } else if s == "bold magenta" { + Some(Color::Indexed(13)) + } else if s == "bold cyan" { + Some(Color::Indexed(14)) + } else if s == "bold white" { + Some(Color::Indexed(15)) + } else if s == "black" { + Some(Color::Indexed(0)) + } else if s == "red" { + Some(Color::Indexed(1)) + } else if s == "green" { + Some(Color::Indexed(2)) + } else if s == "yellow" { + Some(Color::Indexed(3)) + } else if s == "blue" { + Some(Color::Indexed(4)) + } else if s == "magenta" { + Some(Color::Indexed(5)) + } else if s == "cyan" { + Some(Color::Indexed(6)) + } else if s == "white" { + Some(Color::Indexed(7)) + } else { + None + } +} + +#[cfg(test)] +mod tests { + use pretty_assertions::assert_eq; + + use super::*; + + #[test] + fn test_parse_style_default() { + let style = parse_style(""); + assert_eq!(style, Style::default()); + } + + #[test] + fn test_parse_style_foreground() { + let style = parse_style("red"); + assert_eq!(style.fg, Some(Color::Indexed(1))); + } + + #[test] + fn test_parse_style_background() { + let style = parse_style("on blue"); + assert_eq!(style.bg, Some(Color::Indexed(4))); + } + + #[test] + fn test_parse_style_modifiers() { + let style = parse_style("underline red on blue"); + assert_eq!(style.fg, Some(Color::Indexed(1))); + assert_eq!(style.bg, Some(Color::Indexed(4))); + } + + #[test] + fn test_process_color_string() { + let (color, modifiers) = process_color_string("underline bold inverse gray"); + assert_eq!(color, "gray"); + assert!(modifiers.contains(Modifier::UNDERLINED)); + assert!(modifiers.contains(Modifier::BOLD)); + assert!(modifiers.contains(Modifier::REVERSED)); + } + + #[test] + fn test_parse_color_rgb() { + let color = parse_color("rgb123"); + let expected = 16 + 1 * 36 + 2 * 6 + 3; + assert_eq!(color, Some(Color::Indexed(expected))); + } + + #[test] + fn test_parse_color_unknown() { + let color = parse_color("unknown"); + assert_eq!(color, None); + } + + #[test] + fn test_config() -> Result<()> { + let c = Config::new()?; + assert_eq!( + c.keybindings + .get(&Mode::Home) + .unwrap() + .get(&parse_key_sequence("").unwrap_or_default()) + .unwrap(), + &Action::Quit + ); + Ok(()) + } + + #[test] + fn test_simple_keys() { + assert_eq!( + parse_key_event("a").unwrap(), + KeyEvent::new(KeyCode::Char('a'), KeyModifiers::empty()) + ); + + assert_eq!( + parse_key_event("enter").unwrap(), + KeyEvent::new(KeyCode::Enter, KeyModifiers::empty()) + ); + + assert_eq!( + parse_key_event("esc").unwrap(), + KeyEvent::new(KeyCode::Esc, KeyModifiers::empty()) + ); + } + + #[test] + fn test_with_modifiers() { + assert_eq!( + parse_key_event("ctrl-a").unwrap(), + KeyEvent::new(KeyCode::Char('a'), KeyModifiers::CONTROL) + ); + + assert_eq!( + parse_key_event("alt-enter").unwrap(), + KeyEvent::new(KeyCode::Enter, KeyModifiers::ALT) + ); + + assert_eq!( + parse_key_event("shift-esc").unwrap(), + KeyEvent::new(KeyCode::Esc, KeyModifiers::SHIFT) + ); + } + + #[test] + fn test_multiple_modifiers() { + assert_eq!( + parse_key_event("ctrl-alt-a").unwrap(), + KeyEvent::new( + KeyCode::Char('a'), + KeyModifiers::CONTROL | KeyModifiers::ALT + ) + ); + + assert_eq!( + parse_key_event("ctrl-shift-enter").unwrap(), + KeyEvent::new(KeyCode::Enter, KeyModifiers::CONTROL | KeyModifiers::SHIFT) + ); + } + + #[test] + fn test_reverse_multiple_modifiers() { + assert_eq!( + key_event_to_string(&KeyEvent::new( + KeyCode::Char('a'), + KeyModifiers::CONTROL | KeyModifiers::ALT + )), + "ctrl-alt-a".to_string() + ); + } + + #[test] + fn test_invalid_keys() { + assert!(parse_key_event("invalid-key").is_err()); + assert!(parse_key_event("ctrl-invalid-key").is_err()); + } + + #[test] + fn test_case_insensitivity() { + assert_eq!( + parse_key_event("CTRL-a").unwrap(), + KeyEvent::new(KeyCode::Char('a'), KeyModifiers::CONTROL) + ); + + assert_eq!( + parse_key_event("AlT-eNtEr").unwrap(), + KeyEvent::new(KeyCode::Enter, KeyModifiers::ALT) + ); + } +} diff --git a/src/bin/tui/error.rs b/src/bin/tui/error.rs deleted file mode 100644 index f1dd913..0000000 --- a/src/bin/tui/error.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub type Result = std::result::Result; - -pub enum Error {} diff --git a/src/bin/tui/interpreter_display.rs b/src/bin/tui/interpreter_display.rs deleted file mode 100644 index 8ac54e5..0000000 --- a/src/bin/tui/interpreter_display.rs +++ /dev/null @@ -1,53 +0,0 @@ -use std::{fs::read_to_string, path::PathBuf, time::SystemTime}; - -use dust_lang::{Interpreter, Map, Value}; -use ratatui::Frame; - -use crate::{value_display::ValueDisplay, Action, Elm, Result}; - -pub struct InterpreterDisplay { - interpreter: Interpreter, - path: PathBuf, - value_display: ValueDisplay, - modified: SystemTime, -} - -impl InterpreterDisplay { - pub fn new(context: Map, path: PathBuf) -> Result { - let interpreter = Interpreter::new(context)?; - let value_display = ValueDisplay::new(Value::default()); - let modified = SystemTime::now(); - - Ok(Self { - interpreter, - path, - value_display, - modified, - }) - } -} - -impl Elm for InterpreterDisplay { - fn update(&mut self, message: Action) -> Result> { - match message { - Action::Tick => { - let last_modified = self.path.metadata()?.modified()?; - - if last_modified != self.modified { - let source = read_to_string(&self.path)?; - let value = self.interpreter.run(&source)?; - - self.value_display = ValueDisplay::new(value); - self.modified = last_modified; - } - } - _ => {} - } - - self.value_display.update(message) - } - - fn view(&self, frame: &mut Frame) { - self.value_display.view(frame) - } -} diff --git a/src/bin/tui/log.rs b/src/bin/tui/log.rs deleted file mode 100644 index 00bd511..0000000 --- a/src/bin/tui/log.rs +++ /dev/null @@ -1,83 +0,0 @@ -use std::path::PathBuf; - -use color_eyre::eyre::Result; -use directories::ProjectDirs; -use lazy_static::lazy_static; -pub use tracing::error; -use tracing_error::ErrorLayer; -use tracing_subscriber::{self, layer::SubscriberExt, util::SubscriberInitExt, Layer}; - -lazy_static! { - pub static ref PROJECT_NAME: String = env!("CARGO_CRATE_NAME").to_uppercase().to_string(); - pub static ref DATA_FOLDER: Option = - std::env::var(format!("{}_DATA", PROJECT_NAME.clone())) - .ok() - .map(PathBuf::from); - pub static ref LOG_ENV: String = format!("{}_LOGLEVEL", PROJECT_NAME.clone()); - pub static ref LOG_FILE: String = format!("{}.log", env!("CARGO_PKG_NAME")); -} - -pub fn project_directory() -> Option { - ProjectDirs::from("io", "jeffa", env!("CARGO_PKG_NAME")) -} - -pub fn get_data_dir() -> PathBuf { - if let Some(path) = DATA_FOLDER.clone() { - path - } else if let Some(proj_dirs) = project_directory() { - proj_dirs.data_local_dir().to_path_buf() - } else { - PathBuf::from(".").join(".data") - } -} - -pub fn initialize_logging() -> Result<()> { - let directory = get_data_dir(); - std::fs::create_dir_all(directory.clone())?; - let log_path = directory.join(LOG_FILE.clone()); - let log_file = std::fs::File::create(log_path)?; - std::env::set_var( - "RUST_LOG", - std::env::var("RUST_LOG") - .or_else(|_| std::env::var(LOG_ENV.clone())) - .unwrap_or_else(|_| format!("{}=info", env!("CARGO_CRATE_NAME"))), - ); - let file_subscriber = tracing_subscriber::fmt::layer() - .with_file(true) - .with_line_number(true) - .with_writer(log_file) - .with_target(false) - .with_ansi(false) - .with_filter(tracing_subscriber::filter::EnvFilter::from_default_env()); - tracing_subscriber::registry() - .with(file_subscriber) - .with(ErrorLayer::default()) - .init(); - Ok(()) -} - -/// Similar to the `std::dbg!` macro, but generates `tracing` events rather -/// than printing to stdout. -/// -/// By default, the verbosity level for the generated events is `DEBUG`, but -/// this can be customized. -#[macro_export] -macro_rules! trace_dbg { - (target: $target:expr, level: $level:expr, $ex:expr) => {{ - match $ex { - value => { - tracing::event!(target: $target, $level, ?value, stringify!($ex)); - value - } - } - }}; - (level: $level:expr, $ex:expr) => { - trace_dbg!(target: module_path!(), level: $level, $ex) - }; - (target: $target:expr, $ex:expr) => { - trace_dbg!(target: $target, level: tracing::Level::DEBUG, $ex) - }; - ($ex:expr) => { - trace_dbg!(level: tracing::Level::DEBUG, $ex) - }; -} diff --git a/src/bin/tui/main.rs b/src/bin/tui/main.rs index c8f8add..a611157 100644 --- a/src/bin/tui/main.rs +++ b/src/bin/tui/main.rs @@ -1,67 +1,46 @@ -pub mod app; -pub mod interpreter_display; -pub mod log; -pub mod terminal; -pub mod value_display; +#![allow(dead_code)] +#![allow(unused_imports)] +#![allow(unused_variables)] -use std::path::PathBuf; +pub mod action; +pub mod app; +pub mod cli; +pub mod components; +pub mod config; +pub mod mode; +pub mod tui; +pub mod utils; + +use std::env; + +use clap::Parser; +use cli::Cli; +use color_eyre::eyre::Result; use crate::{ app::App, - log::{get_data_dir, initialize_logging}, + utils::{initialize_logging, initialize_panic_handler, version}, }; -use clap::Parser; -use color_eyre::Result; -use crossterm::event::{KeyEvent, MouseEvent}; -use dust_lang::Value; -use ratatui::Frame; -use serde::{Deserialize, Serialize}; -use tokio::sync::mpsc; -#[derive(Parser)] -#[command(author, version, about, long_about = None)] -struct Cli { - /// File with source to be run and watched by the shell. - path: Option, -} +async fn tokio_main() -> Result<()> { + initialize_panic_handler()?; + initialize_logging()?; -pub trait Elm { - fn update(&mut self, message: Action) -> Result>; - fn view(&self, frame: &mut Frame); -} + env::set_var("DUST_OUTPUT_MODE", "SILENT"); -#[derive(Clone, Debug, Serialize, Deserialize)] -pub enum Action { - Init, - Quit, - Error, - Closed, - Tick, - Render, - FocusGained, - FocusLost, - Paste(String), - Key(KeyEvent), - Mouse(MouseEvent), - Resize(u16, u16), - UpdateValue(Value), + let args = Cli::parse(); + let mut app = App::new(args.path)?; + app.run().await?; + + Ok(()) } #[tokio::main] -async fn main() { - initialize_logging().unwrap(); - - let args = Cli::parse(); - let (action_tx, action_rx) = mpsc::unbounded_channel(); - let path = if let Some(path) = args.path { - PathBuf::from(path) +async fn main() -> Result<()> { + if let Err(e) = tokio_main().await { + eprintln!("{} error: Something went wrong", env!("CARGO_PKG_NAME")); + Err(e) } else { - PathBuf::from(format!("{}/scratch.ds", get_data_dir().to_string_lossy())) - }; - let mut app = App::new(action_rx, action_tx, path).unwrap(); - let run_result = app.run().await; - - if let Err(report) = run_result { - eprintln!("{report}") + Ok(()) } } diff --git a/src/bin/tui/mode.rs b/src/bin/tui/mode.rs new file mode 100644 index 0000000..1775f94 --- /dev/null +++ b/src/bin/tui/mode.rs @@ -0,0 +1,7 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum Mode { + #[default] + Home, +} diff --git a/src/bin/tui/terminal.rs b/src/bin/tui/terminal.rs deleted file mode 100644 index cdb943b..0000000 --- a/src/bin/tui/terminal.rs +++ /dev/null @@ -1,229 +0,0 @@ -use std::{ - io::{stderr, Stderr}, - ops::{Deref, DerefMut}, - time::Duration, -}; - -use color_eyre::eyre::Result; -use crossterm::{ - cursor, - event::{ - DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, EnableMouseCapture, - Event, KeyEventKind, - }, - terminal::{EnterAlternateScreen, LeaveAlternateScreen}, -}; -use futures::{FutureExt, StreamExt}; -use ratatui::prelude::CrosstermBackend; -use tokio::{ - sync::mpsc::{self, UnboundedReceiver, UnboundedSender}, - task::JoinHandle, -}; -use tokio_util::sync::CancellationToken; - -use crate::{log, Action}; - -pub struct Terminal { - pub terminal: ratatui::Terminal>, - pub task: JoinHandle<()>, - pub cancellation_token: CancellationToken, - pub event_rx: UnboundedReceiver, - pub event_tx: UnboundedSender, - pub frame_rate: f64, - pub tick_rate: f64, - pub mouse: bool, - pub paste: bool, -} - -impl Terminal { - pub fn new() -> Result { - let tick_rate = 4.0; - let frame_rate = 60.0; - let terminal = ratatui::Terminal::new(CrosstermBackend::new(stderr()))?; - let (event_tx, event_rx) = mpsc::unbounded_channel(); - let cancellation_token = CancellationToken::new(); - let task = tokio::spawn(async {}); - let mouse = false; - let paste = false; - Ok(Self { - terminal, - task, - cancellation_token, - event_rx, - event_tx, - frame_rate, - tick_rate, - mouse, - paste, - }) - } - - pub fn tick_rate(mut self, tick_rate: f64) -> Self { - self.tick_rate = tick_rate; - self - } - - pub fn frame_rate(mut self, frame_rate: f64) -> Self { - self.frame_rate = frame_rate; - self - } - - pub fn mouse(mut self, mouse: bool) -> Self { - self.mouse = mouse; - self - } - - pub fn paste(mut self, paste: bool) -> Self { - self.paste = paste; - self - } - - pub fn start(&mut self) { - let tick_delay = std::time::Duration::from_secs_f64(1.0 / self.tick_rate); - let render_delay = std::time::Duration::from_secs_f64(1.0 / self.frame_rate); - self.cancel(); - self.cancellation_token = CancellationToken::new(); - let _cancellation_token = self.cancellation_token.clone(); - let _event_tx = self.event_tx.clone(); - self.task = tokio::spawn(async move { - let mut reader = crossterm::event::EventStream::new(); - let mut tick_interval = tokio::time::interval(tick_delay); - let mut render_interval = tokio::time::interval(render_delay); - _event_tx.send(Action::Init).unwrap(); - loop { - let tick_delay = tick_interval.tick(); - let render_delay = render_interval.tick(); - let crossterm_event = reader.next().fuse(); - tokio::select! { - _ = _cancellation_token.cancelled() => { - break; - } - maybe_event = crossterm_event => { - match maybe_event { - Some(Ok(evt)) => { - match evt { - Event::Key(key) => { - if key.kind == KeyEventKind::Press { - _event_tx.send(Action::Key(key)).unwrap(); - } - }, - Event::Mouse(mouse) => { - _event_tx.send(Action::Mouse(mouse)).unwrap(); - }, - Event::Resize(x, y) => { - _event_tx.send(Action::Resize(x, y)).unwrap(); - }, - Event::FocusLost => { - _event_tx.send(Action::FocusLost).unwrap(); - }, - Event::FocusGained => { - _event_tx.send(Action::FocusGained).unwrap(); - }, - Event::Paste(s) => { - _event_tx.send(Action::Paste(s)).unwrap(); - }, - } - } - Some(Err(_)) => { - _event_tx.send(Action::Error).unwrap(); - } - None => {}, - } - }, - _ = tick_delay => { - _event_tx.send(Action::Tick).unwrap(); - }, - _ = render_delay => { - _event_tx.send(Action::Render).unwrap(); - }, - } - } - }); - } - - pub fn stop(&self) -> Result<()> { - self.cancel(); - let mut counter = 0; - while !self.task.is_finished() { - std::thread::sleep(Duration::from_millis(1)); - counter += 1; - if counter > 50 { - self.task.abort(); - } - if counter > 100 { - log::error!("Failed to abort task in 100 milliseconds for unknown reason"); - break; - } - } - Ok(()) - } - - pub fn enter(&mut self) -> Result<()> { - crossterm::terminal::enable_raw_mode()?; - crossterm::execute!(std::io::stderr(), EnterAlternateScreen, cursor::Hide)?; - if self.mouse { - crossterm::execute!(std::io::stderr(), EnableMouseCapture)?; - } - if self.paste { - crossterm::execute!(std::io::stderr(), EnableBracketedPaste)?; - } - self.start(); - Ok(()) - } - - pub fn exit(&mut self) -> Result<()> { - self.stop()?; - if crossterm::terminal::is_raw_mode_enabled()? { - self.flush()?; - if self.paste { - crossterm::execute!(std::io::stderr(), DisableBracketedPaste)?; - } - if self.mouse { - crossterm::execute!(std::io::stderr(), DisableMouseCapture)?; - } - crossterm::execute!(std::io::stderr(), LeaveAlternateScreen, cursor::Show)?; - crossterm::terminal::disable_raw_mode()?; - } - Ok(()) - } - - pub fn cancel(&self) { - self.cancellation_token.cancel(); - } - - pub fn suspend(&mut self) -> Result<()> { - self.exit()?; - #[cfg(not(windows))] - signal_hook::low_level::raise(signal_hook::consts::signal::SIGTSTP)?; - Ok(()) - } - - pub fn resume(&mut self) -> Result<()> { - self.enter()?; - Ok(()) - } - - pub async fn next(&mut self) -> Option { - self.event_rx.recv().await - } -} - -impl Deref for Terminal { - type Target = ratatui::Terminal>; - - fn deref(&self) -> &Self::Target { - &self.terminal - } -} - -impl DerefMut for Terminal { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.terminal - } -} - -impl Drop for Terminal { - fn drop(&mut self) { - self.exit().unwrap(); - } -} diff --git a/src/bin/tui/lib.rs b/src/bin/tui/tui.rs similarity index 75% rename from src/bin/tui/lib.rs rename to src/bin/tui/tui.rs index 811d933..aa9783b 100644 --- a/src/bin/tui/lib.rs +++ b/src/bin/tui/tui.rs @@ -1,10 +1,3 @@ -pub mod app; -pub mod buffer; -pub mod error; -pub mod interpreter_display; -pub mod log; -pub mod value_display; - use std::{ ops::{Deref, DerefMut}, time::Duration, @@ -19,9 +12,8 @@ use crossterm::{ }, terminal::{EnterAlternateScreen, LeaveAlternateScreen}, }; -use dust_lang::Value; use futures::{FutureExt, StreamExt}; -use ratatui::{backend::CrosstermBackend as Backend, Frame}; +use ratatui::backend::CrosstermBackend as Backend; use serde::{Deserialize, Serialize}; use tokio::{ sync::mpsc::{self, UnboundedReceiver, UnboundedSender}, @@ -29,13 +21,14 @@ use tokio::{ }; use tokio_util::sync::CancellationToken; -pub trait Elm { - fn update(&mut self, message: Action) -> Result>; - fn view(&self, frame: &mut Frame); +pub type IO = std::io::Stderr; +pub fn io() -> IO { + std::io::stderr() } +pub type Frame<'a> = ratatui::Frame<'a>; #[derive(Clone, Debug, Serialize, Deserialize)] -pub enum Action { +pub enum Event { Init, Quit, Error, @@ -48,15 +41,14 @@ pub enum Action { Key(KeyEvent), Mouse(MouseEvent), Resize(u16, u16), - UpdateValue(Value), } pub struct Tui { - pub terminal: ratatui::Terminal>, + pub terminal: ratatui::Terminal>, pub task: JoinHandle<()>, pub cancellation_token: CancellationToken, - pub event_rx: UnboundedReceiver, - pub event_tx: UnboundedSender, + pub event_rx: UnboundedReceiver, + pub event_tx: UnboundedSender, pub frame_rate: f64, pub tick_rate: f64, pub mouse: bool, @@ -65,14 +57,15 @@ pub struct Tui { impl Tui { pub fn new() -> Result { - let tick_rate = 4.0; - let frame_rate = 60.0; - let terminal = ratatui::Terminal::new(Backend::new(std::io::stderr()))?; + let tick_rate = 2.0; + let frame_rate = 30.0; + let terminal = ratatui::Terminal::new(Backend::new(io()))?; let (event_tx, event_rx) = mpsc::unbounded_channel(); let cancellation_token = CancellationToken::new(); let task = tokio::spawn(async {}); let mouse = false; let paste = false; + Ok(Self { terminal, task, @@ -109,19 +102,25 @@ impl Tui { pub fn start(&mut self) { let tick_delay = std::time::Duration::from_secs_f64(1.0 / self.tick_rate); let render_delay = std::time::Duration::from_secs_f64(1.0 / self.frame_rate); + self.cancel(); self.cancellation_token = CancellationToken::new(); + let _cancellation_token = self.cancellation_token.clone(); let _event_tx = self.event_tx.clone(); + self.task = tokio::spawn(async move { let mut reader = crossterm::event::EventStream::new(); let mut tick_interval = tokio::time::interval(tick_delay); let mut render_interval = tokio::time::interval(render_delay); - _event_tx.send(Action::Init).unwrap(); + + _event_tx.send(Event::Init).unwrap(); + loop { let tick_delay = tick_interval.tick(); let render_delay = render_interval.tick(); let crossterm_event = reader.next().fuse(); + tokio::select! { _ = _cancellation_token.cancelled() => { break; @@ -132,37 +131,37 @@ impl Tui { match evt { CrosstermEvent::Key(key) => { if key.kind == KeyEventKind::Press { - _event_tx.send(Action::Key(key)).unwrap(); + _event_tx.send(Event::Key(key)).unwrap(); } }, CrosstermEvent::Mouse(mouse) => { - _event_tx.send(Action::Mouse(mouse)).unwrap(); + _event_tx.send(Event::Mouse(mouse)).unwrap(); }, CrosstermEvent::Resize(x, y) => { - _event_tx.send(Action::Resize(x, y)).unwrap(); + _event_tx.send(Event::Resize(x, y)).unwrap(); }, CrosstermEvent::FocusLost => { - _event_tx.send(Action::FocusLost).unwrap(); + _event_tx.send(Event::FocusLost).unwrap(); }, CrosstermEvent::FocusGained => { - _event_tx.send(Action::FocusGained).unwrap(); + _event_tx.send(Event::FocusGained).unwrap(); }, CrosstermEvent::Paste(s) => { - _event_tx.send(Action::Paste(s)).unwrap(); + _event_tx.send(Event::Paste(s)).unwrap(); }, } } Some(Err(_)) => { - _event_tx.send(Action::Error).unwrap(); + _event_tx.send(Event::Error).unwrap(); } None => {}, } }, _ = tick_delay => { - _event_tx.send(Action::Tick).unwrap(); + _event_tx.send(Event::Tick).unwrap(); }, _ = render_delay => { - _event_tx.send(Action::Render).unwrap(); + _event_tx.send(Event::Render).unwrap(); }, } } @@ -171,47 +170,62 @@ impl Tui { pub fn stop(&self) -> Result<()> { self.cancel(); + let mut counter = 0; + while !self.task.is_finished() { std::thread::sleep(Duration::from_millis(1)); + counter += 1; + if counter > 50 { self.task.abort(); } + if counter > 100 { log::error!("Failed to abort task in 100 milliseconds for unknown reason"); break; } } + Ok(()) } pub fn enter(&mut self) -> Result<()> { crossterm::terminal::enable_raw_mode()?; - crossterm::execute!(std::io::stderr(), EnterAlternateScreen, cursor::Hide)?; + crossterm::execute!(io(), EnterAlternateScreen, cursor::Hide)?; + if self.mouse { - crossterm::execute!(std::io::stderr(), EnableMouseCapture)?; + crossterm::execute!(io(), EnableMouseCapture)?; } + if self.paste { - crossterm::execute!(std::io::stderr(), EnableBracketedPaste)?; + crossterm::execute!(io(), EnableBracketedPaste)?; } + self.start(); + Ok(()) } pub fn exit(&mut self) -> Result<()> { self.stop()?; + if crossterm::terminal::is_raw_mode_enabled()? { self.flush()?; + if self.paste { - crossterm::execute!(std::io::stderr(), DisableBracketedPaste)?; + crossterm::execute!(io(), DisableBracketedPaste)?; } + if self.mouse { - crossterm::execute!(std::io::stderr(), DisableMouseCapture)?; + crossterm::execute!(io(), DisableMouseCapture)?; } - crossterm::execute!(std::io::stderr(), LeaveAlternateScreen, cursor::Show)?; + + crossterm::execute!(io(), LeaveAlternateScreen, cursor::Show)?; crossterm::terminal::disable_raw_mode()?; } + Ok(()) } @@ -231,13 +245,13 @@ impl Tui { Ok(()) } - pub async fn next(&mut self) -> Option { + pub async fn next(&mut self) -> Option { self.event_rx.recv().await } } impl Deref for Tui { - type Target = ratatui::Terminal>; + type Target = ratatui::Terminal>; fn deref(&self) -> &Self::Target { &self.terminal diff --git a/src/bin/tui/utils.rs b/src/bin/tui/utils.rs new file mode 100644 index 0000000..f00e99e --- /dev/null +++ b/src/bin/tui/utils.rs @@ -0,0 +1,169 @@ +use std::path::PathBuf; + +use color_eyre::eyre::Result; +use directories::ProjectDirs; +use lazy_static::lazy_static; +use tracing::error; +use tracing_error::ErrorLayer; +use tracing_subscriber::{ + self, prelude::__tracing_subscriber_SubscriberExt, util::SubscriberInitExt, Layer, +}; + +lazy_static! { + pub static ref PROJECT_NAME: String = env!("CARGO_CRATE_NAME").to_uppercase().to_string(); + pub static ref DATA_FOLDER: Option = + std::env::var(format!("{}_DATA", PROJECT_NAME.clone())) + .ok() + .map(PathBuf::from); + pub static ref CONFIG_FOLDER: Option = + std::env::var(format!("{}_CONFIG", PROJECT_NAME.clone())) + .ok() + .map(PathBuf::from); + pub static ref LOG_ENV: String = format!("{}_LOGLEVEL", PROJECT_NAME.clone()); + pub static ref LOG_FILE: String = format!("{}.log", env!("CARGO_PKG_NAME")); +} + +fn project_directory() -> Option { + ProjectDirs::from("com", "kdheepak", env!("CARGO_PKG_NAME")) +} + +pub fn initialize_panic_handler() -> Result<()> { + let (panic_hook, eyre_hook) = color_eyre::config::HookBuilder::default() + .panic_section(format!( + "This is a bug. Consider reporting it at {}", + env!("CARGO_PKG_REPOSITORY") + )) + .capture_span_trace_by_default(false) + .display_location_section(false) + .display_env_section(false) + .into_hooks(); + eyre_hook.install()?; + std::panic::set_hook(Box::new(move |panic_info| { + if let Ok(mut t) = crate::tui::Tui::new() { + if let Err(r) = t.exit() { + error!("Unable to exit Terminal: {:?}", r); + } + } + + #[cfg(not(debug_assertions))] + { + use human_panic::{handle_dump, print_msg, Metadata}; + let meta = Metadata { + version: env!("CARGO_PKG_VERSION").into(), + name: env!("CARGO_PKG_NAME").into(), + authors: env!("CARGO_PKG_AUTHORS").replace(':', ", ").into(), + homepage: env!("CARGO_PKG_HOMEPAGE").into(), + }; + + let file_path = handle_dump(&meta, panic_info); + // prints human-panic message + print_msg(file_path, &meta) + .expect("human-panic: printing error message to console failed"); + eprintln!("{}", panic_hook.panic_report(panic_info)); // prints color-eyre stack trace to stderr + } + let msg = format!("{}", panic_hook.panic_report(panic_info)); + log::error!("Error: {}", strip_ansi_escapes::strip_str(msg)); + + #[cfg(debug_assertions)] + { + // Better Panic stacktrace that is only enabled when debugging. + better_panic::Settings::auto() + .most_recent_first(false) + .lineno_suffix(true) + .verbosity(better_panic::Verbosity::Full) + .create_panic_handler()(panic_info); + } + + std::process::exit(libc::EXIT_FAILURE); + })); + Ok(()) +} + +pub fn get_data_dir() -> PathBuf { + let directory = if let Some(s) = DATA_FOLDER.clone() { + s + } else if let Some(proj_dirs) = project_directory() { + proj_dirs.data_local_dir().to_path_buf() + } else { + PathBuf::from(".").join(".data") + }; + directory +} + +pub fn get_config_dir() -> PathBuf { + let directory = if let Some(s) = CONFIG_FOLDER.clone() { + s + } else if let Some(proj_dirs) = project_directory() { + proj_dirs.config_local_dir().to_path_buf() + } else { + PathBuf::from(".").join(".config") + }; + directory +} + +pub fn initialize_logging() -> Result<()> { + let directory = get_data_dir(); + std::fs::create_dir_all(directory.clone())?; + let log_path = directory.join(LOG_FILE.clone()); + let log_file = std::fs::File::create(log_path)?; + std::env::set_var( + "RUST_LOG", + std::env::var("RUST_LOG") + .or_else(|_| std::env::var(LOG_ENV.clone())) + .unwrap_or_else(|_| format!("{}=info", env!("CARGO_CRATE_NAME"))), + ); + let file_subscriber = tracing_subscriber::fmt::layer() + .with_file(true) + .with_line_number(true) + .with_writer(log_file) + .with_target(false) + .with_ansi(false) + .with_filter(tracing_subscriber::filter::EnvFilter::from_default_env()); + tracing_subscriber::registry() + .with(file_subscriber) + .with(ErrorLayer::default()) + .init(); + Ok(()) +} + +/// Similar to the `std::dbg!` macro, but generates `tracing` events rather +/// than printing to stdout. +/// +/// By default, the verbosity level for the generated events is `DEBUG`, but +/// this can be customized. +#[macro_export] +macro_rules! trace_dbg { + (target: $target:expr, level: $level:expr, $ex:expr) => {{ + match $ex { + value => { + tracing::event!(target: $target, $level, ?value, stringify!($ex)); + value + } + } + }}; + (level: $level:expr, $ex:expr) => { + trace_dbg!(target: module_path!(), level: $level, $ex) + }; + (target: $target:expr, $ex:expr) => { + trace_dbg!(target: $target, level: tracing::Level::DEBUG, $ex) + }; + ($ex:expr) => { + trace_dbg!(level: tracing::Level::DEBUG, $ex) + }; +} + +pub fn version() -> String { + let author = clap::crate_authors!(); + + // let current_exe_path = PathBuf::from(clap::crate_name!()).display().to_string(); + let config_dir_path = get_config_dir().display().to_string(); + let data_dir_path = get_data_dir().display().to_string(); + + format!( + "\ +authors: {author} + +Config directory: {config_dir_path} +Data directory: {data_dir_path}" + ) +} diff --git a/src/bin/tui/value_display.rs b/src/bin/tui/value_display.rs deleted file mode 100644 index 2124f85..0000000 --- a/src/bin/tui/value_display.rs +++ /dev/null @@ -1,52 +0,0 @@ -use dust_lang::Value; -use ratatui::{ - style::{Color, Style}, - widgets::{Block, Borders, List, Paragraph}, - Frame, -}; - -use crate::{Action, Elm, Result}; - -pub struct ValueDisplay { - value: Value, -} - -impl ValueDisplay { - pub fn new(value: Value) -> Self { - ValueDisplay { value } - } -} - -impl Elm for ValueDisplay { - fn update(&mut self, _message: Action) -> Result> { - Ok(None) - } - - fn view(&self, frame: &mut Frame) { - match &self.value { - Value::List(list) => { - let widget = List::new(list.items().iter().map(|value| value.to_string())) - .block(Block::default().title("list").borders(Borders::all())); - - frame.render_widget(widget, frame.size()); - } - Value::Map(_) => todo!(), - Value::Function(_) => todo!(), - Value::String(string) => { - let widget = - Paragraph::new(string.as_str()).style(Style::default().fg(Color::Green)); - - frame.render_widget(widget, frame.size()); - } - Value::Float(_) => todo!(), - Value::Integer(integer) => { - let widget = - Paragraph::new(integer.to_string()).style(Style::default().fg(Color::Red)); - - frame.render_widget(widget, frame.size()); - } - Value::Boolean(_) => todo!(), - Value::Option(_) => todo!(), - } - } -} diff --git a/src/built_in_functions/output.rs b/src/built_in_functions/output.rs index 6735410..ced0c19 100644 --- a/src/built_in_functions/output.rs +++ b/src/built_in_functions/output.rs @@ -1,4 +1,25 @@ -use crate::{BuiltInFunction, Map, Result, Type, Value}; +use lazy_static::lazy_static; + +use crate::{BuiltInFunction, Error, Map, Result, Type, Value}; + +lazy_static! { + static ref OUTPUT_MODE: OutputMode = { + if let Ok(variable) = std::env::var("DUST_OUTPUT_MODE") { + if variable == "SILENT" { + OutputMode::Silent + } else { + OutputMode::Normal + } + } else { + OutputMode::Normal + } + }; +} + +pub enum OutputMode { + Normal, + Silent, +} pub struct Output; @@ -8,11 +29,15 @@ impl BuiltInFunction for Output { } fn run(&self, arguments: &[Value], _context: &Map) -> Result { - for argument in arguments { - println!("{argument}"); + Error::expect_argument_amount(self, 1, arguments.len())?; + + let value = arguments.first().unwrap(); + + if let OutputMode::Normal = *OUTPUT_MODE { + println!("{value}"); } - Ok(Value::Option(None)) + Ok(Value::default()) } fn r#type(&self) -> Type { diff --git a/src/interpret.rs b/src/interpret.rs index 0ebc40c..f8223c3 100644 --- a/src/interpret.rs +++ b/src/interpret.rs @@ -41,9 +41,6 @@ pub fn interpret(source: &str) -> Result { /// ); /// ``` pub fn interpret_with_context(source: &str, context: Map) -> Result { - let mut parser = Parser::new(); - parser.set_language(language())?; - let mut interpreter = Interpreter::new(context)?; let value = interpreter.run(source)?; @@ -91,7 +88,7 @@ impl Interpreter { if let Some(abstract_tree) = &self.abstract_tree { abstract_tree.run(source, &self.context) } else { - Ok(Value::Option(None)) + Ok(Value::none()) } } diff --git a/src/value/function.rs b/src/value/function.rs index f9ae590..20fc2f2 100644 --- a/src/value/function.rs +++ b/src/value/function.rs @@ -37,13 +37,13 @@ impl Function { &self.r#type } - pub fn return_type(&self) -> Result<&Type> { + pub fn return_type(&self) -> &Type { match &self.r#type { Type::Function { parameter_types: _, return_type, - } => Ok(return_type.as_ref()), - _ => todo!(), + } => return_type.as_ref(), + _ => &Type::None, } } @@ -65,10 +65,31 @@ impl Function { impl Display for Function { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!( - f, - "Function {{ parameters: {:?}, body: {:?} }}", - self.parameters, self.body - ) + write!(f, "(")?; + + let (parameter_types, return_type) = if let Type::Function { + parameter_types, + return_type, + } = &self.r#type + { + (parameter_types, return_type) + } else { + return Err(fmt::Error); + }; + + for (index, (parameter, r#type)) in self + .parameters + .iter() + .zip(parameter_types.iter()) + .enumerate() + { + write!(f, "{} <{}>", parameter.inner(), r#type)?; + + if index != self.parameters.len() - 1 { + write!(f, ", ")?; + } + } + + write!(f, ") -> {}", return_type) } } diff --git a/src/value/mod.rs b/src/value/mod.rs index 69d42f3..de253ca 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -41,7 +41,7 @@ pub enum Value { impl Default for Value { fn default() -> Self { - Value::Option(None) + Value::none() } } @@ -87,6 +87,10 @@ impl Value { r#type } + pub fn none() -> Self { + Value::Option(None) + } + pub fn is_string(&self) -> bool { matches!(self, Value::String(_)) } diff --git a/tree-sitter-dust/highlights.scm b/tree-sitter-dust/highlights.scm index 5f4aaa8..ca0e05e 100644 --- a/tree-sitter-dust/highlights.scm +++ b/tree-sitter-dust/highlights.scm @@ -39,6 +39,7 @@ [ "async" "else" + "else if" "false" "for" "if"