Improve builtin search mode (#585)

* Improve builtin search mode

* Remove commented out code

* Make search ranking and algorithm more extensible

* Flatten messages

BREAKING: xplr.config.general.sort_and_filter_ui.search_identifier -> xplr.config.general.sort_and_filter_ui.search_identifiers

Messages:

- Search
- SearchFromInput
- SearchFuzzy
- SearchFuzzyUnranked
- SearchFuzzyUnrankedFromInput
- SearchRegexUnrankedFromInput
- SearchRegex
- SearchRegexUnranked
- SearchRegexUnrankedFromInput
- SearchRegexUnrankedFromInput
- CycleSearchAlgorithm
- EnableRankedSearch
- DisableRankedSearch
- ToggleRankedSearch

Static config:

xplr.config.general.search.algorithm = "Fuzzy"

* Handle search ranking in search algorithm

* Make CycleSearchAlgorithm only cycle between algorithms, without changing ranking

* Separate algorithm and ordering

* Minor doc updates

* Some cleanup

* Final touch

* Cycle -> Toggle

---------

Co-authored-by: Arijit Basu <sayanarijit@gmail.com>
pull/588/head
Noah Mayr 1 year ago committed by GitHub
parent 2240c74db2
commit ecd7ff9a4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

338
Cargo.lock generated

@ -54,6 +54,12 @@ version = "1.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61"
[[package]]
name = "arrayvec"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
[[package]]
name = "assert_cmd"
version = "2.0.8"
@ -85,6 +91,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "beef"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1"
[[package]]
name = "bitflags"
version = "1.3.2"
@ -153,7 +165,7 @@ dependencies = [
"num-integer",
"num-traits",
"serde",
"time",
"time 0.1.45",
"wasm-bindgen",
"winapi",
]
@ -191,9 +203,13 @@ version = "3.2.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5"
dependencies = [
"atty",
"bitflags",
"clap_lex",
"indexmap",
"once_cell",
"strsim",
"termcolor",
"textwrap",
]
@ -258,6 +274,20 @@ dependencies = [
"itertools",
]
[[package]]
name = "crossbeam"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2801af0d36612ae591caa9568261fddce32ce6e08a7275ea334a06a4ad021a2c"
dependencies = [
"cfg-if",
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-epoch",
"crossbeam-queue",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.6"
@ -288,10 +318,20 @@ dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
"memoffset",
"memoffset 0.7.1",
"scopeguard",
]
[[package]]
name = "crossbeam-queue"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.14"
@ -370,6 +410,82 @@ dependencies = [
"syn",
]
[[package]]
name = "darling"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e"
dependencies = [
"darling_core",
"quote",
"syn",
]
[[package]]
name = "defer-drop"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f613ec9fa66a6b28cdb1842b27f9adf24f39f9afc4dcdd9fdecee4aca7945c57"
dependencies = [
"crossbeam-channel",
"once_cell",
]
[[package]]
name = "derive_builder"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07adf7be193b71cc36b193d0f5fe60b918a3a9db4dad0449f57bcfd519704a3"
dependencies = [
"derive_builder_macro",
]
[[package]]
name = "derive_builder_core"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f91d4cfa921f1c05904dc3c57b4a32c38aed3340cce209f3a6fd1478babafc4"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "derive_builder_macro"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f0314b72bed045f3a68671b3c86328386762c93f82d98c65c3cb5e5f573dd68"
dependencies = [
"derive_builder_core",
"syn",
]
[[package]]
name = "difflib"
version = "0.4.0"
@ -385,6 +501,16 @@ dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-next"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
dependencies = [
"cfg-if",
"dirs-sys-next",
]
[[package]]
name = "dirs-sys"
version = "0.3.7"
@ -396,6 +522,17 @@ dependencies = [
"winapi",
]
[[package]]
name = "dirs-sys-next"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]]
name = "doc-comment"
version = "0.3.3"
@ -408,6 +545,19 @@ version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
[[package]]
name = "env_logger"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
]
[[package]]
name = "erased-serde"
version = "0.3.24"
@ -417,6 +567,12 @@ dependencies = [
"serde",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "fuzzy-matcher"
version = "0.3.7"
@ -489,6 +645,12 @@ dependencies = [
"libm",
]
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "iana-time-zone"
version = "0.1.53"
@ -513,6 +675,12 @@ dependencies = [
"cxx-build",
]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "indexmap"
version = "1.9.2"
@ -627,6 +795,15 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "memoffset"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
"autocfg",
]
[[package]]
name = "memoffset"
version = "0.7.1"
@ -694,6 +871,31 @@ version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308d96db8debc727c3fd9744aac51751243420e46edf401010908da7f8d5e57c"
[[package]]
name = "nix"
version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069"
dependencies = [
"bitflags",
"cfg-if",
"libc",
]
[[package]]
name = "nix"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4"
dependencies = [
"autocfg",
"bitflags",
"cfg-if",
"libc",
"memoffset 0.6.5",
"pin-utils",
]
[[package]]
name = "nom"
version = "7.1.2"
@ -808,6 +1010,12 @@ dependencies = [
"once_cell",
]
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
version = "0.3.26"
@ -958,6 +1166,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustversion"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70"
[[package]]
name = "ryu"
version = "1.0.12"
@ -1029,6 +1243,12 @@ dependencies = [
"unsafe-libyaml",
]
[[package]]
name = "shlex"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
[[package]]
name = "signal-hook"
version = "0.3.14"
@ -1059,6 +1279,35 @@ dependencies = [
"libc",
]
[[package]]
name = "skim"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cebed5f897cd6c0d80fbe30adb36c0abf7400e93043a63ae56458495642b3485"
dependencies = [
"atty",
"beef",
"bitflags",
"chrono",
"clap",
"crossbeam",
"defer-drop",
"derive_builder",
"env_logger",
"fuzzy-matcher",
"lazy_static",
"log",
"nix 0.25.1",
"rayon",
"regex",
"shlex",
"time 0.3.17",
"timer",
"tuikit",
"unicode-width",
"vte",
]
[[package]]
name = "smallvec"
version = "1.10.0"
@ -1081,6 +1330,12 @@ dependencies = [
"unicode_categories",
]
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.107"
@ -1092,6 +1347,17 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "term"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f"
dependencies = [
"dirs-next",
"rustversion",
"winapi",
]
[[package]]
name = "termcolor"
version = "1.1.3"
@ -1158,6 +1424,31 @@ dependencies = [
"winapi",
]
[[package]]
name = "time"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376"
dependencies = [
"serde",
"time-core",
]
[[package]]
name = "time-core"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
[[package]]
name = "timer"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31d42176308937165701f50638db1c31586f183f1aab416268216577aec7306b"
dependencies = [
"chrono",
]
[[package]]
name = "tinytemplate"
version = "1.2.1"
@ -1193,6 +1484,20 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "tuikit"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e19c6ab038babee3d50c8c12ff8b910bdb2196f62278776422f50390d8e53d8"
dependencies = [
"bitflags",
"lazy_static",
"log",
"nix 0.24.3",
"term",
"unicode-width",
]
[[package]]
name = "unicase"
version = "2.6.0"
@ -1242,12 +1547,39 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc7ed8ba44ca06be78ea1ad2c3682a43349126c8818054231ee6f4748012aed2"
[[package]]
name = "utf8parse"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "vte"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aae21c12ad2ec2d168c236f369c38ff332bc1134f7246350dca641437365045"
dependencies = [
"arrayvec",
"utf8parse",
"vte_generate_state_changes",
]
[[package]]
name = "vte_generate_state_changes"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "wait-timeout"
version = "0.2.0"
@ -1469,7 +1801,6 @@ dependencies = [
"criterion",
"crossterm",
"dirs",
"fuzzy-matcher",
"gethostname",
"humansize",
"indexmap",
@ -1485,6 +1816,7 @@ dependencies = [
"serde",
"serde_json",
"serde_yaml",
"skim",
"snailquote",
"textwrap",
"tui",

@ -32,13 +32,13 @@ dirs = "4.0.0"
ansi-to-tui = "2.0.0"
regex = "1.7.1"
gethostname = "0.4.1"
fuzzy-matcher = "0.3.7"
serde_json = "1.0.91"
path-absolutize = "3.0.14"
which = "4.3.0"
nu-ansi-term = "0.46.0"
textwrap = "0.16"
snailquote = "0.3.1"
skim = "0.10.2"
[dependencies.lscolors]
version = "0.13.0"

@ -22,6 +22,7 @@
- [Input Operation][39]
- [Borders][31]
- [Style][11]
- [Searching][41]
- [Sorting][12]
- [Filtering][13]
- [Column Renderer][26]
@ -79,3 +80,4 @@
[38]: messages.md
[39]: input-operation.md
[40]: xplr.util.md
[41]: searching.md

@ -23,7 +23,7 @@ of [modes][4] and the key mappings for each mode.
| G | | go to bottom |
| V | ctrl-a | select/unselect all |
| ctrl-d | | duplicate as |
| ctrl-i | tab | next visited path |
| ctrl-i | | next visited path |
| ctrl-n | | next selection |
| ctrl-o | | last visited path |
| ctrl-p | | prev selection |
@ -49,41 +49,12 @@ of [modes][4] and the key mappings for each mode.
| ~ | | go home |
| [0-9] | | input |
### search
| key | remaps | action |
| ------ | ------ | ---------------- |
| ctrl-n | down | down |
| ctrl-p | up | up |
| enter | | submit |
| esc | | cancel |
| left | | back |
| right | | enter |
| tab | | toggle selection |
### go_to
| key | remaps | action |
| --- | ------ | -------------- |
| f | | follow symlink |
| g | | top |
| i | | initial $PWD |
| p | | path |
| x | | open in gui |
### go_to_path
| key | remaps | action |
| ----- | ------ | ------------ |
| enter | | submit |
| tab | | try complete |
### duplicate_as
### debug_error
| key | remaps | action |
| ----- | ------ | ------------ |
| enter | | submit |
| tab | | try complete |
| key | remaps | action |
| ----- | ------ | ------------------- |
| enter | | open logs in editor |
| q | | quit |
### sort
@ -109,33 +80,25 @@ of [modes][4] and the key mappings for each mode.
| r | | by relative path |
| s | | by size |
### debug_error
| key | remaps | action |
| ----- | ------ | ------------------- |
| enter | | open logs in editor |
| q | | quit |
### delete
### relative_path_does_not_match_regex
| key | remaps | action |
| --- | ------ | ------------ |
| D | | force delete |
| d | | delete |
| key | remaps | action |
| ----- | ------ | ------ |
| enter | | submit |
### create
### switch_layout
| key | remaps | action |
| --- | ------ | ---------------- |
| d | | create directory |
| f | | create file |
| key | remaps | action |
| --- | ------ | -------------------- |
| 1 | | default |
| 2 | | no help menu |
| 3 | | no selection panel |
| 4 | | no help or selection |
### create_directory
### recover
| key | remaps | action |
| ----- | ------ | ------------ |
| enter | | submit |
| tab | | try complete |
| key | remaps | action |
| --- | ------ | ------ |
### vroot
@ -148,33 +111,14 @@ of [modes][4] and the key mappings for each mode.
| v | | toggle vroot |
| ~ | | vroot $HOME |
### relative_path_does_match_regex
| key | remaps | action |
| ----- | ------ | ------ |
| enter | | submit |
### quit
| key | remaps | action |
| ----- | ------ | ----------------------- |
| enter | | just quit |
| f | | quit printing focus |
| p | | quit printing pwd |
| r | | quit printing result |
| s | | quit printing selection |
### filter
### go_to_path
| 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 |
| key | remaps | action |
| ----- | ------ | ------------ |
| enter | | submit |
| tab | | try complete |
### create_file
### rename
| key | remaps | action |
| ----- | ------ | ------------ |
@ -193,41 +137,33 @@ of [modes][4] and the key mappings for each mode.
| s | | softlink here |
| u | | clear selection |
### switch_layout
| key | remaps | action |
| --- | ------ | -------------------- |
| 1 | | default |
| 2 | | no help menu |
| 3 | | no selection panel |
| 4 | | no help or selection |
### rename
### create_file
| key | remaps | action |
| ----- | ------ | ------------ |
| enter | | submit |
| tab | | try complete |
### relative_path_does_not_match_regex
### delete
| key | remaps | action |
| ----- | ------ | ------ |
| enter | | submit |
| key | remaps | action |
| --- | ------ | ------------ |
| D | | force delete |
| d | | delete |
### number
### create_directory
| key | remaps | action |
| ----- | ------ | -------- |
| down | j | to down |
| enter | | to index |
| k | up | to up |
| [0-9] | | input |
| key | remaps | action |
| ----- | ------ | ------------ |
| enter | | submit |
| tab | | try complete |
### recover
### create
| key | remaps | action |
| --- | ------ | ------ |
| key | remaps | action |
| --- | ------ | ---------------- |
| d | | create directory |
| f | | create file |
### action
@ -242,3 +178,72 @@ of [modes][4] and the key mappings for each mode.
| s | | selection operations |
| v | | vroot |
| [0-9] | | go to index |
### filter
| 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 |
### search
| key | remaps | action |
| ------ | ------ | ----------------------- |
| ctrl-a | | toggle search algorithm |
| ctrl-f | | fuzzy search |
| ctrl-n | down | down |
| ctrl-p | up | up |
| ctrl-r | | regex search |
| ctrl-s | | sort (no search order) |
| ctrl-z | | toggle ordering |
| enter | | submit |
| esc | | cancel |
| left | | back |
| right | | enter |
| tab | | toggle selection |
### duplicate_as
| key | remaps | action |
| ----- | ------ | ------------ |
| enter | | submit |
| tab | | try complete |
### go_to
| key | remaps | action |
| --- | ------ | -------------- |
| f | | follow symlink |
| g | | top |
| i | | initial $PWD |
| p | | path |
| x | | open in gui |
### relative_path_does_match_regex
| key | remaps | action |
| ----- | ------ | ------ |
| enter | | submit |
### quit
| key | remaps | action |
| ----- | ------ | ----------------------- |
| enter | | just quit |
| f | | quit printing focus |
| p | | quit printing pwd |
| r | | quit printing result |
| s | | quit printing selection |
### number
| key | remaps | action |
| ----- | ------ | -------- |
| down | j | to down |
| enter | | to index |
| k | up | to up |
| [0-9] | | input |

@ -193,6 +193,18 @@ Style for each item in the selection list.
Type: [Style](https://xplr.dev/en/style)
#### xplr.config.general.search.algorithm
The default search algorithm
Type: [Search Algorithm](https://xplr.dev/en/searching#algorithm)
#### xplr.config.general.search.unordered
The default search ordering
Type: boolean
#### xplr.config.general.default_ui.prefix
The content that is placed before the item name for each row by default.
@ -334,12 +346,24 @@ 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
#### xplr.config.general.sort_and_filter_ui.search_identifiers
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_direction_identifiers.ordered.format
The shape of ordered indicator for search ordering identifiers in Sort & filter panel.
Type: nullable string
#### xplr.config.general.sort_and_filter_ui.search_direction_identifiers.unordered.format
The shape of unordered indicator for search ordering identifiers in Sort & filter panel.
Type: nullable string
#### xplr.config.general.panel_ui.default.title.format
The content for panel title by default.

@ -368,26 +368,9 @@ Type: list of [Node Sorter Applicable][81]
### searcher
Type: nullable [Node Searcher][82]
The searcher to use (if any).
## Node Searcher
Node Searcher contains the following fields:
- [pattern][83]
- [recoverable_focus][84]
### pattern
The patters used to search.
Type: string
### recoverable_focus
Where to focus when search is cancelled.
Type: nullable string
Type: nullable [Node Searcher Applicable][82]
## Also Ssee:
@ -457,7 +440,5 @@ Type: nullable string
[79]: #searcher
[80]: filtering.md#node-filter-applicable
[81]: sorting.md#node-sorter-applicable
[82]: #node-searcher
[83]: #pattern
[84]: #recoverable_focus
[82]: searching.md#node-searcher-applicable
[85]: xplr.util.md

@ -1030,6 +1030,28 @@ Example:
### Search Operations
#### Search
Search files using the current or default (fuzzy) search algorithm.
You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely.
It gets reset automatically when changing directory.
Type: { Search = "string" }
Example:
- Lua: `{ Search = "pattern" }`
- YAML: `Search: pattern`
#### SearchFromInput
Calls `Search` with the input taken from the input buffer.
Example:
- Lua: `"SearchFromInput"`
- YAML: `SearchFromInput`
#### SearchFuzzy
Search files using fuzzy match algorithm.
@ -1048,12 +1070,126 @@ Example:
Calls `SearchFuzzy` with the input taken from the input buffer.
You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely.
It gets reset automatically when changing directory.
Example:
- Lua: `"SearchFuzzyFromInput"`
- YAML: `SearchFuzzyFromInput`
#### SearchFuzzyUnordered
Like `SearchFuzzy`, but doesn't not perform rank based sorting.
You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely.
It gets reset automatically when changing directory.
Type: { SearchFuzzyUnordered = "string" }
Example:
- Lua: `{ SearchFuzzyUnordered = "pattern" }`
- YAML: `SearchFuzzyUnordered: pattern`
#### SearchFuzzyUnorderedFromInput
Calls `SearchFuzzyUnordered` with the input taken from the input buffer.
You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely.
It gets reset automatically when changing directory.
Example:
- Lua: `"SearchFuzzyUnorderedFromInput"`
- YAML: `SearchFuzzyUnorderedFromInput`
#### SearchRegex
Search files using regex 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: { SearchRegex = "string" }
Example:
- Lua: `{ SearchRegex = "pattern" }`
- YAML: `SearchRegex: pattern`
#### SearchRegexFromInput
Calls `SearchRegex` with the input taken from the input buffer.
You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely.
It gets reset automatically when changing directory.
Example:
- Lua: `"SearchRegexFromInput"`
- YAML: `SearchRegexFromInput`
#### SearchRegexUnordered
Like `SearchRegex`, but doesn't not perform rank based sorting.
You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely.
It gets reset automatically when changing directory.
Type: { SearchRegexUnordered = "string" }
Example:
- Lua: `{ SearchRegexUnordered = "pattern" }`
- YAML: `SearchRegexUnordered: pattern`
#### SearchRegexUnorderedFromInput
Calls `SearchRegexUnordered` with the input taken from the input buffer.
You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely.
It gets reset automatically when changing directory.
Example:
- Lua: `"SearchRegexUnorderedFromInput"`
- YAML: `SearchRegexUnorderedFromInput`
#### ToggleSearchAlgorithm
Toggles between different search algorithms, without changing the input
buffer
You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely.
Example:
- Lua: `"ToggleSearchAlgorithm"`
- YAML: `ToggleSearchAlgorithm`
#### EnableSearchOrder
Enables ranked search without changing the input buffer.
You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely.
Example:
- Lua: `"EnableOrderedSearch"`
- YAML: `EnableSearchOrder`
#### DisableSearchOrder
Disabled ranked search without changing the input buffer.
You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely.
Example:
- Lua: `"DisableSearchOrder"`
- YAML: `DisableSearchOrder`
#### ToggleSearchOrder
Toggles ranked search without changing the input buffer.
Example:
- Lua: `"ToggleSearchOrder"`
- YAML: `ToggleSearchOrder`
#### AcceptSearch
Accepts the search by keeping the latest focus while in search mode.

@ -0,0 +1,77 @@
# Searching
xplr supports searching paths using different algorithm. The search mechanism
generally appears between filters and sorters in the `Sort & filter` panel.
Example:
```
fzy:foo↓
```
This line means that the nodes visible on the table are being filtered using the
[fuzzy matching][1] algorithm on the input `foo`. The arrow means that ranking based
ordering is being applied, i.e. [sorters][2] are being ignored.
## Node Searcher Applicable
Node Searcher contains the following fields:
- [pattern][3]
- [recoverable_focus][4]
- [algorithm][5]
- [unordered][7]
### pattern
The patters used to search.
Type: string
### recoverable_focus
Where to focus when search is cancelled.
Type: nullable string
### algorithm
Search algorithm to use. Defaults to the value set in
[xplr.config.general.search.algorithm][8].
It can be one of the following:
- Fuzzy
- Regex
### unordered
Whether to skip ordering the search result by algorithm based ranking. Defaults
to the value set in [xplr.config.general.search.unordered][9].
Type: boolean
## Example:
```lua
local searcher = {
pattern = "pattern to search",
recoverable_focus = "/path/to/focus/on/cancel",
algorithm = "Fuzzy",
unordered = false,
}
xplr.util.explore({ searcher = searcher })
```
See [xplr.util.explore][6].
[1]: https://en.wikipedia.org/wiki/Approximate_string_matching
[2]: sorting.md
[3]: #pattern
[4]: #recoverable_focus
[5]: #algorithm
[6]: xplr.util.md#xplrutilexplore
[7]: #unordered
[8]: general-config.md#xplrconfiggeneralsearchalgorithm
[9]: general-config.md#xplrconfiggeneralsearchunordered

@ -9,7 +9,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;
use crate::msg::in_::external::NodeSearcherApplicable;
pub use crate::msg::in_::external::NodeSorter;
pub use crate::msg::in_::external::NodeSorterApplicable;
pub use crate::msg::in_::ExternalMsg;
@ -19,6 +19,7 @@ pub use crate::msg::out::MsgOut;
pub use crate::node::Node;
pub use crate::node::ResolvedNode;
pub use crate::pipe::Pipe;
use crate::search::SearchAlgorithm;
use crate::ui::Layout;
use anyhow::{bail, Result};
use chrono::{DateTime, Local};
@ -526,8 +527,32 @@ 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(),
Search(p) => self.search(p),
SearchFromInput => self.search_from_input(),
SearchFuzzy(p) => self.search_with(p, SearchAlgorithm::Fuzzy, false),
SearchFuzzyFromInput => {
self.search_from_input_with(SearchAlgorithm::Fuzzy, false)
}
SearchRegex(p) => self.search_with(p, SearchAlgorithm::Regex, false),
SearchRegexFromInput => {
self.search_from_input_with(SearchAlgorithm::Regex, false)
}
SearchFuzzyUnordered(p) => {
self.search_with(p, SearchAlgorithm::Fuzzy, true)
}
SearchFuzzyUnorderedFromInput => {
self.search_from_input_with(SearchAlgorithm::Fuzzy, true)
}
SearchRegexUnordered(p) => {
self.search_with(p, SearchAlgorithm::Regex, true)
}
SearchRegexUnorderedFromInput => {
self.search_from_input_with(SearchAlgorithm::Regex, true)
}
EnableSearchOrder => self.enable_search_order(),
DisableSearchOrder => self.disable_search_order(),
ToggleSearchOrder => self.toggle_search_order(),
ToggleSearchAlgorithm => self.toggle_search_algorithm(),
AcceptSearch => self.accept_search(),
CancelSearch => self.cancel_search(),
EnableMouse => self.enable_mouse(),
@ -1611,7 +1636,34 @@ impl App {
Ok(self)
}
pub fn search_fuzzy(mut self, pattern: String) -> Result<Self> {
pub fn search(self, pattern: String) -> Result<Self> {
let (algorithm, unordered) = self
.explorer_config
.searcher
.as_ref()
.map(|s| (s.algorithm, s.unordered))
.unwrap_or((
self.config.general.search.algorithm,
self.config.general.search.unordered,
));
self.search_with(pattern, algorithm, unordered)
}
fn search_from_input(self) -> Result<Self> {
if let Some(pattern) = self.input.buffer.as_ref().map(Input::to_string) {
self.search(pattern)
} else {
Ok(self)
}
}
pub fn search_with(
mut self,
pattern: String,
algorithm: SearchAlgorithm,
unordered: bool,
) -> Result<Self> {
let rf = self
.explorer_config
.searcher
@ -1619,18 +1671,54 @@ impl App {
.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));
self.explorer_config.searcher = Some(NodeSearcherApplicable::new(
pattern, rf, algorithm, unordered,
));
Ok(self)
}
fn search_fuzzy_from_input(self) -> Result<Self> {
fn search_from_input_with(
self,
algorithm: SearchAlgorithm,
unordered: bool,
) -> Result<Self> {
if let Some(pattern) = self.input.buffer.as_ref().map(Input::to_string) {
self.search_fuzzy(pattern)
self.search_with(pattern, algorithm, unordered)
} else {
Ok(self)
}
}
fn enable_search_order(mut self) -> Result<Self> {
self.explorer_config.searcher = self
.explorer_config
.searcher
.map(|s| s.enable_search_order());
Ok(self)
}
fn disable_search_order(mut self) -> Result<Self> {
self.explorer_config.searcher = self
.explorer_config
.searcher
.map(|s| s.disable_search_order());
Ok(self)
}
fn toggle_search_order(mut self) -> Result<Self> {
self.explorer_config.searcher = self
.explorer_config
.searcher
.map(|s| s.toggle_search_order());
Ok(self)
}
fn toggle_search_algorithm(mut self) -> Result<Self> {
self.explorer_config.searcher =
self.explorer_config.searcher.map(|s| s.toggle_algorithm());
Ok(self)
}
fn accept_search(mut self) -> Result<Self> {
let focus = self
.directory_buffer

@ -4,6 +4,7 @@ use crate::app::NodeFilter;
use crate::app::NodeSorter;
use crate::app::NodeSorterApplicable;
use crate::node::Node;
use crate::search::SearchAlgorithm;
use crate::ui::Border;
use crate::ui::BorderType;
use crate::ui::Constraint;
@ -188,6 +189,16 @@ pub struct SelectionConfig {
pub item: UiElement,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SearchConfig {
#[serde(default)]
pub algorithm: SearchAlgorithm,
#[serde(default)]
pub unordered: bool,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct LogsConfig {
@ -214,6 +225,16 @@ pub struct SortDirectionIdentifiersUi {
pub reverse: UiElement,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SearchDirectionIdentifiersUi {
#[serde(default)]
pub ordered: UiElement,
#[serde(default)]
pub unordered: UiElement,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SortAndFilterUi {
@ -233,7 +254,10 @@ pub struct SortAndFilterUi {
pub filter_identifiers: HashMap<NodeFilter, UiElement>,
#[serde(default)]
pub search_identifier: Option<UiElement>,
pub search_direction_identifiers: SearchDirectionIdentifiersUi,
#[serde(default)]
pub search_identifiers: HashMap<SearchAlgorithm, UiElement>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
@ -294,6 +318,9 @@ pub struct GeneralConfig {
#[serde(default)]
pub selection: SelectionConfig,
#[serde(default)]
pub search: SearchConfig,
#[serde(default)]
pub default_ui: UiConfig,

@ -2,21 +2,14 @@ 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 fn explore(parent: &PathBuf, config: &ExplorerConfig) -> Result<Vec<Node>> {
let dirs = fs::read_dir(parent)?;
let mut nodes = dirs
let nodes = dirs
.filter_map(|d| {
d.ok().map(|e| {
e.path()
@ -26,26 +19,24 @@ pub fn explore(parent: &PathBuf, config: &ExplorerConfig) -> Result<Vec<Node>> {
})
})
.map(|name| Node::new(parent.to_string_lossy().to_string(), name))
.filter(|n| config.filter(n))
.collect::<Vec<Node>>();
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::<Vec<(_, _)>>();
.filter(|n| config.filter(n));
nodes.sort_by(|(_, s1), (_, s2)| s2.cmp(s1));
nodes.into_iter().map(|(n, _)| n).collect::<Vec<_>>()
let mut nodes = if let Some(searcher) = config.searcher.as_ref() {
searcher.search(nodes)
} else {
nodes.sort_by(|a, b| config.sort(a, b));
nodes
nodes.collect()
};
let is_ordered_search = config
.searcher
.as_ref()
.map(|s| !s.unordered)
.unwrap_or(false);
if !is_ordered_search {
nodes.sort_by(|a, b| config.sort(a, b));
}
Ok(nodes)
}

@ -256,6 +256,16 @@ xplr.config.general.selection.item.format = "builtin.fmt_general_selection_item"
-- Type: [Style](https://xplr.dev/en/style)
xplr.config.general.selection.item.style = {}
-- The default search algorithm
--
-- Type: [Search Algorithm](https://xplr.dev/en/searching#algorithm)
xplr.config.general.search.algorithm = "Fuzzy"
-- The default search ordering
--
-- Type: boolean
xplr.config.general.search.unordered = false
-- The content that is placed before the item name for each row by default.
--
-- Type: nullable string
@ -462,11 +472,22 @@ xplr.config.general.sort_and_filter_ui.filter_identifiers = {
-- 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 = {},
xplr.config.general.sort_and_filter_ui.search_identifiers = {
Fuzzy = { format = "fzy:", style = {} },
Regex = { format = "reg:", style = {} },
}
-- The shape of ordered indicator for search ordering identifiers in Sort & filter panel.
--
-- Type: nullable string
xplr.config.general.sort_and_filter_ui.search_direction_identifiers.ordered.format =
""
-- The shape of unordered indicator for search ordering identifiers in Sort & filter panel.
--
-- Type: nullable string
xplr.config.general.sort_and_filter_ui.search_direction_identifiers.unordered.format = ""
-- The content for panel title by default.
--
-- Type: nullable string
@ -2116,6 +2137,42 @@ xplr.config.modes.builtin.search = {
"FocusNext",
},
},
["ctrl-z"] = {
help = "toggle ordering",
messages = {
"ToggleSearchOrder",
"ExplorePwdAsync",
},
},
["ctrl-a"] = {
help = "toggle search algorithm",
messages = {
"ToggleSearchAlgorithm",
"ExplorePwdAsync",
},
},
["ctrl-r"] = {
help = "regex search",
messages = {
"SearchRegexFromInput",
"ExplorePwdAsync",
},
},
["ctrl-f"] = {
help = "fuzzy search",
messages = {
"SearchFuzzyFromInput",
"ExplorePwdAsync",
},
},
["ctrl-s"] = {
help = "sort (no search order)",
messages = {
"DisableSearchOrder",
"ExplorePwdAsync",
{ SwitchModeBuiltinKeepingInputBuffer = "sort" },
},
},
["right"] = {
help = "enter",
messages = {
@ -2155,7 +2212,7 @@ xplr.config.modes.builtin.search = {
default = {
messages = {
"UpdateInputBufferFromKey",
"SearchFuzzyFromInput",
"SearchFromInput",
"ExplorePwdAsync",
},
},
@ -2365,7 +2422,12 @@ xplr.config.modes.builtin.sort = {
["enter"] = {
help = "submit",
messages = {
"PopMode",
"PopModeKeepingInputBuffer",
},
},
["esc"] = {
messages = {
"PopModeKeepingInputBuffer",
},
},
["m"] = {

@ -17,6 +17,7 @@ pub mod permissions;
pub mod pipe;
pub mod pwd_watcher;
pub mod runner;
pub mod search;
pub mod ui;
pub mod yaml;

@ -1,8 +1,11 @@
use crate::{app::Node, input::InputOperation};
use crate::app::Node;
use crate::input::InputOperation;
use crate::search::PathItem;
use crate::search::SearchAlgorithm;
use indexmap::IndexSet;
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::{cmp::Ordering, sync::Arc};
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub enum ExternalMsg {
@ -921,6 +924,26 @@ pub enum ExternalMsg {
/// ### Search Operations --------------------------------------------------
/// Search files using the current or default (fuzzy) search algorithm.
/// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely.
/// It gets reset automatically when changing directory.
///
/// Type: { Search = "string" }
///
/// Example:
///
/// - Lua: `{ Search = "pattern" }`
/// - YAML: `Search: pattern`
Search(String),
/// Calls `Search` with the input taken from the input buffer.
///
/// Example:
///
/// - Lua: `"SearchFromInput"`
/// - YAML: `SearchFromInput`
SearchFromInput,
/// Search files using fuzzy match algorithm.
/// It keeps the filters, but overrides the sorters.
/// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely.
@ -936,6 +959,7 @@ pub enum ExternalMsg {
/// Calls `SearchFuzzy` with the input taken from the input buffer.
/// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely.
/// It gets reset automatically when changing directory.
///
/// Example:
///
@ -943,6 +967,109 @@ pub enum ExternalMsg {
/// - YAML: `SearchFuzzyFromInput`
SearchFuzzyFromInput,
/// Like `SearchFuzzy`, but doesn't not perform rank based sorting.
/// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely.
/// It gets reset automatically when changing directory.
///
/// Type: { SearchFuzzyUnordered = "string" }
///
/// Example:
///
/// - Lua: `{ SearchFuzzyUnordered = "pattern" }`
/// - YAML: `SearchFuzzyUnordered: pattern`
SearchFuzzyUnordered(String),
/// Calls `SearchFuzzyUnordered` with the input taken from the input buffer.
/// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely.
/// It gets reset automatically when changing directory.
///
/// Example:
///
/// - Lua: `"SearchFuzzyUnorderedFromInput"`
/// - YAML: `SearchFuzzyUnorderedFromInput`
SearchFuzzyUnorderedFromInput,
/// Search files using regex 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: { SearchRegex = "string" }
///
/// Example:
///
/// - Lua: `{ SearchRegex = "pattern" }`
/// - YAML: `SearchRegex: pattern`
SearchRegex(String),
/// Calls `SearchRegex` with the input taken from the input buffer.
/// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely.
/// It gets reset automatically when changing directory.
///
/// Example:
///
/// - Lua: `"SearchRegexFromInput"`
/// - YAML: `SearchRegexFromInput`
SearchRegexFromInput,
/// Like `SearchRegex`, but doesn't not perform rank based sorting.
/// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely.
/// It gets reset automatically when changing directory.
///
/// Type: { SearchRegexUnordered = "string" }
///
/// Example:
///
/// - Lua: `{ SearchRegexUnordered = "pattern" }`
/// - YAML: `SearchRegexUnordered: pattern`
SearchRegexUnordered(String),
/// Calls `SearchRegexUnordered` with the input taken from the input buffer.
/// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely.
/// It gets reset automatically when changing directory.
///
/// Example:
///
/// - Lua: `"SearchRegexUnorderedFromInput"`
/// - YAML: `SearchRegexUnorderedFromInput`
SearchRegexUnorderedFromInput,
/// Toggles between different search algorithms, without changing the input
/// buffer
/// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely.
///
/// Example:
///
/// - Lua: `"ToggleSearchAlgorithm"`
/// - YAML: `ToggleSearchAlgorithm`
ToggleSearchAlgorithm,
/// Enables ranked search without changing the input buffer.
/// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely.
///
/// Example:
///
/// - Lua: `"EnableOrderedSearch"`
/// - YAML: `EnableSearchOrder`
EnableSearchOrder,
/// Disabled ranked search without changing the input buffer.
/// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely.
///
/// Example:
///
/// - Lua: `"DisableSearchOrder"`
/// - YAML: `DisableSearchOrder`
DisableSearchOrder,
/// Toggles ranked search without changing the input buffer.
///
/// Example:
///
/// - Lua: `"ToggleSearchOrder"`
/// - YAML: `ToggleSearchOrder`
ToggleSearchOrder,
/// Accepts the search by keeping the latest focus while in search mode.
/// Automatically calls `ExplorePwd`.
///
@ -1651,18 +1778,78 @@ impl NodeFilterApplicable {
}
#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct NodeSearcher {
pub struct NodeSearcherApplicable {
pub pattern: String,
#[serde(default)]
pub recoverable_focus: Option<String>,
#[serde(default)]
pub algorithm: SearchAlgorithm,
#[serde(default)]
pub unordered: bool,
}
impl NodeSearcher {
pub fn new(pattern: String, recoverable_focus: Option<String>) -> Self {
impl NodeSearcherApplicable {
pub fn new(
pattern: String,
recoverable_focus: Option<String>,
algorithm: SearchAlgorithm,
unordered: bool,
) -> Self {
Self {
pattern,
recoverable_focus,
algorithm,
unordered,
}
}
pub fn search<I>(&self, nodes: I) -> Vec<Node>
where
I: IntoIterator<Item = Node>,
{
let engine = self.algorithm.engine(&self.pattern);
let ranked_nodes = nodes.into_iter().filter_map(|n| {
let item = Arc::new(PathItem::from(n.relative_path.clone()));
engine.match_item(item).map(|res| (n, res.rank))
});
if self.unordered {
ranked_nodes.map(|(n, _)| n).collect()
} else {
let mut ranked_nodes = ranked_nodes.collect::<Vec<_>>();
ranked_nodes.sort_by(|(_, s1), (_, s2)| s2.cmp(s1));
ranked_nodes.into_iter().map(|(n, _)| n).collect()
}
}
pub fn enable_search_order(self) -> Self {
Self {
unordered: false,
..self
}
}
pub fn disable_search_order(self) -> Self {
Self {
unordered: true,
..self
}
}
pub fn toggle_search_order(self) -> Self {
Self {
unordered: !self.unordered,
..self
}
}
pub fn toggle_algorithm(self) -> Self {
Self {
algorithm: self.algorithm.toggle(),
..self
}
}
}
@ -1676,7 +1863,7 @@ pub struct ExplorerConfig {
pub sorters: IndexSet<NodeSorterApplicable>,
#[serde(default)]
pub searcher: Option<NodeSearcher>,
pub searcher: Option<NodeSearcherApplicable>,
}
impl ExplorerConfig {

@ -0,0 +1,50 @@
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
use skim::prelude::{ExactOrFuzzyEngineFactory, RegexEngineFactory};
use skim::{MatchEngine, MatchEngineFactory, SkimItem};
lazy_static! {
static ref FUZZY_FACTORY: ExactOrFuzzyEngineFactory =
ExactOrFuzzyEngineFactory::builder().build();
static ref REGEX_FACTORY: RegexEngineFactory = RegexEngineFactory::builder().build();
}
pub struct PathItem {
path: String,
}
impl From<String> for PathItem {
fn from(value: String) -> Self {
Self { path: value }
}
}
impl SkimItem for PathItem {
fn text(&self) -> std::borrow::Cow<str> {
std::borrow::Cow::from(&self.path)
}
}
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum SearchAlgorithm {
#[default]
Fuzzy,
Regex,
}
impl SearchAlgorithm {
pub fn toggle(self) -> Self {
match self {
Self::Fuzzy => Self::Regex,
Self::Regex => Self::Fuzzy,
}
}
pub fn engine(&self, pattern: &str) -> Box<dyn MatchEngine> {
match self {
Self::Fuzzy => FUZZY_FACTORY.create_engine(pattern),
Self::Regex => REGEX_FACTORY.create_engine(pattern),
}
}
}

@ -1017,11 +1017,7 @@ fn draw_sort_n_filter<B: Backend>(
let ui = app.config.general.sort_and_filter_ui.to_owned();
let filter_by: &IndexSet<NodeFilterApplicable> = &app.explorer_config.filters;
let sort_by: &IndexSet<NodeSorterApplicable> = &app.explorer_config.sorters;
let search = app
.explorer_config
.searcher
.as_ref()
.map(|s| s.pattern.clone());
let search = app.explorer_config.searcher.as_ref();
let defaultui = &ui.default_identifier;
let forwardui = defaultui
@ -1031,6 +1027,15 @@ fn draw_sort_n_filter<B: Backend>(
.to_owned()
.extend(&ui.sort_direction_identifiers.reverse);
let orderedui = defaultui
.to_owned()
.extend(&ui.search_direction_identifiers.ordered);
let unorderedui = defaultui
.to_owned()
.extend(&ui.search_direction_identifiers.unordered);
let is_ordered_search = search.as_ref().map(|s| !s.unordered).unwrap_or(false);
let mut spans = filter_by
.iter()
.map(|f| {
@ -1048,12 +1053,36 @@ fn draw_sort_n_filter<B: Backend>(
})
.unwrap_or((Span::raw("f"), Span::raw("")))
})
.chain(search.iter().map(|s| {
ui.search_identifiers
.get(&s.algorithm)
.map(|u| {
let direction = if s.unordered {
&unorderedui
} else {
&orderedui
};
let ui = defaultui.to_owned().extend(u);
let f = ui
.format
.as_ref()
.map(|f| format!("{f}{p}", p = &s.pattern))
.unwrap_or_else(|| s.pattern.clone());
(
Span::styled(f, ui.style.into()),
Span::styled(
direction.format.to_owned().unwrap_or_default(),
direction.style.to_owned().into(),
),
)
})
.unwrap_or((Span::raw("/"), Span::raw(&s.pattern)))
}))
.chain(
sort_by
.iter()
.map(|s| {
let direction = if s.reverse { &reverseui } else { &forwardui };
ui.sorter_identifiers
.get(&s.sorter)
.map(|u| {
@ -1071,23 +1100,8 @@ fn draw_sort_n_filter<B: Backend>(
})
.unwrap_or((Span::raw("s"), Span::raw("")))
})
.take(if search.is_some() { 0 } else { sort_by.len() }),
.take(if !is_ordered_search { sort_by.len() } else { 0 }),
)
.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.to_owned().into(),
),
Span::styled(s, ui.style.into()),
)
})
.unwrap_or((Span::raw("/"), Span::raw(s)))
}))
.zip(std::iter::repeat(Span::styled(
ui.separator.format.to_owned().unwrap_or_default(),
ui.separator.style.to_owned().into(),

Loading…
Cancel
Save