From c41397957d4d60750b195d4f9dbf97a8b4dd4434 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20=C2=B7=20Misaki=20Masa?= Date: Fri, 10 Nov 2023 09:25:23 +0800 Subject: [PATCH] refactor: extracting commands into separate files to make them easier to maintain (#338) --- cspell.json | 2 +- yazi-core/src/completion/commands/arrow.rs | 12 +- yazi-core/src/completion/commands/close.rs | 10 +- yazi-core/src/completion/commands/show.rs | 2 +- yazi-core/src/completion/commands/trigger.rs | 2 +- yazi-core/src/help/commands/arrow.rs | 56 +++ yazi-core/src/help/commands/escape.rs | 21 ++ yazi-core/src/help/commands/filter.rs | 17 + yazi-core/src/help/commands/mod.rs | 3 + yazi-core/src/help/help.rs | 69 +--- yazi-core/src/help/mod.rs | 1 + yazi-core/src/input/commands/backward.rs | 35 ++ yazi-core/src/input/commands/close.rs | 34 ++ yazi-core/src/input/commands/complete.rs | 2 +- yazi-core/src/input/commands/delete.rs | 35 ++ yazi-core/src/input/commands/escape.rs | 36 ++ yazi-core/src/input/commands/forward.rs | 42 +++ yazi-core/src/input/commands/insert.rs | 29 ++ yazi-core/src/input/commands/mod.rs | 12 + yazi-core/src/input/commands/move_.rs | 57 +++ yazi-core/src/input/commands/paste.rs | 31 ++ yazi-core/src/input/commands/redo.rs | 13 + yazi-core/src/input/commands/undo.rs | 21 ++ yazi-core/src/input/commands/visual.rs | 14 + yazi-core/src/input/commands/yank.rs | 30 ++ yazi-core/src/input/input.rs | 223 +---------- yazi-core/src/manager/commands/close.rs | 12 +- yazi-core/src/manager/commands/create.rs | 14 +- yazi-core/src/manager/commands/link.rs | 22 ++ yazi-core/src/manager/commands/mod.rs | 7 + yazi-core/src/manager/commands/open.rs | 48 ++- yazi-core/src/manager/commands/paste.rs | 21 ++ yazi-core/src/manager/commands/quit.rs | 21 +- yazi-core/src/manager/commands/remove.rs | 25 ++ yazi-core/src/manager/commands/rename.rs | 49 ++- yazi-core/src/manager/commands/suspend.rs | 9 +- yazi-core/src/manager/commands/tab_close.rs | 35 ++ yazi-core/src/manager/commands/tab_create.rs | 41 +++ yazi-core/src/manager/commands/tab_swap.rs | 26 ++ yazi-core/src/manager/commands/tab_switch.rs | 35 ++ yazi-core/src/manager/commands/yank.rs | 16 +- yazi-core/src/manager/manager.rs | 2 +- yazi-core/src/manager/tabs.rs | 64 +--- yazi-core/src/select/commands/arrow.rs | 48 +++ yazi-core/src/select/commands/close.rs | 29 ++ yazi-core/src/select/commands/mod.rs | 2 + yazi-core/src/select/mod.rs | 1 + yazi-core/src/select/select.rs | 53 +-- yazi-core/src/tab/commands/arrow.rs | 28 +- yazi-core/src/tab/commands/backstack.rs | 14 +- yazi-core/src/tab/commands/cd.rs | 42 ++- yazi-core/src/tab/commands/copy.rs | 16 +- yazi-core/src/tab/commands/enter.rs | 12 +- yazi-core/src/tab/commands/find.rs | 86 +++-- yazi-core/src/tab/commands/jump.rs | 37 +- yazi-core/src/tab/commands/leave.rs | 12 +- yazi-core/src/tab/commands/search.rs | 34 +- yazi-core/src/tab/commands/select.rs | 29 +- yazi-core/src/tab/commands/shell.rs | 40 +- yazi-core/src/tab/commands/visual_mode.rs | 15 +- yazi-core/src/tasks/commands/arrow.rs | 36 ++ yazi-core/src/tasks/commands/cancel.rs | 22 ++ yazi-core/src/tasks/commands/inspect.rs | 77 ++++ yazi-core/src/tasks/commands/mod.rs | 4 + yazi-core/src/tasks/commands/toggle.rs | 20 + yazi-core/src/tasks/mod.rs | 1 + yazi-core/src/tasks/tasks.rs | 104 +----- yazi-fm/src/executor.rs | 369 ++++++++----------- yazi-plugin/preset/ui.lua | 1 - yazi-shared/src/fns.rs | 9 - 70 files changed, 1571 insertions(+), 826 deletions(-) create mode 100644 yazi-core/src/help/commands/arrow.rs create mode 100644 yazi-core/src/help/commands/escape.rs create mode 100644 yazi-core/src/help/commands/filter.rs create mode 100644 yazi-core/src/help/commands/mod.rs create mode 100644 yazi-core/src/input/commands/backward.rs create mode 100644 yazi-core/src/input/commands/close.rs create mode 100644 yazi-core/src/input/commands/delete.rs create mode 100644 yazi-core/src/input/commands/escape.rs create mode 100644 yazi-core/src/input/commands/forward.rs create mode 100644 yazi-core/src/input/commands/insert.rs create mode 100644 yazi-core/src/input/commands/move_.rs create mode 100644 yazi-core/src/input/commands/paste.rs create mode 100644 yazi-core/src/input/commands/redo.rs create mode 100644 yazi-core/src/input/commands/undo.rs create mode 100644 yazi-core/src/input/commands/visual.rs create mode 100644 yazi-core/src/input/commands/yank.rs create mode 100644 yazi-core/src/manager/commands/link.rs create mode 100644 yazi-core/src/manager/commands/paste.rs create mode 100644 yazi-core/src/manager/commands/remove.rs create mode 100644 yazi-core/src/manager/commands/tab_close.rs create mode 100644 yazi-core/src/manager/commands/tab_create.rs create mode 100644 yazi-core/src/manager/commands/tab_swap.rs create mode 100644 yazi-core/src/manager/commands/tab_switch.rs create mode 100644 yazi-core/src/select/commands/arrow.rs create mode 100644 yazi-core/src/select/commands/close.rs create mode 100644 yazi-core/src/select/commands/mod.rs create mode 100644 yazi-core/src/tasks/commands/arrow.rs create mode 100644 yazi-core/src/tasks/commands/cancel.rs create mode 100644 yazi-core/src/tasks/commands/inspect.rs create mode 100644 yazi-core/src/tasks/commands/mod.rs create mode 100644 yazi-core/src/tasks/commands/toggle.rs diff --git a/cspell.json b/cspell.json index 89b6f851..118d17d3 100644 --- a/cspell.json +++ b/cspell.json @@ -1 +1 @@ -{"version":"0.2","words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags"],"language":"en","flagWords":[]} +{"language":"en","flagWords":[],"version":"0.2","words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE"]} diff --git a/yazi-core/src/completion/commands/arrow.rs b/yazi-core/src/completion/commands/arrow.rs index 735b5e94..c5296585 100644 --- a/yazi-core/src/completion/commands/arrow.rs +++ b/yazi-core/src/completion/commands/arrow.rs @@ -2,10 +2,14 @@ use yazi_config::keymap::Exec; use crate::completion::Completion; -pub struct Opt(isize); +pub struct Opt { + step: isize, +} impl From<&Exec> for Opt { - fn from(e: &Exec) -> Self { Self(e.args.first().and_then(|s| s.parse().ok()).unwrap_or(0)) } + fn from(e: &Exec) -> Self { + Self { step: e.args.first().and_then(|s| s.parse().ok()).unwrap_or(0) } + } } impl Completion { @@ -38,7 +42,7 @@ impl Completion { } pub fn arrow(&mut self, opt: impl Into) -> bool { - let step = opt.into().0; - if step > 0 { self.next(step as usize) } else { self.prev(step.unsigned_abs()) } + let opt = opt.into() as Opt; + if opt.step > 0 { self.next(opt.step as usize) } else { self.prev(opt.step.unsigned_abs()) } } } diff --git a/yazi-core/src/completion/commands/close.rs b/yazi-core/src/completion/commands/close.rs index ea3eaa65..84e0d7fa 100644 --- a/yazi-core/src/completion/commands/close.rs +++ b/yazi-core/src/completion/commands/close.rs @@ -2,16 +2,18 @@ use yazi_config::keymap::{Exec, KeymapLayer}; use crate::{completion::Completion, emit}; -pub struct Opt(bool); +pub struct Opt { + submit: bool, +} impl From<&Exec> for Opt { - fn from(e: &Exec) -> Self { Self(e.named.contains_key("submit")) } + fn from(e: &Exec) -> Self { Self { submit: e.named.contains_key("submit") } } } impl Completion { pub fn close(&mut self, opt: impl Into) -> bool { - let submit = opt.into().0; - if submit { + let opt = opt.into() as Opt; + if opt.submit { emit!(Call( Exec::call("complete", vec![self.selected().into()]).with("ticket", self.ticket).vec(), KeymapLayer::Input diff --git a/yazi-core/src/completion/commands/show.rs b/yazi-core/src/completion/commands/show.rs index 204e5ab9..28059b82 100644 --- a/yazi-core/src/completion/commands/show.rs +++ b/yazi-core/src/completion/commands/show.rs @@ -24,7 +24,7 @@ impl<'a> From<&'a Exec> for Opt<'a> { impl Completion { pub fn show<'a>(&mut self, opt: impl Into>) -> bool { - let opt = opt.into(); + let opt = opt.into() as Opt; if self.ticket != opt.ticket { return false; } diff --git a/yazi-core/src/completion/commands/trigger.rs b/yazi-core/src/completion/commands/trigger.rs index 5ed07e07..23331097 100644 --- a/yazi-core/src/completion/commands/trigger.rs +++ b/yazi-core/src/completion/commands/trigger.rs @@ -29,7 +29,7 @@ impl Completion { } pub fn trigger<'a>(&mut self, opt: impl Into>) -> bool { - let opt = opt.into(); + let opt = opt.into() as Opt; if opt.ticket < self.ticket { return false; } diff --git a/yazi-core/src/help/commands/arrow.rs b/yazi-core/src/help/commands/arrow.rs new file mode 100644 index 00000000..af4ca625 --- /dev/null +++ b/yazi-core/src/help/commands/arrow.rs @@ -0,0 +1,56 @@ +use yazi_config::keymap::Exec; + +use crate::help::Help; + +pub struct Opt { + step: isize, +} + +impl From<&Exec> for Opt { + fn from(e: &Exec) -> Self { + Self { step: e.args.first().and_then(|s| s.parse().ok()).unwrap_or(0) } + } +} +impl From for Opt { + fn from(step: isize) -> Self { Self { step } } +} + +impl Help { + #[inline] + pub fn arrow(&mut self, opt: impl Into) -> bool { + let max = self.bindings.len().saturating_sub(1); + self.offset = self.offset.min(max); + self.cursor = self.cursor.min(max); + + let opt = opt.into() as Opt; + if opt.step > 0 { self.next(opt.step as usize) } else { self.prev(opt.step.unsigned_abs()) } + } + + fn next(&mut self, step: usize) -> bool { + let len = self.bindings.len(); + if len == 0 { + return false; + } + + let old = self.cursor; + self.cursor = (self.cursor + step).min(len - 1); + + let limit = Self::limit(); + if self.cursor >= (self.offset + limit).min(len).saturating_sub(5) { + self.offset = len.saturating_sub(limit).min(self.offset + self.cursor - old); + } + + old != self.cursor + } + + fn prev(&mut self, step: usize) -> bool { + let old = self.cursor; + self.cursor = self.cursor.saturating_sub(step); + + if self.cursor < self.offset + 5 { + self.offset = self.offset.saturating_sub(old - self.cursor); + } + + old != self.cursor + } +} diff --git a/yazi-core/src/help/commands/escape.rs b/yazi-core/src/help/commands/escape.rs new file mode 100644 index 00000000..e27cbec3 --- /dev/null +++ b/yazi-core/src/help/commands/escape.rs @@ -0,0 +1,21 @@ +use yazi_config::keymap::Exec; + +use crate::help::Help; + +pub struct Opt; + +impl From<&Exec> for Opt { + fn from(_: &Exec) -> Self { Self } +} + +impl Help { + pub fn escape(&mut self, _: impl Into) -> bool { + if self.in_filter.is_some() { + self.in_filter = None; + self.filter_apply(); + true + } else { + self.toggle(self.layer) + } + } +} diff --git a/yazi-core/src/help/commands/filter.rs b/yazi-core/src/help/commands/filter.rs new file mode 100644 index 00000000..54865299 --- /dev/null +++ b/yazi-core/src/help/commands/filter.rs @@ -0,0 +1,17 @@ +use yazi_config::keymap::Exec; + +use crate::help::Help; + +pub struct Opt; + +impl From<&Exec> for Opt { + fn from(_: &Exec) -> Self { Self } +} + +impl Help { + pub fn filter(&mut self, _: impl Into) -> bool { + self.in_filter = Some(Default::default()); + self.filter_apply(); + true + } +} diff --git a/yazi-core/src/help/commands/mod.rs b/yazi-core/src/help/commands/mod.rs new file mode 100644 index 00000000..c1e9ca7a --- /dev/null +++ b/yazi-core/src/help/commands/mod.rs @@ -0,0 +1,3 @@ +mod arrow; +mod escape; +mod filter; diff --git a/yazi-core/src/help/help.rs b/yazi-core/src/help/help.rs index a43d3481..97696135 100644 --- a/yazi-core/src/help/help.rs +++ b/yazi-core/src/help/help.rs @@ -7,16 +7,16 @@ use crate::{emit, input::Input}; #[derive(Default)] pub struct Help { - pub visible: bool, - pub layer: KeymapLayer, - bindings: Vec, + pub visible: bool, + pub layer: KeymapLayer, + pub(super) bindings: Vec, // Filter - keyword: Option, - in_filter: Option, + keyword: Option, + pub(super) in_filter: Option, - offset: usize, - cursor: usize, + pub(super) offset: usize, + pub(super) cursor: usize, } impl Help { @@ -38,60 +38,7 @@ impl Help { true } - pub fn escape(&mut self) -> bool { - if self.in_filter.is_some() { - self.in_filter = None; - self.filter_apply(); - true - } else { - self.toggle(self.layer) - } - } - - #[inline] - pub fn arrow(&mut self, step: isize) -> bool { - let max = self.bindings.len().saturating_sub(1); - self.offset = self.offset.min(max); - self.cursor = self.cursor.min(max); - - if step > 0 { self.next(step as usize) } else { self.prev(step.unsigned_abs()) } - } - - pub fn next(&mut self, step: usize) -> bool { - let len = self.bindings.len(); - if len == 0 { - return false; - } - - let old = self.cursor; - self.cursor = (self.cursor + step).min(len - 1); - - let limit = Self::limit(); - if self.cursor >= (self.offset + limit).min(len).saturating_sub(5) { - self.offset = len.saturating_sub(limit).min(self.offset + self.cursor - old); - } - - old != self.cursor - } - - pub fn prev(&mut self, step: usize) -> bool { - let old = self.cursor; - self.cursor = self.cursor.saturating_sub(step); - - if self.cursor < self.offset + 5 { - self.offset = self.offset.saturating_sub(old - self.cursor); - } - - old != self.cursor - } - - pub fn filter(&mut self) -> bool { - self.in_filter = Some(Default::default()); - self.filter_apply(); - true - } - - fn filter_apply(&mut self) -> bool { + pub(super) fn filter_apply(&mut self) -> bool { let kw = self.in_filter.as_ref().map(|i| i.value()).filter(|v| !v.is_empty()); if self.keyword.as_deref() == kw { return false; diff --git a/yazi-core/src/help/mod.rs b/yazi-core/src/help/mod.rs index 6525211a..3420abd0 100644 --- a/yazi-core/src/help/mod.rs +++ b/yazi-core/src/help/mod.rs @@ -1,3 +1,4 @@ +mod commands; mod help; pub use help::*; diff --git a/yazi-core/src/input/commands/backward.rs b/yazi-core/src/input/commands/backward.rs new file mode 100644 index 00000000..4105e414 --- /dev/null +++ b/yazi-core/src/input/commands/backward.rs @@ -0,0 +1,35 @@ +use yazi_config::keymap::Exec; +use yazi_shared::CharKind; + +use crate::input::Input; + +pub struct Opt; + +impl From<&Exec> for Opt { + fn from(_: &Exec) -> Self { Self } +} + +impl Input { + pub fn backward(&mut self, _: impl Into) -> bool { + let snap = self.snap(); + if snap.cursor == 0 { + return self.move_(0); + } + + let idx = snap.idx(snap.cursor).unwrap_or(snap.len()); + let mut it = snap.value[..idx].chars().rev().enumerate(); + let mut prev = CharKind::new(it.next().unwrap().1); + for (i, c) in it { + let c = CharKind::new(c); + if prev != CharKind::Space && prev != c { + return self.move_(-(i as isize)); + } + prev = c; + } + + if prev != CharKind::Space { + return self.move_(-(snap.len() as isize)); + } + false + } +} diff --git a/yazi-core/src/input/commands/close.rs b/yazi-core/src/input/commands/close.rs new file mode 100644 index 00000000..0dd4d8ad --- /dev/null +++ b/yazi-core/src/input/commands/close.rs @@ -0,0 +1,34 @@ +use yazi_config::keymap::{Exec, KeymapLayer}; +use yazi_shared::InputError; + +use crate::{emit, input::Input}; + +pub struct Opt { + submit: bool, +} + +impl From<&Exec> for Opt { + fn from(e: &Exec) -> Self { Self { submit: e.named.contains_key("submit") } } +} +impl From for Opt { + fn from(submit: bool) -> Self { Self { submit } } +} + +impl Input { + pub fn close(&mut self, opt: impl Into) -> bool { + let opt = opt.into() as Opt; + + if self.completion { + emit!(Call(Exec::call("close", vec![]).vec(), KeymapLayer::Completion)); + } + + if let Some(cb) = self.callback.take() { + let value = self.snap_mut().value.clone(); + _ = cb.send(if opt.submit { Ok(value) } else { Err(InputError::Canceled(value)) }); + } + + self.ticket = self.ticket.wrapping_add(1); + self.visible = false; + true + } +} diff --git a/yazi-core/src/input/commands/complete.rs b/yazi-core/src/input/commands/complete.rs index 83d4c68c..c6d4dd49 100644 --- a/yazi-core/src/input/commands/complete.rs +++ b/yazi-core/src/input/commands/complete.rs @@ -18,7 +18,7 @@ impl<'a> From<&'a Exec> for Opt<'a> { impl Input { pub fn complete<'a>(&mut self, opt: impl Into>) -> bool { - let opt = opt.into(); + let opt = opt.into() as Opt; if self.ticket != opt.ticket { return false; } diff --git a/yazi-core/src/input/commands/delete.rs b/yazi-core/src/input/commands/delete.rs new file mode 100644 index 00000000..b36e6905 --- /dev/null +++ b/yazi-core/src/input/commands/delete.rs @@ -0,0 +1,35 @@ +use yazi_config::keymap::Exec; + +use crate::input::{op::InputOp, Input}; + +pub struct Opt { + cut: bool, + insert: bool, +} + +impl From<&Exec> for Opt { + fn from(e: &Exec) -> Self { + Self { cut: e.named.contains_key("cut"), insert: e.named.contains_key("insert") } + } +} + +impl Input { + pub fn delete(&mut self, opt: impl Into) -> bool { + let opt = opt.into() as Opt; + match self.snap().op { + InputOp::None => { + self.snap_mut().op = InputOp::Delete(opt.cut, opt.insert, self.snap().cursor); + false + } + InputOp::Select(start) => { + self.snap_mut().op = InputOp::Delete(opt.cut, opt.insert, start); + return self.handle_op(self.snap().cursor, true).then(|| self.move_(0)).is_some(); + } + InputOp::Delete(..) => { + self.snap_mut().op = InputOp::Delete(opt.cut, opt.insert, 0); + return self.move_(self.snap().len() as isize); + } + _ => false, + } + } +} diff --git a/yazi-core/src/input/commands/escape.rs b/yazi-core/src/input/commands/escape.rs new file mode 100644 index 00000000..4e260bc8 --- /dev/null +++ b/yazi-core/src/input/commands/escape.rs @@ -0,0 +1,36 @@ +use yazi_config::keymap::{Exec, KeymapLayer}; + +use crate::{emit, input::{op::InputOp, Input, InputMode}}; + +pub struct Opt; + +impl From<&Exec> for Opt { + fn from(_: &Exec) -> Self { Self } +} +impl From<()> for Opt { + fn from(_: ()) -> Self { Self } +} + +impl Input { + pub fn escape(&mut self, _: impl Into) -> bool { + let snap = self.snap_mut(); + match snap.mode { + InputMode::Normal if snap.op == InputOp::None => { + self.close(false); + } + InputMode::Normal => { + snap.op = InputOp::None; + } + InputMode::Insert => { + snap.mode = InputMode::Normal; + self.move_(-1); + + if self.completion { + emit!(Call(Exec::call("close", vec![]).vec(), KeymapLayer::Completion)); + } + } + } + self.snaps.tag(); + true + } +} diff --git a/yazi-core/src/input/commands/forward.rs b/yazi-core/src/input/commands/forward.rs new file mode 100644 index 00000000..55cd685e --- /dev/null +++ b/yazi-core/src/input/commands/forward.rs @@ -0,0 +1,42 @@ +use yazi_config::keymap::Exec; +use yazi_shared::CharKind; + +use crate::input::{op::InputOp, Input}; + +pub struct Opt { + end_of_word: bool, +} + +impl From<&Exec> for Opt { + fn from(e: &Exec) -> Self { Self { end_of_word: e.named.contains_key("end-of-word") } } +} + +impl Input { + pub fn forward(&mut self, opt: impl Into) -> bool { + let opt = opt.into() as Opt; + + let snap = self.snap(); + if snap.value.is_empty() { + return self.move_(0); + } + + let mut it = snap.value.chars().skip(snap.cursor).enumerate(); + let mut prev = CharKind::new(it.next().unwrap().1); + for (i, c) in it { + let c = CharKind::new(c); + let b = if opt.end_of_word { + prev != CharKind::Space && prev != c && i != 1 + } else { + c != CharKind::Space && c != prev + }; + if b && !matches!(snap.op, InputOp::None | InputOp::Select(_)) { + return self.move_(i as isize); + } else if b { + return self.move_(if opt.end_of_word { i - 1 } else { i } as isize); + } + prev = c; + } + + self.move_(snap.len() as isize) + } +} diff --git a/yazi-core/src/input/commands/insert.rs b/yazi-core/src/input/commands/insert.rs new file mode 100644 index 00000000..4e1c8758 --- /dev/null +++ b/yazi-core/src/input/commands/insert.rs @@ -0,0 +1,29 @@ +use yazi_config::keymap::Exec; + +use crate::input::Input; + +pub struct Opt { + append: bool, +} + +impl From<&Exec> for Opt { + fn from(e: &Exec) -> Self { Self { append: e.named.contains_key("append") } } +} +impl From for Opt { + fn from(append: bool) -> Self { Self { append } } +} + +impl Input { + pub fn insert(&mut self, opt: impl Into) -> bool { + if !self.snap_mut().insert() { + return false; + } + + let opt = opt.into() as Opt; + if opt.append { + self.move_(1); + } + + true + } +} diff --git a/yazi-core/src/input/commands/mod.rs b/yazi-core/src/input/commands/mod.rs index df7f200d..da2185be 100644 --- a/yazi-core/src/input/commands/mod.rs +++ b/yazi-core/src/input/commands/mod.rs @@ -1 +1,13 @@ +mod backward; +mod close; mod complete; +mod delete; +mod escape; +mod forward; +mod insert; +mod move_; +mod paste; +mod redo; +mod undo; +mod visual; +mod yank; diff --git a/yazi-core/src/input/commands/move_.rs b/yazi-core/src/input/commands/move_.rs new file mode 100644 index 00000000..cda394e0 --- /dev/null +++ b/yazi-core/src/input/commands/move_.rs @@ -0,0 +1,57 @@ +use unicode_width::UnicodeWidthStr; +use yazi_config::keymap::Exec; + +use crate::input::{op::InputOp, snap::InputSnap, Input}; + +pub struct Opt { + step: isize, + in_operating: bool, +} + +impl From<&Exec> for Opt { + fn from(e: &Exec) -> Self { + Self { + step: e.args.first().and_then(|s| s.parse().ok()).unwrap_or(0), + in_operating: e.named.contains_key("in-operating"), + } + } +} +impl From for Opt { + fn from(step: isize) -> Self { Self { step, in_operating: false } } +} + +impl Input { + pub fn move_(&mut self, opt: impl Into) -> bool { + let opt = opt.into() as Opt; + + let snap = self.snap(); + if opt.in_operating && snap.op == InputOp::None { + return false; + } + + let b = self.handle_op( + if opt.step <= 0 { + snap.cursor.saturating_sub(opt.step.unsigned_abs()) + } else { + snap.count().min(snap.cursor + opt.step as usize) + }, + false, + ); + + let snap = self.snap_mut(); + if snap.cursor < snap.offset { + snap.offset = snap.cursor; + } else if snap.value.is_empty() { + snap.offset = 0; + } else { + let delta = snap.mode.delta(); + let s = snap.slice(snap.offset..snap.cursor + delta); + if s.width() >= /*TODO: hardcode*/ 50 - 2 { + let s = s.chars().rev().collect::(); + snap.offset = snap.cursor - InputSnap::find_window(&s, 0).end.saturating_sub(delta); + } + } + + b + } +} diff --git a/yazi-core/src/input/commands/paste.rs b/yazi-core/src/input/commands/paste.rs new file mode 100644 index 00000000..5145845d --- /dev/null +++ b/yazi-core/src/input/commands/paste.rs @@ -0,0 +1,31 @@ +use yazi_config::keymap::Exec; + +use crate::{external, input::{op::InputOp, Input}}; + +pub struct Opt { + before: bool, +} + +impl From<&Exec> for Opt { + fn from(e: &Exec) -> Self { Self { before: e.named.contains_key("before") } } +} + +impl Input { + pub fn paste(&mut self, opt: impl Into) -> bool { + if let Some(start) = self.snap().op.start() { + self.snap_mut().op = InputOp::Delete(false, false, start); + self.handle_op(self.snap().cursor, true); + } + + let s = futures::executor::block_on(external::clipboard_get()).unwrap_or_default(); + if s.is_empty() { + return false; + } + + let opt = opt.into() as Opt; + self.insert(!opt.before); + self.type_str(&s.to_string_lossy()); + self.escape(()); + true + } +} diff --git a/yazi-core/src/input/commands/redo.rs b/yazi-core/src/input/commands/redo.rs new file mode 100644 index 00000000..1722d664 --- /dev/null +++ b/yazi-core/src/input/commands/redo.rs @@ -0,0 +1,13 @@ +use yazi_config::keymap::Exec; + +use crate::input::Input; + +pub struct Opt; + +impl From<&Exec> for Opt { + fn from(_: &Exec) -> Self { Self } +} + +impl Input { + pub fn redo(&mut self, _: impl Into) -> bool { self.snaps.redo() } +} diff --git a/yazi-core/src/input/commands/undo.rs b/yazi-core/src/input/commands/undo.rs new file mode 100644 index 00000000..6ef8d81e --- /dev/null +++ b/yazi-core/src/input/commands/undo.rs @@ -0,0 +1,21 @@ +use yazi_config::keymap::Exec; + +use crate::input::{Input, InputMode}; + +pub struct Opt; + +impl From<&Exec> for Opt { + fn from(_: &Exec) -> Self { Self } +} + +impl Input { + pub fn undo(&mut self, _: impl Into) -> bool { + if !self.snaps.undo() { + return false; + } + if self.snap().mode == InputMode::Insert { + self.escape(()); + } + true + } +} diff --git a/yazi-core/src/input/commands/visual.rs b/yazi-core/src/input/commands/visual.rs new file mode 100644 index 00000000..509dc084 --- /dev/null +++ b/yazi-core/src/input/commands/visual.rs @@ -0,0 +1,14 @@ +use yazi_config::keymap::Exec; + +use crate::input::Input; + +pub struct Opt; + +impl From<&Exec> for Opt { + fn from(_: &Exec) -> Self { Self } +} + +impl Input { + #[inline] + pub fn visual(&mut self, _: impl Into) -> bool { self.snap_mut().visual() } +} diff --git a/yazi-core/src/input/commands/yank.rs b/yazi-core/src/input/commands/yank.rs new file mode 100644 index 00000000..e8c32c6c --- /dev/null +++ b/yazi-core/src/input/commands/yank.rs @@ -0,0 +1,30 @@ +use yazi_config::keymap::Exec; + +use crate::input::{op::InputOp, Input}; + +pub struct Opt; + +impl From<&Exec> for Opt { + fn from(_: &Exec) -> Self { Self } +} + +impl Input { + pub fn yank(&mut self, _: impl Into) -> bool { + match self.snap().op { + InputOp::None => { + self.snap_mut().op = InputOp::Yank(self.snap().cursor); + false + } + InputOp::Select(start) => { + self.snap_mut().op = InputOp::Yank(start); + return self.handle_op(self.snap().cursor, true).then(|| self.move_(0)).is_some(); + } + InputOp::Yank(_) => { + self.snap_mut().op = InputOp::Yank(0); + self.move_(self.snap().len() as isize); + false + } + _ => false, + } + } +} diff --git a/yazi-core/src/input/input.rs b/yazi-core/src/input/input.rs index d9df0339..08476fea 100644 --- a/yazi-core/src/input/input.rs +++ b/yazi-core/src/input/input.rs @@ -3,11 +3,11 @@ use std::ops::Range; use crossterm::event::KeyCode; use tokio::sync::mpsc::UnboundedSender; use unicode_width::UnicodeWidthStr; -use yazi_config::keymap::{Exec, Key, KeymapLayer}; -use yazi_shared::{CharKind, InputError}; +use yazi_config::keymap::Key; +use yazi_shared::InputError; use super::{mode::InputMode, op::InputOp, InputOpt, InputSnap, InputSnaps}; -use crate::{emit, external, Position}; +use crate::{external, Position}; #[derive(Default)] pub struct Input { @@ -19,9 +19,9 @@ pub struct Input { pub position: Position, // Typing - callback: Option>>, - realtime: bool, - completion: bool, + pub(super) callback: Option>>, + realtime: bool, + pub(super) completion: bool, // Shell pub(super) highlight: bool, @@ -45,157 +45,6 @@ impl Input { self.highlight = opt.highlight; } - pub fn close(&mut self, submit: bool) -> bool { - if self.completion { - emit!(Call(Exec::call("close", vec![]).vec(), KeymapLayer::Completion)); - } - - if let Some(cb) = self.callback.take() { - let value = self.snap_mut().value.clone(); - _ = cb.send(if submit { Ok(value) } else { Err(InputError::Canceled(value)) }); - } - - self.ticket = self.ticket.wrapping_add(1); - self.visible = false; - true - } - - pub fn escape(&mut self) -> bool { - let snap = self.snap_mut(); - match snap.mode { - InputMode::Normal if snap.op == InputOp::None => { - self.close(false); - } - InputMode::Normal => { - snap.op = InputOp::None; - } - InputMode::Insert => { - snap.mode = InputMode::Normal; - self.move_(-1); - - if self.completion { - emit!(Call(Exec::call("close", vec![]).vec(), KeymapLayer::Completion)); - } - } - } - self.snaps.tag(); - true - } - - pub fn insert(&mut self, append: bool) -> bool { - if !self.snap_mut().insert() { - return false; - } - if append { - self.move_(1); - } - true - } - - #[inline] - pub fn visual(&mut self) -> bool { self.snap_mut().visual() } - - #[inline] - pub fn undo(&mut self) -> bool { - if !self.snaps.undo() { - return false; - } - if self.snap().mode == InputMode::Insert { - self.escape(); - } - true - } - - #[inline] - pub fn redo(&mut self) -> bool { - if !self.snaps.redo() { - return false; - } - true - } - - pub fn move_(&mut self, step: isize) -> bool { - let snap = self.snap(); - let b = self.handle_op( - if step <= 0 { - snap.cursor.saturating_sub(step.unsigned_abs()) - } else { - snap.count().min(snap.cursor + step as usize) - }, - false, - ); - - let snap = self.snap_mut(); - if snap.cursor < snap.offset { - snap.offset = snap.cursor; - } else if snap.value.is_empty() { - snap.offset = 0; - } else { - let delta = snap.mode.delta(); - let s = snap.slice(snap.offset..snap.cursor + delta); - if s.width() >= /*TODO: hardcode*/ 50 - 2 { - let s = s.chars().rev().collect::(); - snap.offset = snap.cursor - InputSnap::find_window(&s, 0).end.saturating_sub(delta); - } - } - - b - } - - #[inline] - pub fn move_in_operating(&mut self, step: isize) -> bool { - if self.snap_mut().op == InputOp::None { false } else { self.move_(step) } - } - - pub fn backward(&mut self) -> bool { - let snap = self.snap(); - if snap.cursor == 0 { - return self.move_(0); - } - - let idx = snap.idx(snap.cursor).unwrap_or(snap.len()); - let mut it = snap.value[..idx].chars().rev().enumerate(); - let mut prev = CharKind::new(it.next().unwrap().1); - for (i, c) in it { - let c = CharKind::new(c); - if prev != CharKind::Space && prev != c { - return self.move_(-(i as isize)); - } - prev = c; - } - - if prev != CharKind::Space { - return self.move_(-(snap.len() as isize)); - } - false - } - - pub fn forward(&mut self, end: bool) -> bool { - let snap = self.snap(); - if snap.value.is_empty() { - return self.move_(0); - } - - let mut it = snap.value.chars().skip(snap.cursor).enumerate(); - let mut prev = CharKind::new(it.next().unwrap().1); - for (i, c) in it { - let c = CharKind::new(c); - let b = if end { - prev != CharKind::Space && prev != c && i != 1 - } else { - c != CharKind::Space && c != prev - }; - if b && !matches!(snap.op, InputOp::None | InputOp::Select(_)) { - return self.move_(i as isize); - } else if b { - return self.move_(if end { i - 1 } else { i } as isize); - } - prev = c; - } - - self.move_(snap.len() as isize) - } - pub fn type_(&mut self, key: &Key) -> bool { if self.mode() != InputMode::Insert { return false; @@ -238,61 +87,7 @@ impl Input { true } - pub fn delete(&mut self, cut: bool, insert: bool) -> bool { - match self.snap().op { - InputOp::None => { - self.snap_mut().op = InputOp::Delete(cut, insert, self.snap().cursor); - false - } - InputOp::Select(start) => { - self.snap_mut().op = InputOp::Delete(cut, insert, start); - return self.handle_op(self.snap().cursor, true).then(|| self.move_(0)).is_some(); - } - InputOp::Delete(..) => { - self.snap_mut().op = InputOp::Delete(cut, insert, 0); - return self.move_(self.snap().len() as isize); - } - _ => false, - } - } - - pub fn yank(&mut self) -> bool { - match self.snap().op { - InputOp::None => { - self.snap_mut().op = InputOp::Yank(self.snap().cursor); - false - } - InputOp::Select(start) => { - self.snap_mut().op = InputOp::Yank(start); - return self.handle_op(self.snap().cursor, true).then(|| self.move_(0)).is_some(); - } - InputOp::Yank(_) => { - self.snap_mut().op = InputOp::Yank(0); - self.move_(self.snap().len() as isize); - false - } - _ => false, - } - } - - pub fn paste(&mut self, before: bool) -> bool { - if let Some(start) = self.snap().op.start() { - self.snap_mut().op = InputOp::Delete(false, false, start); - self.handle_op(self.snap().cursor, true); - } - - let s = futures::executor::block_on(external::clipboard_get()).unwrap_or_default(); - if s.is_empty() { - return false; - } - - self.insert(!before); - self.type_str(&s.to_string_lossy()); - self.escape(); - true - } - - fn handle_op(&mut self, cursor: usize, include: bool) -> bool { + pub(super) fn handle_op(&mut self, cursor: usize, include: bool) -> bool { let old = self.snap().clone(); let snap = self.snaps.current_mut(); @@ -384,8 +179,8 @@ impl Input { } #[inline] - fn snap(&self) -> &InputSnap { self.snaps.current() } + pub(super) fn snap(&self) -> &InputSnap { self.snaps.current() } #[inline] - fn snap_mut(&mut self) -> &mut InputSnap { self.snaps.current_mut() } + pub(super) fn snap_mut(&mut self) -> &mut InputSnap { self.snaps.current_mut() } } diff --git a/yazi-core/src/manager/commands/close.rs b/yazi-core/src/manager/commands/close.rs index 7ba14adc..45eaa08c 100644 --- a/yazi-core/src/manager/commands/close.rs +++ b/yazi-core/src/manager/commands/close.rs @@ -1,10 +1,18 @@ +use yazi_config::keymap::Exec; + use crate::{manager::Manager, tasks::Tasks}; +pub struct Opt; + +impl From<&Exec> for Opt { + fn from(_: &Exec) -> Self { Self } +} + impl Manager { - pub fn close(&mut self, tasks: &Tasks) -> bool { + pub fn close(&mut self, _: impl Into, tasks: &Tasks) -> bool { if self.tabs.len() > 1 { return self.tabs.close(self.tabs.idx); } - self.quit(tasks, false) + self.quit((), tasks) } } diff --git a/yazi-core/src/manager/commands/create.rs b/yazi-core/src/manager/commands/create.rs index cb24e53b..b46099ea 100644 --- a/yazi-core/src/manager/commands/create.rs +++ b/yazi-core/src/manager/commands/create.rs @@ -1,12 +1,22 @@ use std::path::PathBuf; use tokio::fs::{self}; +use yazi_config::keymap::Exec; use yazi_shared::Url; use crate::{emit, files::{File, FilesOp}, input::InputOpt, manager::Manager}; +pub struct Opt { + force: bool, +} + +impl From<&Exec> for Opt { + fn from(e: &Exec) -> Self { Self { force: e.named.contains_key("force") } } +} + impl Manager { - pub fn create(&self, force: bool) -> bool { + pub fn create(&self, opt: impl Into) -> bool { + let opt = opt.into() as Opt; let cwd = self.cwd().to_owned(); tokio::spawn(async move { let mut result = emit!(Input(InputOpt::top("Create:"))); @@ -15,7 +25,7 @@ impl Manager { }; let path = cwd.join(&name); - if !force && fs::symlink_metadata(&path).await.is_ok() { + if !opt.force && fs::symlink_metadata(&path).await.is_ok() { match emit!(Input(InputOpt::top("Overwrite an existing file? (y/N)"))).recv().await { Some(Ok(c)) if c == "y" || c == "Y" => (), _ => return Ok(()), diff --git a/yazi-core/src/manager/commands/link.rs b/yazi-core/src/manager/commands/link.rs new file mode 100644 index 00000000..74be120e --- /dev/null +++ b/yazi-core/src/manager/commands/link.rs @@ -0,0 +1,22 @@ +use yazi_config::keymap::Exec; + +use crate::{manager::Manager, tasks::Tasks}; + +pub struct Opt { + relative: bool, + force: bool, +} + +impl From<&Exec> for Opt { + fn from(e: &Exec) -> Self { + Self { relative: e.named.contains_key("relative"), force: e.named.contains_key("force") } + } +} + +impl Manager { + pub fn link(&mut self, opt: impl Into, tasks: &Tasks) -> bool { + let opt = opt.into() as Opt; + let (cut, ref src) = self.yanked; + !cut && tasks.file_link(src, self.cwd(), opt.relative, opt.force) + } +} diff --git a/yazi-core/src/manager/commands/mod.rs b/yazi-core/src/manager/commands/mod.rs index fe726c10..f3db8752 100644 --- a/yazi-core/src/manager/commands/mod.rs +++ b/yazi-core/src/manager/commands/mod.rs @@ -1,9 +1,16 @@ mod close; mod create; +mod link; mod open; +mod paste; mod peek; mod quit; mod refresh; +mod remove; mod rename; mod suspend; +mod tab_close; +mod tab_create; +mod tab_swap; +mod tab_switch; mod yank; diff --git a/yazi-core/src/manager/commands/open.rs b/yazi-core/src/manager/commands/open.rs index eb860454..50fca89f 100644 --- a/yazi-core/src/manager/commands/open.rs +++ b/yazi-core/src/manager/commands/open.rs @@ -1,10 +1,36 @@ -use yazi_config::OPEN; +use std::ffi::OsString; + +use yazi_config::{keymap::Exec, OPEN}; use yazi_shared::MIME_DIR; use crate::{emit, external, manager::Manager, select::SelectOpt}; +pub struct Opt { + interactive: bool, +} + +impl From<&Exec> for Opt { + fn from(e: &Exec) -> Self { Self { interactive: e.named.contains_key("interactive") } } +} + impl Manager { - pub fn open(&mut self, interactive: bool) -> bool { + async fn open_interactive(files: Vec<(OsString, String)>) { + let openers = OPEN.common_openers(&files); + if openers.is_empty() { + return; + } + + let result = emit!(Select(SelectOpt::hovered( + "Open with:", + openers.iter().map(|o| o.desc.clone()).collect() + ))); + + if let Ok(choice) = result.await { + emit!(Open(files, Some(openers[choice].clone()))); + } + } + + pub fn open(&mut self, opt: impl Into) -> bool { let mut files: Vec<_> = self .selected() .into_iter() @@ -20,6 +46,7 @@ impl Manager { return false; } + let opt = opt.into() as Opt; tokio::spawn(async move { let todo: Vec<_> = files.iter().filter(|(_, m)| m.is_none()).map(|(u, _)| u).collect(); if let Ok(mut mimes) = external::file(&todo).await { @@ -35,23 +62,12 @@ impl Manager { let files: Vec<_> = files.into_iter().filter_map(|(u, m)| m.map(|m| (u.into_os_string(), m))).collect(); - if !interactive { - emit!(Open(files, None)); + if opt.interactive { + Self::open_interactive(files).await; return; } - let openers = OPEN.common_openers(&files); - if openers.is_empty() { - return; - } - - let result = emit!(Select(SelectOpt::hovered( - "Open with:", - openers.iter().map(|o| o.desc.clone()).collect() - ))); - if let Ok(choice) = result.await { - emit!(Open(files, Some(openers[choice].clone()))); - } + emit!(Open(files, None)); }); false } diff --git a/yazi-core/src/manager/commands/paste.rs b/yazi-core/src/manager/commands/paste.rs new file mode 100644 index 00000000..31877e3a --- /dev/null +++ b/yazi-core/src/manager/commands/paste.rs @@ -0,0 +1,21 @@ +use yazi_config::keymap::Exec; + +use crate::{manager::Manager, tasks::Tasks}; + +pub struct Opt { + force: bool, +} + +impl From<&Exec> for Opt { + fn from(e: &Exec) -> Self { Self { force: e.named.contains_key("force") } } +} + +impl Manager { + pub fn paste(&mut self, opt: impl Into, tasks: &Tasks) -> bool { + let dest = self.cwd(); + let (cut, ref src) = self.yanked; + + let opt = opt.into() as Opt; + if cut { tasks.file_cut(src, dest, opt.force) } else { tasks.file_copy(src, dest, opt.force) } + } +} diff --git a/yazi-core/src/manager/commands/quit.rs b/yazi-core/src/manager/commands/quit.rs index 5bc3dbdc..41304dbb 100644 --- a/yazi-core/src/manager/commands/quit.rs +++ b/yazi-core/src/manager/commands/quit.rs @@ -1,10 +1,25 @@ +use yazi_config::keymap::Exec; + use crate::{emit, input::InputOpt, manager::Manager, tasks::Tasks}; +#[derive(Default)] +pub struct Opt { + no_cwd_file: bool, +} +impl From<()> for Opt { + fn from(_: ()) -> Self { Self::default() } +} +impl From<&Exec> for Opt { + fn from(e: &Exec) -> Self { Self { no_cwd_file: e.named.contains_key("no-cwd-file") } } +} + impl Manager { - pub fn quit(&self, tasks: &Tasks, no_cwd_file: bool) -> bool { + pub fn quit(&self, opt: impl Into, tasks: &Tasks) -> bool { + let opt = opt.into() as Opt; + let tasks = tasks.len(); if tasks == 0 { - emit!(Quit(no_cwd_file)); + emit!(Quit(opt.no_cwd_file)); return false; } @@ -15,7 +30,7 @@ impl Manager { if let Some(Ok(choice)) = result.recv().await { if choice == "y" || choice == "Y" { - emit!(Quit(no_cwd_file)); + emit!(Quit(opt.no_cwd_file)); } } }); diff --git a/yazi-core/src/manager/commands/remove.rs b/yazi-core/src/manager/commands/remove.rs new file mode 100644 index 00000000..7f9c4e6b --- /dev/null +++ b/yazi-core/src/manager/commands/remove.rs @@ -0,0 +1,25 @@ +use yazi_config::keymap::Exec; + +use crate::{manager::Manager, tasks::Tasks}; + +pub struct Opt { + force: bool, + permanently: bool, +} + +impl From<&Exec> for Opt { + fn from(e: &Exec) -> Self { + Self { + force: e.named.contains_key("force"), + permanently: e.named.contains_key("permanently"), + } + } +} + +impl Manager { + pub fn remove(&mut self, opt: impl Into, tasks: &Tasks) -> bool { + let opt = opt.into() as Opt; + let targets = self.selected().into_iter().map(|f| f.url()).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 6071dd28..8c31da33 100644 --- a/yazi-core/src/manager/commands/rename.rs +++ b/yazi-core/src/manager/commands/rename.rs @@ -2,13 +2,36 @@ use std::{collections::BTreeSet, ffi::OsStr, io::{stdout, BufWriter, Write}, pat use anyhow::{anyhow, bail, Result}; use tokio::{fs::{self, OpenOptions}, io::{stdin, AsyncReadExt, AsyncWriteExt}}; -use yazi_config::{OPEN, PREVIEW}; +use yazi_config::{keymap::Exec, OPEN, PREVIEW}; use yazi_shared::{max_common_root, Defer, Term, Url}; use crate::{emit, external::{self, ShellOpt}, files::{File, FilesOp}, input::InputOpt, manager::Manager, Event, BLOCKER}; +pub struct Opt { + force: bool, +} + +impl From<&Exec> for Opt { + fn from(e: &Exec) -> Self { Self { force: e.named.contains_key("force") } } +} + impl Manager { - pub fn rename(&self, force: bool) -> bool { + async fn rename_and_hover(old: Url, new: Url) -> Result<()> { + fs::rename(&old, &new).await?; + if old.parent() != new.parent() { + return Ok(()); + } + + let parent = old.parent_url().unwrap(); + emit!(Files(FilesOp::Deleting(parent, BTreeSet::from([old])))); + + let file = File::from(new.clone()).await?; + emit!(Files(FilesOp::Creating(file.parent().unwrap(), file.into_map()))); + emit!(Hover(new)); + Ok(()) + } + + pub fn rename(&self, opt: impl Into) -> bool { if self.active().in_selecting() { return self.bulk_rename(); } @@ -17,21 +40,7 @@ impl Manager { return false; }; - async fn rename_and_hover(old: Url, new: Url) -> Result<()> { - fs::rename(&old, &new).await?; - if old.parent() != new.parent() { - return Ok(()); - } - - let parent = old.parent_url().unwrap(); - emit!(Files(FilesOp::Deleting(parent, BTreeSet::from([old])))); - - let file = File::from(new.clone()).await?; - emit!(Files(FilesOp::Creating(file.parent().unwrap(), file.into_map()))); - emit!(Hover(new)); - Ok(()) - } - + let opt = opt.into() as Opt; tokio::spawn(async move { let mut result = emit!(Input( InputOpt::hovered("Rename:").with_value(hovered.file_name().unwrap().to_string_lossy()) @@ -42,15 +51,15 @@ impl Manager { }; let new = hovered.parent().unwrap().join(name); - if force || fs::symlink_metadata(&new).await.is_err() { - rename_and_hover(hovered, Url::from(new)).await.ok(); + if opt.force || fs::symlink_metadata(&new).await.is_err() { + Self::rename_and_hover(hovered, Url::from(new)).await.ok(); return; } let mut result = emit!(Input(InputOpt::hovered("Overwrite an existing file? (y/N)"))); if let Some(Ok(choice)) = result.recv().await { if choice == "y" || choice == "Y" { - rename_and_hover(hovered, Url::from(new)).await.ok(); + Self::rename_and_hover(hovered, Url::from(new)).await.ok(); } }; }); diff --git a/yazi-core/src/manager/commands/suspend.rs b/yazi-core/src/manager/commands/suspend.rs index caee38ea..f88bed06 100644 --- a/yazi-core/src/manager/commands/suspend.rs +++ b/yazi-core/src/manager/commands/suspend.rs @@ -1,7 +1,14 @@ +use yazi_config::keymap::Exec; + use crate::manager::Manager; +pub struct Opt; +impl From<&Exec> for Opt { + fn from(_: &Exec) -> Self { Self } +} + impl Manager { - pub fn suspend(&mut self) -> bool { + pub fn suspend(&mut self, _: impl Into) -> bool { #[cfg(unix)] tokio::spawn(async move { crate::emit!(Stop(true)).await; diff --git a/yazi-core/src/manager/commands/tab_close.rs b/yazi-core/src/manager/commands/tab_close.rs new file mode 100644 index 00000000..addd3e7c --- /dev/null +++ b/yazi-core/src/manager/commands/tab_close.rs @@ -0,0 +1,35 @@ +use yazi_config::keymap::Exec; + +use crate::manager::Tabs; + +pub struct Opt { + idx: usize, +} + +impl From<&Exec> for Opt { + fn from(e: &Exec) -> Self { + Self { idx: e.args.first().and_then(|i| i.parse().ok()).unwrap_or(0) } + } +} + +impl From for Opt { + fn from(idx: usize) -> Self { Self { idx } } +} + +impl Tabs { + pub fn close(&mut self, opt: impl Into) -> bool { + let opt = opt.into() as Opt; + + let len = self.items.len(); + if len < 2 || opt.idx >= len { + return false; + } + + self.items.remove(opt.idx); + if opt.idx <= self.idx { + self.set_idx(self.absolute(1)); + } + + true + } +} diff --git a/yazi-core/src/manager/commands/tab_create.rs b/yazi-core/src/manager/commands/tab_create.rs new file mode 100644 index 00000000..e125a6c5 --- /dev/null +++ b/yazi-core/src/manager/commands/tab_create.rs @@ -0,0 +1,41 @@ +use yazi_config::keymap::Exec; +use yazi_shared::Url; + +use crate::{manager::Tabs, tab::Tab}; + +const MAX_TABS: usize = 9; + +pub struct Opt { + url: Option, + current: bool, +} + +impl From<&Exec> for Opt { + fn from(e: &Exec) -> Self { + let mut opt = Self { url: None, current: e.named.contains_key("current") }; + + if !opt.current { + opt.url = Some(e.args.first().map_or_else(|| Url::from("."), Url::from)); + } + opt + } +} + +impl Tabs { + pub fn create(&mut self, opt: impl Into) -> bool { + if self.items.len() >= MAX_TABS { + return false; + } + + let opt = opt.into() as Opt; + let url = if opt.current { self.active().current.cwd.to_owned() } else { opt.url.unwrap() }; + + let mut tab = Tab::from(url); + tab.conf = self.active().conf.clone(); + tab.apply_files_attrs(false); + + self.items.insert(self.idx + 1, tab); + self.set_idx(self.idx + 1); + true + } +} diff --git a/yazi-core/src/manager/commands/tab_swap.rs b/yazi-core/src/manager/commands/tab_swap.rs new file mode 100644 index 00000000..00a52207 --- /dev/null +++ b/yazi-core/src/manager/commands/tab_swap.rs @@ -0,0 +1,26 @@ +use yazi_config::keymap::Exec; + +use crate::manager::Tabs; + +pub struct Opt { + step: isize, +} + +impl From<&Exec> for Opt { + fn from(e: &Exec) -> Self { + Self { step: e.args.first().and_then(|s| s.parse().ok()).unwrap_or(0) } + } +} + +impl Tabs { + pub fn swap(&mut self, opt: impl Into) -> bool { + let idx = self.absolute(opt.into().step); + if idx == self.idx { + return false; + } + + self.items.swap(self.idx, idx); + self.set_idx(idx); + true + } +} diff --git a/yazi-core/src/manager/commands/tab_switch.rs b/yazi-core/src/manager/commands/tab_switch.rs new file mode 100644 index 00000000..27c7bafa --- /dev/null +++ b/yazi-core/src/manager/commands/tab_switch.rs @@ -0,0 +1,35 @@ +use yazi_config::keymap::Exec; + +use crate::manager::Tabs; + +pub struct Opt { + step: isize, + relative: bool, +} + +impl From<&Exec> for Opt { + fn from(e: &Exec) -> Self { + Self { + step: e.args.first().and_then(|s| s.parse().ok()).unwrap_or(0), + relative: e.named.contains_key("relative"), + } + } +} + +impl Tabs { + pub fn switch(&mut self, opt: impl Into) -> bool { + let opt = opt.into() as Opt; + let idx = if opt.relative { + (self.idx as isize + opt.step).rem_euclid(self.items.len() as isize) as usize + } else { + opt.step as usize + }; + + if idx == self.idx || idx >= self.items.len() { + return false; + } + + self.set_idx(idx); + true + } +} diff --git a/yazi-core/src/manager/commands/yank.rs b/yazi-core/src/manager/commands/yank.rs index ad92f0d4..bd43646a 100644 --- a/yazi-core/src/manager/commands/yank.rs +++ b/yazi-core/src/manager/commands/yank.rs @@ -1,8 +1,20 @@ +use yazi_config::keymap::Exec; + use crate::manager::Manager; +pub struct Opt { + cut: bool, +} + +impl From<&Exec> for Opt { + fn from(e: &Exec) -> Self { Self { cut: e.named.contains_key("cut") } } +} + impl Manager { - pub fn yank(&mut self, cut: bool) -> bool { - self.yanked.0 = cut; + pub fn yank(&mut self, opt: impl Into) -> bool { + let opt = opt.into() as Opt; + + self.yanked.0 = opt.cut; self.yanked.1 = self.selected().into_iter().map(|f| f.url()).collect(); true } diff --git a/yazi-core/src/manager/manager.rs b/yazi-core/src/manager/manager.rs index eafb7b3f..b67b911c 100644 --- a/yazi-core/src/manager/manager.rs +++ b/yazi-core/src/manager/manager.rs @@ -57,7 +57,7 @@ impl Manager { if url == self.cwd() { self.current_mut().update(op); - self.active_mut().leave(); + self.active_mut().leave(()); true } else if matches!(self.parent(), Some(p) if &p.cwd == url) { self.active_mut().parent.as_mut().unwrap().update(op) diff --git a/yazi-core/src/manager/tabs.rs b/yazi-core/src/manager/tabs.rs index edaf178d..5bd2ab6d 100644 --- a/yazi-core/src/manager/tabs.rs +++ b/yazi-core/src/manager/tabs.rs @@ -3,11 +3,9 @@ use yazi_shared::Url; use crate::{emit, tab::Tab}; -const MAX_TABS: usize = 9; - pub struct Tabs { - pub idx: usize, - items: Vec, + pub idx: usize, + pub(super) items: Vec, } impl Tabs { @@ -17,62 +15,8 @@ impl Tabs { tabs } - pub fn create(&mut self, url: &Url) -> bool { - if self.items.len() >= MAX_TABS { - return false; - } - - let mut tab = Tab::from(url); - tab.conf = self.active().conf.clone(); - tab.apply_files_attrs(false); - - self.items.insert(self.idx + 1, tab); - self.set_idx(self.idx + 1); - true - } - - pub fn switch(&mut self, idx: isize, rel: bool) -> bool { - let idx = if rel { - (self.idx as isize + idx).rem_euclid(self.items.len() as isize) as usize - } else { - idx as usize - }; - - if idx == self.idx || idx >= self.items.len() { - return false; - } - - self.set_idx(idx); - true - } - - pub fn swap(&mut self, rel: isize) -> bool { - let idx = self.absolute(rel); - if idx == self.idx { - return false; - } - - self.items.swap(self.idx, idx); - self.set_idx(idx); - true - } - - pub fn close(&mut self, idx: usize) -> bool { - let len = self.items.len(); - if len < 2 || idx >= len { - return false; - } - - self.items.remove(idx); - if idx <= self.idx { - self.set_idx(self.absolute(1)); - } - - true - } - #[inline] - fn absolute(&self, rel: isize) -> usize { + pub(super) fn absolute(&self, rel: isize) -> usize { if rel > 0 { (self.idx + rel as usize).min(self.items.len() - 1) } else { @@ -81,7 +25,7 @@ impl Tabs { } #[inline] - fn set_idx(&mut self, idx: usize) { + pub(super) fn set_idx(&mut self, idx: usize) { self.idx = idx; self.active_mut().preview.reset(|l| l.is_image()); emit!(Refresh); diff --git a/yazi-core/src/select/commands/arrow.rs b/yazi-core/src/select/commands/arrow.rs new file mode 100644 index 00000000..e06a3ddc --- /dev/null +++ b/yazi-core/src/select/commands/arrow.rs @@ -0,0 +1,48 @@ +use yazi_config::keymap::Exec; + +use crate::select::Select; + +pub struct Opt { + step: isize, +} + +impl From<&Exec> for Opt { + fn from(e: &Exec) -> Self { + Self { step: e.args.first().and_then(|s| s.parse().ok()).unwrap_or(0) } + } +} + +impl Select { + fn next(&mut self, step: usize) -> bool { + let len = self.items.len(); + if len == 0 { + return false; + } + + let old = self.cursor; + self.cursor = (self.cursor + step).min(len - 1); + + let limit = self.limit(); + if self.cursor >= len.min(self.offset + limit) { + self.offset = len.saturating_sub(limit).min(self.offset + self.cursor - old); + } + + old != self.cursor + } + + fn prev(&mut self, step: usize) -> bool { + let old = self.cursor; + self.cursor = self.cursor.saturating_sub(step); + + if self.cursor < self.offset { + self.offset = self.offset.saturating_sub(old - self.cursor); + } + + old != self.cursor + } + + pub fn arrow(&mut self, opt: impl Into) -> bool { + let opt = opt.into() as Opt; + if opt.step > 0 { self.next(opt.step as usize) } else { self.prev(opt.step.unsigned_abs()) } + } +} diff --git a/yazi-core/src/select/commands/close.rs b/yazi-core/src/select/commands/close.rs new file mode 100644 index 00000000..edc575df --- /dev/null +++ b/yazi-core/src/select/commands/close.rs @@ -0,0 +1,29 @@ +use anyhow::anyhow; +use yazi_config::keymap::Exec; + +use crate::select::Select; + +pub struct Opt { + submit: bool, +} + +impl From<&Exec> for Opt { + fn from(e: &Exec) -> Self { Self { submit: e.named.contains_key("submit") } } +} +impl From for Opt { + fn from(submit: bool) -> Self { Self { submit } } +} + +impl Select { + pub fn close(&mut self, opt: impl Into) -> bool { + let opt = opt.into() as Opt; + if let Some(cb) = self.callback.take() { + _ = cb.send(if opt.submit { Ok(self.cursor) } else { Err(anyhow!("canceled")) }); + } + + self.cursor = 0; + self.offset = 0; + self.visible = false; + true + } +} diff --git a/yazi-core/src/select/commands/mod.rs b/yazi-core/src/select/commands/mod.rs new file mode 100644 index 00000000..9225a7a8 --- /dev/null +++ b/yazi-core/src/select/commands/mod.rs @@ -0,0 +1,2 @@ +mod arrow; +mod close; diff --git a/yazi-core/src/select/mod.rs b/yazi-core/src/select/mod.rs index fa446e89..3b4cf878 100644 --- a/yazi-core/src/select/mod.rs +++ b/yazi-core/src/select/mod.rs @@ -1,3 +1,4 @@ +mod commands; mod option; mod select; diff --git a/yazi-core/src/select/select.rs b/yazi-core/src/select/select.rs index ed7762c7..38fb14a6 100644 --- a/yazi-core/src/select/select.rs +++ b/yazi-core/src/select/select.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, Result}; +use anyhow::Result; use tokio::sync::oneshot::Sender; use super::SelectOpt; @@ -6,13 +6,13 @@ use crate::Position; #[derive(Default)] pub struct Select { - title: String, - items: Vec, - pub position: Position, + title: String, + pub(super) items: Vec, + pub position: Position, - offset: usize, - cursor: usize, - callback: Option>>, + pub(super) offset: usize, + pub(super) cursor: usize, + pub(super) callback: Option>>, pub visible: bool, } @@ -29,45 +29,6 @@ impl Select { self.visible = true; } - pub fn close(&mut self, submit: bool) -> bool { - if let Some(cb) = self.callback.take() { - _ = cb.send(if submit { Ok(self.cursor) } else { Err(anyhow!("canceled")) }); - } - - self.cursor = 0; - self.offset = 0; - self.visible = false; - true - } - - pub fn next(&mut self, step: usize) -> bool { - let len = self.items.len(); - if len == 0 { - return false; - } - - let old = self.cursor; - self.cursor = (self.cursor + step).min(len - 1); - - let limit = self.limit(); - if self.cursor >= len.min(self.offset + limit) { - self.offset = len.saturating_sub(limit).min(self.offset + self.cursor - old); - } - - old != self.cursor - } - - pub fn prev(&mut self, step: usize) -> bool { - let old = self.cursor; - self.cursor = self.cursor.saturating_sub(step); - - if self.cursor < self.offset { - self.offset = self.offset.saturating_sub(old - self.cursor); - } - - old != self.cursor - } - #[inline] pub fn window(&self) -> &[String] { let end = (self.offset + self.limit()).min(self.items.len()); diff --git a/yazi-core/src/tab/commands/arrow.rs b/yazi-core/src/tab/commands/arrow.rs index 0e3d59d0..dd6a8282 100644 --- a/yazi-core/src/tab/commands/arrow.rs +++ b/yazi-core/src/tab/commands/arrow.rs @@ -1,8 +1,32 @@ +use yazi_config::keymap::Exec; + use crate::{emit, tab::Tab, Step}; +pub struct Opt { + step: Step, +} + +impl From<&Exec> for Opt { + fn from(e: &Exec) -> Self { + Self { step: e.args.first().and_then(|s| s.parse().ok()).unwrap_or_default() } + } +} + +impl From for Opt +where + T: Into, +{ + fn from(t: T) -> Self { Self { step: t.into() } } +} + impl Tab { - pub fn arrow(&mut self, step: Step) -> bool { - let ok = if step.is_positive() { self.current.next(step) } else { self.current.prev(step) }; + pub fn arrow(&mut self, opt: impl Into) -> bool { + let opt = opt.into() as Opt; + let ok = if opt.step.is_positive() { + self.current.next(opt.step) + } else { + self.current.prev(opt.step) + }; if !ok { return false; } diff --git a/yazi-core/src/tab/commands/backstack.rs b/yazi-core/src/tab/commands/backstack.rs index 5d11b7ab..c565ca51 100644 --- a/yazi-core/src/tab/commands/backstack.rs +++ b/yazi-core/src/tab/commands/backstack.rs @@ -1,14 +1,24 @@ +use yazi_config::keymap::Exec; + use crate::tab::Tab; +pub struct Opt; +impl From<()> for Opt { + fn from(_: ()) -> Self { Self } +} +impl From<&Exec> for Opt { + fn from(_: &Exec) -> Self { Self } +} + impl Tab { - pub fn back(&mut self) -> bool { + pub fn back(&mut self, _: impl Into) -> bool { if let Some(url) = self.backstack.shift_backward().cloned() { self.cd(url); } false } - pub fn forward(&mut self) -> bool { + pub fn forward(&mut self, _: impl Into) -> bool { 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 4ff3240f..c3b4a322 100644 --- a/yazi-core/src/tab/commands/cd.rs +++ b/yazi-core/src/tab/commands/cd.rs @@ -7,9 +7,31 @@ use yazi_shared::{expand_path, Debounce, InputError, Url}; use crate::{emit, input::InputOpt, tab::Tab}; +pub struct Opt { + target: Url, + interactive: bool, +} + +impl From<&Exec> for Opt { + fn from(e: &Exec) -> Self { + Self { + target: e.args.first().map(Url::from).unwrap_or_default(), + interactive: e.named.contains_key("interactive"), + } + } +} +impl From for Opt { + fn from(target: Url) -> Self { Self { target, interactive: false } } +} + impl Tab { - pub fn cd(&mut self, target: Url) -> bool { - if self.current.cwd == target { + pub fn cd(&mut self, opt: impl Into) -> bool { + let opt = opt.into() as Opt; + if opt.interactive { + return self.cd_interactive(opt); + } + + if self.current.cwd == opt.target { return false; } @@ -19,30 +41,34 @@ impl Tab { } // Current - let rep = self.history_new(&target); + let rep = self.history_new(&opt.target); let rep = mem::replace(&mut self.current, rep); if rep.cwd.is_regular() { self.history.insert(rep.cwd.clone(), rep); } // Parent - if let Some(parent) = target.parent_url() { + if let Some(parent) = opt.target.parent_url() { self.parent = Some(self.history_new(&parent)); } // Backstack - if target.is_regular() { - self.backstack.push(target.clone()); + if opt.target.is_regular() { + self.backstack.push(opt.target.clone()); } emit!(Refresh); true } - pub fn cd_interactive(&mut self, target: Url) -> bool { + fn cd_interactive(&mut self, opt: impl Into) -> bool { + let opt = opt.into() as Opt; + tokio::spawn(async move { let rx = emit!(Input( - InputOpt::top("Change directory:").with_value(target.to_string_lossy()).with_completion() + InputOpt::top("Change directory:") + .with_value(opt.target.to_string_lossy()) + .with_completion() )); let rx = Debounce::new(UnboundedReceiverStream::new(rx), Duration::from_millis(50)); diff --git a/yazi-core/src/tab/commands/copy.rs b/yazi-core/src/tab/commands/copy.rs index 45f98cb0..bc864585 100644 --- a/yazi-core/src/tab/commands/copy.rs +++ b/yazi-core/src/tab/commands/copy.rs @@ -1,13 +1,25 @@ use std::ffi::{OsStr, OsString}; +use yazi_config::keymap::Exec; + use crate::{external, tab::Tab}; +pub struct Opt<'a> { + type_: &'a str, +} + +impl<'a> From<&'a Exec> for Opt<'a> { + fn from(e: &'a Exec) -> Self { Self { type_: e.args.first().map(|s| s.as_str()).unwrap_or("") } } +} + impl Tab { - pub fn copy(&self, type_: &str) -> bool { + pub fn copy<'a>(&self, opt: impl Into>) -> bool { + let opt = opt.into() as Opt; + let mut s = OsString::new(); let mut it = self.selected().into_iter().peekable(); while let Some(f) = it.next() { - s.push(match type_ { + s.push(match opt.type_ { "path" => f.url.as_os_str(), "dirname" => f.url.parent().map_or(OsStr::new(""), |p| p.as_os_str()), "filename" => f.name().unwrap_or(OsStr::new("")), diff --git a/yazi-core/src/tab/commands/enter.rs b/yazi-core/src/tab/commands/enter.rs index b3b248f9..d58367cb 100644 --- a/yazi-core/src/tab/commands/enter.rs +++ b/yazi-core/src/tab/commands/enter.rs @@ -1,9 +1,19 @@ use std::mem; +use yazi_config::keymap::Exec; + use crate::{emit, tab::Tab}; +pub struct Opt; +impl From<()> for Opt { + fn from(_: ()) -> Self { Self } +} +impl From<&Exec> for Opt { + fn from(_: &Exec) -> Self { Self } +} + impl Tab { - pub fn enter(&mut self) -> bool { + pub fn enter(&mut self, _: impl Into) -> bool { let Some(hovered) = self.current.hovered().filter(|h| h.is_dir()).map(|h| h.url()) else { return false; }; diff --git a/yazi-core/src/tab/commands/find.rs b/yazi-core/src/tab/commands/find.rs index 4e2937ab..6aa54e1f 100644 --- a/yazi-core/src/tab/commands/find.rs +++ b/yazi-core/src/tab/commands/find.rs @@ -7,30 +7,40 @@ use yazi_shared::{Debounce, InputError}; use crate::{emit, input::InputOpt, tab::{Finder, FinderCase, Tab}}; -impl Tab { - pub fn find(&mut self, query: Option<&str>, prev: bool, case: FinderCase) -> bool { - if let Some(query) = query { - let Ok(finder) = Finder::new(query, case) else { - return false; - }; +pub struct Opt<'a> { + query: Option<&'a str>, + prev: bool, + case: FinderCase, +} - let step = if prev { - finder.prev(&self.current.files, self.current.cursor, true) - } else { - finder.next(&self.current.files, self.current.cursor, true) - }; - - if let Some(step) = step { - self.arrow(step.into()); - } - - self.finder = Some(finder); - return true; +impl<'a> From<&'a Exec> for Opt<'a> { + fn from(e: &'a Exec) -> Self { + Self { + query: e.args.first().map(|s| s.as_str()), + prev: e.named.contains_key("previous"), + case: match (e.named.contains_key("smart"), e.named.contains_key("insensitive")) { + (true, _) => FinderCase::Smart, + (_, false) => FinderCase::Sensitive, + (_, true) => FinderCase::Insensitive, + }, } + } +} +pub struct ArrowOpt { + prev: bool, +} + +impl From<&Exec> for ArrowOpt { + fn from(e: &Exec) -> Self { Self { prev: e.named.contains_key("previous") } } +} + +impl Tab { + pub fn find<'a>(&mut self, opt: impl Into>) -> bool { + let opt = opt.into() as Opt; tokio::spawn(async move { let rx = emit!(Input( - InputOpt::top(if prev { "Find previous:" } else { "Find next:" }).with_realtime() + InputOpt::top(if opt.prev { "Find previous:" } else { "Find next:" }).with_realtime() )); let rx = Debounce::new(UnboundedReceiverStream::new(rx), Duration::from_millis(50)); @@ -38,10 +48,10 @@ impl Tab { while let Some(Ok(s)) | Some(Err(InputError::Typed(s))) = rx.next().await { emit!(Call( - Exec::call("find", vec![s]) - .with_bool("previous", prev) - .with_bool("smart", case == FinderCase::Smart) - .with_bool("insensitive", case == FinderCase::Insensitive) + Exec::call("find_do", vec![s]) + .with_bool("previous", opt.prev) + .with_bool("smart", opt.case == FinderCase::Smart) + .with_bool("insensitive", opt.case == FinderCase::Insensitive) .vec(), KeymapLayer::Manager )); @@ -50,18 +60,42 @@ impl Tab { false } - pub fn find_arrow(&mut self, prev: bool) -> bool { + pub fn find_do<'a>(&mut self, opt: impl Into>) -> bool { + let opt = opt.into() as Opt; + let Some(query) = opt.query else { + return false; + }; + + let Ok(finder) = Finder::new(query, opt.case) else { + return false; + }; + + let step = if opt.prev { + finder.prev(&self.current.files, self.current.cursor, true) + } else { + finder.next(&self.current.files, self.current.cursor, true) + }; + + if let Some(step) = step { + self.arrow(step); + } + + self.finder = Some(finder); + true + } + + pub fn find_arrow(&mut self, opt: impl Into) -> bool { let Some(finder) = &mut self.finder else { return false; }; let b = finder.catchup(&self.current.files); - let step = if prev { + let step = if opt.into().prev { finder.prev(&self.current.files, self.current.cursor, false) } else { finder.next(&self.current.files, self.current.cursor, false) }; - b | step.is_some_and(|s| self.arrow(s.into())) + b | step.is_some_and(|s| self.arrow(s)) } } diff --git a/yazi-core/src/tab/commands/jump.rs b/yazi-core/src/tab/commands/jump.rs index 6a0aea4c..a3ab16fb 100644 --- a/yazi-core/src/tab/commands/jump.rs +++ b/yazi-core/src/tab/commands/jump.rs @@ -3,22 +3,49 @@ use yazi_shared::{ends_with_slash, Defer}; use crate::{emit, external::{self, FzfOpt, ZoxideOpt}, tab::Tab, Event, BLOCKER}; -impl Tab { - pub fn jump(&self, global: bool) -> bool { - let cwd = self.current.cwd.clone(); +pub struct Opt { + type_: OptType, +} +#[derive(PartialEq, Eq)] +pub enum OptType { + None, + Fzf, + Zoxide, +} + +impl From<&Exec> for Opt { + fn from(e: &Exec) -> Self { + Self { + type_: match e.args.first().map(|s| s.as_str()) { + Some("fzf") => OptType::Fzf, + Some("zoxide") => OptType::Zoxide, + _ => OptType::None, + }, + } + } +} + +impl Tab { + pub fn jump(&self, opt: impl Into) -> bool { + let opt = opt.into() as Opt; + if opt.type_ == OptType::None { + return false; + } + + let cwd = self.current.cwd.clone(); tokio::spawn(async move { let _guard = BLOCKER.acquire().await.unwrap(); let _defer = Defer::new(|| Event::Stop(false, None).emit()); emit!(Stop(true)).await; - let url = if global { + let url = if opt.type_ == OptType::Fzf { external::fzf(FzfOpt { cwd }).await } else { external::zoxide(ZoxideOpt { cwd }).await }?; - let op = if global && !ends_with_slash(&url) { "reveal" } else { "cd" }; + let op = if opt.type_ == OptType::Fzf && !ends_with_slash(&url) { "reveal" } else { "cd" }; emit!(Call(Exec::call(op, vec![url.to_string()]).vec(), KeymapLayer::Manager)); Ok::<(), anyhow::Error>(()) }); diff --git a/yazi-core/src/tab/commands/leave.rs b/yazi-core/src/tab/commands/leave.rs index 2a067246..842422f1 100644 --- a/yazi-core/src/tab/commands/leave.rs +++ b/yazi-core/src/tab/commands/leave.rs @@ -1,9 +1,19 @@ use std::mem; +use yazi_config::keymap::Exec; + use crate::{emit, tab::Tab}; +pub struct Opt; +impl From<()> for Opt { + fn from(_: ()) -> Self { Self } +} +impl From<&Exec> for Opt { + fn from(_: &Exec) -> Self { Self } +} + impl Tab { - pub fn leave(&mut self) -> bool { + pub fn leave(&mut self, _: impl Into) -> bool { let current = self .current .hovered() diff --git a/yazi-core/src/tab/commands/search.rs b/yazi-core/src/tab/commands/search.rs index 80191ce2..93acade8 100644 --- a/yazi-core/src/tab/commands/search.rs +++ b/yazi-core/src/tab/commands/search.rs @@ -7,8 +7,36 @@ use yazi_config::keymap::{Exec, KeymapLayer}; use crate::{emit, external, files::FilesOp, input::InputOpt, tab::Tab}; +pub struct Opt { + pub type_: OptType, +} + +#[derive(PartialEq, Eq)] +pub enum OptType { + None, + Rg, + Fd, +} + +impl From<&Exec> for Opt { + fn from(e: &Exec) -> Self { + Self { + type_: match e.args.first().map(|s| s.as_str()) { + Some("fd") => OptType::Fd, + Some("rg") => OptType::Rg, + _ => OptType::None, + }, + } + } +} + impl Tab { - pub fn search(&mut self, grep: bool) -> bool { + pub fn search(&mut self, opt: impl Into) -> bool { + let opt = opt.into() as Opt; + if opt.type_ == OptType::None { + return self.search_stop(); + } + if let Some(handle) = self.search.take() { handle.abort(); } @@ -22,7 +50,7 @@ impl Tab { }; cwd = cwd.into_search(subject.clone()); - let rx = if grep { + let rx = if opt.type_ == OptType::Rg { external::rg(external::RgOpt { cwd: cwd.clone(), hidden, subject }) } else { external::fd(external::FdOpt { cwd: cwd.clone(), hidden, glob: false, subject }) @@ -45,7 +73,7 @@ impl Tab { true } - pub fn search_stop(&mut self) -> bool { + pub(super) fn search_stop(&mut self) -> bool { if let Some(handle) = self.search.take() { handle.abort(); } diff --git a/yazi-core/src/tab/commands/select.rs b/yazi-core/src/tab/commands/select.rs index 3a6e5b53..5d7cac87 100644 --- a/yazi-core/src/tab/commands/select.rs +++ b/yazi-core/src/tab/commands/select.rs @@ -1,12 +1,35 @@ +use yazi_config::keymap::Exec; + use crate::tab::Tab; +pub struct Opt { + state: Option, +} + +impl From<&Exec> for Opt { + fn from(e: &Exec) -> Self { + Self { + state: match e.named.get("state").map(|s| s.as_bytes()) { + Some(b"true") => Some(true), + Some(b"false") => Some(false), + _ => None, + }, + } + } +} +impl From> for Opt { + fn from(state: Option) -> Self { Self { state } } +} + impl Tab { - pub fn select(&mut self, state: Option) -> bool { + pub fn select(&mut self, opt: impl Into) -> bool { if let Some(u) = self.current.hovered().map(|h| h.url()) { - return self.current.files.select(&u, state); + return self.current.files.select(&u, opt.into().state); } false } - pub fn select_all(&mut self, state: Option) -> bool { self.current.files.select_all(state) } + pub fn select_all(&mut self, opt: impl Into) -> bool { + self.current.files.select_all(opt.into().state) + } } diff --git a/yazi-core/src/tab/commands/shell.rs b/yazi-core/src/tab/commands/shell.rs index 98a39c67..2fd1ba37 100644 --- a/yazi-core/src/tab/commands/shell.rs +++ b/yazi-core/src/tab/commands/shell.rs @@ -1,25 +1,41 @@ -use yazi_config::open::Opener; +use yazi_config::{keymap::Exec, open::Opener}; use crate::{emit, input::InputOpt, tab::Tab}; +pub struct Opt { + cmd: String, + block: bool, + confirm: bool, +} + +impl<'a> From<&'a Exec> for Opt { + fn from(e: &'a Exec) -> Self { + Self { + cmd: e.args.first().map(|e| e.to_owned()).unwrap_or_default(), + block: e.named.contains_key("block"), + confirm: e.named.contains_key("confirm"), + } + } +} + impl Tab { - pub fn shell(&self, exec: &str, block: bool, confirm: bool) -> bool { + pub fn shell(&self, opt: impl Into) -> bool { let selected: Vec<_> = self .selected() .into_iter() .map(|f| (f.url.as_os_str().to_owned(), Default::default())) .collect(); - let mut exec = exec.to_owned(); + let mut opt = opt.into() as Opt; tokio::spawn(async move { - if !confirm || exec.is_empty() { + if !opt.confirm || opt.cmd.is_empty() { let mut result = emit!(Input( - InputOpt::top(if block { "Shell (block):" } else { "Shell:" }) - .with_value(&exec) + InputOpt::top(if opt.block { "Shell (block):" } else { "Shell:" }) + .with_value(opt.cmd) .with_highlight() )); match result.recv().await { - Some(Ok(e)) => exec = e, + Some(Ok(e)) => opt.cmd = e, _ => return, } } @@ -27,12 +43,12 @@ impl Tab { emit!(Open( selected, Some(Opener { - exec, - block, + exec: opt.cmd, + block: opt.block, orphan: false, - desc: Default::default(), - for_: None, - spread: true + desc: Default::default(), + for_: None, + spread: true, }) )); }); diff --git a/yazi-core/src/tab/commands/visual_mode.rs b/yazi-core/src/tab/commands/visual_mode.rs index 9cc5d3cd..15ac386b 100644 --- a/yazi-core/src/tab/commands/visual_mode.rs +++ b/yazi-core/src/tab/commands/visual_mode.rs @@ -1,12 +1,23 @@ use std::collections::BTreeSet; +use yazi_config::keymap::Exec; + use crate::tab::{Mode, Tab}; +pub struct Opt { + unset: bool, +} + +impl From<&Exec> for Opt { + fn from(e: &Exec) -> Self { Self { unset: e.named.contains_key("unset") } } +} + impl Tab { - pub fn visual_mode(&mut self, unset: bool) -> bool { + pub fn visual_mode(&mut self, opt: impl Into) -> bool { + let opt = opt.into() as Opt; let idx = self.current.cursor; - if unset { + if opt.unset { self.mode = Mode::Unset(idx, BTreeSet::from([idx])); } else { self.mode = Mode::Select(idx, BTreeSet::from([idx])); diff --git a/yazi-core/src/tasks/commands/arrow.rs b/yazi-core/src/tasks/commands/arrow.rs new file mode 100644 index 00000000..22333208 --- /dev/null +++ b/yazi-core/src/tasks/commands/arrow.rs @@ -0,0 +1,36 @@ +use yazi_config::keymap::Exec; + +use crate::tasks::Tasks; + +pub struct Opt { + step: isize, +} + +impl From<&Exec> for Opt { + fn from(e: &Exec) -> Self { + Self { step: e.args.first().and_then(|s| s.parse().ok()).unwrap_or(0) } + } +} + +impl Tasks { + #[allow(clippy::should_implement_trait)] + fn next(&mut self) -> bool { + let limit = Self::limit().min(self.len()); + + let old = self.cursor; + self.cursor = limit.saturating_sub(1).min(self.cursor + 1); + + old != self.cursor + } + + fn prev(&mut self) -> bool { + let old = self.cursor; + self.cursor = self.cursor.saturating_sub(1); + old != self.cursor + } + + pub fn arrow(&mut self, opt: impl Into) -> bool { + let opt = opt.into() as Opt; + if opt.step > 0 { self.next() } else { self.prev() } + } +} diff --git a/yazi-core/src/tasks/commands/cancel.rs b/yazi-core/src/tasks/commands/cancel.rs new file mode 100644 index 00000000..ea028859 --- /dev/null +++ b/yazi-core/src/tasks/commands/cancel.rs @@ -0,0 +1,22 @@ +use yazi_config::keymap::Exec; + +use crate::tasks::Tasks; + +pub struct Opt; + +impl From<&Exec> for Opt { + fn from(_: &Exec) -> Self { Self } +} + +impl Tasks { + pub fn cancel(&mut self, _: impl Into) -> bool { + let id = self.scheduler.running.read().get_id(self.cursor); + if id.map(|id| self.scheduler.cancel(id)) != Some(true) { + return false; + } + + let len = self.scheduler.running.read().len(); + self.cursor = self.cursor.min(len.saturating_sub(1)); + true + } +} diff --git a/yazi-core/src/tasks/commands/inspect.rs b/yazi-core/src/tasks/commands/inspect.rs new file mode 100644 index 00000000..6649a6e8 --- /dev/null +++ b/yazi-core/src/tasks/commands/inspect.rs @@ -0,0 +1,77 @@ +use std::io::{stdout, Write}; + +use crossterm::terminal::{disable_raw_mode, enable_raw_mode}; +use tokio::{io::{stdin, AsyncReadExt}, select, sync::mpsc, time}; +use yazi_config::keymap::Exec; +use yazi_shared::{Defer, Term}; + +use crate::{emit, tasks::Tasks, Event, BLOCKER}; + +pub struct Opt; + +impl From<&Exec> for Opt { + fn from(_: &Exec) -> Self { Self } +} + +impl Tasks { + pub fn inspect(&self, _: impl Into) -> bool { + let Some(id) = self.scheduler.running.read().get_id(self.cursor) else { + return false; + }; + + let scheduler = self.scheduler.clone(); + tokio::spawn(async move { + let _guard = BLOCKER.acquire().await.unwrap(); + let (tx, mut rx) = mpsc::unbounded_channel(); + + let buffered = { + let mut running = scheduler.running.write(); + let Some(task) = running.get_mut(id) else { return }; + + task.logger = Some(tx); + task.logs.clone() + }; + + emit!(Stop(true)).await; + let _defer = Defer::new(|| { + disable_raw_mode().ok(); + Event::Stop(false, None).emit(); + }); + + Term::clear(&mut stdout()).ok(); + stdout().write_all(buffered.as_bytes()).ok(); + enable_raw_mode().ok(); + + let mut stdin = stdin(); + let mut quit = [0; 10]; + loop { + select! { + Some(line) = rx.recv() => { + let mut stdout = stdout().lock(); + stdout.write_all(line.as_bytes()).ok(); + stdout.write_all(b"\r\n").ok(); + } + _ = time::sleep(time::Duration::from_millis(100)) => { + if scheduler.running.read().get(id).is_none() { + stdout().write_all(b"Task finished, press `q` to quit\r\n").ok(); + break; + } + }, + Ok(_) = stdin.read(&mut quit) => { + if quit[0] == b'q' { + break; + } + } + } + } + + if let Some(task) = scheduler.running.write().get_mut(id) { + task.logger = None; + } + while quit[0] != b'q' { + stdin.read(&mut quit).await.ok(); + } + }); + false + } +} diff --git a/yazi-core/src/tasks/commands/mod.rs b/yazi-core/src/tasks/commands/mod.rs new file mode 100644 index 00000000..e6c3cafd --- /dev/null +++ b/yazi-core/src/tasks/commands/mod.rs @@ -0,0 +1,4 @@ +mod arrow; +mod cancel; +mod inspect; +mod toggle; diff --git a/yazi-core/src/tasks/commands/toggle.rs b/yazi-core/src/tasks/commands/toggle.rs new file mode 100644 index 00000000..1e335701 --- /dev/null +++ b/yazi-core/src/tasks/commands/toggle.rs @@ -0,0 +1,20 @@ +use yazi_config::keymap::Exec; + +use crate::{emit, tasks::Tasks}; + +pub struct Opt; + +impl From<&Exec> for Opt { + fn from(_: &Exec) -> Self { Self } +} +impl From<()> for Opt { + fn from(_: ()) -> Self { Self } +} + +impl Tasks { + pub fn toggle(&mut self, _: impl Into) -> bool { + self.visible = !self.visible; + emit!(Peek); // Show/hide preview for images + true + } +} diff --git a/yazi-core/src/tasks/mod.rs b/yazi-core/src/tasks/mod.rs index b79f612a..79983611 100644 --- a/yazi-core/src/tasks/mod.rs +++ b/yazi-core/src/tasks/mod.rs @@ -1,3 +1,4 @@ +mod commands; mod running; mod scheduler; mod task; diff --git a/yazi-core/src/tasks/tasks.rs b/yazi-core/src/tasks/tasks.rs index 00a6ee02..8cc1b52f 100644 --- a/yazi-core/src/tasks/tasks.rs +++ b/yazi-core/src/tasks/tasks.rs @@ -1,17 +1,15 @@ -use std::{collections::{BTreeMap, HashMap, HashSet}, ffi::OsStr, io::{stdout, Write}, path::Path, sync::Arc}; +use std::{collections::{BTreeMap, HashMap, HashSet}, ffi::OsStr, path::Path, sync::Arc}; -use crossterm::terminal::{disable_raw_mode, enable_raw_mode}; use serde::Serialize; -use tokio::{io::{stdin, AsyncReadExt}, select, sync::mpsc, time}; use tracing::debug; use yazi_config::{manager::SortBy, open::Opener, OPEN}; -use yazi_shared::{Defer, MimeKind, Term, Url}; +use yazi_shared::{MimeKind, Term, Url}; use super::{running::Running, task::TaskSummary, Scheduler, TASKS_PADDING, TASKS_PERCENT}; -use crate::{emit, files::{File, Files}, input::InputOpt, Event, BLOCKER}; +use crate::{emit, files::{File, Files}, input::InputOpt}; pub struct Tasks { - scheduler: Arc, + pub(super) scheduler: Arc, pub visible: bool, pub cursor: usize, @@ -33,105 +31,11 @@ impl Tasks { (Term::size().rows * TASKS_PERCENT / 100).saturating_sub(TASKS_PADDING) as usize } - pub fn toggle(&mut self) -> bool { - self.visible = !self.visible; - emit!(Peek); // Show/hide preview for images - true - } - - #[allow(clippy::should_implement_trait)] - pub fn next(&mut self) -> bool { - let limit = Self::limit().min(self.len()); - - let old = self.cursor; - self.cursor = limit.saturating_sub(1).min(self.cursor + 1); - - old != self.cursor - } - - pub fn prev(&mut self) -> bool { - let old = self.cursor; - self.cursor = self.cursor.saturating_sub(1); - old != self.cursor - } - pub fn paginate(&self) -> Vec { let running = self.scheduler.running.read(); running.values().take(Self::limit()).map(Into::into).collect() } - pub fn inspect(&self) -> bool { - let Some(id) = self.scheduler.running.read().get_id(self.cursor) else { - return false; - }; - - let scheduler = self.scheduler.clone(); - tokio::spawn(async move { - let _guard = BLOCKER.acquire().await.unwrap(); - let (tx, mut rx) = mpsc::unbounded_channel(); - - let buffered = { - let mut running = scheduler.running.write(); - let Some(task) = running.get_mut(id) else { return }; - - task.logger = Some(tx); - task.logs.clone() - }; - - emit!(Stop(true)).await; - let _defer = Defer::new(|| { - disable_raw_mode().ok(); - Event::Stop(false, None).emit(); - }); - - Term::clear(&mut stdout()).ok(); - stdout().write_all(buffered.as_bytes()).ok(); - enable_raw_mode().ok(); - - let mut stdin = stdin(); - let mut quit = [0; 10]; - loop { - select! { - Some(line) = rx.recv() => { - let mut stdout = stdout().lock(); - stdout.write_all(line.as_bytes()).ok(); - stdout.write_all(b"\r\n").ok(); - } - _ = time::sleep(time::Duration::from_millis(100)) => { - if scheduler.running.read().get(id).is_none() { - stdout().write_all(b"Task finished, press `q` to quit\r\n").ok(); - break; - } - }, - Ok(_) = stdin.read(&mut quit) => { - if quit[0] == b'q' { - break; - } - } - } - } - - if let Some(task) = scheduler.running.write().get_mut(id) { - task.logger = None; - } - while quit[0] != b'q' { - stdin.read(&mut quit).await.ok(); - } - }); - false - } - - pub fn cancel(&mut self) -> bool { - let id = self.scheduler.running.read().get_id(self.cursor); - if id.map(|id| self.scheduler.cancel(id)) != Some(true) { - return false; - } - - let len = self.scheduler.running.read().len(); - self.cursor = self.cursor.min(len.saturating_sub(1)); - true - } - pub fn file_open(&self, targets: &[(impl AsRef, impl AsRef)]) -> bool { let mut openers = BTreeMap::new(); for (path, mime) in targets { diff --git a/yazi-fm/src/executor.rs b/yazi-fm/src/executor.rs index 9a27f06e..0277d193 100644 --- a/yazi-fm/src/executor.rs +++ b/yazi-fm/src/executor.rs @@ -1,6 +1,5 @@ use yazi_config::{keymap::{Control, Exec, Key, KeymapLayer}, KEYMAP}; -use yazi_core::{input::InputMode, tab::FinderCase, Ctx}; -use yazi_shared::{expand_url, optional_bool, Url}; +use yazi_core::{input::InputMode, Ctx}; pub(super) struct Executor<'a> { cx: &'a mut Ctx, @@ -71,260 +70,212 @@ impl<'a> Executor<'a> { } fn manager(&mut self, exec: &Exec) -> bool { - match exec.cmd.as_str() { - "escape" => self.cx.manager.active_mut().escape(exec), - "quit" => self.cx.manager.quit(&self.cx.tasks, exec.named.contains_key("no-cwd-file")), - "close" => self.cx.manager.close(&self.cx.tasks), - "suspend" => self.cx.manager.suspend(), + macro_rules! on { + (MANAGER, $name:ident $(,$args:expr)*) => { + if exec.cmd == stringify!($name) { + return self.cx.manager.$name(exec, $($args),*); + } + }; + (ACTIVE, $name:ident) => { + if exec.cmd == stringify!($name) { + return self.cx.manager.active_mut().$name(exec); + } + }; + (TABS, $name:ident) => { + if exec.cmd == concat!("tab_", stringify!($name)) { + return self.cx.manager.tabs.$name(exec); + } + }; + } - // Navigation - "arrow" => { - let step = exec.args.first().and_then(|s| s.parse().ok()).unwrap_or_default(); - self.cx.manager.active_mut().arrow(step) - } - "peek" => { + on!(ACTIVE, escape); + on!(MANAGER, quit, &self.cx.tasks); + on!(MANAGER, close, &self.cx.tasks); + on!(MANAGER, suspend); + + // Navigation + on!(ACTIVE, arrow); + on!(ACTIVE, leave); + on!(ACTIVE, enter); + on!(ACTIVE, back); + on!(ACTIVE, forward); + on!(ACTIVE, cd); + on!(ACTIVE, reveal); + + // Selection + on!(ACTIVE, select); + on!(ACTIVE, select_all); + on!(ACTIVE, visual_mode); + + // Operation + on!(MANAGER, open); + on!(MANAGER, yank); + on!(MANAGER, paste, &self.cx.tasks); + on!(MANAGER, link, &self.cx.tasks); + on!(MANAGER, remove, &self.cx.tasks); + on!(MANAGER, create); + on!(MANAGER, rename); + on!(ACTIVE, copy); + on!(ACTIVE, shell); + on!(ACTIVE, hidden); + on!(ACTIVE, linemode); + on!(ACTIVE, search); + on!(ACTIVE, jump); + + // Find + on!(ACTIVE, find); + on!(ACTIVE, find_arrow); + + // Sorting + on!(ACTIVE, sort); + + // Tabs + on!(TABS, create); + on!(TABS, close); + on!(TABS, switch); + on!(TABS, swap); + + match exec.cmd.as_bytes() { + b"peek" => { let step = exec.args.first().and_then(|s| s.parse().ok()).unwrap_or(0); self.cx.manager.active_mut().preview.arrow(step); self.cx.manager.peek(true, self.cx.image_layer()) } - "leave" => self.cx.manager.active_mut().leave(), - "enter" => self.cx.manager.active_mut().enter(), - "back" => self.cx.manager.active_mut().back(), - "forward" => self.cx.manager.active_mut().forward(), - "cd" => { - let url = exec.args.first().map(Url::from).unwrap_or_default(); - if exec.named.contains_key("interactive") { - self.cx.manager.active_mut().cd_interactive(url) - } else { - self.cx.manager.active_mut().cd(expand_url(url)) - } - } - "reveal" => self.cx.manager.active_mut().reveal(exec), - - // Selection - "select" => { - let state = exec.named.get("state").cloned().unwrap_or("none".to_string()); - self.cx.manager.active_mut().select(optional_bool(&state)) - } - "select_all" => { - let state = exec.named.get("state").cloned().unwrap_or("none".to_string()); - self.cx.manager.active_mut().select_all(optional_bool(&state)) - } - "visual_mode" => self.cx.manager.active_mut().visual_mode(exec.named.contains_key("unset")), - - // Operation - "open" => self.cx.manager.open(exec.named.contains_key("interactive")), - "yank" => self.cx.manager.yank(exec.named.contains_key("cut")), - "paste" => { - let dest = self.cx.manager.cwd(); - let (cut, ref src) = self.cx.manager.yanked; - - let force = exec.named.contains_key("force"); - if cut { - self.cx.tasks.file_cut(src, dest, force) - } else { - self.cx.tasks.file_copy(src, dest, force) - } - } - "link" => { - let (cut, ref src) = self.cx.manager.yanked; - !cut - && self.cx.tasks.file_link( - src, - self.cx.manager.cwd(), - exec.named.contains_key("relative"), - exec.named.contains_key("force"), - ) - } - "remove" => { - let targets = self.cx.manager.selected().into_iter().map(|f| f.url()).collect(); - let force = exec.named.contains_key("force"); - let permanently = exec.named.contains_key("permanently"); - self.cx.tasks.file_remove(targets, force, permanently) - } - "create" => self.cx.manager.create(exec.named.contains_key("force")), - "rename" => self.cx.manager.rename(exec.named.contains_key("force")), - "copy" => self.cx.manager.active().copy(exec.args.first().map(|s| s.as_str()).unwrap_or("")), - "shell" => self.cx.manager.active().shell( - exec.args.first().map(|e| e.as_str()).unwrap_or(""), - exec.named.contains_key("block"), - exec.named.contains_key("confirm"), - ), - "hidden" => self.cx.manager.active_mut().hidden(exec), - "linemode" => self.cx.manager.active_mut().linemode(exec), - "search" => match exec.args.first().map(|s| s.as_str()).unwrap_or("") { - "rg" => self.cx.manager.active_mut().search(true), - "fd" => self.cx.manager.active_mut().search(false), - _ => self.cx.manager.active_mut().search_stop(), - }, - "jump" => match exec.args.first().map(|s| s.as_str()).unwrap_or("") { - "fzf" => self.cx.manager.active_mut().jump(true), - "zoxide" => self.cx.manager.active_mut().jump(false), - _ => false, - }, - - // Find - "find" => { - let query = exec.args.first().map(|s| s.as_str()); - let prev = exec.named.contains_key("previous"); - let case = match (exec.named.contains_key("smart"), exec.named.contains_key("insensitive")) - { - (true, _) => FinderCase::Smart, - (_, false) => FinderCase::Sensitive, - (_, true) => FinderCase::Insensitive, - }; - self.cx.manager.active_mut().find(query, prev, case) - } - "find_arrow" => self.cx.manager.active_mut().find_arrow(exec.named.contains_key("previous")), - - // Sorting - "sort" => { - let b = self.cx.manager.active_mut().sort(exec); - self.cx.tasks.precache_size(&self.cx.manager.current().files); - b - } - - // Tabs - "tab_create" => { - let path = if exec.named.contains_key("current") { - self.cx.manager.cwd().to_owned() - } else { - exec.args.first().map(Url::from).unwrap_or_else(|| Url::from("/")) - }; - self.cx.manager.tabs.create(&path) - } - "tab_close" => { - let idx = exec.args.first().and_then(|i| i.parse().ok()).unwrap_or(0); - self.cx.manager.tabs.close(idx) - } - "tab_switch" => { - let step = exec.args.first().and_then(|s| s.parse().ok()).unwrap_or(0); - let rel = exec.named.contains_key("relative"); - self.cx.manager.tabs.switch(step, rel) - } - "tab_swap" => { - let step = exec.args.first().and_then(|s| s.parse().ok()).unwrap_or(0); - self.cx.manager.tabs.swap(step) - } - // Tasks - "tasks_show" => self.cx.tasks.toggle(), - + b"tasks_show" => self.cx.tasks.toggle(()), // Help - "help" => self.cx.help.toggle(KeymapLayer::Manager), - + b"help" => self.cx.help.toggle(KeymapLayer::Manager), _ => false, } } fn tasks(&mut self, exec: &Exec) -> bool { + macro_rules! on { + ($name:ident) => { + if exec.cmd == stringify!($name) { + return self.cx.tasks.$name(exec); + } + }; + ($name:ident, $alias:literal) => { + if exec.cmd == $alias { + return self.cx.tasks.$name(exec); + } + }; + } + + on!(toggle, "close"); + on!(arrow); + on!(inspect); + on!(cancel); + match exec.cmd.as_str() { - "close" => self.cx.tasks.toggle(), - - "arrow" => { - let step = exec.args.first().and_then(|s| s.parse().ok()).unwrap_or(0); - if step > 0 { self.cx.tasks.next() } else { self.cx.tasks.prev() } - } - - "inspect" => self.cx.tasks.inspect(), - "cancel" => self.cx.tasks.cancel(), - "help" => self.cx.help.toggle(KeymapLayer::Tasks), _ => false, } } fn select(&mut self, exec: &Exec) -> bool { - match exec.cmd.as_str() { - "close" => self.cx.select.close(exec.named.contains_key("submit")), - - "arrow" => { - let step: isize = exec.args.first().and_then(|s| s.parse().ok()).unwrap_or(0); - if step > 0 { - self.cx.select.next(step as usize) - } else { - self.cx.select.prev(step.unsigned_abs()) + macro_rules! on { + ($name:ident) => { + if exec.cmd == stringify!($name) { + return self.cx.select.$name(exec); } - } + }; + } + on!(close); + on!(arrow); + + match exec.cmd.as_str() { "help" => self.cx.help.toggle(KeymapLayer::Select), _ => false, } } fn input(&mut self, exec: &Exec) -> bool { - match exec.cmd.as_str() { - "close" => return self.cx.input.close(exec.named.contains_key("submit")), - "escape" => return self.cx.input.escape(), + macro_rules! on { + ($name:ident) => { + if exec.cmd == stringify!($name) { + return self.cx.input.$name(exec); + } + }; + ($name:ident, $alias:literal) => { + if exec.cmd == $alias { + return self.cx.input.$name(exec); + } + }; + } - "move" => { - let step = exec.args.first().and_then(|s| s.parse().ok()).unwrap_or(0); - let in_operating = exec.named.contains_key("in-operating"); - return if in_operating { - self.cx.input.move_in_operating(step) - } else { - self.cx.input.move_(step) - }; - } + on!(close); + on!(escape); + on!(move_, "move"); - "complete" => { - return if exec.args.is_empty() { - self.cx.completion.trigger(exec) - } else { - self.cx.input.complete(exec) - }; - } - _ => {} + if exec.cmd.as_str() == "complete" { + return if exec.args.is_empty() { + self.cx.completion.trigger(exec) + } else { + self.cx.input.complete(exec) + }; } match self.cx.input.mode() { - InputMode::Normal => match exec.cmd.as_str() { - "insert" => self.cx.input.insert(exec.named.contains_key("append")), - "visual" => self.cx.input.visual(), + InputMode::Normal => { + on!(insert); + on!(visual); - "backward" => self.cx.input.backward(), - "forward" => self.cx.input.forward(exec.named.contains_key("end-of-word")), - "delete" => { - self.cx.input.delete(exec.named.contains_key("cut"), exec.named.contains_key("insert")) + on!(backward); + on!(forward); + on!(delete); + + on!(yank); + on!(paste); + + on!(undo); + on!(redo); + + match exec.cmd.as_str() { + "help" => self.cx.help.toggle(KeymapLayer::Input), + _ => false, } - - "yank" => self.cx.input.yank(), - "paste" => self.cx.input.paste(exec.named.contains_key("before")), - - "undo" => self.cx.input.undo(), - "redo" => self.cx.input.redo(), - - "help" => self.cx.help.toggle(KeymapLayer::Input), - _ => false, - }, + } InputMode::Insert => false, } } fn help(&mut self, exec: &Exec) -> bool { + macro_rules! on { + ($name:ident) => { + if exec.cmd == stringify!($name) { + return self.cx.help.$name(exec); + } + }; + } + + on!(escape); + on!(arrow); + on!(filter); + match exec.cmd.as_str() { "close" => self.cx.help.toggle(KeymapLayer::Help), - "escape" => self.cx.help.escape(), - - "arrow" => { - let step = exec.args.first().and_then(|s| s.parse().ok()).unwrap_or(0); - self.cx.help.arrow(step) - } - - "filter" => self.cx.help.filter(), - _ => false, } } fn completion(&mut self, exec: &Exec) -> bool { + macro_rules! on { + ($name:ident) => { + if exec.cmd == stringify!($name) { + return self.cx.completion.$name(exec); + } + }; + } + + on!(trigger); + on!(show); + on!(close); + on!(arrow); + match exec.cmd.as_str() { - "trigger" => self.cx.completion.trigger(exec), - "show" => self.cx.completion.show(exec), - "close" => self.cx.completion.close(exec), - - "arrow" => self.cx.completion.arrow(exec), - "help" => self.cx.help.toggle(KeymapLayer::Completion), _ => false, } diff --git a/yazi-plugin/preset/ui.lua b/yazi-plugin/preset/ui.lua index 534ed366..b408de4b 100644 --- a/yazi-plugin/preset/ui.lua +++ b/yazi-plugin/preset/ui.lua @@ -56,7 +56,6 @@ function ui.highlight_ranges(s, ranges) if r[1] > last then spans[#spans + 1] = ui.Span(s:sub(last + 1, r[1])) end - -- TODO: use a customable style spans[#spans + 1] = ui.Span(s:sub(r[1] + 1, r[2])):style(THEME.manager.find_keyword) last = r[2] end diff --git a/yazi-shared/src/fns.rs b/yazi-shared/src/fns.rs index 59e0e24a..22ee445e 100644 --- a/yazi-shared/src/fns.rs +++ b/yazi-shared/src/fns.rs @@ -141,15 +141,6 @@ pub fn path_relative_to<'a>(path: &'a Path, root: &Path) -> Cow<'a, Path> { Cow::from(buf) } -#[inline] -pub fn optional_bool(s: &str) -> Option { - match s { - "true" => Some(true), - "false" => Some(false), - _ => None, - } -} - #[cfg(test)] mod tests { use std::{borrow::Cow, path::Path};