Add regex support and dynamic input prompt

- Add new regex filters
  - `RelativePathDoesMatchRegex`
  - `RelativePathDoesNotMatchRegex`
  - `IRelativePathDoesMatchRegex`
  - `IRelativePathDoesNotMatchRegex`
  - `AbsolutePathDoesMatchRegex`
  - `AbsolutePathDoesNotMatchRegex`
  - `IAbsolutePathDoesMatchRegex`
  - `IAbsolutePathDoesNotMatchRegex`
- Search mode now defaults to regex
- Added new message `SetInputPrompt` to set the input prompt
  dynamically.
pull/475/head
Arijit Basu 2 years ago committed by Arijit Basu
parent 5765698fb7
commit 1de737cefa

12
Cargo.lock generated

@ -2,6 +2,15 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "ansi-to-tui-forked" name = "ansi-to-tui-forked"
version = "0.5.2-fix.offset" version = "0.5.2-fix.offset"
@ -693,6 +702,8 @@ version = "1.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286"
dependencies = [ dependencies = [
"aho-corasick",
"memchr",
"regex-syntax", "regex-syntax",
] ]
@ -1151,6 +1162,7 @@ dependencies = [
"mime_guess", "mime_guess",
"mlua", "mlua",
"natord", "natord",
"regex",
"serde", "serde",
"serde_yaml", "serde_yaml",
"tui", "tui",

@ -31,6 +31,7 @@ serde_yaml = "0.8.24"
crossterm = "0.23.2" crossterm = "0.23.2"
dirs = "4.0.0" dirs = "4.0.0"
ansi-to-tui-forked = "0.5.2-fix.offset" ansi-to-tui-forked = "0.5.2-fix.offset"
regex = "1.5.5"
[dependencies.lazy_static] [dependencies.lazy_static]
version = "1.4.0" version = "1.4.0"

@ -277,6 +277,17 @@ YAML: `FollowSymlink`
### Reading Input ### Reading Input
#### SetInputPrompt
Set the input prompt temporarily, until the input buffer is reset.
Type: { SetInputPrompt = { format = nullable string }
Example:
- Lua: `{ SetInputPrompt = "→" }`
- YAML: `SetInputPrompt: →`
#### UpdateInputBuffer #### UpdateInputBuffer
Update the input buffer using cursor based operations. Update the input buffer using cursor based operations.

@ -107,15 +107,15 @@ The builtin filter mode.
Type: [Mode](https://xplr.dev/en/mode) Type: [Mode](https://xplr.dev/en/mode)
#### xplr.config.modes.builtin.relative_path_does_contain #### xplr.config.modes.builtin.relative_path_does_match_regex
The builtin relative_path_does_contain mode. The builtin relative_path_does_match_regex mode.
Type: [Mode](https://xplr.dev/en/mode) Type: [Mode](https://xplr.dev/en/mode)
#### xplr.config.modes.builtin.relative_path_does_not_contain #### xplr.config.modes.builtin.relative_path_does_not_match_regex
The builtin relative_path_does_not_contain mode. The builtin relative_path_does_not_match_regex mode.
Type: [Mode](https://xplr.dev/en/mode) Type: [Mode](https://xplr.dev/en/mode)

@ -151,6 +151,12 @@ pub struct LuaContextLight {
pub explorer_config: ExplorerConfig, pub explorer_config: ExplorerConfig,
} }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InputBuffer {
pub buffer: Option<Input>,
pub prompt: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct App { pub struct App {
pub version: String, pub version: String,
@ -162,7 +168,7 @@ pub struct App {
pub msg_out: VecDeque<MsgOut>, pub msg_out: VecDeque<MsgOut>,
pub mode: Mode, pub mode: Mode,
pub layout: Layout, pub layout: Layout,
pub input: Option<Input>, pub input: InputBuffer,
pub pid: u32, pub pid: u32,
pub session_path: String, pub session_path: String,
pub pipe: Pipe, pub pipe: Pipe,
@ -277,6 +283,11 @@ impl App {
env::set_current_dir(&pwd)?; env::set_current_dir(&pwd)?;
let pwd = pwd.to_string_lossy().to_string(); let pwd = pwd.to_string_lossy().to_string();
let input = InputBuffer {
buffer: Default::default(),
prompt: config.general.prompt.format.clone(),
};
let mut app = Self { let mut app = Self {
version: VERSION.to_string(), version: VERSION.to_string(),
config, config,
@ -287,7 +298,7 @@ impl App {
msg_out: Default::default(), msg_out: Default::default(),
mode, mode,
layout, layout,
input: Default::default(), input,
pid, pid,
session_path: session_path.clone(), session_path: session_path.clone(),
pipe: Pipe::from_session_path(&session_path)?, pipe: Pipe::from_session_path(&session_path)?,
@ -404,6 +415,7 @@ impl App {
LastVisitedPath => self.last_visited_path(), LastVisitedPath => self.last_visited_path(),
NextVisitedPath => self.next_visited_path(), NextVisitedPath => self.next_visited_path(),
FollowSymlink => self.follow_symlink(), FollowSymlink => self.follow_symlink(),
SetInputPrompt(p) => self.set_input_prompt(p),
UpdateInputBuffer(op) => self.update_input_buffer(op), UpdateInputBuffer(op) => self.update_input_buffer(op),
UpdateInputBufferFromKey => { UpdateInputBufferFromKey => {
self.update_input_buffer_from_key(key) self.update_input_buffer_from_key(key)
@ -655,6 +667,7 @@ impl App {
fn focus_previous_by_relative_index_from_input(self) -> Result<Self> { fn focus_previous_by_relative_index_from_input(self) -> Result<Self> {
if let Some(index) = self if let Some(index) = self
.input .input
.buffer
.as_ref() .as_ref()
.and_then(|i| i.value().parse::<usize>().ok()) .and_then(|i| i.value().parse::<usize>().ok())
{ {
@ -699,6 +712,7 @@ impl App {
fn focus_next_by_relative_index_from_input(self) -> Result<Self> { fn focus_next_by_relative_index_from_input(self) -> Result<Self> {
if let Some(index) = self if let Some(index) = self
.input .input
.buffer
.as_ref() .as_ref()
.and_then(|i| i.value().parse::<usize>().ok()) .and_then(|i| i.value().parse::<usize>().ok())
{ {
@ -794,14 +808,19 @@ impl App {
} }
} }
fn set_input_prompt(mut self, p: Option<String>) -> Result<Self> {
self.input.prompt = p;
Ok(self)
}
fn update_input_buffer(mut self, op: InputOperation) -> Result<Self> { fn update_input_buffer(mut self, op: InputOperation) -> Result<Self> {
if let Some(buf) = self.input.as_mut() { if let Some(buf) = self.input.buffer.as_mut() {
buf.handle(op.into()); buf.handle(op.into());
self.logs_hidden = true; self.logs_hidden = true;
} else { } else {
let mut buf = Input::default(); let mut buf = Input::default();
if buf.handle(op.into()).is_some() { if buf.handle(op.into()).is_some() {
self.input = Some(buf); self.input.buffer = Some(buf);
self.logs_hidden = true; self.logs_hidden = true;
}; };
} }
@ -817,13 +836,13 @@ impl App {
} }
fn buffer_input(mut self, input: &str) -> Result<Self> { fn buffer_input(mut self, input: &str) -> Result<Self> {
if let Some(buf) = self.input.as_mut() { if let Some(buf) = self.input.buffer.as_mut() {
buf.handle(InputRequest::GoToEnd); buf.handle(InputRequest::GoToEnd);
for c in input.chars() { for c in input.chars() {
buf.handle(InputRequest::InsertChar(c)); buf.handle(InputRequest::InsertChar(c));
} }
} else { } else {
self.input = Some(Input::default().with_value(input.into())); self.input.buffer = Some(Input::default().with_value(input.into()));
}; };
self.logs_hidden = true; self.logs_hidden = true;
Ok(self) Ok(self)
@ -838,13 +857,13 @@ impl App {
} }
fn set_input_buffer(mut self, string: String) -> Result<Self> { fn set_input_buffer(mut self, string: String) -> Result<Self> {
self.input = Some(Input::default().with_value(string)); self.input.buffer = Some(Input::default().with_value(string));
self.logs_hidden = true; self.logs_hidden = true;
Ok(self) Ok(self)
} }
fn remove_input_buffer_last_character(mut self) -> Result<Self> { fn remove_input_buffer_last_character(mut self) -> Result<Self> {
if let Some(buf) = self.input.as_mut() { if let Some(buf) = self.input.buffer.as_mut() {
buf.handle(InputRequest::GoToEnd); buf.handle(InputRequest::GoToEnd);
buf.handle(InputRequest::DeletePrevChar); buf.handle(InputRequest::DeletePrevChar);
self.logs_hidden = true; self.logs_hidden = true;
@ -853,7 +872,7 @@ impl App {
} }
fn remove_input_buffer_last_word(mut self) -> Result<Self> { fn remove_input_buffer_last_word(mut self) -> Result<Self> {
if let Some(buf) = self.input.as_mut() { if let Some(buf) = self.input.buffer.as_mut() {
buf.handle(InputRequest::GoToEnd); buf.handle(InputRequest::GoToEnd);
buf.handle(InputRequest::DeletePrevWord); buf.handle(InputRequest::DeletePrevWord);
self.logs_hidden = true; self.logs_hidden = true;
@ -862,7 +881,10 @@ impl App {
} }
fn reset_input_buffer(mut self) -> Result<Self> { fn reset_input_buffer(mut self) -> Result<Self> {
self.input = None; self.input = InputBuffer {
buffer: Default::default(),
prompt: self.config.general.prompt.format.clone(),
};
Ok(self) Ok(self)
} }
@ -880,6 +902,7 @@ impl App {
fn focus_by_index_from_input(self) -> Result<Self> { fn focus_by_index_from_input(self) -> Result<Self> {
if let Some(index) = self if let Some(index) = self
.input .input
.buffer
.as_ref() .as_ref()
.and_then(|i| i.value().parse::<usize>().ok()) .and_then(|i| i.value().parse::<usize>().ok())
{ {
@ -948,7 +971,7 @@ impl App {
} }
fn focus_path_from_input(self) -> Result<Self> { fn focus_path_from_input(self) -> Result<Self> {
if let Some(p) = self.input.clone() { if let Some(p) = self.input.buffer.clone() {
self.focus_path(p.value(), true) self.focus_path(p.value(), true)
} else { } else {
Ok(self) Ok(self)
@ -969,10 +992,8 @@ impl App {
} }
fn pop_mode(self) -> Result<Self> { fn pop_mode(self) -> Result<Self> {
self.pop_mode_keeping_input_buffer().map(|mut a| { self.pop_mode_keeping_input_buffer()
a.input = None; .and_then(App::reset_input_buffer)
a
})
} }
fn pop_mode_keeping_input_buffer(mut self) -> Result<Self> { fn pop_mode_keeping_input_buffer(mut self) -> Result<Self> {
@ -983,10 +1004,8 @@ impl App {
} }
fn switch_mode(self, mode: &str) -> Result<Self> { fn switch_mode(self, mode: &str) -> Result<Self> {
self.switch_mode_keeping_input_buffer(mode).map(|mut a| { self.switch_mode_keeping_input_buffer(mode)
a.input = None; .and_then(App::reset_input_buffer)
a
})
} }
fn switch_mode_keeping_input_buffer(mut self, mode: &str) -> Result<Self> { fn switch_mode_keeping_input_buffer(mut self, mode: &str) -> Result<Self> {
@ -1001,10 +1020,7 @@ impl App {
fn switch_mode_builtin(self, mode: &str) -> Result<Self> { fn switch_mode_builtin(self, mode: &str) -> Result<Self> {
self.switch_mode_builtin_keeping_input_buffer(mode) self.switch_mode_builtin_keeping_input_buffer(mode)
.map(|mut a| { .and_then(App::reset_input_buffer)
a.input = None;
a
})
} }
fn switch_mode_builtin_keeping_input_buffer( fn switch_mode_builtin_keeping_input_buffer(
@ -1022,10 +1038,7 @@ impl App {
fn switch_mode_custom(self, mode: &str) -> Result<Self> { fn switch_mode_custom(self, mode: &str) -> Result<Self> {
self.switch_mode_custom_keeping_input_buffer(mode) self.switch_mode_custom_keeping_input_buffer(mode)
.map(|mut a| { .and_then(App::reset_input_buffer)
a.input = None;
a
})
} }
fn switch_mode_custom_keeping_input_buffer( fn switch_mode_custom_keeping_input_buffer(
@ -1246,7 +1259,7 @@ impl App {
mut self, mut self,
filter: NodeFilter, filter: NodeFilter,
) -> Result<Self> { ) -> Result<Self> {
if let Some(input) = self.input.as_ref() { if let Some(input) = self.input.buffer.as_ref() {
self.explorer_config self.explorer_config
.filters .filters
.insert(NodeFilterApplicable::new( .insert(NodeFilterApplicable::new(
@ -1269,7 +1282,7 @@ impl App {
mut self, mut self,
filter: NodeFilter, filter: NodeFilter,
) -> Result<Self> { ) -> Result<Self> {
if let Some(input) = self.input.as_ref() { if let Some(input) = self.input.buffer.as_ref() {
let nfa = NodeFilterApplicable::new(filter, input.value().into()); let nfa = NodeFilterApplicable::new(filter, input.value().into());
self.explorer_config.filters.retain(|f| f != &nfa); self.explorer_config.filters.retain(|f| f != &nfa);
}; };
@ -1540,8 +1553,8 @@ impl App {
&builtin.delete, &builtin.delete,
&builtin.sort, &builtin.sort,
&builtin.filter, &builtin.filter,
&builtin.relative_path_does_contain, &builtin.relative_path_does_match_regex,
&builtin.relative_path_does_not_contain, &builtin.relative_path_does_not_match_regex,
&builtin.switch_layout, &builtin.switch_layout,
] ]
.iter().map(|m| (&m.name, m.to_owned())) .iter().map(|m| (&m.name, m.to_owned()))
@ -1629,7 +1642,7 @@ impl App {
selection: self.selection.clone(), selection: self.selection.clone(),
mode: self.mode.clone(), mode: self.mode.clone(),
layout: self.layout.clone(), layout: self.layout.clone(),
input_buffer: self.input.as_ref().map(|i| i.value().into()), input_buffer: self.input.buffer.as_ref().map(|i| i.value().into()),
pid: self.pid, pid: self.pid,
session_path: self.session_path.clone(), session_path: self.session_path.clone(),
explorer_config: self.explorer_config.clone(), explorer_config: self.explorer_config.clone(),
@ -1646,7 +1659,7 @@ impl App {
selection: self.selection.clone(), selection: self.selection.clone(),
mode: self.mode.clone(), mode: self.mode.clone(),
layout: self.layout.clone(), layout: self.layout.clone(),
input_buffer: self.input.as_ref().map(|i| i.value().into()), input_buffer: self.input.buffer.as_ref().map(|i| i.value().into()),
pid: self.pid, pid: self.pid,
session_path: self.session_path.clone(), session_path: self.session_path.clone(),
explorer_config: self.explorer_config.clone(), explorer_config: self.explorer_config.clone(),

@ -570,10 +570,10 @@ pub struct BuiltinModesConfig {
pub filter: Mode, pub filter: Mode,
#[serde(default)] #[serde(default)]
pub relative_path_does_contain: Mode, pub relative_path_does_match_regex: Mode,
#[serde(default)] #[serde(default)]
pub relative_path_does_not_contain: Mode, pub relative_path_does_not_match_regex: Mode,
#[serde(default)] #[serde(default)]
pub sort: Mode, pub sort: Mode,
@ -609,17 +609,17 @@ impl BuiltinModesConfig {
"search" => Some(&self.search), "search" => Some(&self.search),
"sort" => Some(&self.sort), "sort" => Some(&self.sort),
"filter" => Some(&self.filter), "filter" => Some(&self.filter),
"relative_path_does_contain" => { "relative_path_does_match_regex" => {
Some(&self.relative_path_does_contain) Some(&self.relative_path_does_match_regex)
} }
"relative path does contain" => { "relative path does match regex" => {
Some(&self.relative_path_does_contain) Some(&self.relative_path_does_match_regex)
} }
"relative_path_does_not_contain" => { "relative_path_does_not_match_regex" => {
Some(&self.relative_path_does_not_contain) Some(&self.relative_path_does_not_match_regex)
} }
"relative path does not contain" => { "relative path does not match regex" => {
Some(&self.relative_path_does_not_contain) Some(&self.relative_path_does_not_match_regex)
} }
"switch layout" => Some(&self.switch_layout), "switch layout" => Some(&self.switch_layout),
"switch_layout" => Some(&self.switch_layout), "switch_layout" => Some(&self.switch_layout),

@ -403,6 +403,28 @@ xplr.config.general.sort_and_filter_ui.sorter_identifiers = {
-- * format: nullable string -- * format: nullable string
-- * style: [Style](https://xplr.dev/en/style) -- * style: [Style](https://xplr.dev/en/style)
xplr.config.general.sort_and_filter_ui.filter_identifiers = { xplr.config.general.sort_and_filter_ui.filter_identifiers = {
RelativePathDoesContain = { format = "rel=~", style = {} },
RelativePathDoesEndWith = { format = "rel=$", style = {} },
RelativePathDoesNotContain = { format = "rel!~", style = {} },
RelativePathDoesNotEndWith = { format = "rel!$", style = {} },
RelativePathDoesNotStartWith = { format = "rel!^", style = {} },
RelativePathDoesStartWith = { format = "rel=^", style = {} },
RelativePathIs = { format = "rel==", style = {} },
RelativePathIsNot = { format = "rel!=", style = {} },
RelativePathDoesMatchRegex = { format = "rel=/", style = {} },
RelativePathDoesNotMatchRegex = { format = "rel!/", style = {} },
IRelativePathDoesContain = { format = "[i]rel=~", style = {} },
IRelativePathDoesEndWith = { format = "[i]rel=$", style = {} },
IRelativePathDoesNotContain = { format = "[i]rel!~", style = {} },
IRelativePathDoesNotEndWith = { format = "[i]rel!$", style = {} },
IRelativePathDoesNotStartWith = { format = "[i]rel!^", style = {} },
IRelativePathDoesStartWith = { format = "[i]rel=^", style = {} },
IRelativePathIs = { format = "[i]rel==", style = {} },
IRelativePathIsNot = { format = "[i]rel!=", style = {} },
IRelativePathDoesMatchRegex = { format = "[i]rel=/", style = {} },
IRelativePathDoesNotMatchRegex = { format = "[i]rel!/", style = {} },
AbsolutePathDoesContain = { format = "abs=~", style = {} }, AbsolutePathDoesContain = { format = "abs=~", style = {} },
AbsolutePathDoesEndWith = { format = "abs=$", style = {} }, AbsolutePathDoesEndWith = { format = "abs=$", style = {} },
AbsolutePathDoesNotContain = { format = "abs!~", style = {} }, AbsolutePathDoesNotContain = { format = "abs!~", style = {} },
@ -411,6 +433,9 @@ xplr.config.general.sort_and_filter_ui.filter_identifiers = {
AbsolutePathDoesStartWith = { format = "abs=^", style = {} }, AbsolutePathDoesStartWith = { format = "abs=^", style = {} },
AbsolutePathIs = { format = "abs==", style = {} }, AbsolutePathIs = { format = "abs==", style = {} },
AbsolutePathIsNot = { format = "abs!=", style = {} }, AbsolutePathIsNot = { format = "abs!=", style = {} },
AbsolutePathDoesMatchRegex = { format = "abs=/", style = {} },
AbsolutePathDoesNotMatchRegex = { format = "abs!/", style = {} },
IAbsolutePathDoesContain = { format = "[i]abs=~", style = {} }, IAbsolutePathDoesContain = { format = "[i]abs=~", style = {} },
IAbsolutePathDoesEndWith = { format = "[i]abs=$", style = {} }, IAbsolutePathDoesEndWith = { format = "[i]abs=$", style = {} },
IAbsolutePathDoesNotContain = { format = "[i]abs!~", style = {} }, IAbsolutePathDoesNotContain = { format = "[i]abs!~", style = {} },
@ -419,22 +444,8 @@ xplr.config.general.sort_and_filter_ui.filter_identifiers = {
IAbsolutePathDoesStartWith = { format = "[i]abs=^", style = {} }, IAbsolutePathDoesStartWith = { format = "[i]abs=^", style = {} },
IAbsolutePathIs = { format = "[i]abs==", style = {} }, IAbsolutePathIs = { format = "[i]abs==", style = {} },
IAbsolutePathIsNot = { format = "[i]abs!=", style = {} }, IAbsolutePathIsNot = { format = "[i]abs!=", style = {} },
IRelativePathDoesContain = { format = "[i]rel=~", style = {} }, IAbsolutePathDoesMatchRegex = { format = "[i]abs=/", style = {} },
IRelativePathDoesEndWith = { format = "[i]rel=$", style = {} }, IAbsolutePathDoesNotMatchRegex = { format = "[i]abs!/", style = {} },
IRelativePathDoesNotContain = { format = "[i]rel!~", style = {} },
IRelativePathDoesNotEndWith = { format = "[i]rel!$", style = {} },
IRelativePathDoesNotStartWith = { format = "[i]rel!^", style = {} },
IRelativePathDoesStartWith = { format = "[i]rel=^", style = {} },
IRelativePathIs = { format = "[i]rel==", style = {} },
IRelativePathIsNot = { format = "[i]rel!=", style = {} },
RelativePathDoesContain = { format = "rel=~", style = {} },
RelativePathDoesEndWith = { format = "rel=$", style = {} },
RelativePathDoesNotContain = { format = "rel!~", style = {} },
RelativePathDoesNotEndWith = { format = "rel!$", style = {} },
RelativePathDoesNotStartWith = { format = "rel!^", style = {} },
RelativePathDoesStartWith = { format = "rel=^", style = {} },
RelativePathIs = { format = "rel==", style = {} },
RelativePathIsNot = { format = "rel!=", style = {} },
} }
-- The content for panel title by default. -- The content for panel title by default.
@ -1016,7 +1027,8 @@ xplr.config.modes.builtin.default = {
messages = { messages = {
"PopMode", "PopMode",
{ SwitchModeBuiltin = "search" }, { SwitchModeBuiltin = "search" },
{ SetInputBuffer = "" }, { SetInputPrompt = "/" },
{ SetInputBuffer = "(?i)" },
"ExplorePwdAsync", "ExplorePwdAsync",
}, },
}, },
@ -1984,7 +1996,7 @@ xplr.config.modes.builtin.search = {
enter = { enter = {
help = "focus", help = "focus",
messages = { messages = {
{ RemoveNodeFilterFromInput = "IRelativePathDoesContain" }, { RemoveNodeFilterFromInput = "RelativePathDoesMatchRegex" },
"PopMode", "PopMode",
"ExplorePwdAsync", "ExplorePwdAsync",
}, },
@ -1992,7 +2004,7 @@ xplr.config.modes.builtin.search = {
right = { right = {
help = "enter", help = "enter",
messages = { messages = {
{ RemoveNodeFilterFromInput = "IRelativePathDoesContain" }, { RemoveNodeFilterFromInput = "RelativePathDoesMatchRegex" },
"Enter", "Enter",
{ SetInputBuffer = "" }, { SetInputBuffer = "" },
"ExplorePwdAsync", "ExplorePwdAsync",
@ -2001,7 +2013,7 @@ xplr.config.modes.builtin.search = {
left = { left = {
help = "back", help = "back",
messages = { messages = {
{ RemoveNodeFilterFromInput = "IRelativePathDoesContain" }, { RemoveNodeFilterFromInput = "RelativePathDoesMatchRegex" },
"Back", "Back",
{ SetInputBuffer = "" }, { SetInputBuffer = "" },
"ExplorePwdAsync", "ExplorePwdAsync",
@ -2023,9 +2035,9 @@ xplr.config.modes.builtin.search = {
}, },
default = { default = {
messages = { messages = {
{ RemoveNodeFilterFromInput = "IRelativePathDoesContain" }, { RemoveNodeFilterFromInput = "RelativePathDoesMatchRegex" },
"UpdateInputBufferFromKey", "UpdateInputBufferFromKey",
{ AddNodeFilterFromInput = "IRelativePathDoesContain" }, { AddNodeFilterFromInput = "RelativePathDoesMatchRegex" },
"ExplorePwdAsync", "ExplorePwdAsync",
}, },
}, },
@ -2046,19 +2058,34 @@ xplr.config.modes.builtin.filter = {
name = "filter", name = "filter",
key_bindings = { key_bindings = {
on_key = { on_key = {
["r"] = {
help = "relative path does match regex",
messages = {
{ SwitchModeBuiltin = "relative_path_does_match_regex" },
{
SetInputPrompt = xplr.config.general.sort_and_filter_ui.filter_identifiers.RelativePathDoesMatchRegex.format,
},
{ SetInputBuffer = "" },
{ AddNodeFilterFromInput = "RelativePathDoesMatchRegex" },
"ExplorePwdAsync",
},
},
["R"] = { ["R"] = {
help = "relative does not contain", help = "relative path does not match regex",
messages = { messages = {
{ SwitchModeBuiltin = "relative_path_does_not_contain" }, { SwitchModeBuiltin = "relative_path_does_not_match_regex" },
{
SetInputPrompt = xplr.config.general.sort_and_filter_ui.filter_identifiers.RelativePathDoesNotMatchRegex.format,
},
{ SetInputBuffer = "" }, { SetInputBuffer = "" },
{ AddNodeFilterFromInput = "IRelativePathDoesNotContain" }, { AddNodeFilterFromInput = "RelativePathDoesNotMatchRegex" },
"ExplorePwdAsync", "ExplorePwdAsync",
}, },
}, },
["ctrl-c"] = { enter = {
help = "terminate", help = "done",
messages = { messages = {
"Terminate", "PopMode",
}, },
}, },
["ctrl-r"] = { ["ctrl-r"] = {
@ -2075,19 +2102,10 @@ xplr.config.modes.builtin.filter = {
"ExplorePwdAsync", "ExplorePwdAsync",
}, },
}, },
enter = { ["ctrl-c"] = {
help = "done", help = "terminate",
messages = {
"PopMode",
},
},
["r"] = {
help = "relative does contain",
messages = { messages = {
{ SwitchModeBuiltin = "relative_path_does_contain" }, "Terminate",
{ SetInputBuffer = "" },
{ AddNodeFilterFromInput = "IRelativePathDoesContain" },
"ExplorePwdAsync",
}, },
}, },
}, },
@ -2097,11 +2115,11 @@ xplr.config.modes.builtin.filter = {
xplr.config.modes.builtin.filter.key_bindings.on_key["esc"] = xplr.config.modes.builtin.filter.key_bindings.on_key["esc"] =
xplr.config.modes.builtin.filter.key_bindings.on_key.enter xplr.config.modes.builtin.filter.key_bindings.on_key.enter
-- The builtin relative_path_does_contain mode. -- The builtin relative_path_does_match_regex mode.
-- --
-- Type: [Mode](https://xplr.dev/en/mode) -- Type: [Mode](https://xplr.dev/en/mode)
xplr.config.modes.builtin.relative_path_does_contain = { xplr.config.modes.builtin.relative_path_does_match_regex = {
name = "relative path does contain", name = "relative path does match regex",
key_bindings = { key_bindings = {
on_key = { on_key = {
["ctrl-c"] = { ["ctrl-c"] = {
@ -2119,7 +2137,7 @@ xplr.config.modes.builtin.relative_path_does_contain = {
esc = { esc = {
help = "cancel", help = "cancel",
messages = { messages = {
{ RemoveNodeFilterFromInput = "IRelativePathDoesContain" }, { RemoveNodeFilterFromInput = "RelativePathDoesMatchRegex" },
"PopMode", "PopMode",
"ExplorePwdAsync", "ExplorePwdAsync",
}, },
@ -2127,20 +2145,20 @@ xplr.config.modes.builtin.relative_path_does_contain = {
}, },
default = { default = {
messages = { messages = {
{ RemoveNodeFilterFromInput = "IRelativePathDoesContain" }, { RemoveNodeFilterFromInput = "RelativePathDoesMatchRegex" },
"UpdateInputBufferFromKey", "UpdateInputBufferFromKey",
{ AddNodeFilterFromInput = "IRelativePathDoesContain" }, { AddNodeFilterFromInput = "RelativePathDoesMatchRegex" },
"ExplorePwdAsync", "ExplorePwdAsync",
}, },
}, },
}, },
} }
-- The builtin relative_path_does_not_contain mode. -- The builtin relative_path_does_not_match_regex mode.
-- --
-- Type: [Mode](https://xplr.dev/en/mode) -- Type: [Mode](https://xplr.dev/en/mode)
xplr.config.modes.builtin.relative_path_does_not_contain = { xplr.config.modes.builtin.relative_path_does_not_match_regex = {
name = "relative path does not contain", name = "relative path does not match regex",
key_bindings = { key_bindings = {
on_key = { on_key = {
["ctrl-c"] = { ["ctrl-c"] = {
@ -2158,7 +2176,7 @@ xplr.config.modes.builtin.relative_path_does_not_contain = {
esc = { esc = {
help = "cancel", help = "cancel",
messages = { messages = {
{ RemoveNodeFilterFromInput = "IRelativePathDoesNotContain" }, { RemoveNodeFilterFromInput = "RelativePathDoesNotMatchRegex" },
"PopMode", "PopMode",
"ExplorePwdAsync", "ExplorePwdAsync",
}, },
@ -2166,9 +2184,9 @@ xplr.config.modes.builtin.relative_path_does_not_contain = {
}, },
default = { default = {
messages = { messages = {
{ RemoveNodeFilterFromInput = "IRelativePathDoesNotContain" }, { RemoveNodeFilterFromInput = "RelativePathDoesNotMatchRegex" },
"UpdateInputBufferFromKey", "UpdateInputBufferFromKey",
{ AddNodeFilterFromInput = "IRelativePathDoesNotContain" }, { AddNodeFilterFromInput = "RelativePathDoesNotMatchRegex" },
"ExplorePwdAsync", "ExplorePwdAsync",
}, },
}, },

@ -1,5 +1,6 @@
use crate::{app::Node, input::InputOperation}; use crate::{app::Node, input::InputOperation};
use indexmap::IndexSet; use indexmap::IndexSet;
use regex::Regex;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::cmp::Ordering; use std::cmp::Ordering;
@ -47,7 +48,7 @@ pub enum ExternalMsg {
/// ///
/// Example: /// Example:
/// ///
/// - Lua: `"ClearScreen"`` /// - Lua: `"ClearScreen"`
/// - YAML: `ClearScreen` /// - YAML: `ClearScreen`
ClearScreen, ClearScreen,
@ -103,7 +104,7 @@ pub enum ExternalMsg {
/// Focus on the `-n`th node relative to the current focus where `n` is a /// Focus on the `-n`th node relative to the current focus where `n` is a
/// given value. /// given value.
/// ///
/// Type: { FocusPreviousByRelativeIndex = int } /// Type: { FocusPreviousByRelativeIndex = int }
/// ///
/// Example: /// Example:
@ -237,7 +238,17 @@ pub enum ExternalMsg {
/// YAML: `FollowSymlink` /// YAML: `FollowSymlink`
FollowSymlink, FollowSymlink,
/// ### Reading Input ------------------------------------------------------ /// ### Reading Input -----------------------------------------------------
/// Set the input prompt temporarily, until the input buffer is reset.
///
/// Type: { SetInputPrompt = { format = nullable string }
///
/// Example:
///
/// - Lua: `{ SetInputPrompt = "→" }`
/// - YAML: `SetInputPrompt: →`
SetInputPrompt(Option<String>),
/// Update the input buffer using cursor based operations. /// Update the input buffer using cursor based operations.
/// ///
@ -258,7 +269,7 @@ pub enum ExternalMsg {
UpdateInputBufferFromKey, UpdateInputBufferFromKey,
/// Append/buffer the given string into the input buffer. /// Append/buffer the given string into the input buffer.
/// ///
/// Type: { BufferInput = "string" } /// Type: { BufferInput = "string" }
/// ///
/// Example: /// Example:
@ -279,7 +290,7 @@ pub enum ExternalMsg {
/// Set/rewrite the input buffer with the given string. /// Set/rewrite the input buffer with the given string.
/// When the input buffer is not-null (even if empty string) /// When the input buffer is not-null (even if empty string)
/// it will show in the UI. /// it will show in the UI.
/// ///
/// Type: { SetInputBuffer = "string" } /// Type: { SetInputBuffer = "string" }
/// ///
/// Example: /// Example:
@ -289,7 +300,7 @@ pub enum ExternalMsg {
SetInputBuffer(String), SetInputBuffer(String),
/// Remove input buffer's last character. /// Remove input buffer's last character.
/// ///
/// Example: /// Example:
/// ///
/// - Lua: `"RemoveInputBufferLastCharacter"` /// - Lua: `"RemoveInputBufferLastCharacter"`
@ -1208,6 +1219,12 @@ pub enum NodeFilter {
IRelativePathDoesEndWith, IRelativePathDoesEndWith,
IRelativePathDoesNotEndWith, IRelativePathDoesNotEndWith,
RelativePathDoesMatchRegex,
RelativePathDoesNotMatchRegex,
IRelativePathDoesMatchRegex,
IRelativePathDoesNotMatchRegex,
AbsolutePathIs, AbsolutePathIs,
AbsolutePathIsNot, AbsolutePathIsNot,
@ -1231,6 +1248,12 @@ pub enum NodeFilter {
IAbsolutePathDoesEndWith, IAbsolutePathDoesEndWith,
IAbsolutePathDoesNotEndWith, IAbsolutePathDoesNotEndWith,
AbsolutePathDoesMatchRegex,
AbsolutePathDoesNotMatchRegex,
IAbsolutePathDoesMatchRegex,
IAbsolutePathDoesNotMatchRegex,
} }
impl NodeFilter { impl NodeFilter {
@ -1293,6 +1316,24 @@ impl NodeFilter {
.to_lowercase() .to_lowercase()
.ends_with(&input.to_lowercase()), .ends_with(&input.to_lowercase()),
Self::RelativePathDoesMatchRegex => Regex::new(input)
.map(|r| r.is_match(&node.relative_path))
.unwrap_or(false),
Self::IRelativePathDoesMatchRegex => {
Regex::new(&input.to_lowercase())
.map(|r| r.is_match(&node.relative_path.to_lowercase()))
.unwrap_or(false)
}
Self::RelativePathDoesNotMatchRegex => !Regex::new(input)
.map(|r| r.is_match(&node.relative_path))
.unwrap_or(false),
Self::IRelativePathDoesNotMatchRegex => {
!Regex::new(&input.to_lowercase())
.map(|r| r.is_match(&node.relative_path.to_lowercase()))
.unwrap_or(false)
}
Self::AbsolutePathIs => node.absolute_path.eq(input), Self::AbsolutePathIs => node.absolute_path.eq(input),
Self::IAbsolutePathIs => { Self::IAbsolutePathIs => {
node.absolute_path.eq_ignore_ascii_case(input) node.absolute_path.eq_ignore_ascii_case(input)
@ -1348,6 +1389,24 @@ impl NodeFilter {
.absolute_path .absolute_path
.to_lowercase() .to_lowercase()
.ends_with(&input.to_lowercase()), .ends_with(&input.to_lowercase()),
Self::AbsolutePathDoesMatchRegex => Regex::new(input)
.map(|r| r.is_match(&node.absolute_path))
.unwrap_or(false),
Self::IAbsolutePathDoesMatchRegex => {
Regex::new(&input.to_lowercase())
.map(|r| r.is_match(&node.absolute_path.to_lowercase()))
.unwrap_or(false)
}
Self::AbsolutePathDoesNotMatchRegex => !Regex::new(input)
.map(|r| r.is_match(&node.absolute_path))
.unwrap_or(false),
Self::IAbsolutePathDoesNotMatchRegex => {
!Regex::new(&input.to_lowercase())
.map(|r| r.is_match(&node.absolute_path.to_lowercase()))
.unwrap_or(false)
}
} }
} }
} }

@ -80,6 +80,7 @@ fn call(app: &app::App, cmd: app::Command, silent: bool) -> Result<ExitStatus> {
.env( .env(
"XPLR_INPUT_BUFFER", "XPLR_INPUT_BUFFER",
app.input app.input
.buffer
.as_ref() .as_ref()
.map(|i| i.value().to_string()) .map(|i| i.value().to_string())
.unwrap_or_default(), .unwrap_or_default(),

@ -801,7 +801,7 @@ fn draw_input_buffer<B: Backend>(
app: &app::App, app: &app::App,
_: &Lua, _: &Lua,
) { ) {
if let Some(input) = app.input.as_ref() { if let Some(input) = app.input.buffer.as_ref() {
let panel_config = &app.config.general.panel_ui; let panel_config = &app.config.general.panel_ui;
let config = panel_config let config = panel_config
.default .default
@ -818,10 +818,8 @@ fn draw_input_buffer<B: Backend>(
} else { } else {
0 0
} + app } + app
.config .input
.general
.prompt .prompt
.format
.as_ref() .as_ref()
.map(|t| t.chars().count() as u16) .map(|t| t.chars().count() as u16)
.unwrap_or(0); .unwrap_or(0);
@ -843,12 +841,7 @@ fn draw_input_buffer<B: Backend>(
let input_buf = Paragraph::new(Spans::from(vec![ let input_buf = Paragraph::new(Spans::from(vec![
Span::styled( Span::styled(
app.config app.input.prompt.to_owned().unwrap_or_default(),
.general
.prompt
.format
.to_owned()
.unwrap_or_default(),
app.config.general.prompt.style.to_owned().into(), app.config.general.prompt.style.to_owned().into(),
), ),
Span::raw(input.value()), Span::raw(input.value()),
@ -1271,7 +1264,7 @@ pub fn draw_layout<B: Backend>(
draw_selection(f, screen_size, layout_size, app, lua) draw_selection(f, screen_size, layout_size, app, lua)
} }
Layout::InputAndLogs => { Layout::InputAndLogs => {
if app.input.is_some() { if app.input.buffer.is_some() {
draw_input_buffer(f, screen_size, layout_size, app, lua); draw_input_buffer(f, screen_size, layout_size, app, lua);
} else { } else {
draw_logs(f, screen_size, layout_size, app, lua); draw_logs(f, screen_size, layout_size, app, lua);

Loading…
Cancel
Save