diff --git a/yazi-config/preset/keymap.toml b/yazi-config/preset/keymap.toml index 5ae3867d5..fba119b2a 100644 --- a/yazi-config/preset/keymap.toml +++ b/yazi-config/preset/keymap.toml @@ -31,8 +31,8 @@ keymap = [ { on = [ "" ], exec = "arrow -100%", desc = "Move cursor up one page" }, { on = [ "" ], exec = "arrow 100%", desc = "Move cursor down one page" }, - { on = [ "h" ], exec = [ "escape --visual", "leave" ], desc = "Go back to the parent directory" }, - { on = [ "l" ], exec = [ "escape --visual", "enter" ], desc = "Enter the child directory" }, + { on = [ "h" ], exec = "leave", desc = "Go back to the parent directory" }, + { on = [ "l" ], exec = "enter", desc = "Enter the child directory" }, { on = [ "H" ], exec = "back", desc = "Go back to the previous directory" }, { on = [ "L" ], exec = "forward", desc = "Go forward to the next directory" }, @@ -58,29 +58,29 @@ keymap = [ { on = [ "" ], exec = "select_all --state=none", desc = "Inverse selection of all files" }, # Operation - { on = [ "o" ], exec = [ "escape --visual", "open" ], desc = "Open the selected files" }, - { on = [ "O" ], exec = [ "escape --visual", "open --interactive" ], desc = "Open the selected files interactively" }, - { on = [ "" ], exec = [ "escape --visual", "open" ], desc = "Open the selected files" }, - { on = [ "" ], exec = [ "escape --visual", "open --interactive" ], desc = "Open the selected files interactively" }, - { on = [ "y" ], exec = [ "escape --visual", "yank" ], desc = "Copy the selected files" }, - { on = [ "Y" ], exec = "unyank", desc = "Cancel the yank status of files" }, - { on = [ "x" ], exec = [ "escape --visual", "yank --cut" ], desc = "Cut the selected files" }, - { on = [ "p" ], exec = "paste", desc = "Paste the files" }, - { on = [ "P" ], exec = "paste --force", desc = "Paste the files (overwrite if the destination exists)" }, - { on = [ "-" ], exec = "link", desc = "Symlink the absolute path of files" }, - { on = [ "_" ], exec = "link --relative", desc = "Symlink the relative path of files" }, - { on = [ "d" ], exec = [ "escape --visual", "remove" ], desc = "Move the files to the trash" }, - { on = [ "D" ], exec = [ "escape --visual", "remove --permanently" ], desc = "Permanently delete the files" }, - { on = [ "a" ], exec = "create", desc = "Create a file or directory (ends with / for directories)" }, - { on = [ "r" ], exec = [ "escape --visual", "rename --cursor=before_ext" ], desc = "Rename a file or directory" }, - { on = [ ";" ], exec = [ "escape --visual", "shell" ], desc = "Run a shell command" }, - { on = [ ":" ], exec = [ "escape --visual", "shell --block" ], desc = "Run a shell command (block the UI until the command finishes)" }, - { on = [ "." ], exec = "hidden toggle", desc = "Toggle the visibility of hidden files" }, - { on = [ "s" ], exec = "search fd", desc = "Search files by name using fd" }, - { on = [ "S" ], exec = "search rg", desc = "Search files by content using ripgrep" }, - { on = [ "" ], exec = "search none", desc = "Cancel the ongoing search" }, - { on = [ "z" ], exec = "jump zoxide", desc = "Jump to a directory using zoxide" }, - { on = [ "Z" ], exec = "jump fzf", desc = "Jump to a directory, or reveal a file using fzf" }, + { on = [ "o" ], exec = "open", desc = "Open the selected files" }, + { on = [ "O" ], exec = "open --interactive", desc = "Open the selected files interactively" }, + { on = [ "" ], exec = "open", desc = "Open the selected files" }, + { on = [ "" ], exec = "open --interactive", desc = "Open the selected files interactively" }, + { on = [ "y" ], exec = "yank", desc = "Copy the selected files" }, + { on = [ "Y" ], exec = "unyank", desc = "Cancel the yank status of files" }, + { on = [ "x" ], exec = "yank --cut", desc = "Cut the selected files" }, + { on = [ "p" ], exec = "paste", desc = "Paste the files" }, + { on = [ "P" ], exec = "paste --force", desc = "Paste the files (overwrite if the destination exists)" }, + { on = [ "-" ], exec = "link", desc = "Symlink the absolute path of files" }, + { on = [ "_" ], exec = "link --relative", desc = "Symlink the relative path of files" }, + { on = [ "d" ], exec = "remove", desc = "Move the files to the trash" }, + { on = [ "D" ], exec = "remove --permanently", desc = "Permanently delete the files" }, + { on = [ "a" ], exec = "create", desc = "Create a file or directory (ends with / for directories)" }, + { on = [ "r" ], exec = "rename --cursor=before_ext", desc = "Rename a file or directory" }, + { on = [ ";" ], exec = "shell", desc = "Run a shell command" }, + { on = [ ":" ], exec = "shell --block", desc = "Run a shell command (block the UI until the command finishes)" }, + { on = [ "." ], exec = "hidden toggle", desc = "Toggle the visibility of hidden files" }, + { on = [ "s" ], exec = "search fd", desc = "Search files by name using fd" }, + { on = [ "S" ], exec = "search rg", desc = "Search files by content using ripgrep" }, + { on = [ "" ], exec = "search none", desc = "Cancel the ongoing search" }, + { on = [ "z" ], exec = "jump zoxide", desc = "Jump to a directory using zoxide" }, + { on = [ "Z" ], exec = "jump fzf", desc = "Jump to a directory, or reveal a file using fzf" }, # Linemode { on = [ "m", "s" ], exec = "linemode size", desc = "Set linemode to size" }, @@ -89,10 +89,10 @@ keymap = [ { on = [ "m", "n" ], exec = "linemode none", desc = "Set linemode to none" }, # Copy - { on = [ "c", "c" ], exec = [ "escape --visual", "copy path" ], desc = "Copy the absolute path" }, - { on = [ "c", "d" ], exec = [ "escape --visual", "copy dirname" ], desc = "Copy the path of the parent directory" }, - { on = [ "c", "f" ], exec = [ "escape --visual", "copy filename" ], desc = "Copy the name of the file" }, - { on = [ "c", "n" ], exec = [ "escape --visual", "copy name_without_ext" ], desc = "Copy the name of the file without the extension" }, + { on = [ "c", "c" ], exec = "copy path", desc = "Copy the absolute path" }, + { on = [ "c", "d" ], exec = "copy dirname", desc = "Copy the path of the parent directory" }, + { on = [ "c", "f" ], exec = "copy filename", desc = "Copy the name of the file" }, + { on = [ "c", "n" ], exec = "copy name_without_ext", desc = "Copy the name of the file without the extension" }, # Filter { on = [ "f" ], exec = "filter --smart", desc = "Filter the files" }, diff --git a/yazi-config/src/theme/theme.rs b/yazi-config/src/theme/theme.rs index 66d6e479f..8534c90fe 100644 --- a/yazi-config/src/theme/theme.rs +++ b/yazi-config/src/theme/theme.rs @@ -31,10 +31,10 @@ pub struct Manager { #[validate(range(min = 1, message = "Must be greater than 0"))] tab_width: u8, - // Selected counter - count_selected: Style, + // Count count_copied: Style, count_cut: Style, + count_selected: Style, // Border pub border_symbol: String, diff --git a/yazi-core/src/help/help.rs b/yazi-core/src/help/help.rs index 0126c1553..f28989458 100644 --- a/yazi-core/src/help/help.rs +++ b/yazi-core/src/help/help.rs @@ -1,7 +1,7 @@ use crossterm::event::KeyCode; use unicode_width::UnicodeWidthStr; use yazi_config::{keymap::{Control, Key}, KEYMAP}; -use yazi_shared::{render, term::Term, Layer}; +use yazi_shared::{render, render_and, term::Term, Layer}; use super::HELP_MARGIN; use crate::input::Input; @@ -49,8 +49,7 @@ impl Help { } Key { code: KeyCode::Enter, shift: false, ctrl: false, alt: false } => { self.in_filter = None; - render!(); - return true; // Don't do the `filter_apply` below, since we already have the filtered results. + return render_and!(true); // Don't do the `filter_apply` below, since we already have the filtered results. } Key { code: KeyCode::Backspace, shift: false, ctrl: false, alt: false } => { input.backspace(false); diff --git a/yazi-core/src/manager/commands/open.rs b/yazi-core/src/manager/commands/open.rs index 73e103250..9e6977743 100644 --- a/yazi-core/src/manager/commands/open.rs +++ b/yazi-core/src/manager/commands/open.rs @@ -25,8 +25,11 @@ impl From for Opt { impl Manager { pub fn open(&mut self, opt: impl Into, tasks: &Tasks) { - let mut opt = opt.into() as Opt; + if !self.active_mut().try_escape_visual() { + return; + } + let mut opt = opt.into() as Opt; let selected = if opt.hovered { self.hovered().map(|h| vec![&h.url]).unwrap_or_default() } else { diff --git a/yazi-core/src/manager/commands/paste.rs b/yazi-core/src/manager/commands/paste.rs index 8b2c95309..a968bbe5f 100644 --- a/yazi-core/src/manager/commands/paste.rs +++ b/yazi-core/src/manager/commands/paste.rs @@ -18,10 +18,11 @@ impl Manager { let opt = opt.into() as Opt; let dest = self.cwd(); - if self.yanked.cut { - tasks.file_cut(&self.yanked, dest, opt.force); - } else { + if !self.yanked.cut { tasks.file_copy(&self.yanked, dest, opt.force, opt.follow); } + + tasks.file_cut(&self.yanked, dest, opt.force); + self.unyank(()); } } diff --git a/yazi-core/src/manager/commands/remove.rs b/yazi-core/src/manager/commands/remove.rs index 15e9f70e8..9352b9961 100644 --- a/yazi-core/src/manager/commands/remove.rs +++ b/yazi-core/src/manager/commands/remove.rs @@ -18,6 +18,10 @@ impl From for Opt { impl Manager { pub fn remove(&mut self, opt: impl Into, tasks: &Tasks) { + if !self.active_mut().try_escape_visual() { + return; + } + let opt = opt.into() as Opt; let targets = self.selected_or_hovered().into_iter().cloned().collect(); tasks.file_remove(targets, opt.force, opt.permanently); diff --git a/yazi-core/src/manager/commands/rename.rs b/yazi-core/src/manager/commands/rename.rs index 80271fd2f..3164b40b7 100644 --- a/yazi-core/src/manager/commands/rename.rs +++ b/yazi-core/src/manager/commands/rename.rs @@ -52,8 +52,10 @@ impl Manager { Ok(Self::_hover(Some(new))) } - pub fn rename(&self, opt: impl Into) { - if !self.active().selected.is_empty() { + pub fn rename(&mut self, opt: impl Into) { + if !self.active_mut().try_escape_visual() { + return; + } else if !self.active().selected.is_empty() { return self.bulk_rename(); } diff --git a/yazi-core/src/manager/commands/unyank.rs b/yazi-core/src/manager/commands/unyank.rs index 6071bdf9e..710876933 100644 --- a/yazi-core/src/manager/commands/unyank.rs +++ b/yazi-core/src/manager/commands/unyank.rs @@ -2,8 +2,17 @@ use yazi_shared::{event::Cmd, render}; use crate::manager::Manager; +pub struct Opt; + +impl From for Opt { + fn from(_: Cmd) -> Self { Self } +} +impl From<()> for Opt { + fn from(_: ()) -> Self { Self } +} + impl Manager { - pub fn unyank(&mut self, _: Cmd) { + pub fn unyank(&mut self, _: impl Into) { render!(!self.yanked.is_empty()); self.yanked = Default::default(); diff --git a/yazi-core/src/manager/commands/yank.rs b/yazi-core/src/manager/commands/yank.rs index ab5dee174..5383382d5 100644 --- a/yazi-core/src/manager/commands/yank.rs +++ b/yazi-core/src/manager/commands/yank.rs @@ -14,6 +14,10 @@ impl From for Opt { impl Manager { pub fn yank(&mut self, opt: impl Into) { + if !self.active_mut().try_escape_visual() { + return; + } + let selected: HashSet<_> = self.selected_or_hovered().into_iter().cloned().collect(); if selected.is_empty() { return; diff --git a/yazi-core/src/tab/commands/back.rs b/yazi-core/src/tab/commands/back.rs new file mode 100644 index 000000000..3aec0be25 --- /dev/null +++ b/yazi-core/src/tab/commands/back.rs @@ -0,0 +1,7 @@ +use yazi_shared::event::Cmd; + +use crate::tab::Tab; + +impl Tab { + pub fn back(&mut self, _: Cmd) { self.backstack.shift_backward().cloned().map(|u| self.cd(u)); } +} diff --git a/yazi-core/src/tab/commands/backstack.rs b/yazi-core/src/tab/commands/backstack.rs deleted file mode 100644 index 8456233d3..000000000 --- a/yazi-core/src/tab/commands/backstack.rs +++ /dev/null @@ -1,25 +0,0 @@ -use yazi_shared::event::Cmd; - -use crate::tab::Tab; - -pub struct Opt; -impl From<()> for Opt { - fn from(_: ()) -> Self { Self } -} -impl From for Opt { - fn from(_: Cmd) -> Self { Self } -} - -impl Tab { - pub fn back(&mut self, _: impl Into) { - if let Some(url) = self.backstack.shift_backward().cloned() { - self.cd(url); - } - } - - pub fn forward(&mut self, _: impl Into) { - if let Some(url) = self.backstack.shift_forward().cloned() { - self.cd(url); - } - } -} diff --git a/yazi-core/src/tab/commands/cd.rs b/yazi-core/src/tab/commands/cd.rs index f563def72..8f0a4aad7 100644 --- a/yazi-core/src/tab/commands/cd.rs +++ b/yazi-core/src/tab/commands/cd.rs @@ -33,6 +33,10 @@ impl Tab { } pub fn cd(&mut self, opt: impl Into) { + if !self.try_escape_visual() { + return; + } + let opt = opt.into() as Opt; if opt.interactive { return self.cd_interactive(); diff --git a/yazi-core/src/tab/commands/copy.rs b/yazi-core/src/tab/commands/copy.rs index 78477bb62..8e47ea24c 100644 --- a/yazi-core/src/tab/commands/copy.rs +++ b/yazi-core/src/tab/commands/copy.rs @@ -13,8 +13,11 @@ impl From for Opt { } impl Tab { - pub fn copy(&self, opt: impl Into) { + pub fn copy(&mut self, opt: impl Into) { let opt = opt.into() as Opt; + if !self.try_escape_visual() { + return; + } let mut s = OsString::new(); let mut it = self.selected_or_hovered().into_iter().peekable(); diff --git a/yazi-core/src/tab/commands/enter.rs b/yazi-core/src/tab/commands/enter.rs index b01f4579e..be527442a 100644 --- a/yazi-core/src/tab/commands/enter.rs +++ b/yazi-core/src/tab/commands/enter.rs @@ -1,40 +1,9 @@ -use std::mem; +use yazi_shared::event::Cmd; -use yazi_shared::{event::Cmd, render}; - -use crate::{manager::Manager, tab::Tab}; - -pub struct Opt; -impl From<()> for Opt { - fn from(_: ()) -> Self { Self } -} -impl From for Opt { - fn from(_: Cmd) -> Self { Self } -} +use crate::tab::Tab; impl Tab { - pub fn enter(&mut self, _: impl Into) { - let Some(hovered) = self.current.hovered().filter(|h| h.is_dir()).map(|h| h.url()) else { - return; - }; - - // Current - let rep = self.history_new(&hovered); - let rep = mem::replace(&mut self.current, rep); - if rep.cwd.is_regular() { - self.history.insert(rep.cwd.clone(), rep); - } - - // Parent - if let Some(rep) = self.parent.take() { - self.history.insert(rep.cwd.clone(), rep); - } - self.parent = Some(self.history_new(&hovered.parent_url().unwrap())); - - // Backstack - self.backstack.push(hovered); - - Manager::_refresh(); - render!(); + pub fn enter(&mut self, _: Cmd) { + self.current.hovered().filter(|h| h.is_dir()).map(|h| h.url()).map(|u| self.cd(u)); } } diff --git a/yazi-core/src/tab/commands/escape.rs b/yazi-core/src/tab/commands/escape.rs index d30b0b26a..5f6ffcc03 100644 --- a/yazi-core/src/tab/commands/escape.rs +++ b/yazi-core/src/tab/commands/escape.rs @@ -1,5 +1,5 @@ use bitflags::bitflags; -use yazi_shared::{event::Cmd, render}; +use yazi_shared::{event::Cmd, render, render_and}; use crate::{manager::Manager, tab::{Mode, Tab}}; @@ -28,8 +28,36 @@ impl From for Opt { } impl Tab { + pub fn escape(&mut self, opt: impl Into) { + let opt = opt.into() as Opt; + if opt.is_empty() { + _ = self.escape_find() + || self.escape_visual() + || self.escape_select() + || self.escape_filter() + || self.escape_search(); + return; + } + + if opt.contains(Opt::FIND) { + self.escape_find(); + } + if opt.contains(Opt::VISUAL) { + self.escape_visual(); + } + if opt.contains(Opt::SELECT) { + self.escape_select(); + } + if opt.contains(Opt::FILTER) { + self.escape_filter(); + } + if opt.contains(Opt::SEARCH) { + self.escape_search(); + } + } + #[inline] - pub fn escape_find(&mut self) -> bool { self.finder.take().is_some() } + pub fn escape_find(&mut self) -> bool { render_and!(self.finder.take().is_some()) } #[inline] pub fn escape_visual(&mut self) -> bool { @@ -47,8 +75,7 @@ impl Tab { } self.mode = Mode::Normal; - render!(); - true + render_and!(true) } #[inline] @@ -61,49 +88,32 @@ impl Tab { if self.current.hovered().is_some_and(|h| h.is_dir()) { Manager::_peek(true); } - true + render_and!(true) } #[inline] pub fn escape_filter(&mut self) -> bool { - let b = self.current.files.filter().is_some(); + if self.current.files.filter().is_none() { + return false; + } + self.filter_do(super::filter::Opt::default()); - b + render_and!(true) } #[inline] pub fn escape_search(&mut self) -> bool { - let b = self.current.cwd.is_search(); + if !self.current.cwd.is_search() { + return false; + } + self.search_stop(); - b + render_and!(true) } - pub fn escape(&mut self, opt: impl Into) { - let opt = opt.into() as Opt; - if opt.is_empty() { - return render!( - self.escape_find() - || self.escape_visual() - || self.escape_select() - || self.escape_filter() - || self.escape_search() - ); - } - - if opt.contains(Opt::FIND) { - render!(self.escape_find()); - } - if opt.contains(Opt::VISUAL) { - render!(self.escape_visual()); - } - if opt.contains(Opt::SELECT) { - render!(self.escape_select()); - } - if opt.contains(Opt::FILTER) { - render!(self.escape_filter()); - } - if opt.contains(Opt::SEARCH) { - render!(self.escape_search()); - } + #[inline] + pub fn try_escape_visual(&mut self) -> bool { + self.escape_visual(); + true } } diff --git a/yazi-core/src/tab/commands/find.rs b/yazi-core/src/tab/commands/find.rs index 7da0fc279..a506d5583 100644 --- a/yazi-core/src/tab/commands/find.rs +++ b/yazi-core/src/tab/commands/find.rs @@ -58,7 +58,8 @@ impl Tab { return; }; if query.is_empty() { - return self.escape(super::escape::Opt::FIND); + self.escape_find(); + return; } let Ok(finder) = Finder::new(&query, opt.case) else { diff --git a/yazi-core/src/tab/commands/forward.rs b/yazi-core/src/tab/commands/forward.rs new file mode 100644 index 000000000..87bc5057e --- /dev/null +++ b/yazi-core/src/tab/commands/forward.rs @@ -0,0 +1,7 @@ +use yazi_shared::event::Cmd; + +use crate::tab::Tab; + +impl Tab { + pub fn forward(&mut self, _: Cmd) { self.backstack.shift_forward().cloned().map(|u| self.cd(u)); } +} diff --git a/yazi-core/src/tab/commands/leave.rs b/yazi-core/src/tab/commands/leave.rs index c98d4236c..85d96fe65 100644 --- a/yazi-core/src/tab/commands/leave.rs +++ b/yazi-core/src/tab/commands/leave.rs @@ -1,8 +1,6 @@ -use std::mem; +use yazi_shared::event::Cmd; -use yazi_shared::{event::Cmd, render}; - -use crate::{manager::Manager, tab::Tab}; +use crate::tab::Tab; pub struct Opt; impl From<()> for Opt { @@ -14,36 +12,12 @@ impl From for Opt { impl Tab { pub fn leave(&mut self, _: impl Into) { - let current = self + self .current .hovered() .and_then(|h| h.parent()) .filter(|p| *p != self.current.cwd) - .or_else(|| self.current.cwd.parent_url()); - - let Some(current) = current else { - return; - }; - - // Parent - if let Some(rep) = self.parent.take() { - self.history.insert(rep.cwd.clone(), rep); - } - if let Some(parent) = current.parent_url() { - self.parent = Some(self.history_new(&parent)); - } - - // Current - let rep = self.history_new(¤t); - let rep = mem::replace(&mut self.current, rep); - if rep.cwd.is_regular() { - self.history.insert(rep.cwd.clone(), rep); - } - - // Backstack - self.backstack.push(current); - - Manager::_refresh(); - render!(); + .or_else(|| self.current.cwd.parent_url()) + .map(|u| self.cd(u)); } } diff --git a/yazi-core/src/tab/commands/mod.rs b/yazi-core/src/tab/commands/mod.rs index 0f6801e07..8f0c6467c 100644 --- a/yazi-core/src/tab/commands/mod.rs +++ b/yazi-core/src/tab/commands/mod.rs @@ -1,11 +1,12 @@ mod arrow; -mod backstack; +mod back; mod cd; mod copy; mod enter; mod escape; mod filter; mod find; +mod forward; mod hidden; mod jump; mod leave; diff --git a/yazi-core/src/tab/commands/shell.rs b/yazi-core/src/tab/commands/shell.rs index 11c07983b..a74ca0f07 100644 --- a/yazi-core/src/tab/commands/shell.rs +++ b/yazi-core/src/tab/commands/shell.rs @@ -20,7 +20,11 @@ impl From for Opt { } impl Tab { - pub fn shell(&self, opt: impl Into) { + pub fn shell(&mut self, opt: impl Into) { + if !self.try_escape_visual() { + return; + } + let mut opt = opt.into() as Opt; let selected: Vec<_> = self.selected_or_hovered().into_iter().cloned().collect(); diff --git a/yazi-core/src/which/which.rs b/yazi-core/src/which/which.rs index d6227775c..7791263d0 100644 --- a/yazi-core/src/which/which.rs +++ b/yazi-core/src/which/which.rs @@ -1,5 +1,5 @@ use yazi_config::keymap::{ControlCow, Key}; -use yazi_shared::{emit, render, Layer}; +use yazi_shared::{emit, render, render_and, Layer}; #[derive(Default)] pub struct Which { @@ -27,8 +27,7 @@ impl Which { self.reset(); } - render!(); - true + render_and!(true) } fn reset(&mut self) { diff --git a/yazi-plugin/preset/components/header.lua b/yazi-plugin/preset/components/header.lua index 1cfc1d176..f0f84bf49 100644 --- a/yazi-plugin/preset/components/header.lua +++ b/yazi-plugin/preset/components/header.lua @@ -14,7 +14,7 @@ function Header:cwd() return span:style(THEME.manager.cwd) end -function Header:counter() +function Header:count() local yanked = #cx.yanked local count, style @@ -73,7 +73,7 @@ function Header:render(area) local chunks = self:layout(area) local left = ui.Line { self:cwd() } - local right = ui.Line { self:counter(), self:tabs() } + local right = ui.Line { self:count(), self:tabs() } return { ui.Paragraph(chunks[1], { left }), ui.Paragraph(chunks[2], { right }):align(ui.Paragraph.RIGHT), diff --git a/yazi-shared/src/event/render.rs b/yazi-shared/src/event/render.rs index c616f9c50..5fa8fe5af 100644 --- a/yazi-shared/src/event/render.rs +++ b/yazi-shared/src/event/render.rs @@ -7,9 +7,21 @@ macro_rules! render { () => { $crate::event::NEED_RENDER.store(true, std::sync::atomic::Ordering::Relaxed); }; - ($expr:expr) => { - if $expr { + ($cond:expr) => { + if $cond { render!(); } }; } + +#[macro_export] +macro_rules! render_and { + ($cond:expr) => { + if $cond { + render!(); + true + } else { + false + } + }; +}