Restart TUI
This commit is contained in:
parent
dec9e70e4f
commit
9d94cb9af4
568
Cargo.lock
generated
568
Cargo.lock
generated
@ -17,6 +17,17 @@ version = "1.0.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ahash"
|
||||||
|
version = "0.7.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
"once_cell",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ahash"
|
name = "ahash"
|
||||||
version = "0.8.7"
|
version = "0.8.7"
|
||||||
@ -88,7 +99,7 @@ version = "1.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
|
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -98,7 +109,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628"
|
checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"windows-sys",
|
"windows-sys 0.48.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-trait"
|
||||||
|
version = "0.1.75"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fdf6721fb0140e4f897002dd086c06f6c27775df19cfe1fccb21181a48fd2c98"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.37",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -122,12 +144,28 @@ dependencies = [
|
|||||||
"rustc-demangle",
|
"rustc-demangle",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.13.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.21.4"
|
version = "0.21.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2"
|
checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "better-panic"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6fa9e1d11a268684cbd90ed36370d7577afb6c62d912ddff5c15fc34343e5036"
|
||||||
|
dependencies = [
|
||||||
|
"backtrace",
|
||||||
|
"console",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.3.2"
|
version = "1.3.2"
|
||||||
@ -143,6 +181,15 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block-buffer"
|
||||||
|
version = "0.10.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.14.0"
|
version = "3.14.0"
|
||||||
@ -197,6 +244,9 @@ dependencies = [
|
|||||||
"anstyle",
|
"anstyle",
|
||||||
"clap_lex",
|
"clap_lex",
|
||||||
"strsim",
|
"strsim",
|
||||||
|
"terminal_size",
|
||||||
|
"unicase",
|
||||||
|
"unicode-width",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -273,6 +323,37 @@ dependencies = [
|
|||||||
"unicode-width",
|
"unicode-width",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "config"
|
||||||
|
version = "0.13.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23738e11972c7643e4ec947840fc463b6a571afcd3e735bdfce7d03c7a784aca"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"json5",
|
||||||
|
"lazy_static",
|
||||||
|
"nom",
|
||||||
|
"pathdiff",
|
||||||
|
"ron",
|
||||||
|
"rust-ini",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"toml 0.5.11",
|
||||||
|
"yaml-rust",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "console"
|
||||||
|
version = "0.15.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8"
|
||||||
|
dependencies = [
|
||||||
|
"encode_unicode",
|
||||||
|
"lazy_static",
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.45.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.9.3"
|
version = "0.9.3"
|
||||||
@ -289,6 +370,15 @@ version = "0.8.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
|
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cpufeatures"
|
||||||
|
version = "0.2.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-deque"
|
name = "crossbeam-deque"
|
||||||
version = "0.8.3"
|
version = "0.8.3"
|
||||||
@ -365,6 +455,16 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crypto-common"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
"typenum",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "csv"
|
name = "csv"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
@ -386,6 +486,33 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_deref"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dcdbcee2d9941369faba772587a565f4f534e42cb8d17e5295871de730163b2b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 1.0.109",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "diff"
|
||||||
|
version = "0.1.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "digest"
|
||||||
|
version = "0.10.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||||
|
dependencies = [
|
||||||
|
"block-buffer",
|
||||||
|
"crypto-common",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "directories"
|
name = "directories"
|
||||||
version = "5.0.1"
|
version = "5.0.1"
|
||||||
@ -404,24 +531,38 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"option-ext",
|
"option-ext",
|
||||||
"redox_users",
|
"redox_users",
|
||||||
"windows-sys",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dlv-list"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dust-lang"
|
name = "dust-lang"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi_term",
|
"ansi_term",
|
||||||
|
"better-panic",
|
||||||
"cc",
|
"cc",
|
||||||
"clap",
|
"clap",
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
"comfy-table",
|
"comfy-table",
|
||||||
|
"config",
|
||||||
"crossterm 0.27.0",
|
"crossterm 0.27.0",
|
||||||
"csv",
|
"csv",
|
||||||
|
"derive_deref",
|
||||||
"directories",
|
"directories",
|
||||||
"futures",
|
"futures",
|
||||||
"git2",
|
"git2",
|
||||||
|
"human-panic",
|
||||||
|
"json5",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"pretty_assertions",
|
||||||
"rand",
|
"rand",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
"rayon",
|
"rayon",
|
||||||
@ -429,15 +570,19 @@ dependencies = [
|
|||||||
"rustyline",
|
"rustyline",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"serial_int",
|
||||||
"signal-hook",
|
"signal-hook",
|
||||||
|
"strip-ansi-escapes",
|
||||||
|
"strum 0.25.0",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"toml",
|
"toml 0.8.8",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-error",
|
"tracing-error",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"tree-sitter",
|
"tree-sitter",
|
||||||
"tui-textarea",
|
"tui-textarea",
|
||||||
|
"tui-tree-widget",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -446,6 +591,12 @@ version = "1.9.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
|
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "encode_unicode"
|
||||||
|
version = "0.3.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
version = "0.8.33"
|
version = "0.8.33"
|
||||||
@ -475,7 +626,7 @@ checksum = "add4f07d43996f76ef320709726a556a9d4f965d9410d8d0271132d2f8293480"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"errno-dragonfly",
|
"errno-dragonfly",
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -522,7 +673,7 @@ checksum = "ef033ed5e9bad94e55838ca0ca906db0e043f517adda0c8b79c7a8c66c93c1b5"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -644,6 +795,16 @@ dependencies = [
|
|||||||
"slab",
|
"slab",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "generic-array"
|
||||||
|
version = "0.14.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||||
|
dependencies = [
|
||||||
|
"typenum",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.10"
|
version = "0.2.10"
|
||||||
@ -700,6 +861,9 @@ name = "hashbrown"
|
|||||||
version = "0.12.3"
|
version = "0.12.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||||
|
dependencies = [
|
||||||
|
"ahash 0.7.7",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
@ -707,7 +871,7 @@ version = "0.14.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12"
|
checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash 0.8.7",
|
||||||
"allocator-api2",
|
"allocator-api2",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -729,7 +893,7 @@ version = "0.5.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
|
checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -766,6 +930,22 @@ version = "1.0.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "human-panic"
|
||||||
|
version = "1.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a79a67745be0cb8dd2771f03b24c2f25df98d5471fe7a595d668cfa2e6f843d"
|
||||||
|
dependencies = [
|
||||||
|
"anstream",
|
||||||
|
"anstyle",
|
||||||
|
"backtrace",
|
||||||
|
"os_info",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"toml 0.8.8",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper"
|
name = "hyper"
|
||||||
version = "0.14.27"
|
version = "0.14.27"
|
||||||
@ -884,6 +1064,17 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "json5"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1"
|
||||||
|
dependencies = [
|
||||||
|
"pest",
|
||||||
|
"pest_derive",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
@ -947,6 +1138,12 @@ dependencies = [
|
|||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linked-hash-map"
|
||||||
|
version = "0.5.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.4.8"
|
version = "0.4.8"
|
||||||
@ -1008,6 +1205,12 @@ version = "0.3.17"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "minimal-lexical"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
@ -1026,7 +1229,7 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"wasi",
|
"wasi",
|
||||||
"windows-sys",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1067,6 +1270,16 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nom"
|
||||||
|
version = "7.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"minimal-lexical",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nu-ansi-term"
|
name = "nu-ansi-term"
|
||||||
version = "0.46.0"
|
version = "0.46.0"
|
||||||
@ -1152,6 +1365,27 @@ version = "0.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ordered-multimap"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a"
|
||||||
|
dependencies = [
|
||||||
|
"dlv-list",
|
||||||
|
"hashbrown 0.12.3",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "os_info"
|
||||||
|
version = "3.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "006e42d5b888366f1880eda20371fedde764ed2213dc8496f49622fa0c99cd5e"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"serde",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "overload"
|
name = "overload"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@ -1184,7 +1418,7 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"redox_syscall 0.3.5",
|
"redox_syscall 0.3.5",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"windows-targets",
|
"windows-targets 0.48.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1193,12 +1427,63 @@ version = "1.0.14"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
|
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pathdiff"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.0"
|
version = "2.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
|
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pest"
|
||||||
|
version = "2.7.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae9cee2a55a544be8b89dc6848072af97a20f2422603c10865be2a42b580fff5"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"thiserror",
|
||||||
|
"ucd-trie",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pest_derive"
|
||||||
|
version = "2.7.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "81d78524685f5ef2a3b3bd1cafbc9fcabb036253d9b1463e726a91cd16e2dfc2"
|
||||||
|
dependencies = [
|
||||||
|
"pest",
|
||||||
|
"pest_generator",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pest_generator"
|
||||||
|
version = "2.7.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68bd1206e71118b5356dae5ddc61c8b11e28b09ef6a31acbd15ea48a28e0c227"
|
||||||
|
dependencies = [
|
||||||
|
"pest",
|
||||||
|
"pest_meta",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.37",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pest_meta"
|
||||||
|
version = "2.7.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7c747191d4ad9e4a4ab9c8798f1e82a39affe7ef9648390b7e5548d18e099de6"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
"pest",
|
||||||
|
"sha2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project-lite"
|
name = "pin-project-lite"
|
||||||
version = "0.2.13"
|
version = "0.2.13"
|
||||||
@ -1223,6 +1508,16 @@ version = "0.2.17"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pretty_assertions"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66"
|
||||||
|
dependencies = [
|
||||||
|
"diff",
|
||||||
|
"yansi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.67"
|
version = "1.0.67"
|
||||||
@ -1399,7 +1694,7 @@ version = "0.11.22"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b"
|
checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64 0.21.4",
|
||||||
"bytes",
|
"bytes",
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
@ -1431,6 +1726,27 @@ dependencies = [
|
|||||||
"winreg",
|
"winreg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ron"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.13.1",
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rust-ini"
|
||||||
|
version = "0.18.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"ordered-multimap",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-demangle"
|
name = "rustc-demangle"
|
||||||
version = "0.1.23"
|
version = "0.1.23"
|
||||||
@ -1447,7 +1763,7 @@ dependencies = [
|
|||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
"windows-sys",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1503,7 +1819,7 @@ version = "0.1.22"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88"
|
checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1568,9 +1884,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_spanned"
|
name = "serde_spanned"
|
||||||
version = "0.6.3"
|
version = "0.6.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186"
|
checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
@ -1587,6 +1903,23 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serial_int"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2f38a6ace048d852c367559b6d6d7c809d1a1812e500537c9083287feecb3519"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sha2"
|
||||||
|
version = "0.10.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures",
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sharded-slab"
|
name = "sharded-slab"
|
||||||
version = "0.1.7"
|
version = "0.1.7"
|
||||||
@ -1658,7 +1991,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
|
checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1677,6 +2010,15 @@ version = "1.0.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0"
|
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]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
@ -1777,7 +2119,17 @@ dependencies = [
|
|||||||
"fastrand",
|
"fastrand",
|
||||||
"redox_syscall 0.3.5",
|
"redox_syscall 0.3.5",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys",
|
"windows-sys 0.48.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "terminal_size"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7"
|
||||||
|
dependencies = [
|
||||||
|
"rustix",
|
||||||
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1840,7 +2192,7 @@ dependencies = [
|
|||||||
"signal-hook-registry",
|
"signal-hook-registry",
|
||||||
"socket2 0.5.5",
|
"socket2 0.5.5",
|
||||||
"tokio-macros",
|
"tokio-macros",
|
||||||
"windows-sys",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1880,9 +2232,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.8.2"
|
version = "0.5.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d"
|
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml"
|
||||||
|
version = "0.8.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
@ -1892,18 +2253,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_datetime"
|
name = "toml_datetime"
|
||||||
version = "0.6.3"
|
version = "0.6.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
|
checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_edit"
|
name = "toml_edit"
|
||||||
version = "0.20.2"
|
version = "0.21.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338"
|
checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 2.0.2",
|
"indexmap 2.0.2",
|
||||||
"serde",
|
"serde",
|
||||||
@ -2017,6 +2378,37 @@ dependencies = [
|
|||||||
"unicode-width",
|
"unicode-width",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tui-tree-widget"
|
||||||
|
version = "0.16.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "136011b328c4f392499a02c4b5b78d509fb297bf9c10f2bda5d11d65cb946e4c"
|
||||||
|
dependencies = [
|
||||||
|
"ratatui",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typenum"
|
||||||
|
version = "1.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ucd-trie"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicase"
|
||||||
|
version = "2.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
|
||||||
|
dependencies = [
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-bidi"
|
name = "unicode-bidi"
|
||||||
version = "0.3.13"
|
version = "0.3.13"
|
||||||
@ -2067,6 +2459,15 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uuid"
|
||||||
|
version = "1.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "valuable"
|
name = "valuable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -2085,6 +2486,26 @@ version = "0.9.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
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]]
|
[[package]]
|
||||||
name = "want"
|
name = "want"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
@ -2198,13 +2619,37 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.45.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets 0.42.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.48.0"
|
version = "0.48.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-targets",
|
"windows-targets 0.48.5",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.42.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm 0.42.2",
|
||||||
|
"windows_aarch64_msvc 0.42.2",
|
||||||
|
"windows_i686_gnu 0.42.2",
|
||||||
|
"windows_i686_msvc 0.42.2",
|
||||||
|
"windows_x86_64_gnu 0.42.2",
|
||||||
|
"windows_x86_64_gnullvm 0.42.2",
|
||||||
|
"windows_x86_64_msvc 0.42.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2213,51 +2658,93 @@ version = "0.48.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows_aarch64_gnullvm",
|
"windows_aarch64_gnullvm 0.48.5",
|
||||||
"windows_aarch64_msvc",
|
"windows_aarch64_msvc 0.48.5",
|
||||||
"windows_i686_gnu",
|
"windows_i686_gnu 0.48.5",
|
||||||
"windows_i686_msvc",
|
"windows_i686_msvc 0.48.5",
|
||||||
"windows_x86_64_gnu",
|
"windows_x86_64_gnu 0.48.5",
|
||||||
"windows_x86_64_gnullvm",
|
"windows_x86_64_gnullvm 0.48.5",
|
||||||
"windows_x86_64_msvc",
|
"windows_x86_64_msvc 0.48.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.42.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_gnullvm"
|
name = "windows_aarch64_gnullvm"
|
||||||
version = "0.48.5"
|
version = "0.48.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.42.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_msvc"
|
name = "windows_aarch64_msvc"
|
||||||
version = "0.48.5"
|
version = "0.48.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.42.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnu"
|
name = "windows_i686_gnu"
|
||||||
version = "0.48.5"
|
version = "0.48.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.42.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_msvc"
|
name = "windows_i686_msvc"
|
||||||
version = "0.48.5"
|
version = "0.48.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.42.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnu"
|
name = "windows_x86_64_gnu"
|
||||||
version = "0.48.5"
|
version = "0.48.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.42.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnullvm"
|
name = "windows_x86_64_gnullvm"
|
||||||
version = "0.48.5"
|
version = "0.48.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.42.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_msvc"
|
name = "windows_x86_64_msvc"
|
||||||
version = "0.48.5"
|
version = "0.48.5"
|
||||||
@ -2280,9 +2767,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
|
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"windows-sys",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yaml-rust"
|
||||||
|
version = "0.4.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
|
||||||
|
dependencies = [
|
||||||
|
"linked-hash-map",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yansi"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.7.32"
|
version = "0.7.32"
|
||||||
|
15
Cargo.toml
15
Cargo.toml
@ -5,6 +5,7 @@ version = "0.4.0"
|
|||||||
repository = "https://git.jeffa.io/jeff/dust.git"
|
repository = "https://git.jeffa.io/jeff/dust.git"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
authors = ["Jeff Anderson"]
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "dust"
|
name = "dust"
|
||||||
@ -20,15 +21,23 @@ opt-level = 3
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ansi_term = "0.12.1"
|
ansi_term = "0.12.1"
|
||||||
clap = { version = "4.4.4", features = ["derive"] }
|
better-panic = "0.3.0"
|
||||||
|
clap = { version = "4.4.4", features = ["derive", "cargo", "wrap_help", "unicode", "string", "unstable-styles"] }
|
||||||
color-eyre = "0.6.2"
|
color-eyre = "0.6.2"
|
||||||
comfy-table = "7.0.1"
|
comfy-table = "7.0.1"
|
||||||
|
config = "0.13.3"
|
||||||
crossterm = { version = "0.27.0", features = ["serde", "event-stream"] }
|
crossterm = { version = "0.27.0", features = ["serde", "event-stream"] }
|
||||||
csv = "1.2.2"
|
csv = "1.2.2"
|
||||||
|
derive_deref = "1.1.1"
|
||||||
directories = "5.0.1"
|
directories = "5.0.1"
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
git2 = "0.18.1"
|
git2 = "0.18.1"
|
||||||
|
human-panic = "1.2.0"
|
||||||
|
json5 = "0.4.1"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
|
libc = "0.2.148"
|
||||||
|
log = "0.4.20"
|
||||||
|
pretty_assertions = "1.4.0"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
ratatui = "0.25.0"
|
ratatui = "0.25.0"
|
||||||
rayon = "1.8.0"
|
rayon = "1.8.0"
|
||||||
@ -36,7 +45,10 @@ reqwest = { version = "0.11.20", features = ["blocking", "json"] }
|
|||||||
rustyline = { version = "12.0.0", features = ["derive", "with-file-history"] }
|
rustyline = { version = "12.0.0", features = ["derive", "with-file-history"] }
|
||||||
serde = { version = "1.0.188", features = ["derive"] }
|
serde = { version = "1.0.188", features = ["derive"] }
|
||||||
serde_json = "1.0.107"
|
serde_json = "1.0.107"
|
||||||
|
serial_int = "2.0.0"
|
||||||
signal-hook = "0.3.17"
|
signal-hook = "0.3.17"
|
||||||
|
strip-ansi-escapes = "0.2.0"
|
||||||
|
strum = { version = "0.25.0", features = ["derive"] }
|
||||||
tokio = { version = "1.35.1", features = ["signal-hook-registry", "macros", "signal", "rt", "time", "rt-multi-thread"] }
|
tokio = { version = "1.35.1", features = ["signal-hook-registry", "macros", "signal", "rt", "time", "rt-multi-thread"] }
|
||||||
tokio-util = "0.7.10"
|
tokio-util = "0.7.10"
|
||||||
toml = "0.8.1"
|
toml = "0.8.1"
|
||||||
@ -45,6 +57,7 @@ tracing-error = "0.2.0"
|
|||||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||||
tree-sitter = "0.20.10"
|
tree-sitter = "0.20.10"
|
||||||
tui-textarea = { version = "0.4.0", features = ["search"] }
|
tui-textarea = { version = "0.4.0", features = ["search"] }
|
||||||
|
tui-tree-widget = "0.16.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
cc = "1.0"
|
cc = "1.0"
|
||||||
|
14
assets/config.json5
Normal file
14
assets/config.json5
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"keybindings": {
|
||||||
|
"Home": {
|
||||||
|
"<q>": "Quit", // Quit the application
|
||||||
|
"<Ctrl-d>": "Quit", // Another way to quit
|
||||||
|
"<Ctrl-c>": "Quit", // Yet another way to quit
|
||||||
|
"<Ctrl-z>": "Suspend", // Suspend the application
|
||||||
|
"<up>": "Up",
|
||||||
|
"<down>": "Down",
|
||||||
|
"<left>": "Left",
|
||||||
|
"<right>": "Right",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
25
src/bin/tui/action.rs
Normal file
25
src/bin/tui/action.rs
Normal file
@ -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,
|
||||||
|
}
|
@ -1,88 +1,152 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crossterm::event::KeyCode;
|
use color_eyre::eyre::Result;
|
||||||
use dust_lang::Map;
|
use crossterm::event::KeyEvent;
|
||||||
use ratatui::Frame;
|
use ratatui::prelude::Rect;
|
||||||
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
|
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 {
|
pub struct App {
|
||||||
action_rx: UnboundedReceiver<Action>,
|
pub config: Config,
|
||||||
action_tx: UnboundedSender<Action>,
|
pub components: Vec<Box<dyn Component>>,
|
||||||
interpreter_display: InterpreterDisplay,
|
pub should_quit: bool,
|
||||||
should_quit: bool,
|
pub should_suspend: bool,
|
||||||
|
pub mode: Mode,
|
||||||
|
pub last_tick_key_events: Vec<KeyEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub fn new(
|
pub fn new(path: PathBuf) -> Result<Self> {
|
||||||
action_rx: UnboundedReceiver<Action>,
|
let home = Home::new(path)?;
|
||||||
action_tx: UnboundedSender<Action>,
|
let config = Config::new()?;
|
||||||
path: PathBuf,
|
let mode = Mode::Home;
|
||||||
) -> Result<Self> {
|
Ok(Self {
|
||||||
let interpreter_display = InterpreterDisplay::new(Map::new(), path)?;
|
components: vec![Box::new(home)],
|
||||||
|
|
||||||
Ok(App {
|
|
||||||
action_rx,
|
|
||||||
action_tx,
|
|
||||||
interpreter_display,
|
|
||||||
should_quit: false,
|
should_quit: false,
|
||||||
|
should_suspend: false,
|
||||||
|
config,
|
||||||
|
mode,
|
||||||
|
last_tick_key_events: Vec::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(&mut self) -> Result<()> {
|
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 {
|
tui.enter()?;
|
||||||
if self.should_quit {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(action) = terminal.next().await {
|
for component in self.components.iter_mut() {
|
||||||
self.action_tx.send(action)?;
|
component.register_action_handler(action_tx.clone())?;
|
||||||
} 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)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Elm for App {
|
|
||||||
fn update(&mut self, message: Action) -> Result<Option<Action>> {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
12
src/bin/tui/cli.rs
Normal file
12
src/bin/tui/cli.rs
Normal file
@ -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,
|
||||||
|
}
|
126
src/bin/tui/components.rs
Normal file
126
src/bin/tui/components.rs
Normal file
@ -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<Action>) -> 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<Option<Action>>` - An action to be processed or none.
|
||||||
|
fn handle_events(&mut self, event: Option<Event>) -> Result<Option<Action>> {
|
||||||
|
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<Option<Action>>` - An action to be processed or none.
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
fn handle_key_events(&mut self, key: KeyEvent) -> Result<Option<Action>> {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
/// Handle mouse events and produce actions if necessary.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `mouse` - A mouse event to be processed.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Result<Option<Action>>` - An action to be processed or none.
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
fn handle_mouse_events(&mut self, mouse: MouseEvent) -> Result<Option<Action>> {
|
||||||
|
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<Option<Action>>` - An action to be processed or none.
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
||||||
|
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<()>;
|
||||||
|
}
|
90
src/bin/tui/components/fps.rs
Normal file
90
src/bin/tui/components/fps.rs
Normal file
@ -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<Option<Action>> {
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
}
|
99
src/bin/tui/components/home.rs
Normal file
99
src/bin/tui/components/home.rs
Normal file
@ -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<UnboundedSender<Action>>,
|
||||||
|
config: Config,
|
||||||
|
path: PathBuf,
|
||||||
|
source: String,
|
||||||
|
context: Map,
|
||||||
|
context_tree_state: TreeState<String>,
|
||||||
|
context_display: MapDisplay<'a>,
|
||||||
|
output: dust_lang::Result<Value>,
|
||||||
|
last_modified: SystemTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Home<'a> {
|
||||||
|
pub fn new(path: PathBuf) -> Result<Self> {
|
||||||
|
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<Action>) -> 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<Option<Action>> {
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
}
|
5
src/bin/tui/components/list_display.rs
Normal file
5
src/bin/tui/components/list_display.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
use dust_lang::List;
|
||||||
|
|
||||||
|
pub struct ListDisplay {
|
||||||
|
list: List,
|
||||||
|
}
|
106
src/bin/tui/components/map_display.rs
Normal file
106
src/bin/tui/components/map_display.rs
Normal file
@ -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<SerialGenerator<usize>> = Mutex::new(SerialGenerator::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_tree_item<'a>(key: String, value: &Value) -> Result<TreeItem<'a, usize>> {
|
||||||
|
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} <str> = {value}"),
|
||||||
|
),
|
||||||
|
Value::Float(float) => TreeItem::new_leaf(
|
||||||
|
ID_GENERATOR.lock().unwrap().generate(),
|
||||||
|
format!("{key} <float> = {value}"),
|
||||||
|
),
|
||||||
|
Value::Integer(integer) => TreeItem::new_leaf(
|
||||||
|
ID_GENERATOR.lock().unwrap().generate(),
|
||||||
|
format!("{key} <int> = {value}"),
|
||||||
|
),
|
||||||
|
Value::Boolean(_) => todo!(),
|
||||||
|
Value::Option(_) => todo!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(tree_item)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MapDisplay<'a> {
|
||||||
|
tree_state: TreeState<usize>,
|
||||||
|
items: Vec<TreeItem<'a, usize>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> MapDisplay<'a> {
|
||||||
|
pub fn new(map: Map) -> Result<Self> {
|
||||||
|
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<Option<Action>> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
563
src/bin/tui/config.rs
Normal file
563
src/bin/tui/config.rs
Normal file
@ -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<Self, config::ConfigError> {
|
||||||
|
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<Mode, HashMap<Vec<KeyEvent>, Action>>);
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for KeyBindings {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let parsed_map = HashMap::<Mode, HashMap<String, Action>>::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<KeyEvent, String> {
|
||||||
|
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<KeyEvent, String> {
|
||||||
|
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<Vec<KeyEvent>, 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::<Vec<_>>();
|
||||||
|
|
||||||
|
sequences.into_iter().map(parse_key_event).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, Deref, DerefMut)]
|
||||||
|
pub struct Styles(pub HashMap<Mode, HashMap<String, Style>>);
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Styles {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let parsed_map = HashMap::<Mode, HashMap<String, String>>::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<Color> {
|
||||||
|
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::<u8>()
|
||||||
|
.unwrap_or_default();
|
||||||
|
Some(Color::Indexed(c.wrapping_shl(8)))
|
||||||
|
} else if s.contains("color") {
|
||||||
|
let c = s
|
||||||
|
.trim_start_matches("color")
|
||||||
|
.parse::<u8>()
|
||||||
|
.unwrap_or_default();
|
||||||
|
Some(Color::Indexed(c))
|
||||||
|
} else if s.contains("gray") {
|
||||||
|
let c = 232
|
||||||
|
+ s.trim_start_matches("gray")
|
||||||
|
.parse::<u8>()
|
||||||
|
.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("<q>").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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +0,0 @@
|
|||||||
pub type Result<T> = std::result::Result<T, Error>;
|
|
||||||
|
|
||||||
pub enum Error {}
|
|
@ -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<Self> {
|
|
||||||
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<Option<Action>> {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -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<PathBuf> =
|
|
||||||
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> {
|
|
||||||
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)
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,67 +1,46 @@
|
|||||||
pub mod app;
|
#![allow(dead_code)]
|
||||||
pub mod interpreter_display;
|
#![allow(unused_imports)]
|
||||||
pub mod log;
|
#![allow(unused_variables)]
|
||||||
pub mod terminal;
|
|
||||||
pub mod value_display;
|
|
||||||
|
|
||||||
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::{
|
use crate::{
|
||||||
app::App,
|
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)]
|
async fn tokio_main() -> Result<()> {
|
||||||
#[command(author, version, about, long_about = None)]
|
initialize_panic_handler()?;
|
||||||
struct Cli {
|
initialize_logging()?;
|
||||||
/// File with source to be run and watched by the shell.
|
|
||||||
path: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Elm {
|
env::set_var("DUST_OUTPUT_MODE", "SILENT");
|
||||||
fn update(&mut self, message: Action) -> Result<Option<Action>>;
|
|
||||||
fn view(&self, frame: &mut Frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
let args = Cli::parse();
|
||||||
pub enum Action {
|
let mut app = App::new(args.path)?;
|
||||||
Init,
|
app.run().await?;
|
||||||
Quit,
|
|
||||||
Error,
|
Ok(())
|
||||||
Closed,
|
|
||||||
Tick,
|
|
||||||
Render,
|
|
||||||
FocusGained,
|
|
||||||
FocusLost,
|
|
||||||
Paste(String),
|
|
||||||
Key(KeyEvent),
|
|
||||||
Mouse(MouseEvent),
|
|
||||||
Resize(u16, u16),
|
|
||||||
UpdateValue(Value),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() -> Result<()> {
|
||||||
initialize_logging().unwrap();
|
if let Err(e) = tokio_main().await {
|
||||||
|
eprintln!("{} error: Something went wrong", env!("CARGO_PKG_NAME"));
|
||||||
let args = Cli::parse();
|
Err(e)
|
||||||
let (action_tx, action_rx) = mpsc::unbounded_channel();
|
|
||||||
let path = if let Some(path) = args.path {
|
|
||||||
PathBuf::from(path)
|
|
||||||
} else {
|
} else {
|
||||||
PathBuf::from(format!("{}/scratch.ds", get_data_dir().to_string_lossy()))
|
Ok(())
|
||||||
};
|
|
||||||
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}")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
7
src/bin/tui/mode.rs
Normal file
7
src/bin/tui/mode.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
pub enum Mode {
|
||||||
|
#[default]
|
||||||
|
Home,
|
||||||
|
}
|
@ -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<CrosstermBackend<Stderr>>,
|
|
||||||
pub task: JoinHandle<()>,
|
|
||||||
pub cancellation_token: CancellationToken,
|
|
||||||
pub event_rx: UnboundedReceiver<Action>,
|
|
||||||
pub event_tx: UnboundedSender<Action>,
|
|
||||||
pub frame_rate: f64,
|
|
||||||
pub tick_rate: f64,
|
|
||||||
pub mouse: bool,
|
|
||||||
pub paste: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Terminal {
|
|
||||||
pub fn new() -> Result<Self> {
|
|
||||||
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<Action> {
|
|
||||||
self.event_rx.recv().await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for Terminal {
|
|
||||||
type Target = ratatui::Terminal<CrosstermBackend<Stderr>>;
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -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::{
|
use std::{
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
@ -19,9 +12,8 @@ use crossterm::{
|
|||||||
},
|
},
|
||||||
terminal::{EnterAlternateScreen, LeaveAlternateScreen},
|
terminal::{EnterAlternateScreen, LeaveAlternateScreen},
|
||||||
};
|
};
|
||||||
use dust_lang::Value;
|
|
||||||
use futures::{FutureExt, StreamExt};
|
use futures::{FutureExt, StreamExt};
|
||||||
use ratatui::{backend::CrosstermBackend as Backend, Frame};
|
use ratatui::backend::CrosstermBackend as Backend;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
sync::mpsc::{self, UnboundedReceiver, UnboundedSender},
|
sync::mpsc::{self, UnboundedReceiver, UnboundedSender},
|
||||||
@ -29,13 +21,14 @@ use tokio::{
|
|||||||
};
|
};
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
pub trait Elm {
|
pub type IO = std::io::Stderr;
|
||||||
fn update(&mut self, message: Action) -> Result<Option<Action>>;
|
pub fn io() -> IO {
|
||||||
fn view(&self, frame: &mut Frame);
|
std::io::stderr()
|
||||||
}
|
}
|
||||||
|
pub type Frame<'a> = ratatui::Frame<'a>;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub enum Action {
|
pub enum Event {
|
||||||
Init,
|
Init,
|
||||||
Quit,
|
Quit,
|
||||||
Error,
|
Error,
|
||||||
@ -48,15 +41,14 @@ pub enum Action {
|
|||||||
Key(KeyEvent),
|
Key(KeyEvent),
|
||||||
Mouse(MouseEvent),
|
Mouse(MouseEvent),
|
||||||
Resize(u16, u16),
|
Resize(u16, u16),
|
||||||
UpdateValue(Value),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Tui {
|
pub struct Tui {
|
||||||
pub terminal: ratatui::Terminal<Backend<std::io::Stderr>>,
|
pub terminal: ratatui::Terminal<Backend<IO>>,
|
||||||
pub task: JoinHandle<()>,
|
pub task: JoinHandle<()>,
|
||||||
pub cancellation_token: CancellationToken,
|
pub cancellation_token: CancellationToken,
|
||||||
pub event_rx: UnboundedReceiver<Action>,
|
pub event_rx: UnboundedReceiver<Event>,
|
||||||
pub event_tx: UnboundedSender<Action>,
|
pub event_tx: UnboundedSender<Event>,
|
||||||
pub frame_rate: f64,
|
pub frame_rate: f64,
|
||||||
pub tick_rate: f64,
|
pub tick_rate: f64,
|
||||||
pub mouse: bool,
|
pub mouse: bool,
|
||||||
@ -65,14 +57,15 @@ pub struct Tui {
|
|||||||
|
|
||||||
impl Tui {
|
impl Tui {
|
||||||
pub fn new() -> Result<Self> {
|
pub fn new() -> Result<Self> {
|
||||||
let tick_rate = 4.0;
|
let tick_rate = 2.0;
|
||||||
let frame_rate = 60.0;
|
let frame_rate = 30.0;
|
||||||
let terminal = ratatui::Terminal::new(Backend::new(std::io::stderr()))?;
|
let terminal = ratatui::Terminal::new(Backend::new(io()))?;
|
||||||
let (event_tx, event_rx) = mpsc::unbounded_channel();
|
let (event_tx, event_rx) = mpsc::unbounded_channel();
|
||||||
let cancellation_token = CancellationToken::new();
|
let cancellation_token = CancellationToken::new();
|
||||||
let task = tokio::spawn(async {});
|
let task = tokio::spawn(async {});
|
||||||
let mouse = false;
|
let mouse = false;
|
||||||
let paste = false;
|
let paste = false;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
terminal,
|
terminal,
|
||||||
task,
|
task,
|
||||||
@ -109,19 +102,25 @@ impl Tui {
|
|||||||
pub fn start(&mut self) {
|
pub fn start(&mut self) {
|
||||||
let tick_delay = std::time::Duration::from_secs_f64(1.0 / self.tick_rate);
|
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);
|
let render_delay = std::time::Duration::from_secs_f64(1.0 / self.frame_rate);
|
||||||
|
|
||||||
self.cancel();
|
self.cancel();
|
||||||
self.cancellation_token = CancellationToken::new();
|
self.cancellation_token = CancellationToken::new();
|
||||||
|
|
||||||
let _cancellation_token = self.cancellation_token.clone();
|
let _cancellation_token = self.cancellation_token.clone();
|
||||||
let _event_tx = self.event_tx.clone();
|
let _event_tx = self.event_tx.clone();
|
||||||
|
|
||||||
self.task = tokio::spawn(async move {
|
self.task = tokio::spawn(async move {
|
||||||
let mut reader = crossterm::event::EventStream::new();
|
let mut reader = crossterm::event::EventStream::new();
|
||||||
let mut tick_interval = tokio::time::interval(tick_delay);
|
let mut tick_interval = tokio::time::interval(tick_delay);
|
||||||
let mut render_interval = tokio::time::interval(render_delay);
|
let mut render_interval = tokio::time::interval(render_delay);
|
||||||
_event_tx.send(Action::Init).unwrap();
|
|
||||||
|
_event_tx.send(Event::Init).unwrap();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let tick_delay = tick_interval.tick();
|
let tick_delay = tick_interval.tick();
|
||||||
let render_delay = render_interval.tick();
|
let render_delay = render_interval.tick();
|
||||||
let crossterm_event = reader.next().fuse();
|
let crossterm_event = reader.next().fuse();
|
||||||
|
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
_ = _cancellation_token.cancelled() => {
|
_ = _cancellation_token.cancelled() => {
|
||||||
break;
|
break;
|
||||||
@ -132,37 +131,37 @@ impl Tui {
|
|||||||
match evt {
|
match evt {
|
||||||
CrosstermEvent::Key(key) => {
|
CrosstermEvent::Key(key) => {
|
||||||
if key.kind == KeyEventKind::Press {
|
if key.kind == KeyEventKind::Press {
|
||||||
_event_tx.send(Action::Key(key)).unwrap();
|
_event_tx.send(Event::Key(key)).unwrap();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
CrosstermEvent::Mouse(mouse) => {
|
CrosstermEvent::Mouse(mouse) => {
|
||||||
_event_tx.send(Action::Mouse(mouse)).unwrap();
|
_event_tx.send(Event::Mouse(mouse)).unwrap();
|
||||||
},
|
},
|
||||||
CrosstermEvent::Resize(x, y) => {
|
CrosstermEvent::Resize(x, y) => {
|
||||||
_event_tx.send(Action::Resize(x, y)).unwrap();
|
_event_tx.send(Event::Resize(x, y)).unwrap();
|
||||||
},
|
},
|
||||||
CrosstermEvent::FocusLost => {
|
CrosstermEvent::FocusLost => {
|
||||||
_event_tx.send(Action::FocusLost).unwrap();
|
_event_tx.send(Event::FocusLost).unwrap();
|
||||||
},
|
},
|
||||||
CrosstermEvent::FocusGained => {
|
CrosstermEvent::FocusGained => {
|
||||||
_event_tx.send(Action::FocusGained).unwrap();
|
_event_tx.send(Event::FocusGained).unwrap();
|
||||||
},
|
},
|
||||||
CrosstermEvent::Paste(s) => {
|
CrosstermEvent::Paste(s) => {
|
||||||
_event_tx.send(Action::Paste(s)).unwrap();
|
_event_tx.send(Event::Paste(s)).unwrap();
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(Err(_)) => {
|
Some(Err(_)) => {
|
||||||
_event_tx.send(Action::Error).unwrap();
|
_event_tx.send(Event::Error).unwrap();
|
||||||
}
|
}
|
||||||
None => {},
|
None => {},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ = tick_delay => {
|
_ = tick_delay => {
|
||||||
_event_tx.send(Action::Tick).unwrap();
|
_event_tx.send(Event::Tick).unwrap();
|
||||||
},
|
},
|
||||||
_ = render_delay => {
|
_ = 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<()> {
|
pub fn stop(&self) -> Result<()> {
|
||||||
self.cancel();
|
self.cancel();
|
||||||
|
|
||||||
let mut counter = 0;
|
let mut counter = 0;
|
||||||
|
|
||||||
while !self.task.is_finished() {
|
while !self.task.is_finished() {
|
||||||
std::thread::sleep(Duration::from_millis(1));
|
std::thread::sleep(Duration::from_millis(1));
|
||||||
|
|
||||||
counter += 1;
|
counter += 1;
|
||||||
|
|
||||||
if counter > 50 {
|
if counter > 50 {
|
||||||
self.task.abort();
|
self.task.abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
if counter > 100 {
|
if counter > 100 {
|
||||||
log::error!("Failed to abort task in 100 milliseconds for unknown reason");
|
log::error!("Failed to abort task in 100 milliseconds for unknown reason");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn enter(&mut self) -> Result<()> {
|
pub fn enter(&mut self) -> Result<()> {
|
||||||
crossterm::terminal::enable_raw_mode()?;
|
crossterm::terminal::enable_raw_mode()?;
|
||||||
crossterm::execute!(std::io::stderr(), EnterAlternateScreen, cursor::Hide)?;
|
crossterm::execute!(io(), EnterAlternateScreen, cursor::Hide)?;
|
||||||
|
|
||||||
if self.mouse {
|
if self.mouse {
|
||||||
crossterm::execute!(std::io::stderr(), EnableMouseCapture)?;
|
crossterm::execute!(io(), EnableMouseCapture)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.paste {
|
if self.paste {
|
||||||
crossterm::execute!(std::io::stderr(), EnableBracketedPaste)?;
|
crossterm::execute!(io(), EnableBracketedPaste)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.start();
|
self.start();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn exit(&mut self) -> Result<()> {
|
pub fn exit(&mut self) -> Result<()> {
|
||||||
self.stop()?;
|
self.stop()?;
|
||||||
|
|
||||||
if crossterm::terminal::is_raw_mode_enabled()? {
|
if crossterm::terminal::is_raw_mode_enabled()? {
|
||||||
self.flush()?;
|
self.flush()?;
|
||||||
|
|
||||||
if self.paste {
|
if self.paste {
|
||||||
crossterm::execute!(std::io::stderr(), DisableBracketedPaste)?;
|
crossterm::execute!(io(), DisableBracketedPaste)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.mouse {
|
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()?;
|
crossterm::terminal::disable_raw_mode()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,13 +245,13 @@ impl Tui {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn next(&mut self) -> Option<Action> {
|
pub async fn next(&mut self) -> Option<Event> {
|
||||||
self.event_rx.recv().await
|
self.event_rx.recv().await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for Tui {
|
impl Deref for Tui {
|
||||||
type Target = ratatui::Terminal<Backend<std::io::Stderr>>;
|
type Target = ratatui::Terminal<Backend<IO>>;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.terminal
|
&self.terminal
|
169
src/bin/tui/utils.rs
Normal file
169
src/bin/tui/utils.rs
Normal file
@ -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<PathBuf> =
|
||||||
|
std::env::var(format!("{}_DATA", PROJECT_NAME.clone()))
|
||||||
|
.ok()
|
||||||
|
.map(PathBuf::from);
|
||||||
|
pub static ref CONFIG_FOLDER: Option<PathBuf> =
|
||||||
|
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> {
|
||||||
|
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}"
|
||||||
|
)
|
||||||
|
}
|
@ -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<Option<Action>> {
|
|
||||||
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!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
pub struct Output;
|
||||||
|
|
||||||
@ -8,11 +29,15 @@ impl BuiltInFunction for Output {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self, arguments: &[Value], _context: &Map) -> Result<Value> {
|
fn run(&self, arguments: &[Value], _context: &Map) -> Result<Value> {
|
||||||
for argument in arguments {
|
Error::expect_argument_amount(self, 1, arguments.len())?;
|
||||||
println!("{argument}");
|
|
||||||
|
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 {
|
fn r#type(&self) -> Type {
|
||||||
|
@ -41,9 +41,6 @@ pub fn interpret(source: &str) -> Result<Value> {
|
|||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
pub fn interpret_with_context(source: &str, context: Map) -> Result<Value> {
|
pub fn interpret_with_context(source: &str, context: Map) -> Result<Value> {
|
||||||
let mut parser = Parser::new();
|
|
||||||
parser.set_language(language())?;
|
|
||||||
|
|
||||||
let mut interpreter = Interpreter::new(context)?;
|
let mut interpreter = Interpreter::new(context)?;
|
||||||
let value = interpreter.run(source)?;
|
let value = interpreter.run(source)?;
|
||||||
|
|
||||||
@ -91,7 +88,7 @@ impl Interpreter {
|
|||||||
if let Some(abstract_tree) = &self.abstract_tree {
|
if let Some(abstract_tree) = &self.abstract_tree {
|
||||||
abstract_tree.run(source, &self.context)
|
abstract_tree.run(source, &self.context)
|
||||||
} else {
|
} else {
|
||||||
Ok(Value::Option(None))
|
Ok(Value::none())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ pub enum Value {
|
|||||||
|
|
||||||
impl Default for Value {
|
impl Default for Value {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Value::Option(None)
|
Value::none()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,6 +87,10 @@ impl Value {
|
|||||||
r#type
|
r#type
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn none() -> Self {
|
||||||
|
Value::Option(None)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_string(&self) -> bool {
|
pub fn is_string(&self) -> bool {
|
||||||
matches!(self, Value::String(_))
|
matches!(self, Value::String(_))
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,7 @@
|
|||||||
"any"
|
"any"
|
||||||
"async"
|
"async"
|
||||||
"else"
|
"else"
|
||||||
|
"else if"
|
||||||
"false"
|
"false"
|
||||||
"float"
|
"float"
|
||||||
"for"
|
"for"
|
||||||
|
Loading…
Reference in New Issue
Block a user