mirror of
https://github.com/ynqa/jnv.git
synced 2025-12-19 09:19:33 -06:00
Merge pull request #76 from ynqa/v0.6.0/dev
v0.6.0: focus on the definitions in configuration
This commit is contained in:
330
Cargo.lock
generated
330
Cargo.lock
generated
@@ -1,6 +1,6 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
@@ -175,6 +175,9 @@ name = "bitflags"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block2"
|
||||
@@ -224,7 +227,7 @@ version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9"
|
||||
dependencies = [
|
||||
"hashbrown",
|
||||
"hashbrown 0.14.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -370,6 +373,7 @@ dependencies = [
|
||||
"mio",
|
||||
"parking_lot",
|
||||
"rustix",
|
||||
"serde",
|
||||
"signal-hook",
|
||||
"signal-hook-mio",
|
||||
"winapi",
|
||||
@@ -384,6 +388,41 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.3.11"
|
||||
@@ -393,6 +432,67 @@ dependencies = [
|
||||
"powerfmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder"
|
||||
version = "0.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947"
|
||||
dependencies = [
|
||||
"derive_builder_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder_core"
|
||||
version = "0.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder_macro"
|
||||
version = "0.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
|
||||
dependencies = [
|
||||
"derive_builder_core",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
|
||||
dependencies = [
|
||||
"dirs-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "duration-string"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04782251e09dc67c90d694d89e9a3e5fc6cfe883df1b203202de672d812fb299"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.17"
|
||||
@@ -449,7 +549,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"thiserror",
|
||||
"thiserror 1.0.64",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
@@ -463,6 +563,12 @@ dependencies = [
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.5.0"
|
||||
@@ -622,6 +728,12 @@ dependencies = [
|
||||
"allocator-api2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
@@ -640,6 +752,12 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18ae468bcb4dfecf0e4949ee28abbc99076b6a0077f51ddbc94dbfff8e6a870c"
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.25.2"
|
||||
@@ -655,12 +773,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.2.6"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
|
||||
checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
"hashbrown 0.15.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -740,6 +858,9 @@ dependencies = [
|
||||
"async-trait",
|
||||
"clap",
|
||||
"crossterm",
|
||||
"derive_builder",
|
||||
"dirs",
|
||||
"duration-string",
|
||||
"futures",
|
||||
"futures-timer",
|
||||
"jaq-core",
|
||||
@@ -747,8 +868,10 @@ dependencies = [
|
||||
"jaq-parse",
|
||||
"jaq-std",
|
||||
"promkit",
|
||||
"serde",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -759,9 +882,9 @@ checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.153"
|
||||
version = "0.2.169"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
@@ -769,6 +892,16 @@ version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.14"
|
||||
@@ -787,9 +920,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.21"
|
||||
version = "0.4.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
||||
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
@@ -958,6 +1091,12 @@ version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
|
||||
[[package]]
|
||||
name = "option-ext"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.1"
|
||||
@@ -1014,9 +1153,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.81"
|
||||
version = "1.0.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
|
||||
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -1039,9 +1178,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.36"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -1085,6 +1224,17 @@ dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"libredox",
|
||||
"thiserror 2.0.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.4"
|
||||
@@ -1147,18 +1297,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.198"
|
||||
version = "1.0.217"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc"
|
||||
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.198"
|
||||
version = "1.0.217"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9"
|
||||
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1167,16 +1317,26 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.116"
|
||||
version = "1.0.140"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813"
|
||||
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
@@ -1252,9 +1412,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.60"
|
||||
version = "2.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
|
||||
checksum = "46f71c0377baf4ef1cc3e3402ded576dccc315800fbc62dfc7fe04b009773b4a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1267,7 +1427,16 @@ version = "1.0.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
"thiserror-impl 1.0.64",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc"
|
||||
dependencies = [
|
||||
"thiserror-impl 2.0.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1281,6 +1450,17 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiff"
|
||||
version = "0.9.1"
|
||||
@@ -1363,6 +1543,40 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
@@ -1442,7 +1656,16 @@ version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.5",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1462,18 +1685,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.52.5",
|
||||
"windows_aarch64_msvc 0.52.5",
|
||||
"windows_i686_gnu 0.52.5",
|
||||
"windows_aarch64_gnullvm 0.52.6",
|
||||
"windows_aarch64_msvc 0.52.6",
|
||||
"windows_i686_gnu 0.52.6",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc 0.52.5",
|
||||
"windows_x86_64_gnu 0.52.5",
|
||||
"windows_x86_64_gnullvm 0.52.5",
|
||||
"windows_x86_64_msvc 0.52.5",
|
||||
"windows_i686_msvc 0.52.6",
|
||||
"windows_x86_64_gnu 0.52.6",
|
||||
"windows_x86_64_gnullvm 0.52.6",
|
||||
"windows_x86_64_msvc 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1484,9 +1707,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
@@ -1496,9 +1719,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
@@ -1508,15 +1731,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
@@ -1526,9 +1749,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
@@ -1538,9 +1761,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
@@ -1550,9 +1773,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
@@ -1562,9 +1785,18 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x11rb"
|
||||
|
||||
@@ -13,8 +13,11 @@ anyhow = "1.0.95"
|
||||
arboard = "3.4.1"
|
||||
async-trait = "0.1.83"
|
||||
clap = { version = "4.5.23", features = ["derive"] }
|
||||
duration-string = { version = "0.5.2", features = ["serde"] }
|
||||
derive_builder = "0.20.2"
|
||||
# See https://github.com/crossterm-rs/crossterm/issues/935
|
||||
crossterm = { version = "0.28.1", features = ["use-dev-tty", "event-stream", "libc"] }
|
||||
crossterm = { version = "0.28.1", features = ["use-dev-tty", "event-stream", "libc", "serde"] }
|
||||
dirs = "6.0.0"
|
||||
futures = "0.3.30"
|
||||
futures-timer = "3.0.3"
|
||||
jaq-core = "1.2.1"
|
||||
@@ -22,8 +25,10 @@ jaq-interpret = "1.2.1"
|
||||
jaq-parse = "1.0.2"
|
||||
jaq-std = "1.2.1"
|
||||
promkit = "0.6.2"
|
||||
serde = "1.0.217"
|
||||
tokio = { version = "1.42.0", features = ["full"] }
|
||||
tokio-stream = "0.1.16"
|
||||
toml = "0.8.20"
|
||||
|
||||
# The profile that 'cargo dist' will build with
|
||||
[profile.dist]
|
||||
|
||||
222
default.toml
Normal file
222
default.toml
Normal file
@@ -0,0 +1,222 @@
|
||||
# Whether to hide hint messages
|
||||
no_hint = false
|
||||
|
||||
# Editor settings
|
||||
[editor]
|
||||
# Editor mode
|
||||
# "Insert": Insert characters at the cursor position
|
||||
# "Overwrite": Replace characters at the cursor position with new ones
|
||||
mode = "Insert"
|
||||
|
||||
# Characters considered as word boundaries
|
||||
# These are used to define word movement and deletion behavior in the editor
|
||||
word_break_chars = [".", "|", "(", ")", "[", "]"]
|
||||
|
||||
# How to configure colors and text attributes
|
||||
#
|
||||
# Color specification methods:
|
||||
# 1. By name: "black", "red", etc.
|
||||
# 2. By RGB value: "rgb_(255,0,0)" or "#ff0000"
|
||||
# 3. By ANSI value: "ansi_(16)"
|
||||
#
|
||||
# Text attribute specification:
|
||||
# attributes = ["Bold"], etc.
|
||||
#
|
||||
# Configuration example:
|
||||
# style = { foreground = "blue", background = "magenta", attributes = ["Bold"] }
|
||||
#
|
||||
# Detailed information:
|
||||
# - Color: https://docs.rs/crossterm/0.28.1/crossterm/style/enum.Color.html
|
||||
# - Attribute: https://docs.rs/crossterm/0.28.1/crossterm/style/enum.Attribute.html
|
||||
|
||||
# Theme settings when the editor is focused
|
||||
[editor.theme_on_focus]
|
||||
# Prefix shown before the cursor
|
||||
prefix = "❯❯ "
|
||||
# Style for the prefix
|
||||
prefix_style = { foreground = "blue" }
|
||||
# Style for the character under the cursor
|
||||
active_char_style = { background = "magenta" }
|
||||
# Style for all other characters
|
||||
inactive_char_style = {}
|
||||
|
||||
# Theme settings when the editor is unfocused
|
||||
[editor.theme_on_defocus]
|
||||
# Prefix shown when focus is lost
|
||||
prefix = "▼ "
|
||||
# Style for the prefix when unfocused
|
||||
prefix_style = { foreground = "blue", attributes = ["Dim"] }
|
||||
# Style for the character under the cursor when unfocused
|
||||
active_char_style = { attributes = ["Dim"] }
|
||||
# Style for all other characters when unfocused
|
||||
inactive_char_style = { attributes = ["Dim"] }
|
||||
|
||||
# JSON display settings
|
||||
[json]
|
||||
# Maximum number of JSON objects to read from streams (e.g., JSON Lines format)
|
||||
# Limits how many objects are processed to reduce memory usage when handling large data streams
|
||||
# No limit if unset
|
||||
# max_streams =
|
||||
|
||||
# JSON display theme
|
||||
[json.theme]
|
||||
# Number of spaces to use for indentation
|
||||
indent = 2
|
||||
# Style for curly brackets {}
|
||||
curly_brackets_style = { attributes = ["Bold"] }
|
||||
# Style for square brackets []
|
||||
square_brackets_style = { attributes = ["Bold"] }
|
||||
# Style for JSON keys
|
||||
key_style = { foreground = "cyan" }
|
||||
# Style for string values
|
||||
string_value_style = { foreground = "green" }
|
||||
# Style for number values
|
||||
number_value_style = {}
|
||||
# Style for boolean values
|
||||
boolean_value_style = {}
|
||||
# Style for null values
|
||||
null_value_style = { foreground = "grey" }
|
||||
|
||||
# Completion feature settings
|
||||
[completion]
|
||||
# Number of lines to display for completion candidates
|
||||
lines = 3
|
||||
# Cursor character shown before the selected candidate
|
||||
cursor = "❯ "
|
||||
# Style for the selected candidate
|
||||
active_item_style = { foreground = "grey", background = "yellow" }
|
||||
# Style for unselected candidates
|
||||
inactive_item_style = { foreground = "grey" }
|
||||
|
||||
# Settings for background loading of completion candidates
|
||||
#
|
||||
# Number of candidates loaded per chunk for search results
|
||||
# A larger value displays results faster but uses more memory
|
||||
search_result_chunk_size = 100
|
||||
|
||||
# Number of items loaded per batch during background loading
|
||||
# A larger value finishes loading sooner but uses more memory temporarily
|
||||
search_load_chunk_size = 50000
|
||||
|
||||
# Keybinding settings
|
||||
[keybinds]
|
||||
# Key to exit the application
|
||||
exit = [
|
||||
{ Key = { modifiers = "CONTROL", code = { Char = "c" } } }
|
||||
]
|
||||
# Key to copy the query to the clipboard
|
||||
copy_query = [
|
||||
{ Key = { modifiers = "CONTROL", code = { Char = "q" } } }
|
||||
]
|
||||
# Key to copy the result to the clipboard
|
||||
copy_result = [
|
||||
{ Key = { modifiers = "CONTROL", code = { Char = "o" } } }
|
||||
]
|
||||
# Keys to switch focus between editor and JSON viewer
|
||||
switch_mode = [
|
||||
{ Key = { code = "Down", modifiers = "SHIFT" } },
|
||||
{ Key = { code = "Up", modifiers = "SHIFT" } }
|
||||
]
|
||||
|
||||
# Keybindings for editor operations
|
||||
[keybinds.on_editor]
|
||||
# Move cursor left
|
||||
backward = [
|
||||
{ Key = { code = "Left", modifiers = "" } }
|
||||
]
|
||||
# Move cursor right
|
||||
forward = [
|
||||
{ Key = { code = "Right", modifiers = "" } }
|
||||
]
|
||||
# Move cursor to beginning of line
|
||||
move_to_head = [
|
||||
{ Key = { modifiers = "CONTROL", code = { Char = "a" } } }
|
||||
]
|
||||
# Move cursor to end of line
|
||||
move_to_tail = [
|
||||
{ Key = { modifiers = "CONTROL", code = { Char = "e" } } }
|
||||
]
|
||||
# Move cursor to previous word boundary
|
||||
move_to_previous_nearest = [
|
||||
{ Key = { modifiers = "ALT", code = { Char = "b" } } }
|
||||
]
|
||||
# Move cursor to next word boundary
|
||||
move_to_next_nearest = [
|
||||
{ Key = { modifiers = "ALT", code = { Char = "f" } } }
|
||||
]
|
||||
# Delete character at the cursor
|
||||
erase = [
|
||||
{ Key = { code = "Backspace", modifiers = "" } }
|
||||
]
|
||||
# Delete all input
|
||||
erase_all = [
|
||||
{ Key = { modifiers = "CONTROL", code = { Char = "u" } } }
|
||||
]
|
||||
# Delete from cursor to previous word boundary
|
||||
erase_to_previous_nearest = [
|
||||
{ Key = { modifiers = "CONTROL", code = { Char = "w" } } }
|
||||
]
|
||||
# Delete from cursor to next word boundary
|
||||
erase_to_next_nearest = [
|
||||
{ Key = { modifiers = "ALT", code = { Char = "d" } } }
|
||||
]
|
||||
# Trigger completion
|
||||
completion = [
|
||||
{ Key = { code = "Tab", modifiers = "" } }
|
||||
]
|
||||
# Move up in the completion list
|
||||
on_completion.up = [
|
||||
{ Key = { code = "Up", modifiers = "" } }
|
||||
]
|
||||
# Move down in the completion list
|
||||
on_completion.down = [
|
||||
{ Key = { code = "Down", modifiers = "" } },
|
||||
{ Key = { code = "Tab", modifiers = "" } }
|
||||
]
|
||||
|
||||
# Keybindings for JSON viewer operations
|
||||
[keybinds.on_json_viewer]
|
||||
# Move up in JSON viewer
|
||||
up = [
|
||||
{ Key = { code = "Up", modifiers = "" } },
|
||||
{ Key = { modifiers = "CONTROL", code = { Char = "k" } } }
|
||||
]
|
||||
# Move down in JSON viewer
|
||||
down = [
|
||||
{ Key = { modifiers = "CONTROL", code = { Char = "j" } } },
|
||||
{ Key = { code = "Down", modifiers = "" } }
|
||||
]
|
||||
# Move to the top of JSON viewer
|
||||
move_to_head = [
|
||||
{ Key = { modifiers = "CONTROL", code = { Char = "l" } } }
|
||||
]
|
||||
# Move to the bottom of JSON viewer
|
||||
move_to_tail = [
|
||||
{ Key = { modifiers = "CONTROL", code = { Char = "h" } } }
|
||||
]
|
||||
# Toggle expand/collapse of JSON nodes
|
||||
toggle = [
|
||||
{ Key = { code = "Enter", modifiers = "" } }
|
||||
]
|
||||
# Expand all JSON nodes
|
||||
expand = [
|
||||
{ Key = { modifiers = "CONTROL", code = { Char = "p" } } }
|
||||
]
|
||||
# Collapse all JSON nodes
|
||||
collapse = [
|
||||
{ Key = { modifiers = "CONTROL", code = { Char = "n" } } }
|
||||
]
|
||||
|
||||
# Application reactivity settings
|
||||
[reactivity_control]
|
||||
# Delay before processing query input
|
||||
# Prevents excessive updates while user is typing
|
||||
query_debounce_duration = "600ms"
|
||||
|
||||
# Delay before redrawing after window resize
|
||||
# Prevents frequent redraws during continuous resizing
|
||||
resize_debounce_duration = "200ms"
|
||||
|
||||
# Interval for spinner animation updates
|
||||
# Controls the speed of the loading spinner
|
||||
spin_duration = "300ms"
|
||||
339
src/config.rs
Normal file
339
src/config.rs
Normal file
@@ -0,0 +1,339 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crossterm::{
|
||||
event::{KeyCode, KeyModifiers},
|
||||
style::{Attribute, Attributes, Color, ContentStyle},
|
||||
};
|
||||
use promkit::{style::StyleBuilder, text_editor::Mode};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::time::Duration;
|
||||
|
||||
mod content_style;
|
||||
use content_style::content_style_serde;
|
||||
mod duration;
|
||||
use duration::duration_serde;
|
||||
pub mod event;
|
||||
use event::{EventDef, EventDefSet, KeyEventDef};
|
||||
mod text_editor;
|
||||
use text_editor::text_editor_mode_serde;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct EditorConfig {
|
||||
pub theme_on_focus: EditorTheme,
|
||||
pub theme_on_defocus: EditorTheme,
|
||||
#[serde(with = "text_editor_mode_serde")]
|
||||
pub mode: Mode,
|
||||
pub word_break_chars: HashSet<char>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct EditorTheme {
|
||||
pub prefix: String,
|
||||
|
||||
#[serde(with = "content_style_serde")]
|
||||
pub prefix_style: ContentStyle,
|
||||
|
||||
#[serde(with = "content_style_serde")]
|
||||
pub active_char_style: ContentStyle,
|
||||
|
||||
#[serde(with = "content_style_serde")]
|
||||
pub inactive_char_style: ContentStyle,
|
||||
}
|
||||
|
||||
impl Default for EditorConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
theme_on_focus: EditorTheme {
|
||||
prefix: String::from("❯❯ "),
|
||||
prefix_style: StyleBuilder::new().fgc(Color::Blue).build(),
|
||||
active_char_style: StyleBuilder::new().bgc(Color::Magenta).build(),
|
||||
inactive_char_style: StyleBuilder::new().build(),
|
||||
},
|
||||
theme_on_defocus: EditorTheme {
|
||||
prefix: String::from("▼ "),
|
||||
prefix_style: StyleBuilder::new()
|
||||
.fgc(Color::Blue)
|
||||
.attrs(Attributes::from(Attribute::Dim))
|
||||
.build(),
|
||||
active_char_style: StyleBuilder::new()
|
||||
.attrs(Attributes::from(Attribute::Dim))
|
||||
.build(),
|
||||
inactive_char_style: StyleBuilder::new()
|
||||
.attrs(Attributes::from(Attribute::Dim))
|
||||
.build(),
|
||||
},
|
||||
mode: Mode::Insert,
|
||||
word_break_chars: HashSet::from(['.', '|', '(', ')', '[', ']']),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct JsonConfig {
|
||||
pub max_streams: Option<usize>,
|
||||
pub theme: JsonTheme,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct JsonTheme {
|
||||
pub indent: usize,
|
||||
|
||||
#[serde(with = "content_style_serde")]
|
||||
pub curly_brackets_style: ContentStyle,
|
||||
|
||||
#[serde(with = "content_style_serde")]
|
||||
pub square_brackets_style: ContentStyle,
|
||||
|
||||
#[serde(with = "content_style_serde")]
|
||||
pub key_style: ContentStyle,
|
||||
|
||||
#[serde(with = "content_style_serde")]
|
||||
pub string_value_style: ContentStyle,
|
||||
|
||||
#[serde(with = "content_style_serde")]
|
||||
pub number_value_style: ContentStyle,
|
||||
|
||||
#[serde(with = "content_style_serde")]
|
||||
pub boolean_value_style: ContentStyle,
|
||||
|
||||
#[serde(with = "content_style_serde")]
|
||||
pub null_value_style: ContentStyle,
|
||||
}
|
||||
|
||||
impl Default for JsonConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
max_streams: None,
|
||||
theme: JsonTheme {
|
||||
indent: 2,
|
||||
curly_brackets_style: StyleBuilder::new()
|
||||
.attrs(Attributes::from(Attribute::Bold))
|
||||
.build(),
|
||||
square_brackets_style: StyleBuilder::new()
|
||||
.attrs(Attributes::from(Attribute::Bold))
|
||||
.build(),
|
||||
key_style: StyleBuilder::new().fgc(Color::Cyan).build(),
|
||||
string_value_style: StyleBuilder::new().fgc(Color::Green).build(),
|
||||
number_value_style: StyleBuilder::new().build(),
|
||||
boolean_value_style: StyleBuilder::new().build(),
|
||||
null_value_style: StyleBuilder::new().fgc(Color::Grey).build(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct CompletionConfig {
|
||||
pub lines: Option<usize>,
|
||||
pub cursor: String,
|
||||
pub search_result_chunk_size: usize,
|
||||
pub search_load_chunk_size: usize,
|
||||
|
||||
#[serde(with = "content_style_serde")]
|
||||
pub active_item_style: ContentStyle,
|
||||
|
||||
#[serde(with = "content_style_serde")]
|
||||
pub inactive_item_style: ContentStyle,
|
||||
}
|
||||
|
||||
impl Default for CompletionConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
lines: Some(3),
|
||||
cursor: String::from("❯ "),
|
||||
search_result_chunk_size: 100,
|
||||
search_load_chunk_size: 50000,
|
||||
active_item_style: StyleBuilder::new()
|
||||
.fgc(Color::Grey)
|
||||
.bgc(Color::Yellow)
|
||||
.build(),
|
||||
inactive_item_style: StyleBuilder::new().fgc(Color::Grey).build(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove Clone derive
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct Keybinds {
|
||||
pub exit: EventDefSet,
|
||||
pub copy_query: EventDefSet,
|
||||
pub copy_result: EventDefSet,
|
||||
pub switch_mode: EventDefSet,
|
||||
pub on_editor: EditorKeybinds,
|
||||
pub on_json_viewer: JsonViewerKeybinds,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct EditorKeybinds {
|
||||
pub backward: EventDefSet,
|
||||
pub forward: EventDefSet,
|
||||
pub move_to_head: EventDefSet,
|
||||
pub move_to_tail: EventDefSet,
|
||||
pub move_to_previous_nearest: EventDefSet,
|
||||
pub move_to_next_nearest: EventDefSet,
|
||||
pub erase: EventDefSet,
|
||||
pub erase_all: EventDefSet,
|
||||
pub erase_to_previous_nearest: EventDefSet,
|
||||
pub erase_to_next_nearest: EventDefSet,
|
||||
pub completion: EventDefSet,
|
||||
pub on_completion: CompletionKeybinds,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct CompletionKeybinds {
|
||||
pub up: EventDefSet,
|
||||
pub down: EventDefSet,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct JsonViewerKeybinds {
|
||||
pub up: EventDefSet,
|
||||
pub down: EventDefSet,
|
||||
pub move_to_head: EventDefSet,
|
||||
pub move_to_tail: EventDefSet,
|
||||
pub toggle: EventDefSet,
|
||||
pub expand: EventDefSet,
|
||||
pub collapse: EventDefSet,
|
||||
}
|
||||
|
||||
impl Default for Keybinds {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
exit: EventDefSet::from(KeyEventDef::new(KeyCode::Char('c'), KeyModifiers::CONTROL)),
|
||||
copy_query: EventDefSet::from(KeyEventDef::new(
|
||||
KeyCode::Char('q'),
|
||||
KeyModifiers::CONTROL,
|
||||
)),
|
||||
copy_result: EventDefSet::from(KeyEventDef::new(
|
||||
KeyCode::Char('o'),
|
||||
KeyModifiers::CONTROL,
|
||||
)),
|
||||
switch_mode: EventDefSet::from_iter([
|
||||
EventDef::Key(KeyEventDef::new(KeyCode::Down, KeyModifiers::SHIFT)),
|
||||
EventDef::Key(KeyEventDef::new(KeyCode::Up, KeyModifiers::SHIFT)),
|
||||
]),
|
||||
on_editor: EditorKeybinds {
|
||||
backward: EventDefSet::from(KeyEventDef::new(KeyCode::Left, KeyModifiers::NONE)),
|
||||
forward: EventDefSet::from(KeyEventDef::new(KeyCode::Right, KeyModifiers::NONE)),
|
||||
move_to_head: EventDefSet::from(KeyEventDef::new(
|
||||
KeyCode::Char('a'),
|
||||
KeyModifiers::CONTROL,
|
||||
)),
|
||||
move_to_tail: EventDefSet::from(KeyEventDef::new(
|
||||
KeyCode::Char('e'),
|
||||
KeyModifiers::CONTROL,
|
||||
)),
|
||||
move_to_next_nearest: EventDefSet::from(KeyEventDef::new(
|
||||
KeyCode::Char('f'),
|
||||
KeyModifiers::ALT,
|
||||
)),
|
||||
move_to_previous_nearest: EventDefSet::from(KeyEventDef::new(
|
||||
KeyCode::Char('b'),
|
||||
KeyModifiers::ALT,
|
||||
)),
|
||||
erase: EventDefSet::from(KeyEventDef::new(KeyCode::Backspace, KeyModifiers::NONE)),
|
||||
erase_all: EventDefSet::from(KeyEventDef::new(
|
||||
KeyCode::Char('u'),
|
||||
KeyModifiers::CONTROL,
|
||||
)),
|
||||
erase_to_previous_nearest: EventDefSet::from(KeyEventDef::new(
|
||||
KeyCode::Char('w'),
|
||||
KeyModifiers::CONTROL,
|
||||
)),
|
||||
erase_to_next_nearest: EventDefSet::from(KeyEventDef::new(
|
||||
KeyCode::Char('d'),
|
||||
KeyModifiers::ALT,
|
||||
)),
|
||||
completion: EventDefSet::from(KeyEventDef::new(KeyCode::Tab, KeyModifiers::NONE)),
|
||||
on_completion: CompletionKeybinds {
|
||||
up: EventDefSet::from(KeyEventDef::new(KeyCode::Up, KeyModifiers::NONE)),
|
||||
down: EventDefSet::from_iter([
|
||||
EventDef::Key(KeyEventDef::new(KeyCode::Tab, KeyModifiers::NONE)),
|
||||
EventDef::Key(KeyEventDef::new(KeyCode::Down, KeyModifiers::NONE)),
|
||||
]),
|
||||
},
|
||||
},
|
||||
on_json_viewer: JsonViewerKeybinds {
|
||||
up: EventDefSet::from_iter([
|
||||
EventDef::Key(KeyEventDef::new(KeyCode::Char('k'), KeyModifiers::CONTROL)),
|
||||
EventDef::Key(KeyEventDef::new(KeyCode::Up, KeyModifiers::NONE)),
|
||||
]),
|
||||
down: EventDefSet::from_iter([
|
||||
EventDef::Key(KeyEventDef::new(KeyCode::Char('j'), KeyModifiers::CONTROL)),
|
||||
EventDef::Key(KeyEventDef::new(KeyCode::Down, KeyModifiers::NONE)),
|
||||
]),
|
||||
move_to_head: EventDefSet::from(KeyEventDef::new(
|
||||
KeyCode::Char('l'),
|
||||
KeyModifiers::CONTROL,
|
||||
)),
|
||||
move_to_tail: EventDefSet::from(KeyEventDef::new(
|
||||
KeyCode::Char('h'),
|
||||
KeyModifiers::CONTROL,
|
||||
)),
|
||||
toggle: EventDefSet::from(KeyEventDef::new(KeyCode::Enter, KeyModifiers::NONE)),
|
||||
expand: EventDefSet::from(KeyEventDef::new(
|
||||
KeyCode::Char('p'),
|
||||
KeyModifiers::CONTROL,
|
||||
)),
|
||||
collapse: EventDefSet::from(KeyEventDef::new(
|
||||
KeyCode::Char('n'),
|
||||
KeyModifiers::CONTROL,
|
||||
)),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct ReactivityControl {
|
||||
#[serde(with = "duration_serde")]
|
||||
pub query_debounce_duration: Duration,
|
||||
|
||||
#[serde(with = "duration_serde")]
|
||||
pub resize_debounce_duration: Duration,
|
||||
|
||||
#[serde(with = "duration_serde")]
|
||||
pub spin_duration: Duration,
|
||||
}
|
||||
|
||||
impl Default for ReactivityControl {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
query_debounce_duration: Duration::from_millis(600),
|
||||
resize_debounce_duration: Duration::from_millis(200),
|
||||
spin_duration: Duration::from_millis(300),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Note that the config struct and the `.toml` configuration file are
|
||||
/// managed separately because the current toml crate
|
||||
/// does not readily support the following features:
|
||||
///
|
||||
/// - Preserve docstrings as comments in the `.toml` file
|
||||
/// - https://github.com/toml-rs/toml/issues/376
|
||||
/// - Output inline tables
|
||||
/// - https://github.com/toml-rs/toml/issues/592
|
||||
///
|
||||
/// Also difficult to patch `Config` using only the items specified in the configuration file
|
||||
/// (Premise: To address the complexity of configurations,
|
||||
/// it assumes using a macro to avoid managing Option-wrapped structures on our side).s
|
||||
///
|
||||
/// The main challenge is that, for nested structs,
|
||||
/// it is not able to wrap every leaf field with Option<>.
|
||||
/// https://github.com/colin-kiegel/rust-derive-builder/issues/254
|
||||
#[derive(Default, Serialize, Deserialize)]
|
||||
pub struct Config {
|
||||
pub no_hint: bool,
|
||||
pub reactivity_control: ReactivityControl,
|
||||
pub editor: EditorConfig,
|
||||
pub json: JsonConfig,
|
||||
pub completion: CompletionConfig,
|
||||
pub keybinds: Keybinds,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn load_from(content: &str) -> anyhow::Result<Self> {
|
||||
toml::from_str(content).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
65
src/config/content_style.rs
Normal file
65
src/config/content_style.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
use crossterm::style::{Attribute, Attributes, Color, ContentStyle};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct ContentStyleDef {
|
||||
foreground: Option<Color>,
|
||||
background: Option<Color>,
|
||||
underline: Option<Color>,
|
||||
attributes: Option<Vec<Attribute>>,
|
||||
}
|
||||
|
||||
impl From<&ContentStyle> for ContentStyleDef {
|
||||
fn from(style: &ContentStyle) -> Self {
|
||||
ContentStyleDef {
|
||||
foreground: style.foreground_color,
|
||||
background: style.background_color,
|
||||
underline: style.underline_color,
|
||||
attributes: if style.attributes.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
Attribute::iterator()
|
||||
.filter(|x| style.attributes.has(*x))
|
||||
.collect(),
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ContentStyleDef> for ContentStyle {
|
||||
fn from(style_def: ContentStyleDef) -> Self {
|
||||
let mut style = ContentStyle::new();
|
||||
style.foreground_color = style_def.foreground;
|
||||
style.background_color = style_def.background;
|
||||
style.underline_color = style_def.underline;
|
||||
if let Some(attributes) = style_def.attributes {
|
||||
style.attributes = attributes
|
||||
.into_iter()
|
||||
.fold(Attributes::default(), |acc, x| acc | x);
|
||||
}
|
||||
style
|
||||
}
|
||||
}
|
||||
|
||||
pub mod content_style_serde {
|
||||
use super::*;
|
||||
use serde::{Deserializer, Serializer};
|
||||
|
||||
pub fn serialize<S>(style: &ContentStyle, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let style_def = ContentStyleDef::from(style);
|
||||
style_def.serialize(serializer)
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<ContentStyle, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let style_def = ContentStyleDef::deserialize(deserializer)?;
|
||||
Ok(ContentStyle::from(style_def))
|
||||
}
|
||||
}
|
||||
22
src/config/duration.rs
Normal file
22
src/config/duration.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use duration_string::DurationString;
|
||||
use serde::Deserialize;
|
||||
use tokio::time::Duration;
|
||||
|
||||
pub mod duration_serde {
|
||||
use super::*;
|
||||
use serde::{Deserializer, Serializer};
|
||||
|
||||
pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&DurationString::from(*duration).to_string())
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
Ok(DurationString::deserialize(deserializer)?.into())
|
||||
}
|
||||
}
|
||||
102
src/config/event.rs
Normal file
102
src/config/event.rs
Normal file
@@ -0,0 +1,102 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent, MouseEventKind};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub trait Matcher<T> {
|
||||
fn matches(&self, other: &T) -> bool;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct EventDefSet(HashSet<EventDef>);
|
||||
|
||||
impl Matcher<Event> for EventDefSet {
|
||||
fn matches(&self, other: &Event) -> bool {
|
||||
self.0.iter().any(|event_def| event_def.matches(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<EventDef> for EventDefSet {
|
||||
fn from_iter<I: IntoIterator<Item = EventDef>>(iter: I) -> Self {
|
||||
EventDefSet(iter.into_iter().collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<KeyEventDef> for EventDefSet {
|
||||
fn from(key_event_def: KeyEventDef) -> Self {
|
||||
EventDefSet(HashSet::from_iter([EventDef::Key(key_event_def)]))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MouseEventDef> for EventDefSet {
|
||||
fn from(mouse_event_def: MouseEventDef) -> Self {
|
||||
EventDefSet(HashSet::from_iter([EventDef::Mouse(mouse_event_def)]))
|
||||
}
|
||||
}
|
||||
|
||||
/// A part of `crossterm::event::Event`.
|
||||
/// It is used for parsing from a config file or
|
||||
/// for comparison with crossterm::event::Event.
|
||||
/// https://docs.rs/crossterm/0.28.1/crossterm/event/enum.Event.html
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||
pub enum EventDef {
|
||||
Key(KeyEventDef),
|
||||
Mouse(MouseEventDef),
|
||||
}
|
||||
|
||||
impl Matcher<Event> for EventDef {
|
||||
fn matches(&self, other: &Event) -> bool {
|
||||
match (self, other) {
|
||||
(EventDef::Key(key_def), Event::Key(key_event)) => key_def.matches(key_event),
|
||||
(EventDef::Mouse(mouse_def), Event::Mouse(mouse_event)) => {
|
||||
mouse_def.matches(mouse_event)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A part of `crossterm::event::KeyEvent`.
|
||||
/// It is used for parsing from a config file or
|
||||
/// for comparison with crossterm::event::KeyEvent.
|
||||
/// https://docs.rs/crossterm/0.28.1/crossterm/event/struct.KeyEvent.html
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||
pub struct KeyEventDef {
|
||||
code: KeyCode,
|
||||
modifiers: KeyModifiers,
|
||||
}
|
||||
|
||||
impl KeyEventDef {
|
||||
pub fn new(code: KeyCode, modifiers: KeyModifiers) -> Self {
|
||||
KeyEventDef { code, modifiers }
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher<KeyEvent> for KeyEventDef {
|
||||
fn matches(&self, other: &KeyEvent) -> bool {
|
||||
self.code == other.code && self.modifiers == other.modifiers
|
||||
}
|
||||
}
|
||||
|
||||
/// A part of `crossterm::event::MouseEvent`.
|
||||
/// It is used for parsing from a config file or
|
||||
/// for comparison with crossterm::event::MouseEvent.
|
||||
/// https://docs.rs/crossterm/0.28.1/crossterm/event/struct.MouseEvent.html
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||
pub struct MouseEventDef {
|
||||
kind: MouseEventKind,
|
||||
modifiers: KeyModifiers,
|
||||
}
|
||||
|
||||
impl MouseEventDef {
|
||||
#[allow(dead_code)]
|
||||
pub fn new(kind: MouseEventKind, modifiers: KeyModifiers) -> Self {
|
||||
MouseEventDef { kind, modifiers }
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher<MouseEvent> for MouseEventDef {
|
||||
fn matches(&self, other: &MouseEvent) -> bool {
|
||||
self.kind == other.kind && self.modifiers == other.modifiers
|
||||
}
|
||||
}
|
||||
34
src/config/text_editor.rs
Normal file
34
src/config/text_editor.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use promkit::text_editor::Mode;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
pub mod text_editor_mode_serde {
|
||||
use super::*;
|
||||
|
||||
pub fn serialize<S>(mode: &Mode, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mode_str = match mode {
|
||||
Mode::Insert => "Insert",
|
||||
Mode::Overwrite => "Overwrite",
|
||||
// Add other variants if they exist
|
||||
};
|
||||
mode_str.serialize(serializer)
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<Mode, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let mode_str = String::deserialize(deserializer)?;
|
||||
match mode_str.as_str() {
|
||||
"Insert" => Ok(Mode::Insert),
|
||||
"Overwrite" => Ok(Mode::Overwrite),
|
||||
// Add other variants if they exist
|
||||
_ => Err(serde::de::Error::custom(format!(
|
||||
"Unknown Mode variant: {}",
|
||||
mode_str
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
142
src/editor.rs
142
src/editor.rs
@@ -2,30 +2,23 @@ use std::{future::Future, pin::Pin};
|
||||
|
||||
use crossterm::{
|
||||
event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers},
|
||||
style::{Color, ContentStyle},
|
||||
style::Color,
|
||||
};
|
||||
use promkit::{pane::Pane, style::StyleBuilder, text, text_editor, PaneFactory};
|
||||
|
||||
use crate::search::IncrementalSearcher;
|
||||
use crate::{
|
||||
config::{event::Matcher, EditorKeybinds, EditorTheme},
|
||||
search::IncrementalSearcher,
|
||||
};
|
||||
|
||||
pub struct Editor {
|
||||
keybind: Keybind,
|
||||
handler: Handler,
|
||||
state: text_editor::State,
|
||||
focus_theme: EditorTheme,
|
||||
defocus_theme: EditorTheme,
|
||||
guide: text::State,
|
||||
searcher: IncrementalSearcher,
|
||||
}
|
||||
|
||||
pub struct EditorTheme {
|
||||
pub prefix: String,
|
||||
|
||||
/// Style applied to the prompt string.
|
||||
pub prefix_style: ContentStyle,
|
||||
/// Style applied to the currently selected character.
|
||||
pub active_char_style: ContentStyle,
|
||||
/// Style applied to characters that are not currently selected.
|
||||
pub inactive_char_style: ContentStyle,
|
||||
editor_keybinds: EditorKeybinds,
|
||||
}
|
||||
|
||||
impl Editor {
|
||||
@@ -34,9 +27,10 @@ impl Editor {
|
||||
searcher: IncrementalSearcher,
|
||||
focus_theme: EditorTheme,
|
||||
defocus_theme: EditorTheme,
|
||||
editor_keybinds: EditorKeybinds,
|
||||
) -> Self {
|
||||
Self {
|
||||
keybind: BOXED_EDITOR_KEYBIND,
|
||||
handler: BOXED_EDITOR_HANDLER,
|
||||
state,
|
||||
focus_theme,
|
||||
defocus_theme,
|
||||
@@ -45,6 +39,7 @@ impl Editor {
|
||||
style: Default::default(),
|
||||
},
|
||||
searcher,
|
||||
editor_keybinds,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +57,7 @@ impl Editor {
|
||||
self.state.active_char_style = self.defocus_theme.active_char_style;
|
||||
|
||||
self.searcher.leave_search();
|
||||
self.keybind = BOXED_EDITOR_KEYBIND;
|
||||
self.handler = BOXED_EDITOR_HANDLER;
|
||||
|
||||
self.guide.text = Default::default();
|
||||
}
|
||||
@@ -84,20 +79,20 @@ impl Editor {
|
||||
}
|
||||
|
||||
pub async fn operate(&mut self, event: &Event) -> anyhow::Result<()> {
|
||||
(self.keybind)(event, self).await
|
||||
(self.handler)(event, self).await
|
||||
}
|
||||
}
|
||||
|
||||
pub type Keybind = for<'a> fn(
|
||||
pub type Handler = for<'a> fn(
|
||||
&'a Event,
|
||||
&'a mut Editor,
|
||||
) -> Pin<Box<dyn Future<Output = anyhow::Result<()>> + Send + 'a>>;
|
||||
|
||||
const BOXED_EDITOR_KEYBIND: Keybind =
|
||||
const BOXED_EDITOR_HANDLER: Handler =
|
||||
|event, editor| -> Pin<Box<dyn Future<Output = anyhow::Result<()>> + Send + '_>> {
|
||||
Box::pin(edit(event, editor))
|
||||
};
|
||||
const BOXED_SEARCHER_KEYBIND: Keybind =
|
||||
const BOXED_SEARCHER_HANDLER: Handler =
|
||||
|event, editor| -> Pin<Box<dyn Future<Output = anyhow::Result<()>> + Send + '_>> {
|
||||
Box::pin(search(event, editor))
|
||||
};
|
||||
@@ -106,12 +101,7 @@ pub async fn edit<'a>(event: &'a Event, editor: &'a mut Editor) -> anyhow::Resul
|
||||
editor.guide.text = Default::default();
|
||||
|
||||
match event {
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Tab,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE,
|
||||
}) => {
|
||||
key if editor.editor_keybinds.completion.matches(key) => {
|
||||
let prefix = editor.state.texteditor.text_without_cursor().to_string();
|
||||
match editor.searcher.start_search(&prefix) {
|
||||
Ok(result) => match result.head_item {
|
||||
@@ -130,7 +120,7 @@ pub async fn edit<'a>(event: &'a Event, editor: &'a mut Editor) -> anyhow::Resul
|
||||
editor.guide.style = StyleBuilder::new().fgc(Color::Green).build();
|
||||
}
|
||||
editor.state.texteditor.replace(&head);
|
||||
editor.keybind = BOXED_SEARCHER_KEYBIND;
|
||||
editor.handler = BOXED_SEARCHER_HANDLER;
|
||||
}
|
||||
None => {
|
||||
editor.guide.text = format!("No suggestion found for '{}'", prefix);
|
||||
@@ -145,58 +135,27 @@ pub async fn edit<'a>(event: &'a Event, editor: &'a mut Editor) -> anyhow::Resul
|
||||
}
|
||||
|
||||
// Move cursor.
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Left,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE,
|
||||
}) => {
|
||||
key if editor.editor_keybinds.backward.matches(key) => {
|
||||
editor.state.texteditor.backward();
|
||||
}
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Right,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE,
|
||||
}) => {
|
||||
key if editor.editor_keybinds.forward.matches(key) => {
|
||||
editor.state.texteditor.forward();
|
||||
}
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Char('a'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE,
|
||||
}) => {
|
||||
key if editor.editor_keybinds.move_to_head.matches(key) => {
|
||||
editor.state.texteditor.move_to_head();
|
||||
}
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Char('e'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE,
|
||||
}) => {
|
||||
key if editor.editor_keybinds.move_to_tail.matches(key) => {
|
||||
editor.state.texteditor.move_to_tail();
|
||||
}
|
||||
|
||||
// Move cursor to the nearest character.
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Char('b'),
|
||||
modifiers: KeyModifiers::ALT,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE,
|
||||
}) => {
|
||||
key if editor.editor_keybinds.move_to_previous_nearest.matches(key) => {
|
||||
editor
|
||||
.state
|
||||
.texteditor
|
||||
.move_to_previous_nearest(&editor.state.word_break_chars);
|
||||
}
|
||||
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Char('f'),
|
||||
modifiers: KeyModifiers::ALT,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE,
|
||||
}) => {
|
||||
key if editor.editor_keybinds.move_to_next_nearest.matches(key) => {
|
||||
editor
|
||||
.state
|
||||
.texteditor
|
||||
@@ -204,42 +163,25 @@ pub async fn edit<'a>(event: &'a Event, editor: &'a mut Editor) -> anyhow::Resul
|
||||
}
|
||||
|
||||
// Erase char(s).
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Backspace,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE,
|
||||
}) => {
|
||||
key if editor.editor_keybinds.erase.matches(key) => {
|
||||
editor.state.texteditor.erase();
|
||||
}
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Char('u'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE,
|
||||
}) => {
|
||||
key if editor.editor_keybinds.erase_all.matches(key) => {
|
||||
editor.state.texteditor.erase_all();
|
||||
}
|
||||
|
||||
// Erase to the nearest character.
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Char('w'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE,
|
||||
}) => {
|
||||
key if editor
|
||||
.editor_keybinds
|
||||
.erase_to_previous_nearest
|
||||
.matches(key) =>
|
||||
{
|
||||
editor
|
||||
.state
|
||||
.texteditor
|
||||
.erase_to_previous_nearest(&editor.state.word_break_chars);
|
||||
}
|
||||
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Char('d'),
|
||||
modifiers: KeyModifiers::ALT,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE,
|
||||
}) => {
|
||||
key if editor.editor_keybinds.erase_to_next_nearest.matches(key) => {
|
||||
editor
|
||||
.state
|
||||
.texteditor
|
||||
@@ -270,18 +212,7 @@ pub async fn edit<'a>(event: &'a Event, editor: &'a mut Editor) -> anyhow::Resul
|
||||
|
||||
pub async fn search<'a>(event: &'a Event, editor: &'a mut Editor) -> anyhow::Result<()> {
|
||||
match event {
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Tab,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE,
|
||||
})
|
||||
| Event::Key(KeyEvent {
|
||||
code: KeyCode::Down,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE,
|
||||
}) => {
|
||||
key if editor.editor_keybinds.on_completion.down.matches(key) => {
|
||||
editor.searcher.down_with_load();
|
||||
editor
|
||||
.state
|
||||
@@ -289,12 +220,7 @@ pub async fn search<'a>(event: &'a Event, editor: &'a mut Editor) -> anyhow::Res
|
||||
.replace(&editor.searcher.get_current_item());
|
||||
}
|
||||
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Up,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE,
|
||||
}) => {
|
||||
key if editor.editor_keybinds.on_completion.up.matches(key) => {
|
||||
editor.searcher.up();
|
||||
editor
|
||||
.state
|
||||
@@ -304,7 +230,7 @@ pub async fn search<'a>(event: &'a Event, editor: &'a mut Editor) -> anyhow::Res
|
||||
|
||||
_ => {
|
||||
editor.searcher.leave_search();
|
||||
editor.keybind = BOXED_EDITOR_KEYBIND;
|
||||
editor.handler = BOXED_EDITOR_HANDLER;
|
||||
return edit(event, editor).await;
|
||||
}
|
||||
}
|
||||
|
||||
85
src/json.rs
85
src/json.rs
@@ -1,5 +1,5 @@
|
||||
use crossterm::{
|
||||
event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers},
|
||||
event::Event,
|
||||
style::{Attribute, Attributes},
|
||||
};
|
||||
use jaq_interpret::{Ctx, FilterT, ParseCtx, RcIter, Val};
|
||||
@@ -14,20 +14,23 @@ use promkit::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
config::{event::Matcher, JsonViewerKeybinds},
|
||||
processor::{ViewProvider, Visualizer},
|
||||
search::SearchProvider,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
// #[derive(Clone)]
|
||||
pub struct Json {
|
||||
state: jsonstream::State,
|
||||
json: &'static [serde_json::Value],
|
||||
keybinds: JsonViewerKeybinds,
|
||||
}
|
||||
|
||||
impl Json {
|
||||
pub fn new(
|
||||
formatter: RowFormatter,
|
||||
input_stream: &'static [serde_json::Value],
|
||||
keybinds: JsonViewerKeybinds,
|
||||
) -> anyhow::Result<Self> {
|
||||
Ok(Self {
|
||||
json: input_stream,
|
||||
@@ -36,88 +39,42 @@ impl Json {
|
||||
formatter,
|
||||
lines: Default::default(),
|
||||
},
|
||||
keybinds,
|
||||
})
|
||||
}
|
||||
|
||||
fn operate(&mut self, event: &Event) {
|
||||
match event {
|
||||
// Move up.
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Up,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE,
|
||||
})
|
||||
| Event::Key(KeyEvent {
|
||||
code: KeyCode::Char('k'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE,
|
||||
}) => {
|
||||
event if self.keybinds.up.matches(event) => {
|
||||
self.state.stream.up();
|
||||
}
|
||||
|
||||
// Move down.
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Down,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE,
|
||||
})
|
||||
| Event::Key(KeyEvent {
|
||||
code: KeyCode::Char('j'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE,
|
||||
}) => {
|
||||
event if self.keybinds.down.matches(event) => {
|
||||
self.state.stream.down();
|
||||
}
|
||||
|
||||
// Move to tail
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Char('h'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE,
|
||||
}) => {
|
||||
self.state.stream.tail();
|
||||
}
|
||||
|
||||
// Move to head
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Char('l'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE,
|
||||
}) => {
|
||||
event if self.keybinds.move_to_head.matches(event) => {
|
||||
self.state.stream.head();
|
||||
}
|
||||
|
||||
// Move to tail
|
||||
event if self.keybinds.move_to_tail.matches(event) => {
|
||||
self.state.stream.tail();
|
||||
}
|
||||
|
||||
// Toggle collapse/expand
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Enter,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE,
|
||||
}) => {
|
||||
event if self.keybinds.toggle.matches(event) => {
|
||||
self.state.stream.toggle();
|
||||
}
|
||||
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Char('p'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE,
|
||||
}) => {
|
||||
event if self.keybinds.expand.matches(event) => {
|
||||
self.state.stream.set_nodes_visibility(false);
|
||||
}
|
||||
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Char('n'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE,
|
||||
}) => {
|
||||
event if self.keybinds.collapse.matches(event) => {
|
||||
self.state.stream.set_nodes_visibility(true);
|
||||
}
|
||||
|
||||
@@ -242,10 +199,14 @@ impl JsonStreamProvider {
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl ViewProvider for JsonStreamProvider {
|
||||
async fn provide(&mut self, item: &'static str) -> anyhow::Result<Json> {
|
||||
async fn provide(
|
||||
&mut self,
|
||||
item: &'static str,
|
||||
keybinds: JsonViewerKeybinds,
|
||||
) -> anyhow::Result<Json> {
|
||||
let stream = self.deserialize_json(item)?;
|
||||
let static_stream = Box::leak(stream.into_boxed_slice());
|
||||
Json::new(std::mem::take(&mut self.formatter), static_stream)
|
||||
Json::new(std::mem::take(&mut self.formatter), static_stream, keybinds)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
275
src/main.rs
275
src/main.rs
@@ -1,23 +1,22 @@
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
fs::File,
|
||||
io::{self, Read},
|
||||
io::{self, Read, Write},
|
||||
path::PathBuf,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use anyhow::anyhow;
|
||||
use clap::Parser;
|
||||
use crossterm::style::{Attribute, Attributes, Color};
|
||||
use config::Config;
|
||||
use crossterm::style::Attribute;
|
||||
use promkit::{
|
||||
jsonz::format::RowFormatter,
|
||||
listbox::{self, Listbox},
|
||||
style::StyleBuilder,
|
||||
text_editor,
|
||||
};
|
||||
|
||||
mod editor;
|
||||
use editor::{Editor, EditorTheme};
|
||||
use editor::Editor;
|
||||
mod config;
|
||||
mod json;
|
||||
use json::JsonStreamProvider;
|
||||
mod processor;
|
||||
@@ -31,6 +30,8 @@ use render::{PaneIndex, Renderer, EMPTY_PANE};
|
||||
mod search;
|
||||
use search::{IncrementalSearcher, SearchProvider};
|
||||
|
||||
static DEFAULT_CONFIG: &str = include_str!("../default.toml");
|
||||
|
||||
/// JSON navigator and interactive filter leveraging jq
|
||||
#[derive(Parser)]
|
||||
#[command(
|
||||
@@ -61,74 +62,8 @@ pub struct Args {
|
||||
/// reads from standard input.
|
||||
pub input: Option<PathBuf>,
|
||||
|
||||
#[arg(
|
||||
short = 'e',
|
||||
long = "edit-mode",
|
||||
default_value = "insert",
|
||||
value_parser = edit_mode_validator,
|
||||
help = "Edit mode for the interface ('insert' or 'overwrite').",
|
||||
long_help = r#"
|
||||
Specifies the edit mode for the interface.
|
||||
Acceptable values are "insert" or "overwrite".
|
||||
- "insert" inserts a new input at the cursor's position.
|
||||
- "overwrite" mode replaces existing characters with new input at the cursor's position.
|
||||
"#,
|
||||
)]
|
||||
pub edit_mode: text_editor::Mode,
|
||||
|
||||
#[arg(
|
||||
short = 'i',
|
||||
long = "indent",
|
||||
default_value = "2",
|
||||
help = "Number of spaces used for indentation in the visualized data.",
|
||||
long_help = "
|
||||
Affect the formatting of the displayed JSON,
|
||||
making it more readable by adjusting the indentation level.
|
||||
"
|
||||
)]
|
||||
pub indent: usize,
|
||||
|
||||
#[arg(
|
||||
short = 'n',
|
||||
long = "no-hint",
|
||||
help = "Disables the display of hints.",
|
||||
long_help = "
|
||||
When this option is enabled, it prevents the display of
|
||||
hints that typically guide or offer suggestions to the user.
|
||||
"
|
||||
)]
|
||||
pub no_hint: bool,
|
||||
|
||||
#[arg(
|
||||
long = "max-streams",
|
||||
help = "Maximum number of JSON streams to display",
|
||||
long_help = "
|
||||
Sets the maximum number of JSON streams to load and display.
|
||||
Limiting this value improves performance for large datasets.
|
||||
If not set, all streams will be displayed.
|
||||
"
|
||||
)]
|
||||
pub max_streams: Option<usize>,
|
||||
|
||||
#[arg(
|
||||
long = "suggestions",
|
||||
default_value = "3",
|
||||
help = "Number of autocomplete suggestions to show",
|
||||
long_help = "
|
||||
Sets the number of autocomplete suggestions displayed during incremental search.
|
||||
Higher values show more suggestions but may occupy more screen space.
|
||||
Adjust this value based on your screen size and preference.
|
||||
"
|
||||
)]
|
||||
pub suggestions: usize,
|
||||
}
|
||||
|
||||
fn edit_mode_validator(val: &str) -> Result<text_editor::Mode> {
|
||||
match val {
|
||||
"insert" | "" => Ok(text_editor::Mode::Insert),
|
||||
"overwrite" => Ok(text_editor::Mode::Overwrite),
|
||||
_ => Err(anyhow!("edit-mode must be 'insert' or 'overwrite'")),
|
||||
}
|
||||
#[arg(short = 'c', long = "config", help = "Path to the configuration file.")]
|
||||
pub config_file: Option<PathBuf>,
|
||||
}
|
||||
|
||||
/// Parses the input based on the provided arguments.
|
||||
@@ -138,7 +73,7 @@ fn edit_mode_validator(val: &str) -> Result<text_editor::Mode> {
|
||||
/// that equals "-", data is read from standard input.
|
||||
/// Otherwise, the function attempts to open and
|
||||
/// read from the file specified in the `input` argument.
|
||||
fn parse_input(args: &Args) -> Result<String> {
|
||||
fn parse_input(args: &Args) -> anyhow::Result<String> {
|
||||
let mut ret = String::new();
|
||||
|
||||
match &args.input {
|
||||
@@ -157,81 +92,131 @@ fn parse_input(args: &Args) -> Result<String> {
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Ensures the configuration file exists, creating it with default settings if it doesn't
|
||||
///
|
||||
/// If the file already exists, returns Ok.
|
||||
/// If the file doesn't exist, writes the default configuration in TOML format.
|
||||
/// Returns an error if file creation fails.
|
||||
fn ensure_file_exists(path: &PathBuf) -> anyhow::Result<()> {
|
||||
if path.exists() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(parent) = path.parent() {
|
||||
std::fs::create_dir_all(parent)
|
||||
.map_err(|e| anyhow!("Failed to create directory: {}", e))?;
|
||||
}
|
||||
|
||||
std::fs::File::create(path)?.write_all(DEFAULT_CONFIG.as_bytes())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Determines the configuration file path with the following precedence:
|
||||
/// 1. The provided `config_path` argument, if it exists.
|
||||
/// 2. The default configuration file path in the user's configuration directory.
|
||||
///
|
||||
/// If the configuration file does not exist, it will be created.
|
||||
/// Returns an error if the file creation fails.
|
||||
fn determine_config_file(config_path: Option<PathBuf>) -> anyhow::Result<PathBuf> {
|
||||
// If a custom path is provided
|
||||
if let Some(path) = config_path {
|
||||
ensure_file_exists(&path)?;
|
||||
return Ok(path);
|
||||
}
|
||||
|
||||
// Use the default path
|
||||
let default_path = dirs::config_dir()
|
||||
.ok_or_else(|| anyhow!("Failed to determine the configuration directory"))?
|
||||
// TODO: need versions...?
|
||||
.join("jnv")
|
||||
.join("config.toml");
|
||||
|
||||
ensure_file_exists(&default_path)?;
|
||||
Ok(default_path)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let args = Args::parse();
|
||||
let input = parse_input(&args)?;
|
||||
|
||||
let mut config = Config::default();
|
||||
if let Ok(config_file) = determine_config_file(args.config_file) {
|
||||
// Note that the configuration file absolutely exists.
|
||||
let content = std::fs::read_to_string(&config_file)
|
||||
// TODO: output the message as the initial guide pane.
|
||||
.map_err(|e| anyhow!("Failed to read configuration file: {}", e))?;
|
||||
config = Config::load_from(&content)
|
||||
.map_err(|e| anyhow!("Failed to deserialize configuration file: {}", e))?;
|
||||
}
|
||||
|
||||
let listbox_state = listbox::State {
|
||||
listbox: Listbox::default(),
|
||||
cursor: config.completion.cursor,
|
||||
active_item_style: Some(config.completion.active_item_style),
|
||||
inactive_item_style: Some(config.completion.inactive_item_style),
|
||||
lines: config.completion.lines,
|
||||
};
|
||||
|
||||
let searcher =
|
||||
IncrementalSearcher::new(listbox_state, config.completion.search_result_chunk_size);
|
||||
|
||||
let text_editor_state = text_editor::State {
|
||||
texteditor: Default::default(),
|
||||
history: Default::default(),
|
||||
prefix: config.editor.theme_on_focus.prefix.clone(),
|
||||
mask: Default::default(),
|
||||
prefix_style: config.editor.theme_on_focus.prefix_style,
|
||||
active_char_style: config.editor.theme_on_focus.active_char_style,
|
||||
inactive_char_style: config.editor.theme_on_focus.inactive_char_style,
|
||||
edit_mode: config.editor.mode,
|
||||
word_break_chars: config.editor.word_break_chars,
|
||||
lines: Default::default(),
|
||||
};
|
||||
|
||||
let provider = &mut JsonStreamProvider::new(
|
||||
RowFormatter {
|
||||
curly_brackets_style: config.json.theme.curly_brackets_style,
|
||||
square_brackets_style: config.json.theme.square_brackets_style,
|
||||
key_style: config.json.theme.key_style,
|
||||
string_value_style: config.json.theme.string_value_style,
|
||||
number_value_style: config.json.theme.number_value_style,
|
||||
boolean_value_style: config.json.theme.boolean_value_style,
|
||||
null_value_style: config.json.theme.null_value_style,
|
||||
active_item_attribute: Attribute::Bold,
|
||||
inactive_item_attribute: Attribute::Dim,
|
||||
indent: config.json.theme.indent,
|
||||
},
|
||||
config.json.max_streams,
|
||||
);
|
||||
|
||||
let item = Box::leak(input.into_boxed_str());
|
||||
|
||||
let loading_suggestions_task =
|
||||
searcher.spawn_load_task(provider, item, config.completion.search_load_chunk_size);
|
||||
|
||||
// TODO: re-consider put editor_task of prompt::run into Editor construction time.
|
||||
// Overall, there are several cases where it would be sufficient to
|
||||
// launch a background thread during construction.
|
||||
let editor = Editor::new(
|
||||
text_editor_state,
|
||||
searcher,
|
||||
config.editor.theme_on_focus,
|
||||
config.editor.theme_on_defocus,
|
||||
// TODO: remove clones
|
||||
config.keybinds.on_editor.clone(),
|
||||
);
|
||||
|
||||
// TODO: put all logics here.
|
||||
prompt::run(
|
||||
Box::leak(input.into_boxed_str()),
|
||||
Duration::from_millis(300),
|
||||
Duration::from_millis(600),
|
||||
Duration::from_millis(200),
|
||||
&mut JsonStreamProvider::new(
|
||||
RowFormatter {
|
||||
curly_brackets_style: StyleBuilder::new()
|
||||
.attrs(Attributes::from(Attribute::Bold))
|
||||
.build(),
|
||||
square_brackets_style: StyleBuilder::new()
|
||||
.attrs(Attributes::from(Attribute::Bold))
|
||||
.build(),
|
||||
key_style: StyleBuilder::new().fgc(Color::Cyan).build(),
|
||||
string_value_style: StyleBuilder::new().fgc(Color::Green).build(),
|
||||
number_value_style: StyleBuilder::new().build(),
|
||||
boolean_value_style: StyleBuilder::new().build(),
|
||||
null_value_style: StyleBuilder::new().fgc(Color::Grey).build(),
|
||||
active_item_attribute: Attribute::Bold,
|
||||
inactive_item_attribute: Attribute::Dim,
|
||||
indent: args.indent,
|
||||
},
|
||||
args.max_streams,
|
||||
),
|
||||
text_editor::State {
|
||||
texteditor: Default::default(),
|
||||
history: Default::default(),
|
||||
prefix: String::from("❯❯ "),
|
||||
mask: Default::default(),
|
||||
prefix_style: StyleBuilder::new().fgc(Color::Blue).build(),
|
||||
active_char_style: StyleBuilder::new().bgc(Color::Magenta).build(),
|
||||
inactive_char_style: StyleBuilder::new().build(),
|
||||
edit_mode: args.edit_mode,
|
||||
word_break_chars: HashSet::from(['.', '|', '(', ')', '[', ']']),
|
||||
lines: Default::default(),
|
||||
},
|
||||
EditorTheme {
|
||||
prefix: String::from("❯❯ "),
|
||||
prefix_style: StyleBuilder::new().fgc(Color::Blue).build(),
|
||||
active_char_style: StyleBuilder::new().bgc(Color::Magenta).build(),
|
||||
inactive_char_style: StyleBuilder::new().build(),
|
||||
},
|
||||
EditorTheme {
|
||||
prefix: String::from("▼"),
|
||||
prefix_style: StyleBuilder::new()
|
||||
.fgc(Color::Blue)
|
||||
.attrs(Attributes::from(Attribute::Dim))
|
||||
.build(),
|
||||
active_char_style: StyleBuilder::new()
|
||||
.attrs(Attributes::from(Attribute::Dim))
|
||||
.build(),
|
||||
inactive_char_style: StyleBuilder::new()
|
||||
.attrs(Attributes::from(Attribute::Dim))
|
||||
.build(),
|
||||
},
|
||||
listbox::State {
|
||||
listbox: Listbox::from_displayable(Vec::<String>::new()),
|
||||
cursor: String::from("❯ "),
|
||||
active_item_style: Some(
|
||||
StyleBuilder::new()
|
||||
.fgc(Color::Grey)
|
||||
.bgc(Color::Yellow)
|
||||
.build(),
|
||||
),
|
||||
inactive_item_style: Some(StyleBuilder::new().fgc(Color::Grey).build()),
|
||||
lines: Some(args.suggestions),
|
||||
},
|
||||
100,
|
||||
50000,
|
||||
args.no_hint,
|
||||
item,
|
||||
config.reactivity_control,
|
||||
provider,
|
||||
editor,
|
||||
loading_suggestions_task,
|
||||
config.no_hint,
|
||||
config.keybinds,
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -4,11 +4,15 @@ use async_trait::async_trait;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use super::{Context, State, Visualizer};
|
||||
use crate::{PaneIndex, Renderer};
|
||||
use crate::{config::JsonViewerKeybinds, PaneIndex, Renderer};
|
||||
|
||||
#[async_trait]
|
||||
pub trait ViewProvider {
|
||||
async fn provide(&mut self, item: &'static str) -> anyhow::Result<impl Visualizer>;
|
||||
async fn provide(
|
||||
&mut self,
|
||||
item: &'static str,
|
||||
keybinds: JsonViewerKeybinds,
|
||||
) -> anyhow::Result<impl Visualizer>;
|
||||
}
|
||||
|
||||
pub struct ViewInitializer {
|
||||
@@ -26,6 +30,7 @@ impl ViewInitializer {
|
||||
item: &'static str,
|
||||
area: (u16, u16),
|
||||
shared_renderer: Arc<Mutex<Renderer>>,
|
||||
keybinds: JsonViewerKeybinds,
|
||||
) -> anyhow::Result<impl Visualizer + 'a> {
|
||||
{
|
||||
let mut shared_state = self.shared.lock().await;
|
||||
@@ -35,7 +40,7 @@ impl ViewInitializer {
|
||||
shared_state.state = State::Loading;
|
||||
}
|
||||
|
||||
let mut visualizer = provider.provide(item).await?;
|
||||
let mut visualizer = provider.provide(item, keybinds).await?;
|
||||
let pane = visualizer.create_init_pane(area).await;
|
||||
|
||||
// Set state to Idle to prevent overwriting by spinner frames in terminal.
|
||||
|
||||
@@ -10,16 +10,16 @@ use crossterm::{
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use futures_timer::Delay;
|
||||
use promkit::{listbox, style::StyleBuilder, text, text_editor, PaneFactory};
|
||||
use promkit::{style::StyleBuilder, text, PaneFactory};
|
||||
use tokio::{
|
||||
sync::{mpsc, Mutex, RwLock},
|
||||
task::JoinHandle,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
Context, ContextMonitor, Editor, EditorTheme, IncrementalSearcher, PaneIndex, Processor,
|
||||
Renderer, SearchProvider, SpinnerSpawner, ViewInitializer, ViewProvider, Visualizer,
|
||||
EMPTY_PANE,
|
||||
config::{event::Matcher, Keybinds, ReactivityControl},
|
||||
Context, ContextMonitor, Editor, PaneIndex, Processor, Renderer, SearchProvider,
|
||||
SpinnerSpawner, ViewInitializer, ViewProvider, Visualizer, EMPTY_PANE,
|
||||
};
|
||||
|
||||
fn spawn_debouncer<T: Send + 'static>(
|
||||
@@ -81,32 +81,18 @@ enum Focus {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn run<T: ViewProvider + SearchProvider>(
|
||||
item: &'static str,
|
||||
spin_duration: Duration,
|
||||
query_debounce_duration: Duration,
|
||||
resize_debounce_duration: Duration,
|
||||
reactivity_control: ReactivityControl,
|
||||
provider: &mut T,
|
||||
text_editor_state: text_editor::State,
|
||||
editor_focus_theme: EditorTheme,
|
||||
editor_defocus_theme: EditorTheme,
|
||||
listbox_state: listbox::State,
|
||||
search_result_chunk_size: usize,
|
||||
search_load_chunk_size: usize,
|
||||
editor: Editor,
|
||||
loading_suggestions_task: JoinHandle<anyhow::Result<()>>,
|
||||
no_hint: bool,
|
||||
keybinds: Keybinds,
|
||||
) -> anyhow::Result<()> {
|
||||
enable_raw_mode()?;
|
||||
execute!(io::stdout(), cursor::Hide)?;
|
||||
|
||||
let size = terminal::size()?;
|
||||
|
||||
let searcher = IncrementalSearcher::new(listbox_state, search_result_chunk_size);
|
||||
let loading_suggestions_task = searcher.spawn_load_task(provider, item, search_load_chunk_size);
|
||||
let editor = Editor::new(
|
||||
text_editor_state,
|
||||
searcher,
|
||||
editor_focus_theme,
|
||||
editor_defocus_theme,
|
||||
);
|
||||
|
||||
let shared_renderer = Arc::new(Mutex::new(Renderer::try_init_draw(
|
||||
[
|
||||
editor.create_editor_pane(size.0, size.1),
|
||||
@@ -122,16 +108,23 @@ pub async fn run<T: ViewProvider + SearchProvider>(
|
||||
|
||||
let (last_query_tx, mut last_query_rx) = mpsc::channel(1);
|
||||
let (debounce_query_tx, debounce_query_rx) = mpsc::channel(1);
|
||||
let query_debouncer =
|
||||
spawn_debouncer(debounce_query_rx, last_query_tx, query_debounce_duration);
|
||||
let query_debouncer = spawn_debouncer(
|
||||
debounce_query_rx,
|
||||
last_query_tx,
|
||||
reactivity_control.query_debounce_duration,
|
||||
);
|
||||
|
||||
let (last_resize_tx, mut last_resize_rx) = mpsc::channel::<(u16, u16)>(1);
|
||||
let (debounce_resize_tx, debounce_resize_rx) = mpsc::channel(1);
|
||||
let resize_debouncer =
|
||||
spawn_debouncer(debounce_resize_rx, last_resize_tx, resize_debounce_duration);
|
||||
let resize_debouncer = spawn_debouncer(
|
||||
debounce_resize_rx,
|
||||
last_resize_tx,
|
||||
reactivity_control.resize_debounce_duration,
|
||||
);
|
||||
|
||||
let spinner_spawner = SpinnerSpawner::new(ctx.clone());
|
||||
let spinning = spinner_spawner.spawn_spin_task(shared_renderer.clone(), spin_duration);
|
||||
let spinning =
|
||||
spinner_spawner.spawn_spin_task(shared_renderer.clone(), reactivity_control.spin_duration);
|
||||
|
||||
let mut focus = Focus::Editor;
|
||||
let (editor_event_tx, mut editor_event_rx) = mpsc::channel::<Event>(1);
|
||||
@@ -147,7 +140,13 @@ pub async fn run<T: ViewProvider + SearchProvider>(
|
||||
let processor = Processor::new(ctx.clone());
|
||||
let context_monitor = ContextMonitor::new(ctx.clone());
|
||||
let initializer = ViewInitializer::new(ctx.clone());
|
||||
let initializing = initializer.initialize(provider, item, size, shared_renderer.clone());
|
||||
let initializing = initializer.initialize(
|
||||
provider,
|
||||
item,
|
||||
size,
|
||||
shared_renderer.clone(),
|
||||
keybinds.on_json_viewer,
|
||||
);
|
||||
|
||||
let main_task: JoinHandle<anyhow::Result<()>> = {
|
||||
let mut stream = EventStream::new();
|
||||
@@ -160,12 +159,7 @@ pub async fn run<T: ViewProvider + SearchProvider>(
|
||||
Event::Resize(width, height) => {
|
||||
debounce_resize_tx.send((width, height)).await?;
|
||||
},
|
||||
Event::Key(KeyEvent {
|
||||
code: KeyCode::Char('c'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE,
|
||||
}) => {
|
||||
event if keybinds.exit.matches(&event) => {
|
||||
break 'main
|
||||
},
|
||||
Event::Key(KeyEvent {
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::sync::LazyLock;
|
||||
use crossterm::{self, cursor};
|
||||
use promkit::{pane::Pane, terminal::Terminal};
|
||||
|
||||
// TODO: One Guide is sufficient.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum PaneIndex {
|
||||
Editor = 0,
|
||||
|
||||
Reference in New Issue
Block a user