Merge pull request #76 from ynqa/v0.6.0/dev

v0.6.0: focus on the definitions in configuration
This commit is contained in:
ynqa
2025-03-27 23:48:15 +09:00
committed by GitHub
14 changed files with 1295 additions and 402 deletions

330
Cargo.lock generated
View File

@@ -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"

View File

@@ -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
View 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
View 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)
}
}

View 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
View 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
View 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
View 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
))),
}
}
}

View File

@@ -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;
}
}

View File

@@ -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)
}
}

View File

@@ -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?;

View File

@@ -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.

View File

@@ -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 {

View File

@@ -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,