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"
pull/585/head
Arijit Basu 1 year ago
parent 8ca5ba0ac7
commit b1b9c132f1

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

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

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

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

@ -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<Self> {
pub fn search(self, pattern: String) -> Result<Self> {
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<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,
) -> Result<Self> {
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<Self> {
fn search_from_input_with(self, algorithm: SearchAlgorithm) -> Result<Self> {
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> {
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> {
self.explorer_config.searcher =
self.explorer_config.searcher.map(|s| s.enable_ranking());
Ok(self)
}
fn disable_ranked_search(mut self) -> Result<Self> {
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> {
self.explorer_config.searcher.as_mut().map(|s| {
s.config.order = order;
s
});
fn toggle_ranked_search(mut self) -> Result<Self> {
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> {
self.explorer_config.searcher.as_mut().map(|s| {
s.config.algorithm = algorithm;
s
});
fn cycle_search_algorithm(mut self) -> Result<Self> {
self.explorer_config.searcher =
self.explorer_config.searcher.map(|s| s.cycle_algorithm());
Ok(self)
}

@ -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<NodeFilter, UiElement>,
#[serde(default)]
pub search_identifier: Option<UiElement>,
}
#[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<SearchAlgorithm, UiElement>,
}
#[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,

@ -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<Vec<Node>> {
.collect::<Vec<Node>>();
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::<Vec<_>>()

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

@ -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<String>,
#[serde(default)]
pub config: SearchConfig,
pub algorithm: SearchAlgorithm,
}
impl NodeSearcher {
impl NodeSearcherApplicable {
pub fn new(
pattern: String,
recoverable_focus: Option<String>,
config: SearchConfig,
algorithm: SearchAlgorithm,
) -> Self {
Self {
pattern,
recoverable_focus,
config,
algorithm,
}
}
pub fn search(&self, nodes: Vec<Node>) -> 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<NodeSorterApplicable>,
#[serde(default)]
pub searcher: Option<NodeSearcher>,
pub searcher: Option<NodeSearcherApplicable>,
}
impl ExplorerConfig {

@ -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<dyn MatchEngine> {
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<Node>) -> 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::<Vec<(_, _)>>()
}
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<dyn MatchEngine> {
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<Node>) -> 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::<Vec<(_, _)>>()
}
}

@ -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<B: Backend>(
})
.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<B: Backend>(
.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(),

Loading…
Cancel
Save