From 57483bef41d93b8380a9aaabc2cfaa37a87ebc0f Mon Sep 17 00:00:00 2001 From: Arijit Basu Date: Sun, 25 Sep 2022 12:52:24 +0530 Subject: [PATCH] Use fuzzy search instead of regex search Ref: https://github.com/sayanarijit/xplr/issues/496 --- Cargo.lock | 290 +++++++++++++++------------- Cargo.toml | 21 +- docs/en/src/awesome-plugins.md | 2 + docs/en/src/default-key-bindings.md | 279 ++++++++++++-------------- docs/en/src/general-config.md | 6 + docs/en/src/messages.md | 65 +++++++ docs/en/src/upgrade-guide.md | 18 +- src/app.rs | 62 +++++- src/config.rs | 11 +- src/explorer.rs | 28 ++- src/init.lua | 145 +++++++------- src/lua.rs | 12 +- src/msg/in_/external.rs | 76 ++++++++ src/node.rs | 5 +- src/ui.rs | 53 +++-- 15 files changed, 671 insertions(+), 402 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 12e235f..966a854 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,11 +20,17 @@ dependencies = [ "libc", ] +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + [[package]] name = "ansi-to-tui" -version = "1.0.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1213bdcb277c5489f80dca52a5342ca1bc5cbc8deba211714da49ac669336f22" +checksum = "3460d7beaf8b192c09a55933da038ccd514f00efdb37d7d87f3ce078336b47e9" dependencies = [ "nom", "thiserror", @@ -33,9 +39,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.64" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9a8f622bcf6ff3df478e9deba3e03e4e04b300f8e6a139e192c05fa3490afc7" +checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" [[package]] name = "assert_cmd" @@ -83,7 +89,6 @@ dependencies = [ "lazy_static", "memchr", "regex-automata", - "serde", ] [[package]] @@ -132,15 +137,52 @@ dependencies = [ "winapi", ] +[[package]] +name = "ciborium" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c137568cc60b904a7724001b35ce2630fd00d5d84805fbb608ab89509d788f" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346de753af073cc87b52b2083a506b38ac176a44cfb05497b622e27be899b369" + +[[package]] +name = "ciborium-ll" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213030a2b5a4e0c0892b6652260cf6ccac84827b83a85a534e178e3906c4cf1b" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "clap" -version = "2.34.0" +version = "3.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750" dependencies = [ "bitflags", + "clap_lex", + "indexmap", "textwrap", - "unicode-width", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", ] [[package]] @@ -151,15 +193,16 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "criterion" -version = "0.3.6" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f" +checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb" dependencies = [ + "anes", "atty", "cast", + "ciborium", "clap", "criterion-plot", - "csv", "itertools", "lazy_static", "num-traits", @@ -168,7 +211,6 @@ dependencies = [ "rayon", "regex", "serde", - "serde_cbor", "serde_derive", "serde_json", "tinytemplate", @@ -177,9 +219,9 @@ dependencies = [ [[package]] name = "criterion-plot" -version = "0.4.5" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", "itertools", @@ -208,42 +250,24 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "045ebe27666471bb549370b4b0b3e51b07f56325befa4284db65fc89c02511b1" +checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", "memoffset", - "once_cell", "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" +checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" dependencies = [ "cfg-if", - "once_cell", -] - -[[package]] -name = "crossterm" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2102ea4f781910f8a5b98dd061f4c2023f479ce7bb1236330099ceb5a93cf17" -dependencies = [ - "bitflags", - "crossterm_winapi", - "libc", - "mio", - "parking_lot", - "signal-hook", - "signal-hook-mio", - "winapi", ] [[package]] @@ -271,28 +295,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "csv" -version = "1.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" -dependencies = [ - "bstr", - "csv-core", - "itoa 0.4.8", - "ryu", - "serde", -] - -[[package]] -name = "csv-core" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" -dependencies = [ - "memchr", -] - [[package]] name = "difflib" version = "0.4.0" @@ -340,6 +342,15 @@ dependencies = [ "serde", ] +[[package]] +name = "fuzzy-matcher" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" +dependencies = [ + "thread_local", +] + [[package]] name = "gethostname" version = "0.2.3" @@ -384,20 +395,22 @@ dependencies = [ [[package]] name = "humansize" -version = "1.1.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02296996cb8796d7c6e3bc2d9211b7802812d36999a51bb754123ead7d37d026" +checksum = "a866837516f34ad34fb221f3ee01fd0db75f2c2f6abeda2047dc6963fb04ad9a" +dependencies = [ + "libm", +] [[package]] name = "iana-time-zone" -version = "0.1.47" +version = "0.1.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c495f162af0bf17656d0014a0eded5f3cd2f365fdd204548c2869db89359dc7" +checksum = "fd911b35d940d2bd0bea0f9100068e5b97b51a1cbe13d13382f132e0365257a0" dependencies = [ "android_system_properties", "core-foundation-sys", "js-sys", - "once_cell", "wasm-bindgen", "winapi", ] @@ -415,19 +428,13 @@ dependencies = [ [[package]] name = "itertools" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] -[[package]] -name = "itoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - [[package]] name = "itoa" version = "1.0.3" @@ -436,9 +443,9 @@ checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" [[package]] name = "js-sys" -version = "0.3.59" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" dependencies = [ "wasm-bindgen", ] @@ -451,9 +458,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.132" +version = "0.2.134" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb" + +[[package]] +name = "libm" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" +checksum = "292a948cd991e376cf75541fe5b97a1081d713c618b4f1b9500f8844e49eb565" [[package]] name = "linked-hash-map" @@ -463,9 +476,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "lock_api" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f80bf5aacaf25cbfc8210d1cfb718f2bf3b11c4c54e5afe36c236853a8ec390" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" dependencies = [ "autocfg", "scopeguard", @@ -491,9 +504,9 @@ dependencies = [ [[package]] name = "luajit-src" -version = "210.4.1+restyaa7a722" +version = "210.4.3+resty8384278" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92879345f9a97ee140cfe2e08eff49b101533d784527d46ce6d2dc0096d27b3" +checksum = "19ee5d5afddf1ec76ffa55ca7c3001f2f8a703834beba53c56a38ea6641cef44" dependencies = [ "cc", ] @@ -612,9 +625,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" [[package]] name = "oorandom" @@ -622,6 +635,12 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +[[package]] +name = "os_str_bytes" +version = "6.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" + [[package]] name = "parking_lot" version = "0.12.1" @@ -708,9 +727,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.43" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" +checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b" dependencies = [ "unicode-ident", ] @@ -820,28 +839,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "serde" -version = "1.0.144" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" +checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" dependencies = [ "serde_derive", ] -[[package]] -name = "serde_cbor" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" -dependencies = [ - "half", - "serde", -] - [[package]] name = "serde_derive" -version = "1.0.144" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" +checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" dependencies = [ "proc-macro2", "quote", @@ -854,7 +863,7 @@ version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" dependencies = [ - "itoa 1.0.3", + "itoa", "ryu", "serde", ] @@ -903,15 +912,15 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "syn" -version = "1.0.99" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" +checksum = "e90cde112c4b9690b8cbe810cba9ddd8bc1d7472e2cae317b69e9438c1cba7d2" dependencies = [ "proc-macro2", "quote", @@ -926,33 +935,39 @@ checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" [[package]] name = "textwrap" -version = "0.11.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width", -] +checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16" [[package]] name = "thiserror" -version = "1.0.34" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c1b05ca9d106ba7d2e31a9dab4a64e7be2cce415321966ea3132c49a656e252" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.34" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8f2591983642de85c921015f3f070c665a197ed69e417af436115e3a1407487" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + [[package]] name = "time" version = "0.1.44" @@ -976,13 +991,13 @@ dependencies = [ [[package]] name = "tui" -version = "0.18.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96fe69244ec2af261bced1d9046a6fee6c8c2a6b0228e59e5ba39bc8ba4ed729" +checksum = "ccdd26cbd674007e649a272da4475fb666d3aa0ad0531da7136db6fab0e5bad1" dependencies = [ "bitflags", "cassowary", - "crossterm 0.23.2", + "crossterm", "serde", "unicode-segmentation", "unicode-width", @@ -990,11 +1005,11 @@ dependencies = [ [[package]] name = "tui-input" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fd9573b9b1940b491977c1877284e36953a8cf4f8836cf5dc82996912485de" +checksum = "e4efaf26a91ee5307d54e94e95c6772c522a005feb5a959bf3b57e07f49e0f23" dependencies = [ - "crossterm 0.25.0", + "crossterm", "serde", ] @@ -1009,21 +1024,21 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" +checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" [[package]] name = "unicode-segmentation" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" +checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" [[package]] name = "unicode-width" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "version_check" @@ -1065,9 +1080,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.82" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1075,9 +1090,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.82" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" dependencies = [ "bumpalo", "log", @@ -1090,9 +1105,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.82" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1100,9 +1115,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.82" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" dependencies = [ "proc-macro2", "quote", @@ -1113,15 +1128,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.82" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" [[package]] name = "web-sys" -version = "0.3.59" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed055ab27f941423197eb86b2035720b1a3ce40504df082cac2ecc6ed73335a1" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" dependencies = [ "js-sys", "wasm-bindgen", @@ -1203,15 +1218,16 @@ checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" [[package]] name = "xplr" -version = "0.19.3" +version = "0.19.4" dependencies = [ "ansi-to-tui", "anyhow", "assert_cmd", "chrono", "criterion", - "crossterm 0.25.0", + "crossterm", "dirs", + "fuzzy-matcher", "gethostname", "humansize", "indexmap", diff --git a/Cargo.toml b/Cargo.toml index eb2a06d..7c28584 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ path = './benches/criterion.rs' [package] name = 'xplr' -version = '0.19.3' +version = '0.19.4' authors = ['Arijit Basu '] edition = '2021' description = 'A hackable, minimal, fast TUI file explorer' @@ -22,19 +22,20 @@ categories = ['command-line-interface', 'command-line-utilities'] include = ['src/**/*', 'docs/en/src/**/*', 'LICENSE', 'README.md'] [dependencies] -libc = "0.2.132" -humansize = "1.1.1" +libc = "0.2.134" +humansize = "2.1.0" natord = "1.0.9" -anyhow = "1.0.64" +anyhow = "1.0.65" -# Let's keep this locked. See https://docs.rs/serde_yaml/0.9.11/serde_yaml/ +# Let's keep this locked to 0.8.26. See https://docs.rs/serde_yaml/0.9.11/serde_yaml/ serde_yaml = "0.8.26" crossterm = "0.25.0" dirs = "4.0.0" -ansi-to-tui = "1.0.1" +ansi-to-tui = "2.0.0" regex = "1.6.0" gethostname = "0.2.3" +fuzzy-matcher = "0.3.7" [dependencies.lazy_static] version = "1.4.0" @@ -45,12 +46,12 @@ version = "2.0.4" features = ["rev-mappings"] [dependencies.tui] -version = "0.18.0" +version = "0.19.0" default-features = false features = ['crossterm', 'serde'] [dependencies.serde] -version = "1.0.144" +version = "1.0.145" features = ['derive'] [dependencies.chrono] @@ -66,11 +67,11 @@ version = "0.8.3" features = ['luajit', 'vendored', 'serialize', 'send'] [dependencies.tui-input] -version = "0.5.1" +version = "0.6.0" features = ['serde'] [dev-dependencies] -criterion = "0.3.6" +criterion = "0.4.0" assert_cmd = "2.0.4" [profile.release] diff --git a/docs/en/src/awesome-plugins.md b/docs/en/src/awesome-plugins.md index 47cf983..4848b32 100644 --- a/docs/en/src/awesome-plugins.md +++ b/docs/en/src/awesome-plugins.md @@ -11,6 +11,7 @@ of the following plugins work for you, it's very easy to - [**sayanarijit/dual-pane.xplr**][43] Implements support for dual-pane navigation into xplr. - [**sayanarijit/map.xplr**][38] Visually inspect and interactively execute batch commands using xplr. - [**sayanarijit/offline-docs.xplr**][51] Fetch the appropriate version of xplr docs and browse offline. +- [**sayanarijit/regex-search.xplr**][55] Bring back the regex based seach in xplr. - [**sayanarijit/registers.xplr**][49] Use multiple registers to store the selected paths. - [**sayanarijit/type-to-nav.xplr**][28] Inspired by [nnn's type-to-nav mode][29] for xplr, with some tweaks. @@ -109,3 +110,4 @@ of the following plugins work for you, it's very easy to [52]: https://github.com/sayanarijit/wl-clipboard.xplr [53]: https://github.com/Junker/nuke.xplr [54]: https://github.com/sayanarijit/scp.xplr +[55]: https://github.com/sayanarijit/regex-search.xplr diff --git a/docs/en/src/default-key-bindings.md b/docs/en/src/default-key-bindings.md index b7bd980..7fcbd4c 100644 --- a/docs/en/src/default-key-bindings.md +++ b/docs/en/src/default-key-bindings.md @@ -7,14 +7,10 @@ requirements. When you press `?` in [default mode][3], you can see the complete list of [modes][4] and the key mappings for each mode. -### create_directory - -| key | remaps | action | -| ------ | ------ | ------------ | -| ctrl-c | | terminate | -| enter | | submit | -| esc | | cancel | -| tab | | try complete | +[1]: https://www.vim.org/ +[2]: https://github.com/jarun/nnn/ +[3]: #default +[4]: modes.md ### default @@ -26,7 +22,6 @@ of [modes][4] and the key mappings for each mode. | ? | | global help menu | | G | | go to bottom | | V | ctrl-a | select/unselect all | -| ctrl-c | | terminate | | ctrl-d | | duplicate as | | ctrl-i | tab | next visited path | | ctrl-o | | last visited path | @@ -48,86 +43,64 @@ of [modes][4] and the key mappings for each mode. | ~ | | go home | | [0-9] | | input | -### duplicate_as +### go_to_path -| key | remaps | action | -| ------ | ------ | --------- | -| ctrl-c | | terminate | -| enter | | duplicate | -| esc | | cancel | +| key | remaps | action | +| ----- | ------ | ------------ | +| enter | | submit | +| tab | | try complete | ### relative_path_does_not_match_regex -| key | remaps | action | -| ------ | ------ | --------- | -| ctrl-c | | terminate | -| enter | | submit | -| esc | | cancel | +| key | remaps | action | +| ----- | ------ | ------ | +| enter | | submit | ### number -| key | remaps | action | -| ------ | ------ | --------- | -| ctrl-c | | terminate | -| down | j | to down | -| enter | | to index | -| esc | | cancel | -| k | up | to up | -| [0-9] | | input | +| key | remaps | action | +| ----- | ------ | -------- | +| down | j | to down | +| enter | | to index | +| k | up | to up | +| [0-9] | | input | -### search - -| key | remaps | action | -| ------ | ------ | ---------------- | -| ctrl-c | | terminate | -| ctrl-n | down | down | -| ctrl-p | up | up | -| enter | esc | focus | -| left | | back | -| right | | enter | -| tab | | toggle selection | - -### quit +### selection_ops -| key | remaps | action | -| ------ | ------ | ----------------------- | -| ctrl-c | | terminate | -| enter | | just quit | -| esc | | cancel | -| f | | quit printing focus | -| p | | quit printing pwd | -| r | | quit printing result | -| s | | quit printing selection | +| key | remaps | action | +| --- | ------ | ----------- | +| c | | copy here | +| m | | move here | +| x | | open in gui | -### create +### duplicate_as -| key | remaps | action | -| ------ | ------ | ---------------- | -| ctrl-c | | terminate | -| d | | create directory | -| esc | | cancel | -| f | | create file | +| key | remaps | action | +| ----- | ------ | ------------ | +| enter | | submit | +| tab | | try complete | -### filter +### action -| key | remaps | action | -| --------- | ------ | ---------------------------------- | -| R | | relative path does not match regex | -| backspace | | remove last filter | -| ctrl-c | | terminate | -| ctrl-r | | reset filters | -| ctrl-u | | clear filters | -| enter | esc | submit | -| r | | relative path does match regex | +| key | remaps | action | +| ----- | ------ | -------------------- | +| ! | | shell | +| c | | create | +| e | | open in editor | +| l | | logs | +| m | | toggle mouse | +| q | | quit options | +| s | | selection operations | +| [0-9] | | go to index | -### delete +### go_to -| key | remaps | action | -| ------ | ------ | ------------ | -| D | | force delete | -| ctrl-c | | terminate | -| d | | delete | -| esc | | cancel | +| key | remaps | action | +| --- | ------ | -------------- | +| f | | follow symlink | +| g | | top | +| p | | path | +| x | | open in gui | ### sort @@ -143,116 +116,106 @@ of [modes][4] and the key mappings for each mode. | S | | by size reverse | | backspace | | remove last sorter | | c | | by created | -| ctrl-c | | terminate | | ctrl-r | | reset sorters | | ctrl-u | | clear sorters | | e | | by canonical extension | -| enter | esc | submit | +| enter | | submit | | l | | by last modified | | m | | by canonical mime essence | | n | | by node type | | r | | by relative path | | s | | by size | -### go_to +### rename -| key | remaps | action | -| ------ | ------ | -------------- | -| ctrl-c | | terminate | -| esc | | cancel | -| f | | follow symlink | -| g | | top | -| p | | path | -| x | | open in gui | +| key | remaps | action | +| ----- | ------ | ------------ | +| enter | | submit | +| tab | | try complete | -### create_file +### debug_error -| key | remaps | action | -| ------ | ------ | ------------ | -| ctrl-c | | terminate | -| enter | | submit | -| esc | | cancel | -| tab | | try complete | +| key | remaps | action | +| ----- | ------ | ------------------- | +| enter | | open logs in editor | +| q | | quit | -### recover +### switch_layout -| key | remaps | action | -| ------ | ------ | --------- | -| ctrl-c | | terminate | -| esc | | escape | +| key | remaps | action | +| --- | ------ | -------------------- | +| 1 | | default | +| 2 | | no help menu | +| 3 | | no selection panel | +| 4 | | no help or selection | -### action +### search -| key | remaps | action | -| ------ | ------ | -------------------- | -| ! | | shell | -| c | | create | -| ctrl-c | | terminate | -| e | | open in editor | -| esc | | cancel | -| l | | logs | -| m | | toggle mouse | -| q | | quit options | -| s | | selection operations | -| [0-9] | | go to index | +| key | remaps | action | +| ------ | ------ | ---------------- | +| ctrl-n | down | down | +| ctrl-p | up | up | +| enter | | submit | +| esc | | cancel | +| left | | back | +| right | | enter | +| tab | | toggle selection | -### switch_layout +### create_file -| key | remaps | action | -| ------ | ------ | -------------------- | -| 1 | | default | -| 2 | | no help menu | -| 3 | | no selection panel | -| 4 | | no help or selection | -| ctrl-c | | terminate | -| esc | | cancel | +| key | remaps | action | +| ----- | ------ | ------------ | +| enter | | submit | +| tab | | try complete | -### relative_path_does_match_regex +### recover -| key | remaps | action | -| ------ | ------ | --------- | -| ctrl-c | | terminate | -| enter | | submit | -| esc | | cancel | +| key | remaps | action | +| --- | ------ | ------ | -### debug_error +### create -| key | remaps | action | -| ------ | ------ | ------------------- | -| ctrl-c | | terminate | -| enter | | open logs in editor | -| esc | | escape | -| q | | quit | +| key | remaps | action | +| --- | ------ | ---------------- | +| d | | create directory | +| f | | create file | -### go_to_path +### filter -| key | remaps | action | -| ------ | ------ | ------------ | -| ctrl-c | | terminate | -| enter | | submit | -| esc | | cancel | -| tab | | try complete | +| key | remaps | action | +| --------- | ------ | ---------------------------------- | +| R | | relative path does not match regex | +| backspace | | remove last filter | +| ctrl-r | | reset filters | +| ctrl-u | | clear filters | +| r | | relative path does match regex | -### selection_ops +### delete -| key | remaps | action | -| ------ | ------ | ----------- | -| c | | copy here | -| ctrl-c | | terminate | -| esc | | cancel | -| m | | move here | -| x | | open in gui | +| key | remaps | action | +| --- | ------ | ------------ | +| D | | force delete | +| d | | delete | -### rename +### create_directory -| key | remaps | action | -| ------ | ------ | ------------ | -| ctrl-c | | terminate | -| enter | | rename | -| esc | | cancel | -| tab | | try complete | +| key | remaps | action | +| ----- | ------ | ------------ | +| enter | | submit | +| tab | | try complete | -[1]: https://www.vim.org/ -[2]: https://github.com/jarun/nnn/ -[3]: #default -[4]: modes.md +### quit + +| key | remaps | action | +| ----- | ------ | ----------------------- | +| enter | | just quit | +| f | | quit printing focus | +| p | | quit printing pwd | +| r | | quit printing result | +| s | | quit printing selection | + +### relative_path_does_match_regex + +| key | remaps | action | +| ----- | ------ | ------ | +| enter | | submit | diff --git a/docs/en/src/general-config.md b/docs/en/src/general-config.md index a26b869..4515723 100644 --- a/docs/en/src/general-config.md +++ b/docs/en/src/general-config.md @@ -322,6 +322,12 @@ Type: nullable mapping of the following key-value pairs: - format: nullable string - style: [Style](https://xplr.dev/en/style) +#### xplr.config.general.sort_and_filter_ui.search_identifier + +The identifiers used to denote applied search input. + +Type: { format = nullable string, style = [Style](https://xplr.dev/en/style) } + #### xplr.config.general.panel_ui.default.title.format The content for panel title by default. diff --git a/docs/en/src/messages.md b/docs/en/src/messages.md index 54fc18d..b7a8ae5 100644 --- a/docs/en/src/messages.md +++ b/docs/en/src/messages.md @@ -15,6 +15,7 @@ xplr messages categorized based on their purpose. - [Select Operations](#select-operations) - [Filter Operations](#filter-operations) - [Sort Operations](#sort-operations) +- [Search Operations](#search-operations) - [Mouse Operations](#mouse-operations) - [Fifo Operations](#fifo-operations) - [Logging](#logging) @@ -757,6 +758,8 @@ Example: Add a [filter](https://xplr.dev/en/filtering#filter) to exclude nodes while exploring directories. +You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. +Filters get automatically cleared when changing directories. Type: { AddNodeFilter = { filter = [Filter](https://xplr.dev/en/filtering#filter), input = "string" } @@ -768,6 +771,7 @@ Example: #### RemoveNodeFilter Remove an existing [filter](https://xplr.dev/en/filtering#filter). +You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. Type: { RemoveNodeFilter = { filter = [Filter](https://xplr.dev/en/filtering), input = "string" } @@ -780,6 +784,7 @@ Example: Remove a [filter](https://xplr.dev/en/filtering#filter) if it exists, else, add a it. +You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. Type: { ToggleNodeFilter = { filter = [Filter](https://xplr.dev/en/filtering), input = "string" } @@ -792,6 +797,7 @@ Example: Add a node [filter](https://xplr.dev/en/filtering#filter) reading the input from the buffer. +You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. Type: { AddNodeFilterFromInput = [Filter](https://xplr.dev/en/filtering) } @@ -804,6 +810,7 @@ Example: Remove a node [filter](https://xplr.dev/en/filtering#filter) reading the input from the buffer. +You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. Type: { RemoveNodeFilterFromInput = [Filter](https://xplr.dev/en/filtering) } @@ -815,6 +822,7 @@ Example: #### RemoveLastNodeFilter Remove the last node [filter](https://xplr.dev/en/filtering). +You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. Example: @@ -825,6 +833,7 @@ Example: Reset the node [filters](https://xplr.dev/en/filtering) back to the default configuration. +You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. Example: @@ -834,6 +843,7 @@ Example: #### ClearNodeFilters Clear all the node [filters](https://xplr.dev/en/filtering). +You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. Example: @@ -846,6 +856,7 @@ Example: Add a [sorter](https://xplr.dev/en/sorting#sorter) to sort nodes while exploring directories. +You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. Type: { AddNodeSorter = { sorter = [Sorter](https://xplr.dev/en/sorting#sorter), reverse = bool } } @@ -857,6 +868,7 @@ Example: #### RemoveNodeSorter Remove an existing [sorter](https://xplr.dev/en/sorting#sorter). +You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. Type: { RemoveNodeSorter = [Sorter](https://xplr.dev/en/sorting#sorter) } @@ -868,6 +880,7 @@ Example: #### ReverseNodeSorter Reverse a node [sorter](https://xplr.dev/en/sorting#sorter). +You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. Type: { ReverseNodeSorter = [Sorter](https://xplr.dev/en/sorting#sorter) } @@ -880,6 +893,7 @@ Example: Remove a [sorter](https://xplr.dev/en/sorting#sorter) if it exists, else, add a it. +You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. Type: { ToggleNodeSorter = { sorter = [Sorter](https://xplr.dev/en/sorting#sorter), reverse = bool } } @@ -891,6 +905,7 @@ Example: #### ReverseNodeSorters Reverse the node [sorters](https://xplr.dev/en/sorting#sorter). +You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. Example: @@ -900,6 +915,7 @@ Example: #### RemoveLastNodeSorter Remove the last node [sorter](https://xplr.dev/en/sorting#sorter). +You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. Example: @@ -910,6 +926,7 @@ Example: Reset the node [sorters](https://xplr.dev/en/sorting#sorter) back to the default configuration. +You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. Example: @@ -919,12 +936,60 @@ Example: #### ClearNodeSorters Clear all the node [sorters](https://xplr.dev/en/sorting#sorter). +You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. Example: - Lua: `"ClearNodeSorters"` - YAML: `ClearNodeSorters` +### Search Operations + +#### SearchFuzzy + +Search files using fuzzy match algorithm. +It keeps the filters, but overrides the sorters. +You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. +It gets reset automatically when changing directory. + +Type: { SearchFuzzy = "string" } + +Example: + +- Lua: `{ SearchFuzzy = "pattern" }` +- YAML: `SearchFuzzy: pattern` + +#### SearchFuzzyFromInput + +Calls `SearchFuzzy` with the input taken from the input buffer. +You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. + +Example: + +- Lua: `"SearchFuzzyFromInput"` +- YAML: `SearchFuzzyFromInput` + +#### AcceptSearch + +Accepts the search by keeping the latest focus while in search mode. +Automatically calls `ExplorePwd`. + +Example: + +- Lua: `"AcceptSearch"` +- YAML: `AcceptSearch` + +#### CancelSearch + +Cancels the search by discarding the latest focus and recovering +the focus before search. +Automatically calls `ExplorePwd`. + +Example: + +- Lua: `"CancelSearch"` +- YAML: `CancelSearch` + ### Mouse Operations #### EnableMouse diff --git a/docs/en/src/upgrade-guide.md b/docs/en/src/upgrade-guide.md index 61126a8..77ca164 100644 --- a/docs/en/src/upgrade-guide.md +++ b/docs/en/src/upgrade-guide.md @@ -45,7 +45,7 @@ compatibility. ### Instructions -#### [v0.18.0][46] -> [v0.19.3][47] +#### [v0.18.0][46] -> [v0.19.4][47] - BREAKING: The builtin modes cannot be accessed using space separated names anymore. Use underscore separated mode names. For e.g. @@ -77,6 +77,20 @@ compatibility. - Fixed applying regex based filters via the CLI and `$XPLR_PIPE_MSG_IN` pipe. - You can use the `prompt` field to define input prompt for each mode, instead of using the `SetInputPrompt` message. +- Since version v0.19.4, the native search will default to skim-v2 based fuzzy + matching. `esc` while in search mode will recover the initial focus. People + who prefer the regex based search, can use the `regex-search.xplr` plugin. + The following messages will be available for search based operations: + - SearchFuzzy + - SearchFuzzyFromInput + - AcceptSearch + - CancelSearch +- Since version v0.19.4, quick scrolling operations are supported using the + following messages and keys: + - ScrollUp -------- page-up + - ScrollDown ------ page-down + - ScrollUpHalf ---- { + - ScrollDownHalf -- } Like this project so far? **[Please consider contributing][5]**. @@ -387,4 +401,4 @@ Else do the following: [44]: https://github.com/sayanarijit/xplr/releases/tag/v0.16.4 [45]: https://github.com/sayanarijit/xplr/releases/tag/v0.17.6 [46]: https://github.com/sayanarijit/xplr/releases/tag/v0.18.0 -[47]: https://github.com/sayanarijit/xplr/releases/tag/v0.19.3 +[47]: https://github.com/sayanarijit/xplr/releases/tag/v0.19.4 diff --git a/src/app.rs b/src/app.rs index ce7711c..fddc721 100644 --- a/src/app.rs +++ b/src/app.rs @@ -8,6 +8,7 @@ pub use crate::msg::in_::external::Command; pub use crate::msg::in_::external::ExplorerConfig; pub use crate::msg::in_::external::NodeFilter; pub use crate::msg::in_::external::NodeFilterApplicable; +use crate::msg::in_::external::NodeSearcher; pub use crate::msg::in_::external::NodeSorter; pub use crate::msg::in_::external::NodeSorterApplicable; pub use crate::msg::in_::ExternalMsg; @@ -478,6 +479,10 @@ impl App { ReverseNodeSorters => self.reverse_node_sorters(), ResetNodeSorters => self.reset_node_sorters(), ClearNodeSorters => self.clear_node_sorters(), + SearchFuzzy(p) => self.search_fuzzy(p), + SearchFuzzyFromInput => self.search_fuzzy_from_input(), + AcceptSearch => self.accept_search(), + CancelSearch => self.cancel_search(), EnableMouse => self.enable_mouse(), DisableMouse => self.disable_mouse(), ToggleMouse => self.toggle_mouse(), @@ -735,6 +740,7 @@ impl App { let focus = self.focused_node().map(|n| n.relative_path.clone()); self = self.add_last_focus(pwd, focus)?; self.pwd = dir.to_string_lossy().to_string(); + self.explorer_config.searcher = None; if save_history { self.history = self.history.push(format!("{}/", self.pwd)); } @@ -1146,9 +1152,11 @@ impl App { dir.parent.clone(), dir.focused_node().map(|n| n.relative_path.clone()), )?; + if dir.parent == self.pwd { self.directory_buffer = Some(dir); - } + }; + Ok(self) } @@ -1374,6 +1382,58 @@ impl App { Ok(self) } + pub fn search_fuzzy(mut self, pattern: String) -> Result { + let rf = self + .explorer_config + .searcher + .as_ref() + .map(|s| s.recoverable_focus.clone()) + .unwrap_or_else(|| self.focused_node().map(|n| n.absolute_path.clone())); + + self.explorer_config.searcher = Some(NodeSearcher::new(pattern, rf)); + Ok(self) + } + + fn search_fuzzy_from_input(self) -> Result { + if let Some(pattern) = self.input.buffer.as_ref().map(Input::to_string) { + self.search_fuzzy(pattern) + } else { + Ok(self) + } + } + + fn accept_search(mut self) -> Result { + let focus = self + .directory_buffer + .as_ref() + .and_then(|dir| dir.focused_node()) + .map(|n| n.relative_path.clone()); + + self.explorer_config.searcher = None; + self = self.explore_pwd()?; + + if let Some(f) = focus { + self = self.focus_path(&f, true)?; + } + Ok(self) + } + + fn cancel_search(mut self) -> Result { + let focus = self + .explorer_config + .searcher + .as_ref() + .and_then(|s| s.recoverable_focus.clone()); + + self.explorer_config.searcher = None; + self = self.explore_pwd()?; + + if let Some(f) = focus { + self = self.focus_path(&f, true)?; + } + Ok(self) + } + fn enable_mouse(mut self) -> Result { self.msg_out.push_back(MsgOut::EnableMouse); Ok(self) diff --git a/src/config.rs b/src/config.rs index f77e400..49a52f4 100644 --- a/src/config.rs +++ b/src/config.rs @@ -80,7 +80,7 @@ pub struct NodeTypesConfig { pub special: HashMap, } -#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct UiConfig { #[serde(default)] @@ -93,7 +93,7 @@ pub struct UiConfig { pub style: Style, } -#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct UiElement { #[serde(default)] @@ -189,6 +189,9 @@ pub struct SortAndFilterUi { #[serde(default)] pub filter_identifiers: HashMap, + + #[serde(default)] + pub search_identifier: Option, } #[derive(Debug, Clone, Default, Serialize, Deserialize)] @@ -356,7 +359,7 @@ impl KeyBindings { } } -#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Mode { #[serde(default)] @@ -540,7 +543,7 @@ impl ModesConfig { } } -#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct PanelUiConfig { #[serde(default)] diff --git a/src/explorer.rs b/src/explorer.rs index 5a9102e..54e01f5 100644 --- a/src/explorer.rs +++ b/src/explorer.rs @@ -2,11 +2,18 @@ use crate::app::{ DirectoryBuffer, ExplorerConfig, ExternalMsg, InternalMsg, MsgIn, Node, Task, }; use anyhow::{Error, Result}; +use fuzzy_matcher::skim::SkimMatcherV2; +use fuzzy_matcher::FuzzyMatcher; +use lazy_static::lazy_static; use std::fs; use std::path::PathBuf; use std::sync::mpsc::Sender; use std::thread; +lazy_static! { + static ref FUZZY_MATCHER: SkimMatcherV2 = SkimMatcherV2::default(); +} + pub(crate) fn explore_sync( config: ExplorerConfig, parent: PathBuf, @@ -27,9 +34,26 @@ pub(crate) fn explore_sync( .filter(|n| config.filter(n)) .collect::>(); - nodes.sort_by(|a, b| config.sort(a, b)); + nodes = if let Some(pattern) = config.searcher.as_ref().map(|s| &s.pattern) { + let mut nodes = nodes + .into_iter() + .filter_map(|n| { + FUZZY_MATCHER + .fuzzy_match(&n.relative_path, pattern) + .map(|score| (n, score)) + }) + .collect::>(); + + nodes.sort_by(|(_, s1), (_, s2)| s2.cmp(s1)); + nodes.into_iter().map(|(n, _)| n).collect::>() + } else { + nodes.sort_by(|a, b| config.sort(a, b)); + nodes + }; - let focus_index = if let Some(focus) = focused_path { + let focus_index = if config.searcher.is_some() { + 0 + } else if let Some(focus) = focused_path { let focus_str = focus.to_string_lossy().to_string(); nodes .iter() diff --git a/src/init.lua b/src/init.lua index 7f4ebae..9bfc123 100644 --- a/src/init.lua +++ b/src/init.lua @@ -450,6 +450,14 @@ xplr.config.general.sort_and_filter_ui.filter_identifiers = { IAbsolutePathDoesNotMatchRegex = { format = "[i]abs!/", style = {} }, } +-- The identifiers used to denote applied search input. +-- +-- Type: { format = nullable string, style = [Style](https://xplr.dev/en/style) } +xplr.config.general.sort_and_filter_ui.search_identifier = { + format = "search:", + style = {}, +} + -- The content for panel title by default. -- -- Type: nullable string @@ -666,7 +674,7 @@ xplr.config.general.start_fifo = nil -- Type: [Key Bindings](https://xplr.dev/en/configure-key-bindings#key-bindings) xplr.config.general.global_key_bindings = { on_key = { - esc = { + ["esc"] = { messages = { "PopMode", }, @@ -1065,8 +1073,7 @@ xplr.config.modes.builtin.default = { messages = { "PopMode", { SwitchModeBuiltin = "search" }, - { SetInputBuffer = "(?i)" }, - "ExplorePwdAsync", + { SetInputBuffer = "" }, }, }, ["ctrl-i"] = { @@ -1106,13 +1113,13 @@ xplr.config.modes.builtin.default = { { SwitchModeBuiltin = "delete" }, }, }, - down = { + ["down"] = { help = "down", messages = { "FocusNext", }, }, - enter = { + ["enter"] = { help = "quit with result", messages = { "PrintResultAndQuit", @@ -1132,7 +1139,7 @@ xplr.config.modes.builtin.default = { { SwitchModeBuiltin = "go_to" }, }, }, - left = { + ["left"] = { help = "back", messages = { "Back", @@ -1170,7 +1177,7 @@ xplr.config.modes.builtin.default = { }, }, }, - right = { + ["right"] = { help = "enter", messages = { "Enter", @@ -1183,14 +1190,14 @@ xplr.config.modes.builtin.default = { { SwitchModeBuiltin = "sort" }, }, }, - space = { + ["space"] = { help = "toggle selection", messages = { "ToggleSelection", "FocusNext", }, }, - up = { + ["up"] = { help = "up", messages = { "FocusPrevious", @@ -1308,7 +1315,7 @@ xplr.config.modes.builtin.debug_error = { }, key_bindings = { on_key = { - enter = { + ["enter"] = { help = "open logs in editor", messages = { { @@ -1318,7 +1325,7 @@ xplr.config.modes.builtin.debug_error = { }, }, }, - q = { + ["q"] = { help = "quit", messages = { "Quit", @@ -1369,7 +1376,7 @@ xplr.config.modes.builtin.go_to_path = { name = "go to path", key_bindings = { on_key = { - enter = { + ["enter"] = { help = "submit", messages = { { @@ -1384,7 +1391,7 @@ xplr.config.modes.builtin.go_to_path = { "PopMode", }, }, - tab = { + ["tab"] = { help = "try complete", messages = { { CallLuaSilently = "builtin.try_complete_path" }, @@ -1445,6 +1452,13 @@ xplr.config.modes.builtin.selection_ops = { "PopMode", }, }, + ["u"] = { + help = "clear selection", + messages = { + "ClearSelection", + "PopMode", + }, + }, ["x"] = { help = "open in gui", messages = { @@ -1480,7 +1494,7 @@ xplr.config.modes.builtin.create = { name = "create", key_bindings = { on_key = { - d = { + ["d"] = { help = "create directory", messages = { "PopMode", @@ -1488,7 +1502,7 @@ xplr.config.modes.builtin.create = { { SetInputBuffer = "" }, }, }, - f = { + ["f"] = { help = "create file", messages = { "PopMode", @@ -1508,13 +1522,13 @@ xplr.config.modes.builtin.create_directory = { prompt = "ð ❯ ", key_bindings = { on_key = { - tab = { + ["tab"] = { help = "try complete", messages = { { CallLuaSilently = "builtin.try_complete_path" }, }, }, - enter = { + ["enter"] = { help = "submit", messages = { { @@ -1550,13 +1564,13 @@ xplr.config.modes.builtin.create_file = { prompt = "ƒ ❯ ", key_bindings = { on_key = { - tab = { + ["tab"] = { help = "try complete", messages = { { CallLuaSilently = "builtin.try_complete_path" }, }, }, - enter = { + ["enter"] = { help = "submit", messages = { { @@ -1593,21 +1607,21 @@ xplr.config.modes.builtin.number = { prompt = ":", key_bindings = { on_key = { - down = { + ["down"] = { help = "to down", messages = { "FocusNextByRelativeIndexFromInput", "PopMode", }, }, - enter = { + ["enter"] = { help = "to index", messages = { "FocusByIndexFromInput", "PopMode", }, }, - up = { + ["up"] = { help = "to up", messages = { "FocusPreviousByRelativeIndexFromInput", @@ -1641,21 +1655,21 @@ xplr.config.modes.builtin.go_to = { name = "go to", key_bindings = { on_key = { - f = { + ["f"] = { help = "follow symlink", messages = { "FollowSymlink", "PopMode", }, }, - g = { + ["g"] = { help = "top", messages = { "FocusFirst", "PopMode", }, }, - p = { + ["p"] = { help = "path", messages = { "PopMode", @@ -1663,7 +1677,7 @@ xplr.config.modes.builtin.go_to = { { SetInputBuffer = "" }, }, }, - x = { + ["x"] = { help = "open in gui", messages = { { @@ -1696,13 +1710,13 @@ xplr.config.modes.builtin.rename = { name = "rename", key_bindings = { on_key = { - tab = { + ["tab"] = { help = "try complete", messages = { { CallLuaSilently = "builtin.try_complete_path" }, }, }, - enter = { + ["enter"] = { help = "submit", messages = { { @@ -1738,13 +1752,13 @@ xplr.config.modes.builtin.duplicate_as = { name = "duplicate as", key_bindings = { on_key = { - tab = { + ["tab"] = { help = "try complete", messages = { { CallLuaSilently = "builtin.try_complete_path" }, }, }, - enter = { + ["enter"] = { help = "submit", messages = { { @@ -1915,31 +1929,31 @@ xplr.config.modes.builtin.quit = { name = "quit", key_bindings = { on_key = { - enter = { + ["enter"] = { help = "just quit", messages = { "Quit", }, }, - p = { + ["p"] = { help = "quit printing pwd", messages = { "PrintPwdAndQuit", }, }, - f = { + ["f"] = { help = "quit printing focus", messages = { "PrintFocusPathAndQuit", }, }, - s = { + ["s"] = { help = "quit printing selection", messages = { "PrintSelectionAndQuit", }, }, - r = { + ["r"] = { help = "quit printing result", messages = { "PrintResultAndQuit", @@ -1957,65 +1971,64 @@ xplr.config.modes.builtin.search = { prompt = "/", key_bindings = { on_key = { - down = { - help = "down", + ["up"] = { + help = "up", messages = { - "FocusNext", + "FocusPrevious", }, }, - enter = { - help = "submit", + ["down"] = { + help = "down", messages = { - { RemoveNodeFilterFromInput = "RelativePathDoesMatchRegex" }, - "PopMode", - "ExplorePwdAsync", + "FocusNext", }, }, - right = { + ["right"] = { help = "enter", messages = { - { RemoveNodeFilterFromInput = "RelativePathDoesMatchRegex" }, "Enter", - { SetInputBuffer = "(?i)" }, - "ExplorePwdAsync", + { SetInputBuffer = "" }, }, }, - left = { + ["left"] = { help = "back", messages = { - { RemoveNodeFilterFromInput = "RelativePathDoesMatchRegex" }, "Back", - { SetInputBuffer = "(?i)" }, - "ExplorePwdAsync", + { SetInputBuffer = "" }, }, }, - tab = { + ["tab"] = { help = "toggle selection", messages = { "ToggleSelection", "FocusNext", }, }, - up = { - help = "up", + ["enter"] = { + help = "submit", messages = { - "FocusPrevious", + "AcceptSearch", + "PopMode", + }, + }, + ["esc"] = { + help = "cancel", + messages = { + "CancelSearch", + "PopMode", }, }, }, default = { messages = { - { RemoveNodeFilterFromInput = "RelativePathDoesMatchRegex" }, "UpdateInputBufferFromKey", - { AddNodeFilterFromInput = "RelativePathDoesMatchRegex" }, + "SearchFuzzyFromInput", "ExplorePwdAsync", }, }, }, } -xplr.config.modes.builtin.search.key_bindings.on_key["esc"] = - xplr.config.modes.builtin.search.key_bindings.on_key.enter xplr.config.modes.builtin.search.key_bindings.on_key["ctrl-n"] = xplr.config.modes.builtin.search.key_bindings.on_key.down xplr.config.modes.builtin.search.key_bindings.on_key["ctrl-p"] = @@ -2046,7 +2059,7 @@ xplr.config.modes.builtin.filter = { "ExplorePwdAsync", }, }, - backspace = { + ["backspace"] = { help = "remove last filter", messages = { "RemoveLastNodeFilter", @@ -2079,13 +2092,13 @@ xplr.config.modes.builtin.relative_path_does_match_regex = { prompt = xplr.config.general.sort_and_filter_ui.filter_identifiers.RelativePathDoesMatchRegex.format, key_bindings = { on_key = { - enter = { + ["enter"] = { help = "submit", messages = { "PopMode", }, }, - esc = { + ["esc"] = { messages = { { RemoveNodeFilterFromInput = "RelativePathDoesMatchRegex" }, "PopMode", @@ -2112,13 +2125,13 @@ xplr.config.modes.builtin.relative_path_does_not_match_regex = { prompt = xplr.config.general.sort_and_filter_ui.filter_identifiers.RelativePathDoesNotMatchRegex.format, key_bindings = { on_key = { - enter = { + ["enter"] = { help = "submit", messages = { "PopMode", }, }, - esc = { + ["esc"] = { messages = { { RemoveNodeFilterFromInput = "RelativePathDoesNotMatchRegex" }, "PopMode", @@ -2223,7 +2236,7 @@ xplr.config.modes.builtin.sort = { "ExplorePwdAsync", }, }, - backspace = { + ["backspace"] = { help = "remove last sorter", messages = { "RemoveLastNodeSorter", @@ -2256,7 +2269,7 @@ xplr.config.modes.builtin.sort = { "ExplorePwdAsync", }, }, - enter = { + ["enter"] = { help = "submit", messages = { "PopMode", diff --git a/src/lua.rs b/src/lua.rs index 306604b..59e0bcb 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -146,24 +146,24 @@ mod tests { assert!(check_version(VERSION, "foo path").is_ok()); // Current release if OK - assert!(check_version("0.19.3", "foo path").is_ok()); + assert!(check_version("0.19.4", "foo path").is_ok()); // Prev major release is ERR // - Not yet // Prev minor release is ERR (Change when we get to v1) - assert!(check_version("0.18.3", "foo path").is_err()); + assert!(check_version("0.18.4", "foo path").is_err()); // Prev bugfix release is OK - assert!(check_version("0.19.2", "foo path").is_ok()); + assert!(check_version("0.19.3", "foo path").is_ok()); // Next major release is ERR - assert!(check_version("1.19.3", "foo path").is_err()); + assert!(check_version("1.19.4", "foo path").is_err()); // Next minor release is ERR - assert!(check_version("0.20.3", "foo path").is_err()); + assert!(check_version("0.20.4", "foo path").is_err()); // Next bugfix release is ERR (Change when we get to v1) - assert!(check_version("0.19.4", "foo path").is_err()); + assert!(check_version("0.19.5", "foo path").is_err()); } } diff --git a/src/msg/in_/external.rs b/src/msg/in_/external.rs index 277d283..9aae1be 100644 --- a/src/msg/in_/external.rs +++ b/src/msg/in_/external.rs @@ -675,6 +675,8 @@ pub enum ExternalMsg { /// Add a [filter](https://xplr.dev/en/filtering#filter) to exclude nodes /// while exploring directories. + /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. + /// Filters get automatically cleared when changing directories. /// /// Type: { AddNodeFilter = { filter = [Filter](https://xplr.dev/en/filtering#filter), input = "string" } /// @@ -685,6 +687,7 @@ pub enum ExternalMsg { AddNodeFilter(NodeFilterApplicable), /// Remove an existing [filter](https://xplr.dev/en/filtering#filter). + /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. /// /// Type: { RemoveNodeFilter = { filter = [Filter](https://xplr.dev/en/filtering), input = "string" } /// @@ -696,6 +699,7 @@ pub enum ExternalMsg { /// Remove a [filter](https://xplr.dev/en/filtering#filter) if it exists, /// else, add a it. + /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. /// /// Type: { ToggleNodeFilter = { filter = [Filter](https://xplr.dev/en/filtering), input = "string" } /// @@ -707,6 +711,7 @@ pub enum ExternalMsg { /// Add a node [filter](https://xplr.dev/en/filtering#filter) reading the /// input from the buffer. + /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. /// /// Type: { AddNodeFilterFromInput = [Filter](https://xplr.dev/en/filtering) } /// @@ -718,6 +723,7 @@ pub enum ExternalMsg { /// Remove a node [filter](https://xplr.dev/en/filtering#filter) reading /// the input from the buffer. + /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. /// /// Type: { RemoveNodeFilterFromInput = [Filter](https://xplr.dev/en/filtering) } /// @@ -728,6 +734,7 @@ pub enum ExternalMsg { RemoveNodeFilterFromInput(NodeFilter), /// Remove the last node [filter](https://xplr.dev/en/filtering). + /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. /// /// Example: /// @@ -737,6 +744,7 @@ pub enum ExternalMsg { /// Reset the node [filters](https://xplr.dev/en/filtering) back to the /// default configuration. + /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. /// /// Example: /// @@ -745,6 +753,7 @@ pub enum ExternalMsg { ResetNodeFilters, /// Clear all the node [filters](https://xplr.dev/en/filtering). + /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. /// /// Example: /// @@ -756,6 +765,7 @@ pub enum ExternalMsg { /// Add a [sorter](https://xplr.dev/en/sorting#sorter) to sort nodes while /// exploring directories. + /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. /// /// Type: { AddNodeSorter = { sorter = [Sorter](https://xplr.dev/en/sorting#sorter), reverse = bool } } /// @@ -766,6 +776,7 @@ pub enum ExternalMsg { AddNodeSorter(NodeSorterApplicable), /// Remove an existing [sorter](https://xplr.dev/en/sorting#sorter). + /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. /// /// Type: { RemoveNodeSorter = [Sorter](https://xplr.dev/en/sorting#sorter) } /// @@ -776,6 +787,7 @@ pub enum ExternalMsg { RemoveNodeSorter(NodeSorter), /// Reverse a node [sorter](https://xplr.dev/en/sorting#sorter). + /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. /// /// Type: { ReverseNodeSorter = [Sorter](https://xplr.dev/en/sorting#sorter) } /// @@ -787,6 +799,7 @@ pub enum ExternalMsg { /// Remove a [sorter](https://xplr.dev/en/sorting#sorter) if it exists, /// else, add a it. + /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. /// /// Type: { ToggleNodeSorter = { sorter = [Sorter](https://xplr.dev/en/sorting#sorter), reverse = bool } } /// @@ -797,6 +810,7 @@ pub enum ExternalMsg { ToggleNodeSorter(NodeSorterApplicable), /// Reverse the node [sorters](https://xplr.dev/en/sorting#sorter). + /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. /// /// Example: /// @@ -805,6 +819,7 @@ pub enum ExternalMsg { ReverseNodeSorters, /// Remove the last node [sorter](https://xplr.dev/en/sorting#sorter). + /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. /// /// Example: /// @@ -814,6 +829,7 @@ pub enum ExternalMsg { /// Reset the node [sorters](https://xplr.dev/en/sorting#sorter) back to /// the default configuration. + /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. /// /// Example: /// @@ -822,6 +838,7 @@ pub enum ExternalMsg { ResetNodeSorters, /// Clear all the node [sorters](https://xplr.dev/en/sorting#sorter). + /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. /// /// Example: /// @@ -829,6 +846,49 @@ pub enum ExternalMsg { /// - YAML: `ClearNodeSorters` ClearNodeSorters, + /// ### Search Operations -------------------------------------------------- + + /// Search files using fuzzy match algorithm. + /// It keeps the filters, but overrides the sorters. + /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. + /// It gets reset automatically when changing directory. + /// + /// Type: { SearchFuzzy = "string" } + /// + /// Example: + /// + /// - Lua: `{ SearchFuzzy = "pattern" }` + /// - YAML: `SearchFuzzy: pattern` + SearchFuzzy(String), + + /// Calls `SearchFuzzy` with the input taken from the input buffer. + /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. + /// + /// Example: + /// + /// - Lua: `"SearchFuzzyFromInput"` + /// - YAML: `SearchFuzzyFromInput` + SearchFuzzyFromInput, + + /// Accepts the search by keeping the latest focus while in search mode. + /// Automatically calls `ExplorePwd`. + /// + /// Example: + /// + /// - Lua: `"AcceptSearch"` + /// - YAML: `AcceptSearch` + AcceptSearch, + + /// Cancels the search by discarding the latest focus and recovering + /// the focus before search. + /// Automatically calls `ExplorePwd`. + /// + /// Example: + /// + /// - Lua: `"CancelSearch"` + /// - YAML: `CancelSearch` + CancelSearch, + /// ### Mouse Operations --------------------------------------------------- /// Enable mouse @@ -1513,10 +1573,26 @@ impl NodeFilterApplicable { } } +#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub struct NodeSearcher { + pub pattern: String, + pub recoverable_focus: Option, +} + +impl NodeSearcher { + pub fn new(pattern: String, recoverable_focus: Option) -> Self { + Self { + pattern, + recoverable_focus, + } + } +} + #[derive(Debug, Default, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct ExplorerConfig { pub filters: IndexSet, pub sorters: IndexSet, + pub searcher: Option, } impl ExplorerConfig { diff --git a/src/node.rs b/src/node.rs index befc9ac..aee6c34 100644 --- a/src/node.rs +++ b/src/node.rs @@ -1,5 +1,5 @@ use crate::permissions::Permissions; -use humansize::{file_size_opts as options, FileSize}; +use humansize::{format_size, DECIMAL}; use serde::{Deserialize, Serialize}; use std::cmp::Ordering; use std::os::unix::prelude::MetadataExt; @@ -7,8 +7,7 @@ use std::path::{Path, PathBuf}; use std::time::UNIX_EPOCH; fn to_human_size(size: u64) -> String { - size.file_size(options::CONVENTIONAL) - .unwrap_or_else(|_| format!("{} B", size)) + format_size(size, DECIMAL) } fn mime_essence(path: &Path, is_dir: bool) -> String { diff --git a/src/ui.rs b/src/ui.rs index 396d37d..ece1bff 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -104,7 +104,7 @@ pub enum ContentBody { }, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub enum Layout { Nothing, @@ -251,7 +251,7 @@ impl Modifier { } } -#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize)] +#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Style { pub fg: Option, @@ -861,6 +861,12 @@ fn draw_sort_n_filter( let ui = app.config.general.sort_and_filter_ui.to_owned(); let filter_by: &IndexSet = &app.explorer_config.filters; let sort_by: &IndexSet = &app.explorer_config.sorters; + let search = app + .explorer_config + .searcher + .as_ref() + .map(|s| s.pattern.clone()); + let defaultui = &ui.default_identifier; let forwardui = defaultui .to_owned() @@ -886,25 +892,45 @@ fn draw_sort_n_filter( }) .unwrap_or((Span::raw("f"), Span::raw(""))) }) - .chain(sort_by.iter().map(|s| { - let direction = if s.reverse { &reverseui } else { &forwardui }; - - ui.sorter_identifiers - .get(&s.sorter) + .chain( + sort_by + .iter() + .map(|s| { + let direction = if s.reverse { &reverseui } else { &forwardui }; + + ui.sorter_identifiers + .get(&s.sorter) + .map(|u| { + let ui = defaultui.to_owned().extend(u); + ( + Span::styled( + ui.format.to_owned().unwrap_or_default(), + ui.style.into(), + ), + Span::styled( + direction.format.to_owned().unwrap_or_default(), + direction.style.to_owned().into(), + ), + ) + }) + .unwrap_or((Span::raw("s"), Span::raw(""))) + }) + .take(if search.is_some() { 0 } else { sort_by.len() }), + ) + .chain(search.iter().map(|s| { + ui.search_identifier + .as_ref() .map(|u| { let ui = defaultui.to_owned().extend(u); ( Span::styled( ui.format.to_owned().unwrap_or_default(), - ui.style.into(), - ), - Span::styled( - direction.format.to_owned().unwrap_or_default(), - direction.style.to_owned().into(), + ui.style.to_owned().into(), ), + Span::styled(s, ui.style.into()), ) }) - .unwrap_or((Span::raw("s"), Span::raw(""))) + .unwrap_or((Span::raw("/"), Span::raw(s))) })) .zip(std::iter::repeat(Span::styled( ui.separator.format.to_owned().unwrap_or_default(), @@ -912,6 +938,7 @@ fn draw_sort_n_filter( ))) .flat_map(|((a, b), c)| vec![a, b, c]) .collect::>(); + spans.pop(); let p = Paragraph::new(Spans::from(spans)).block(block(