diff --git a/yazi-config/preset/keymap.toml b/yazi-config/preset/keymap.toml index 1dc00d37..00ed69da 100644 --- a/yazi-config/preset/keymap.toml +++ b/yazi-config/preset/keymap.toml @@ -95,6 +95,9 @@ keymap = [ { on = [ "n" ], exec = "find_arrow" }, { on = [ "N" ], exec = "find_arrow --previous" }, + # Filter + { on = [ "f" ], exec = "filter" }, + # Sorting { on = [ ",", "m" ], exec = "sort modified --dir_first", desc = "Sort by modified time" }, { on = [ ",", "M" ], exec = "sort modified --reverse --dir_first", desc = "Sort by modified time (reverse)" }, diff --git a/yazi-config/preset/yazi.toml b/yazi-config/preset/yazi.toml index 4db32843..c529be7b 100644 --- a/yazi-config/preset/yazi.toml +++ b/yazi-config/preset/yazi.toml @@ -149,6 +149,11 @@ search_title = "Search:" search_origin = "top-center" search_offset = [ 0, 2, 50, 3 ] +# filter +filter_title = "Filter:" +filter_origin = "top-center" +filter_offset = [ 0, 2, 50, 3 ] + # shell shell_title = [ "Shell:", "Shell (block):" ] shell_origin = "top-center" diff --git a/yazi-config/src/popup/input.rs b/yazi-config/src/popup/input.rs index 6453b11f..1d07232d 100644 --- a/yazi-config/src/popup/input.rs +++ b/yazi-config/src/popup/input.rs @@ -35,6 +35,11 @@ pub struct Input { pub find_origin: Origin, pub find_offset: Offset, + // filter + pub filter_title: String, + pub filter_origin: Origin, + pub filter_offset: Offset, + // search pub search_title: String, pub search_origin: Origin, diff --git a/yazi-config/src/popup/options.rs b/yazi-config/src/popup/options.rs index 177788c9..a6ce8496 100644 --- a/yazi-config/src/popup/options.rs +++ b/yazi-config/src/popup/options.rs @@ -77,6 +77,16 @@ impl InputCfg { } } + #[inline] + pub fn filter() -> Self { + Self { + title: INPUT.filter_title.to_owned(), + position: Position::new(INPUT.filter_origin, INPUT.filter_offset), + realtime: true, + ..Default::default() + } + } + #[inline] pub fn search() -> Self { Self { diff --git a/yazi-core/src/tab/commands/escape.rs b/yazi-core/src/tab/commands/escape.rs index c6e13721..db748f33 100644 --- a/yazi-core/src/tab/commands/escape.rs +++ b/yazi-core/src/tab/commands/escape.rs @@ -9,6 +9,7 @@ bitflags! { const VISUAL = 0b0010; const SELECT = 0b0100; const SEARCH = 0b1000; + const FILTER = 0b1001; } } @@ -20,6 +21,7 @@ impl From<&Exec> for Opt { b"visual" => acc | Self::VISUAL, b"select" => acc | Self::SELECT, b"search" => acc | Self::SEARCH, + b"filter" => acc | Self::FILTER, _ => acc, }) } @@ -45,13 +47,23 @@ impl Tab { #[inline] fn escape_search(&mut self) -> bool { self.search_stop() } + #[inline] + fn escape_filter(&mut self) -> bool { + if self.current.files.filter_keyword.is_some() { + self.current.files.set_filter_keyword(""); + return true; + } + false + } + pub fn escape(&mut self, opt: impl Into) -> bool { let opt = opt.into() as Opt; if opt.is_empty() { return self.escape_find() || self.escape_visual() || self.escape_select() - || self.escape_search(); + || self.escape_search() + || self.escape_filter(); } let mut b = false; @@ -67,6 +79,9 @@ impl Tab { if opt.contains(Opt::SEARCH) { b |= self.escape_search(); } + if opt.contains(Opt::FILTER) { + b |= self.escape_filter(); + } b } } diff --git a/yazi-core/src/tab/commands/filter.rs b/yazi-core/src/tab/commands/filter.rs new file mode 100644 index 00000000..49d1070c --- /dev/null +++ b/yazi-core/src/tab/commands/filter.rs @@ -0,0 +1,59 @@ +use std::time::Duration; + +use tokio::pin; +use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt}; +use yazi_config::popup::InputCfg; +use yazi_shared::{emit, event::Exec, Debounce, InputError, Layer}; + +use crate::{input::Input, tab::Tab}; + +pub struct Opt<'a> { + query: Option<&'a str>, +} + +impl<'a> From<&'a Exec> for Opt<'a> { + fn from(e: &'a Exec) -> Self { + Self { query: e.args.first().map(|s| s.as_str()) } + } +} + +impl Tab { + pub fn filter<'a>(&mut self, _opt: impl Into>) -> bool { + tokio::spawn(async move { + let rx = Input::_show(InputCfg::filter()); + + let rx = Debounce::new(UnboundedReceiverStream::new(rx), Duration::from_millis(50)); + pin!(rx); + + while let Some(Ok(s)) | Some(Err(InputError::Typed(s))) = rx.next().await { + emit!(Call(Exec::call("filter_do", vec![s]).vec(), Layer::Manager)); + } + }); + false + } + + pub fn filter_do<'a>(&mut self, opt: impl Into>) -> bool { + let opt = opt.into() as Opt; + let Some(query) = opt.query else { + return false; + }; + + let hovered = &self.current.hovered().map(|f| f.url()); + + self.current.files.set_filter_keyword(query); + + if let Some(hovered_url) = hovered { + match (self.current.files.position(hovered_url), self.current.files.first()) { + (Some(_), _) => { + self.current.hover(hovered_url); + } + (None, Some(first)) => { + self.current.hover(&first.url()); + } + (_, _) => {} + } + } + + true + } +} diff --git a/yazi-core/src/tab/commands/mod.rs b/yazi-core/src/tab/commands/mod.rs index 86834755..5170b0a5 100644 --- a/yazi-core/src/tab/commands/mod.rs +++ b/yazi-core/src/tab/commands/mod.rs @@ -4,6 +4,7 @@ mod cd; mod copy; mod enter; mod escape; +mod filter; mod find; mod hidden; mod jump; diff --git a/yazi-fm/src/executor.rs b/yazi-fm/src/executor.rs index 58a8202b..17a327d2 100644 --- a/yazi-fm/src/executor.rs +++ b/yazi-fm/src/executor.rs @@ -152,6 +152,10 @@ impl<'a> Executor<'a> { on!(ACTIVE, search); on!(ACTIVE, jump); + // Filter + on!(ACTIVE, filter); + on!(ACTIVE, filter_do); + // Find on!(ACTIVE, find); on!(ACTIVE, find_do);