refactor: extracting commands into separate files to make them easier to maintain (#338)

This commit is contained in:
三咲雅 · Misaki Masa 2023-11-10 09:25:23 +08:00 committed by GitHub
parent 1bbb323509
commit c41397957d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
70 changed files with 1571 additions and 826 deletions

View file

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

View file

@ -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<Opt>) -> 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()) }
}
}

View file

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

View file

@ -24,7 +24,7 @@ impl<'a> From<&'a Exec> for Opt<'a> {
impl Completion {
pub fn show<'a>(&mut self, opt: impl Into<Opt<'a>>) -> bool {
let opt = opt.into();
let opt = opt.into() as Opt;
if self.ticket != opt.ticket {
return false;
}

View file

@ -29,7 +29,7 @@ impl Completion {
}
pub fn trigger<'a>(&mut self, opt: impl Into<Opt<'a>>) -> bool {
let opt = opt.into();
let opt = opt.into() as Opt;
if opt.ticket < self.ticket {
return false;
}

View file

@ -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<isize> for Opt {
fn from(step: isize) -> Self { Self { step } }
}
impl Help {
#[inline]
pub fn arrow(&mut self, opt: impl Into<Opt>) -> 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
}
}

View file

@ -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<Opt>) -> bool {
if self.in_filter.is_some() {
self.in_filter = None;
self.filter_apply();
true
} else {
self.toggle(self.layer)
}
}
}

View file

@ -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<Opt>) -> bool {
self.in_filter = Some(Default::default());
self.filter_apply();
true
}
}

View file

@ -0,0 +1,3 @@
mod arrow;
mod escape;
mod filter;

View file

@ -7,16 +7,16 @@ use crate::{emit, input::Input};
#[derive(Default)]
pub struct Help {
pub visible: bool,
pub layer: KeymapLayer,
bindings: Vec<Control>,
pub visible: bool,
pub layer: KeymapLayer,
pub(super) bindings: Vec<Control>,
// Filter
keyword: Option<String>,
in_filter: Option<Input>,
keyword: Option<String>,
pub(super) in_filter: Option<Input>,
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;

View file

@ -1,3 +1,4 @@
mod commands;
mod help;
pub use help::*;

View file

@ -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<Opt>) -> 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
}
}

View file

@ -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<bool> for Opt {
fn from(submit: bool) -> Self { Self { submit } }
}
impl Input {
pub fn close(&mut self, opt: impl Into<Opt>) -> 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
}
}

View file

@ -18,7 +18,7 @@ impl<'a> From<&'a Exec> for Opt<'a> {
impl Input {
pub fn complete<'a>(&mut self, opt: impl Into<Opt<'a>>) -> bool {
let opt = opt.into();
let opt = opt.into() as Opt;
if self.ticket != opt.ticket {
return false;
}

View file

@ -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<Opt>) -> 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,
}
}
}

View file

@ -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<Opt>) -> 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
}
}

View file

@ -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<Opt>) -> 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)
}
}

View file

@ -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<bool> for Opt {
fn from(append: bool) -> Self { Self { append } }
}
impl Input {
pub fn insert(&mut self, opt: impl Into<Opt>) -> bool {
if !self.snap_mut().insert() {
return false;
}
let opt = opt.into() as Opt;
if opt.append {
self.move_(1);
}
true
}
}

View file

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

View file

@ -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<isize> for Opt {
fn from(step: isize) -> Self { Self { step, in_operating: false } }
}
impl Input {
pub fn move_(&mut self, opt: impl Into<Opt>) -> 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::<String>();
snap.offset = snap.cursor - InputSnap::find_window(&s, 0).end.saturating_sub(delta);
}
}
b
}
}

View file

@ -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<Opt>) -> 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
}
}

View file

@ -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<Opt>) -> bool { self.snaps.redo() }
}

View file

@ -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<Opt>) -> bool {
if !self.snaps.undo() {
return false;
}
if self.snap().mode == InputMode::Insert {
self.escape(());
}
true
}
}

View file

@ -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<Opt>) -> bool { self.snap_mut().visual() }
}

View file

@ -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<Opt>) -> 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,
}
}
}

View file

@ -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<UnboundedSender<Result<String, InputError>>>,
realtime: bool,
completion: bool,
pub(super) callback: Option<UnboundedSender<Result<String, InputError>>>,
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::<String>();
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() }
}

View file

@ -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<Opt>, tasks: &Tasks) -> bool {
if self.tabs.len() > 1 {
return self.tabs.close(self.tabs.idx);
}
self.quit(tasks, false)
self.quit((), tasks)
}
}

View file

@ -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<Opt>) -> 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(()),

View file

@ -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<Opt>, 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)
}
}

