From b1b9c132f19d90e1565fe564b8a4d11a931ead6e Mon Sep 17 00:00:00 2001 From: Arijit Basu Date: Sun, 5 Feb 2023 12:11:37 +0530 Subject: [PATCH] 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" --- docs/en/src/SUMMARY.md | 2 + docs/en/src/general-config.md | 8 +- docs/en/src/lua-function-calls.md | 25 +---- docs/en/src/messages.md | 136 +++++++++++++++++++++++ docs/en/src/searching.md | 66 ++++++++++++ src/app.rs | 102 ++++++++++++------ src/config.rs | 26 ++--- src/explorer.rs | 15 ++- src/init.lua | 33 +++--- src/msg/in_/external.rs | 174 +++++++++++++++++++++++++----- src/search.rs | 100 ++++++++--------- src/ui.rs | 39 ++++--- 12 files changed, 531 insertions(+), 195 deletions(-) create mode 100644 docs/en/src/searching.md diff --git a/docs/en/src/SUMMARY.md b/docs/en/src/SUMMARY.md index 0677e98..b3a8abc 100644 --- a/docs/en/src/SUMMARY.md +++ b/docs/en/src/SUMMARY.md @@ -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 diff --git a/docs/en/src/general-config.md b/docs/en/src/general-config.md index 40aa3c3..f9ae10f 100644 --- a/docs/en/src/general-config.md +++ b/docs/en/src/general-config.md @@ -193,6 +193,12 @@ 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: nullable [Search Algorithm](https://xplr.dev/en/searching#search-algorithm) + #### xplr.config.general.default_ui.prefix The content that is placed before the item name for each row by default. @@ -334,7 +340,7 @@ 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. diff --git a/docs/en/src/lua-function-calls.md b/docs/en/src/lua-function-calls.md index 4278d1e..89bc1f8 100644 --- a/docs/en/src/lua-function-calls.md +++ b/docs/en/src/lua-function-calls.md @@ -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 diff --git a/docs/en/src/messages.md b/docs/en/src/messages.md index 43f2ec8..8729083 100644 --- a/docs/en/src/messages.md +++ b/docs/en/src/messages.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` +#### SearchFuzzyUnranked + +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: { SearchFuzzyUnranked = "string" } + +Example: + +- Lua: `{ SearchFuzzyUnranked = "pattern" }` +- YAML: `SearchFuzzyUnranked: pattern` + +#### SearchFuzzyUnrankedFromInput + +Calls `SearchFuzzyUnranked` 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: `"SearchFuzzyUnrankedFromInput"` +- YAML: `SearchFuzzyUnrankedFromInput` + +#### 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` + +#### SearchRegexUnranked + +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: { SearchRegexUnranked = "string" } + +Example: + +- Lua: `{ SearchRegexUnranked = "pattern" }` +- YAML: `SearchRegexUnranked: pattern` + +#### SearchRegexUnrankedFromInput + +Calls `SearchRegexUnranked` 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: `"SearchRegexUnrankedFromInput"` +- YAML: `SearchRegexUnrankedFromInput` + +#### CycleSearchAlgorithm + +Cycles through different search algorithms, without changing the input +buffer +You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. + +Example: + +- Lua: `"CycleSearchAlgorithm"` +- YAML: `CycleSearchAlgorithm` + +#### EnableRankedSearch + +Enables ranked search without changing the input buffer. +You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. + +Example: + +- Lua: `"EnableRankedSearch"` +- YAML: `EnableRankedSearch` + +#### DisableRankedSearch + +Disabled ranked search without changing the input buffer. +You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. + +Example: + +- Lua: `"DisableRankedSearch"` +- YAML: `DisableRankedSearch` + +#### ToggleRankedSearch + +Toggles ranked search without changing the input buffer. + +Example: + +- Lua: `"ToggleRankedSearch"` +- YAML: `ToggleRankedSearch` + #### AcceptSearch Accepts the search by keeping the latest focus while in search mode. diff --git a/docs/en/src/searching.md b/docs/en/src/searching.md new file mode 100644 index 0000000..db41d1a --- /dev/null +++ b/docs/en/src/searching.md @@ -0,0 +1,66 @@ +# Searching + +xplr supports searching paths using different algorithm. The search mechanism +generally appears between filters and sorters in the `Sort & filter` panel. + +Example: + +``` +/fzy↓abc +``` + +This line means that the nodes visible on the table are being filtered using the +[fuzzy matching][1] algorithm. 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] + +### 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 `Fuzzy`. + +It can be one of the following: + +- Fuzzy +- FuzzyUnranked +- Regex +- RegexUnranked + +## Example: + +```lua +local searcher = { + pattern = "pattern to search", + recoverable_focus = "/path/to/focus/on/cancel", + algorithm = "Fuzzy", +} + +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#explore diff --git a/src/app.rs b/src/app.rs index 7ab822f..cf3c5b3 100644 --- a/src/app.rs +++ b/src/app.rs @@ -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; @@ -20,7 +20,6 @@ pub use crate::node::Node; pub use crate::node::ResolvedNode; pub use crate::pipe::Pipe; use crate::search::SearchAlgorithm; -use crate::search::SearchOrder; use crate::ui::Layout; use anyhow::{bail, Result}; use chrono::{DateTime, Local}; @@ -528,11 +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(), - CycleSearchOrder => self.cycle_search_order(), - SetSearchOrder(r) => self.set_search_order(r), - SetSearchAlgorithm(a) => self.set_search_algorithm(a), + Search(p) => self.search(p), + SearchFromInput => self.search_from_input(), + SearchFuzzy(p) => self.search_with(p, SearchAlgorithm::Fuzzy), + SearchFuzzyFromInput => { + self.search_from_input_with(SearchAlgorithm::Fuzzy) + } + SearchRegex(p) => self.search_with(p, SearchAlgorithm::Regex), + SearchRegexFromInput => { + self.search_from_input_with(SearchAlgorithm::Regex) + } + SearchFuzzyUnranked(p) => { + self.search_with(p, SearchAlgorithm::FuzzyUnranked) + } + SearchFuzzyUnrankedFromInput => { + self.search_from_input_with(SearchAlgorithm::FuzzyUnranked) + } + SearchRegexUnranked(p) => { + self.search_with(p, SearchAlgorithm::RegexUnranked) + } + SearchRegexUnrankedFromInput => { + self.search_from_input_with(SearchAlgorithm::RegexUnranked) + } + EnableRankedSearch => self.enable_ranked_search(), + DisableRankedSearch => self.disable_ranked_search(), + ToggleRankedSearch => self.toggle_ranked_search(), + CycleSearchAlgorithm => self.cycle_search_algorithm(), AcceptSearch => self.accept_search(), CancelSearch => self.cancel_search(), EnableMouse => self.enable_mouse(), @@ -1616,7 +1636,29 @@ impl App { Ok(self) } - pub fn search_fuzzy(mut self, pattern: String) -> Result { + pub fn search(self, pattern: String) -> Result { + let algorithm = self + .explorer_config + .searcher + .as_ref() + .map(|s| s.algorithm) + .unwrap_or(self.config.general.search.algorithm); + self.search_with(pattern, algorithm) + } + + fn search_from_input(self) -> Result { + 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, + ) -> Result { let rf = self .explorer_config .searcher @@ -1624,46 +1666,40 @@ 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.config.general.search.clone(), - )); + self.explorer_config.searcher = + Some(NodeSearcherApplicable::new(pattern, rf, algorithm)); Ok(self) } - fn search_fuzzy_from_input(self) -> Result { + fn search_from_input_with(self, algorithm: SearchAlgorithm) -> Result { if let Some(pattern) = self.input.buffer.as_ref().map(Input::to_string) { - self.search_fuzzy(pattern) + self.search_with(pattern, algorithm) } else { Ok(self) } } - fn cycle_search_order(mut self) -> Result { - self.explorer_config.searcher.as_mut().map(|s| { - s.config.order = match s.config.order { - SearchOrder::Ranked => SearchOrder::Sorted, - SearchOrder::Sorted => SearchOrder::Ranked, - }; - s - }); + fn enable_ranked_search(mut self) -> Result { + self.explorer_config.searcher = + self.explorer_config.searcher.map(|s| s.enable_ranking()); + Ok(self) + } + + fn disable_ranked_search(mut self) -> Result { + self.explorer_config.searcher = + self.explorer_config.searcher.map(|s| s.disable_ranking()); Ok(self) } - fn set_search_order(mut self, order: SearchOrder) -> Result { - self.explorer_config.searcher.as_mut().map(|s| { - s.config.order = order; - s - }); + fn toggle_ranked_search(mut self) -> Result { + self.explorer_config.searcher = + self.explorer_config.searcher.map(|s| s.toggle_ranking()); Ok(self) } - fn set_search_algorithm(mut self, algorithm: SearchAlgorithm) -> Result { - self.explorer_config.searcher.as_mut().map(|s| { - s.config.algorithm = algorithm; - s - }); + fn cycle_search_algorithm(mut self) -> Result { + self.explorer_config.searcher = + self.explorer_config.searcher.map(|s| s.cycle_algorithm()); Ok(self) } diff --git a/src/config.rs b/src/config.rs index 50c5d93..1c2ba4f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -5,7 +5,6 @@ use crate::app::NodeSorter; use crate::app::NodeSorterApplicable; use crate::node::Node; use crate::search::SearchAlgorithm; -use crate::search::SearchOrder; use crate::ui::Border; use crate::ui::BorderType; use crate::ui::Constraint; @@ -190,6 +189,13 @@ pub struct SelectionConfig { pub item: UiElement, } +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct SearchConfig { + #[serde(default)] + pub algorithm: SearchAlgorithm, +} + #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct LogsConfig { @@ -235,17 +241,7 @@ pub struct SortAndFilterUi { pub filter_identifiers: HashMap, #[serde(default)] - pub search_identifier: Option, -} - -#[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct SearchConfig { - #[serde(default)] - pub order: SearchOrder, - - #[serde(default)] - pub algorithm: SearchAlgorithm, + pub search_identifiers: HashMap, } #[derive(Debug, Clone, Default, Serialize, Deserialize)] @@ -306,6 +302,9 @@ pub struct GeneralConfig { #[serde(default)] pub selection: SelectionConfig, + #[serde(default)] + pub search: SearchConfig, + #[serde(default)] pub default_ui: UiConfig, @@ -321,9 +320,6 @@ pub struct GeneralConfig { #[serde(default)] pub sort_and_filter_ui: SortAndFilterUi, - #[serde(default)] - pub search: SearchConfig, - #[serde(default)] pub panel_ui: PanelUi, diff --git a/src/explorer.rs b/src/explorer.rs index 17e1758..ca83aae 100644 --- a/src/explorer.rs +++ b/src/explorer.rs @@ -1,7 +1,6 @@ use crate::app::{ DirectoryBuffer, ExplorerConfig, ExternalMsg, InternalMsg, MsgIn, Node, Task, }; -use crate::search::SearchOrder; use anyhow::{Error, Result}; use std::fs; use std::path::PathBuf; @@ -24,14 +23,12 @@ pub fn explore(parent: &PathBuf, config: &ExplorerConfig) -> Result> { .collect::>(); nodes = if let Some(searcher) = config.searcher.as_ref() { - let mut ranked_nodes = - searcher.config.algorithm.search(&searcher.pattern, nodes); - - match searcher.config.order { - SearchOrder::Ranked => ranked_nodes.sort_by(|(_, s1), (_, s2)| s2.cmp(s1)), - SearchOrder::Sorted => { - ranked_nodes.sort_by(|(a, _), (b, _)| config.sort(a, b)) - } + let mut ranked_nodes = searcher.search(nodes); + + if searcher.algorithm.is_ranked() { + ranked_nodes.sort_by(|(_, s1), (_, s2)| s2.cmp(s1)); + } else { + ranked_nodes.sort_by(|(a, _), (b, _)| config.sort(a, b)); }; ranked_nodes.into_iter().map(|(n, _)| n).collect::>() diff --git a/src/init.lua b/src/init.lua index 2ab67c0..c33ee2e 100644 --- a/src/init.lua +++ b/src/init.lua @@ -256,6 +256,11 @@ 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: nullable [Search Algorithm](https://xplr.dev/en/searching#search-algorithm) +xplr.config.general.search.algorithm = "Fuzzy" + -- The content that is placed before the item name for each row by default. -- -- Type: nullable string @@ -462,17 +467,11 @@ 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 = {}, -} - --- -- The configuration --- -- --- -- Type: { algorithm = [SearchAlgorithm], order = [SearchOrder] } -xplr.config.general.search = { - algorithm = { Skim = "Fuzzy" }, - order = "Ranked" +xplr.config.general.sort_and_filter_ui.search_identifiers = { + Fuzzy = { format = "/fzy↓", style = {} }, + FuzzyUnranked = { format = "/fzy:", style = {} }, + Regex = { format = "/reg↓", style = {} }, + RegexUnranked = { format = "/reg:", style = {} }, } -- The content for panel title by default. @@ -2125,30 +2124,30 @@ xplr.config.modes.builtin.search = { }, }, ["ctrl-z"] = { - help = "toggle search ranking", + help = "cycle search algorithm", messages = { - "CycleSearchOrder", + "CycleSearchAlgorithm", "ExplorePwdAsync", }, }, ["ctrl-r"] = { help = "regex search", messages = { - { SetSearchAlgorithm = { Skim = "Regex" } }, + "SearchRegexFromInput", "ExplorePwdAsync", }, }, ["ctrl-f"] = { help = "fuzzy search", messages = { - { SetSearchAlgorithm = { Skim = "Fuzzy" } }, + "SearchFuzzyFromInput", "ExplorePwdAsync", }, }, ["ctrl-s"] = { help = "sort (disables ranking)", messages = { - { SetSearchOrder = "Sorted" }, + "DisableRankedSearch", "ExplorePwdAsync", { SwitchModeBuiltinKeepingInputBuffer = "sort" }, }, @@ -2192,7 +2191,7 @@ xplr.config.modes.builtin.search = { default = { messages = { "UpdateInputBufferFromKey", - "SearchFuzzyFromInput", + "SearchFromInput", "ExplorePwdAsync", }, }, diff --git a/src/msg/in_/external.rs b/src/msg/in_/external.rs index a356de3..0f8d670 100644 --- a/src/msg/in_/external.rs +++ b/src/msg/in_/external.rs @@ -1,9 +1,6 @@ -use crate::{ - app::Node, - config::SearchConfig, - input::InputOperation, - search::{SearchAlgorithm, SearchOrder}, -}; +use crate::app::Node; +use crate::input::InputOperation; +use crate::search::SearchAlgorithm; use indexmap::IndexSet; use regex::Regex; use serde::{Deserialize, Serialize}; @@ -926,6 +923,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. @@ -941,6 +958,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: /// @@ -948,32 +966,108 @@ pub enum ExternalMsg { /// - YAML: `SearchFuzzyFromInput` SearchFuzzyFromInput, - /// Cycles through different search order modes. + /// 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: { SearchFuzzyUnranked = "string" } + /// + /// Example: + /// + /// - Lua: `{ SearchFuzzyUnranked = "pattern" }` + /// - YAML: `SearchFuzzyUnranked: pattern` + SearchFuzzyUnranked(String), + + /// Calls `SearchFuzzyUnranked` 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: `"SearchFuzzyUnrankedFromInput"` + /// - YAML: `SearchFuzzyUnrankedFromInput` + SearchFuzzyUnrankedFromInput, + + /// 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: { SearchRegexUnranked = "string" } + /// + /// Example: + /// + /// - Lua: `{ SearchRegexUnranked = "pattern" }` + /// - YAML: `SearchRegexUnranked: pattern` + SearchRegexUnranked(String), + + /// Calls `SearchRegexUnranked` 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: `"CycleSearchOrder"` - /// - YAML: `CycleSearchOrder` - CycleSearchOrder, + /// - Lua: `"SearchRegexUnrankedFromInput"` + /// - YAML: `SearchRegexUnrankedFromInput` + SearchRegexUnrankedFromInput, - /// Sets how search results should be ordered. + /// Cycles through different search algorithms, without changing the input + /// buffer /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. /// /// Example: /// - /// - Lua: `{ SetSearchOrder = "Ranked" }` - /// - YAML: `SetSearchOrder: Sorted` - SetSearchOrder(SearchOrder), + /// - Lua: `"CycleSearchAlgorithm"` + /// - YAML: `CycleSearchAlgorithm` + CycleSearchAlgorithm, - /// Sets the search algorithm. + /// Enables ranked search without changing the input buffer. /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. /// /// Example: /// - /// - Lua: `{ SetSearchAlgorithm = { Skim = "Fuzzy" } }` - /// - YAML: `SetSearchAlgorithm: {Skim: Regex}` - SetSearchAlgorithm(SearchAlgorithm), + /// - Lua: `"EnableRankedSearch"` + /// - YAML: `EnableRankedSearch` + EnableRankedSearch, + + /// Disabled ranked search without changing the input buffer. + /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitely. + /// + /// Example: + /// + /// - Lua: `"DisableRankedSearch"` + /// - YAML: `DisableRankedSearch` + DisableRankedSearch, + + /// Toggles ranked search without changing the input buffer. + /// + /// Example: + /// + /// - Lua: `"ToggleRankedSearch"` + /// - YAML: `ToggleRankedSearch` + ToggleRankedSearch, /// Accepts the search by keeping the latest focus while in search mode. /// Automatically calls `ExplorePwd`. @@ -1683,26 +1777,58 @@ 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, #[serde(default)] - pub config: SearchConfig, + pub algorithm: SearchAlgorithm, } -impl NodeSearcher { +impl NodeSearcherApplicable { pub fn new( pattern: String, recoverable_focus: Option, - config: SearchConfig, + algorithm: SearchAlgorithm, ) -> Self { Self { pattern, recoverable_focus, - config, + algorithm, + } + } + + pub fn search(&self, nodes: Vec) -> Vec<(Node, [i32; 4])> { + self.algorithm.search(&self.pattern, nodes) + } + + pub fn enable_ranking(self) -> Self { + Self { + algorithm: self.algorithm.enable_ranking(), + ..self + } + } + + pub fn disable_ranking(self) -> Self { + Self { + algorithm: self.algorithm.disable_ranking(), + ..self + } + } + + pub fn toggle_ranking(self) -> Self { + Self { + algorithm: self.algorithm.toggle_ranking(), + ..self + } + } + + pub fn cycle_algorithm(self) -> Self { + Self { + algorithm: self.algorithm.cycle(), + ..self } } } @@ -1716,7 +1842,7 @@ pub struct ExplorerConfig { pub sorters: IndexSet, #[serde(default)] - pub searcher: Option, + pub searcher: Option, } impl ExplorerConfig { diff --git a/src/search.rs b/src/search.rs index f504b66..5025624 100644 --- a/src/search.rs +++ b/src/search.rs @@ -1,11 +1,9 @@ -use std::sync::Arc; - +use crate::node::Node; use lazy_static::lazy_static; use serde::{Deserialize, Serialize}; use skim::prelude::{ExactOrFuzzyEngineFactory, RegexEngineFactory}; use skim::{MatchEngine, MatchEngineFactory, SkimItem}; - -use crate::node::Node; +use std::sync::Arc; lazy_static! { static ref FUZZY_FACTORY: ExactOrFuzzyEngineFactory = @@ -29,77 +27,73 @@ impl SkimItem for PathItem { } } -#[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Hash, Serialize, Deserialize)] #[serde(deny_unknown_fields)] -pub enum SkimAlgorithm { +pub enum SearchAlgorithm { #[default] Fuzzy, + FuzzyUnranked, Regex, + RegexUnranked, } -impl SkimAlgorithm { - fn engine(&self, pattern: &str) -> Box { +impl SearchAlgorithm { + pub fn is_ranked(&self) -> bool { match self { - Self::Fuzzy => FUZZY_FACTORY.create_engine(pattern), - Self::Regex => REGEX_FACTORY.create_engine(pattern), + Self::Fuzzy | Self::Regex => true, + Self::FuzzyUnranked | Self::RegexUnranked => false, } } -} -#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub enum SearchAlgorithm { - Skim(SkimAlgorithm), -} - -impl Default for SearchAlgorithm { - fn default() -> Self { - Self::Skim(SkimAlgorithm::default()) + pub fn cycle(self) -> Self { + match self { + Self::Fuzzy => Self::FuzzyUnranked, + Self::FuzzyUnranked => Self::Regex, + Self::Regex => Self::RegexUnranked, + Self::RegexUnranked => Self::Fuzzy, + } } -} -impl SearchAlgorithm { - pub fn search(&self, pattern: &str, nodes: Vec) -> Vec<(Node, [i32; 4])> { + pub fn enable_ranking(self) -> Self { match self { - Self::Skim(algorithm) => { - let engine = algorithm.engine(pattern); - 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)) - }) - .collect::>() - } + Self::FuzzyUnranked => Self::Fuzzy, + Self::RegexUnranked => Self::Regex, + Self::Fuzzy | Self::Regex => self, } } - pub fn label(&self) -> String { + pub fn disable_ranking(self) -> Self { match self { - SearchAlgorithm::Skim(algorithm) => { - let kind = match algorithm { - SkimAlgorithm::Fuzzy => "fuzzy", - SkimAlgorithm::Regex => "regex", - }; - format!("skim ({kind})") - } + Self::Fuzzy => Self::FuzzyUnranked, + Self::Regex => Self::RegexUnranked, + Self::FuzzyUnranked | Self::RegexUnranked => self, } } -} -#[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub enum SearchOrder { - #[default] - Ranked, - Sorted, -} + pub fn toggle_ranking(self) -> Self { + match self { + Self::Fuzzy => Self::FuzzyUnranked, + Self::FuzzyUnranked => Self::Fuzzy, + Self::Regex => Self::RegexUnranked, + Self::RegexUnranked => Self::Regex, + } + } -impl SearchOrder { - pub fn label(&self) -> String { + fn engine(&self, pattern: &str) -> Box { match self { - SearchOrder::Ranked => "rank".to_string(), - SearchOrder::Sorted => "sort".to_string(), + Self::Fuzzy | Self::FuzzyUnranked => FUZZY_FACTORY.create_engine(pattern), + Self::Regex | Self::RegexUnranked => REGEX_FACTORY.create_engine(pattern), } } + + pub fn search(&self, pattern: &str, nodes: Vec) -> Vec<(Node, [i32; 4])> { + let engine = self.engine(pattern); + 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)) + }) + .collect::>() + } } diff --git a/src/ui.rs b/src/ui.rs index 579a3d2..a29796e 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -3,7 +3,6 @@ use crate::app::{Node, ResolvedNode}; use crate::config::PanelUiConfig; use crate::lua; use crate::permissions::Permissions; -use crate::search::SearchOrder; use crate::{app, path}; use ansi_to_tui::IntoText; use indexmap::IndexSet; @@ -1045,6 +1044,21 @@ fn draw_sort_n_filter( }) .unwrap_or((Span::raw("f"), Span::raw(""))) }) + .chain(search.iter().map(|s| { + ui.search_identifiers + .get(&s.algorithm) + .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.pattern, ui.style.into()), + ) + }) + .unwrap_or((Span::raw("/"), Span::raw(&s.pattern))) + })) .chain( sort_by .iter() @@ -1069,30 +1083,13 @@ fn draw_sort_n_filter( .unwrap_or((Span::raw("s"), Span::raw(""))) }) .take( - if let Some(SearchOrder::Sorted) = - search.map(|s| s.config.order.to_owned()) - { - sort_by.len() - } else { + if search.map(|s| s.algorithm.is_ranked()).unwrap_or(false) { 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.to_owned().into(), - ), - Span::styled(&s.pattern, ui.style.into()), - ) - }) - .unwrap_or((Span::raw("/"), Span::raw(&s.pattern))) - })) .zip(std::iter::repeat(Span::styled( ui.separator.format.to_owned().unwrap_or_default(), ui.separator.style.to_owned().into(),