View file

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

View file

@ -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<Opt>) -> 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
}

View file

@ -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<Opt>, 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) }
}
}

View file

@ -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<Opt>, 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));
}
}
});

View file

@ -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<Opt>, 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)
}
}

View file

@ -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<Opt>) -> 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();
}
};
});

View file

@ -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<Opt>) -> bool {
#[cfg(unix)]
tokio::spawn(async move {
crate::emit!(Stop(true)).await;

View file

@ -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<usize> for Opt {
fn from(idx: usize) -> Self { Self { idx } }
}
impl Tabs {
pub fn close(&mut self, opt: impl Into<Opt>) -> 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
}
}

View file

@ -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<Url>,
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<Opt>) -> 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
}
}

View file

@ -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<Opt>) -> 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
}
}

View file

@ -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<Opt>) -> 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
}
}

View file

@ -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<Opt>) -> 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
}

View file

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

View file

@ -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<Tab>,
pub idx: usize,
pub(super) items: Vec<Tab>,
}
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);

View file

@ -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<Opt>) -> bool {
let opt = opt.into() as Opt;
if opt.step > 0 { self.next(opt.step as usize) } else { self.prev(opt.step.unsigned_abs()) }
}
}

View file

@ -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<bool> for Opt {
fn from(submit: bool) -> Self { Self { submit } }
}
impl Select {
pub fn close(&mut self, opt: impl Into<Opt>) -> 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
}
}

View file

@ -0,0 +1,2 @@
mod arrow;
mod close;

View file

@ -1,3 +1,4 @@
mod commands;
mod option;
mod select;

View file

@ -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<String>,
pub position: Position,
title: String,
pub(super) items: Vec<String>,
pub position: Position,
offset: usize,
cursor: usize,
callback: Option<Sender<Result<usize>>>,
pub(super) offset: usize,
pub(super) cursor: usize,
pub(super) callback: Option<Sender<Result<usize>>>,
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());

View file

@ -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<T> From<T> for Opt
where
T: Into<Step>,
{
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<Opt>) -> 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;
}

View file

@ -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<Opt>) -> 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<Opt>) -> bool {
if let Some(url) = self.backstack.shift_forward().cloned() {
self.cd(url);
}

View file

@ -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<Url> 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<Opt>) -> 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<Opt>) -> 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));

View file

@ -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<Opt<'a>>) -> 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("")),

View file

@ -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<Opt>) -> bool {
let Some(hovered) = self.current.hovered().filter(|h| h.is_dir()).map(|h| h.url()) else {
return false;
};

View file

@ -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<Opt<'a>>) -> 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<Opt<'a>>) -> 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<ArrowOpt>) -> 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))
}
}

View file

@ -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<Opt>) -> 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>(())
});

View file

@ -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<Opt>) -> bool {
let current = self
.current
.hovered()

View file

@ -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<Opt>) -> 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();
}

View file

@ -1,12 +1,35 @@
use yazi_config::keymap::Exec;
use crate::tab::Tab;
pub struct Opt {
state: Option<bool>,
}
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<Option<bool>> for Opt {
fn from(state: Option<bool>) -> Self { Self { state } }
}
impl Tab {
pub fn select(&mut self, state: Option<bool>) -> bool {
pub fn select(&mut self, opt: impl Into<Opt>) -> 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>) -> bool { self.current.files.select_all(state) }
pub fn select_all(&mut self, opt: impl Into<Opt>) -> bool {
self.current.files.select_all(opt.into().state)
}
}

View file

@ -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<Opt>) -> 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,
})
));
});

View file

@ -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<Opt>) -> 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]));

View file

@ -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<Opt>) -> bool {
let opt = opt.into() as Opt;
if opt.step > 0 { self.next() } else { self.prev() }
}
}

View file

@ -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<Opt>) -> 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
}
}

View file

@ -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<Opt>) -> 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
}
}

View file

@ -0,0 +1,4 @@
mod arrow;
mod cancel;
mod inspect;
mod toggle;

View file

@ -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<Opt>) -> bool {
self.visible = !self.visible;
emit!(Peek); // Show/hide preview for images
true
}
}

View file

@ -1,3 +1,4 @@
mod commands;
mod running;
mod scheduler;
mod task;

View file

@ -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<Scheduler>,
pub(super) scheduler: Arc<Scheduler>,
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<TaskSummary> {
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<Path>, impl AsRef<str>)]) -> bool {
let mut openers = BTreeMap::new();
for (path, mime) in targets {

View file

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

View file

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

View file

@ -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<bool> {
match s {
"true" => Some(true),
"false" => Some(false),
_ => None,
}
}
#[cfg(test)]
mod tests {
use std::{borrow::Cow, path::Path};