From e2ead7eecbd7d624289af1225d8fe75db3e6ccb3 Mon Sep 17 00:00:00 2001 From: LightQuantum Date: Sat, 23 Sep 2023 21:19:17 -0700 Subject: [PATCH 1/3] feat: include ignored files on search when hidden files are shown (#212) --- core/src/external/fd.rs | 2 +- core/src/external/rg.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/external/fd.rs b/core/src/external/fd.rs index d914b19b..c1c606db 100644 --- a/core/src/external/fd.rs +++ b/core/src/external/fd.rs @@ -17,7 +17,7 @@ pub fn fd(opt: FdOpt) -> Result> { let mut child = Command::new("fd") .arg("--base-directory") .arg(&opt.cwd) - .arg(if opt.hidden { "--hidden" } else { "--no-hidden" }) + .args(if opt.hidden { ["--hidden", "--no-ignore"] } else { ["--no-hidden", "--ignore"] }) .arg(if opt.glob { "--glob" } else { "--regex" }) .arg(&opt.subject) .kill_on_drop(true) diff --git a/core/src/external/rg.rs b/core/src/external/rg.rs index c53ac06d..73114d50 100644 --- a/core/src/external/rg.rs +++ b/core/src/external/rg.rs @@ -16,7 +16,7 @@ pub fn rg(opt: RgOpt) -> Result> { let mut child = Command::new("rg") .current_dir(&opt.cwd) .args(["--color=never", "--files-with-matches", "--smart-case"]) - .arg(if opt.hidden { "--hidden" } else { "--no-hidden" }) + .args(if opt.hidden { ["--hidden", "--no-ignore"] } else { ["--no-hidden", "--ignore"] }) .arg(&opt.subject) .kill_on_drop(true) .stdout(Stdio::piped()) From d3ed8e7cf8f30e8d1452b947b606aa28156f15d1 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: Sun, 24 Sep 2023 15:55:44 +0800 Subject: [PATCH 2/3] feat: new `orphan` option for opener rules, to keep the process running even when Yazi exited (#216) --- config/src/open/opener.rs | 5 ++++- core/src/external/shell.rs | 16 ++++++++++++---- core/src/manager/manager.rs | 7 ++++--- core/src/manager/tab.rs | 2 +- core/src/tasks/scheduler.rs | 1 + core/src/tasks/workers/process.rs | 23 +++++++++++++++++++---- 6 files changed, 41 insertions(+), 13 deletions(-) diff --git a/config/src/open/opener.rs b/config/src/open/opener.rs index b813c04d..e20a5fe5 100644 --- a/config/src/open/opener.rs +++ b/config/src/open/opener.rs @@ -4,6 +4,7 @@ use serde::{Deserialize, Deserializer}; pub struct Opener { pub exec: String, pub block: bool, + pub orphan: bool, pub display_name: String, pub spread: bool, } @@ -18,6 +19,8 @@ impl<'de> Deserialize<'de> for Opener { pub exec: String, #[serde(default)] pub block: bool, + #[serde(default)] + pub orphan: bool, pub display_name: Option, } @@ -32,6 +35,6 @@ impl<'de> Deserialize<'de> for Opener { .unwrap_or_else(|| shadow.exec.split_whitespace().next().unwrap().to_string()); let spread = shadow.exec.contains("$*") || shadow.exec.contains("$@"); - Ok(Self { exec: shadow.exec, block: shadow.block, display_name, spread }) + Ok(Self { exec: shadow.exec, block: shadow.block, orphan: shadow.orphan, display_name, spread }) } } diff --git a/core/src/external/shell.rs b/core/src/external/shell.rs index c3621ae5..2ca8f185 100644 --- a/core/src/external/shell.rs +++ b/core/src/external/shell.rs @@ -4,9 +4,17 @@ use anyhow::Result; use tokio::process::{Child, Command}; pub struct ShellOpt { - pub cmd: OsString, - pub args: Vec, - pub piped: bool, + pub cmd: OsString, + pub args: Vec, + pub piped: bool, + pub orphan: bool, +} + +impl ShellOpt { + pub fn with_piped(mut self) -> Self { + self.piped = true; + self + } } pub fn shell(opt: ShellOpt) -> Result { @@ -21,7 +29,7 @@ pub fn shell(opt: ShellOpt) -> Result { .stdin(if opt.piped { Stdio::piped() } else { Stdio::inherit() }) .stdout(if opt.piped { Stdio::piped() } else { Stdio::inherit() }) .stderr(if opt.piped { Stdio::piped() } else { Stdio::inherit() }) - .kill_on_drop(true) + .kill_on_drop(!opt.orphan) .spawn()?, ) } diff --git a/core/src/manager/manager.rs b/core/src/manager/manager.rs index e8e8f881..373c418e 100644 --- a/core/src/manager/manager.rs +++ b/core/src/manager/manager.rs @@ -259,9 +259,10 @@ impl Manager { emit!(Stop(true)).await; let mut child = external::shell(ShellOpt { - cmd: (*opener.exec).into(), - args: vec![tmp.to_owned().into()], - piped: false, + cmd: (*opener.exec).into(), + args: vec![tmp.to_owned().into()], + piped: false, + orphan: false, })?; child.wait().await?; diff --git a/core/src/manager/tab.rs b/core/src/manager/tab.rs index 4e5971c1..0b124a64 100644 --- a/core/src/manager/tab.rs +++ b/core/src/manager/tab.rs @@ -379,7 +379,7 @@ impl Tab { emit!(Open( selected, - Some(Opener { exec, block, display_name: Default::default(), spread: true }) + Some(Opener { exec, block, orphan: false, display_name: Default::default(), spread: true }) )); }); diff --git a/core/src/tasks/scheduler.rs b/core/src/tasks/scheduler.rs index 7e319a2e..d48f1002 100644 --- a/core/src/tasks/scheduler.rs +++ b/core/src/tasks/scheduler.rs @@ -310,6 +310,7 @@ impl Scheduler { cmd: opener.exec.into(), args, block: opener.block, + orphan: opener.orphan, cancel: cancel_tx, }) .await diff --git a/core/src/tasks/workers/process.rs b/core/src/tasks/workers/process.rs index 3fade197..6bed7b44 100644 --- a/core/src/tasks/workers/process.rs +++ b/core/src/tasks/workers/process.rs @@ -1,4 +1,4 @@ -use std::ffi::OsString; +use std::{ffi::OsString, mem}; use anyhow::Result; use tokio::{io::{AsyncBufReadExt, BufReader}, select, sync::{mpsc, oneshot}}; @@ -16,9 +16,21 @@ pub(crate) struct ProcessOpOpen { pub cmd: OsString, pub args: Vec, pub block: bool, + pub orphan: bool, pub cancel: oneshot::Sender<()>, } +impl From<&mut ProcessOpOpen> for ShellOpt { + fn from(value: &mut ProcessOpOpen) -> Self { + Self { + cmd: mem::take(&mut value.cmd), + args: mem::take(&mut value.args), + piped: false, + orphan: value.orphan, + } + } +} + impl Process { pub(crate) fn new(sch: mpsc::UnboundedSender) -> Self { Self { sch } } @@ -33,7 +45,7 @@ impl Process { let _guard = BLOCKER.acquire().await.unwrap(); emit!(Stop(true)).await; - match external::shell(ShellOpt { cmd: task.cmd, args: task.args, piped: false }) { + match external::shell(ShellOpt::from(&mut task)) { Ok(mut child) => { child.wait().await.ok(); } @@ -48,13 +60,16 @@ impl Process { } self.sch.send(TaskOp::New(task.id, 0))?; - let mut child = external::shell(ShellOpt { cmd: task.cmd, args: task.args, piped: true })?; + let mut child = external::shell(ShellOpt::from(&mut task).with_piped())?; let mut stdout = BufReader::new(child.stdout.take().unwrap()).lines(); let mut stderr = BufReader::new(child.stderr.take().unwrap()).lines(); loop { select! { - _ = task.cancel.closed() => break, + _ = task.cancel.closed() => { + child.start_kill().ok(); + break; + } Ok(Some(line)) = stdout.next_line() => { self.log(task.id, line)?; } From f7fdda9d9b12f4e97439c2c7ca34ba1d88c48893 Mon Sep 17 00:00:00 2001 From: Collide <44722470+TD-Sky@users.noreply.github.com> Date: Sun, 24 Sep 2023 17:27:10 +0800 Subject: [PATCH 3/3] feat: scroll half/full page with `arrow` percentage supported, and new Vi-like ``, ``, ``, and `` keybindings added (#213) --- app/src/executor.rs | 2 +- config/preset/keymap.toml | 5 ++++ core/src/lib.rs | 4 ++- core/src/manager/folder.rs | 18 ++++++++----- core/src/manager/tab.rs | 14 ++++------ core/src/step.rs | 55 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 80 insertions(+), 18 deletions(-) create mode 100644 core/src/step.rs diff --git a/app/src/executor.rs b/app/src/executor.rs index 3203a0da..51e928db 100644 --- a/app/src/executor.rs +++ b/app/src/executor.rs @@ -61,7 +61,7 @@ impl Executor { // Navigation "arrow" => { - let step = exec.args.get(0).and_then(|s| s.parse().ok()).unwrap_or(0); + let step = exec.args.get(0).and_then(|s| s.parse().ok()).unwrap_or_default(); cx.manager.active_mut().arrow(step) } "peek" => { diff --git a/config/preset/keymap.toml b/config/preset/keymap.toml index 461e8099..d21b91cc 100644 --- a/config/preset/keymap.toml +++ b/config/preset/keymap.toml @@ -13,6 +13,11 @@ keymap = [ { on = [ "K" ], exec = "arrow -5", desc = "Move cursor up 5 lines" }, { on = [ "J" ], exec = "arrow 5", desc = "Move cursor down 5 lines" }, + { on = [ "" ], exec = "arrow -50%", desc = "Move cursor up half page" }, + { on = [ "" ], exec = "arrow 50%", desc = "Move cursor down half page" }, + { on = [ "" ], exec = "arrow -100%", desc = "Move cursor up one page" }, + { on = [ "" ], exec = "arrow 100%", desc = "Move cursor down one page" }, + { on = [ "h" ], exec = "leave", desc = "Go back to the parent directory" }, { on = [ "l" ], exec = "enter", desc = "Enter the child directory" }, diff --git a/core/src/lib.rs b/core/src/lib.rs index 3696401f..e637118f 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -14,8 +14,9 @@ pub mod help; mod highlighter; pub mod input; pub mod manager; -pub mod position; +mod position; pub mod select; +mod step; pub mod tasks; pub mod which; @@ -23,5 +24,6 @@ pub use blocker::*; pub use event::*; pub use highlighter::*; pub use position::*; +pub use step::*; pub fn init() { init_blocker(); } diff --git a/core/src/manager/folder.rs b/core/src/manager/folder.rs index 9d1a832e..6899d1f7 100644 --- a/core/src/manager/folder.rs +++ b/core/src/manager/folder.rs @@ -2,7 +2,7 @@ use config::MANAGER; use ratatui::layout::Rect; use shared::Url; -use crate::{emit, files::{File, Files, FilesOp}}; +use crate::{emit, files::{File, Files, FilesOp}, Step}; #[derive(Default)] pub struct Folder { @@ -58,18 +58,18 @@ impl Folder { true } - pub fn next(&mut self, step: usize) -> bool { + pub fn next(&mut self, step: Step) -> bool { let len = self.files.len(); if len == 0 { return false; } let old = self.cursor; - self.cursor = (self.cursor + step).min(len - 1); + let limit = MANAGER.layout.folder_height(); + self.cursor = step.add(self.cursor, || limit).min(len - 1); self.hovered = self.files.duplicate(self.cursor); self.set_page(false); - let limit = MANAGER.layout.folder_height(); if self.cursor >= (self.offset + limit).min(len).saturating_sub(5) { self.offset = len.saturating_sub(limit).min(self.offset + self.cursor - old); } @@ -77,9 +77,9 @@ impl Folder { old != self.cursor } - pub fn prev(&mut self, step: usize) -> bool { + pub fn prev(&mut self, step: Step) -> bool { let old = self.cursor; - self.cursor = self.cursor.saturating_sub(step); + self.cursor = step.add(self.cursor, || MANAGER.layout.folder_height()); self.hovered = self.files.duplicate(self.cursor); self.set_page(false); @@ -105,7 +105,11 @@ impl Folder { pub fn hover(&mut self, url: &Url) -> bool { let new = self.files.position(url).unwrap_or(self.cursor); - if new > self.cursor { self.next(new - self.cursor) } else { self.prev(self.cursor - new) } + if new > self.cursor { + self.next(Step::from(new - self.cursor)) + } else { + self.prev(Step::from(self.cursor - new)) + } } #[inline] diff --git a/core/src/manager/tab.rs b/core/src/manager/tab.rs index 0b124a64..5f141b7c 100644 --- a/core/src/manager/tab.rs +++ b/core/src/manager/tab.rs @@ -7,7 +7,7 @@ use tokio::{pin, task::JoinHandle}; use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt}; use super::{Finder, Folder, Mode, Preview, PreviewLock}; -use crate::{emit, external::{self, FzfOpt, ZoxideOpt}, files::{File, FilesOp, FilesSorter}, input::InputOpt, Event, BLOCKER}; +use crate::{emit, external::{self, FzfOpt, ZoxideOpt}, files::{File, FilesOp, FilesSorter}, input::InputOpt, Event, Step, BLOCKER}; pub struct Tab { pub(super) mode: Mode, @@ -67,12 +67,8 @@ impl Tab { self.search_stop() } - pub fn arrow(&mut self, step: isize) -> bool { - let ok = if step > 0 { - self.current.next(step as usize) - } else { - self.current.prev(step.unsigned_abs()) - }; + pub fn arrow(&mut self, step: Step) -> bool { + let ok = if step.is_positive() { self.current.next(step) } else { self.current.prev(step) }; if !ok { return false; } @@ -248,7 +244,7 @@ impl Tab { }; if let Some(step) = finder.ring(&self.current.files, self.current.cursor(), prev) { - self.arrow(step); + self.arrow(step.into()); } self.finder = Some(finder); @@ -280,7 +276,7 @@ impl Tab { let mut b = finder.catchup(&self.current.files); if let Some(step) = finder.arrow(&self.current.files, self.current.cursor(), prev) { - b |= self.arrow(step); + b |= self.arrow(step.into()); } b diff --git a/core/src/step.rs b/core/src/step.rs new file mode 100644 index 00000000..5faa2459 --- /dev/null +++ b/core/src/step.rs @@ -0,0 +1,55 @@ +use std::{num::ParseIntError, str::FromStr}; + +pub enum Step { + Fixed(isize), + Percent(i8), +} + +impl Default for Step { + fn default() -> Self { Self::Fixed(0) } +} + +impl FromStr for Step { + type Err = ParseIntError; + + fn from_str(s: &str) -> Result { + Ok(if let Some(s) = s.strip_suffix('%') { + Self::Percent(s.parse()?) + } else { + Self::Fixed(s.parse()?) + }) + } +} + +impl From for Step { + fn from(n: isize) -> Self { Self::Fixed(n) } +} + +impl From for Step { + fn from(n: usize) -> Self { Self::Fixed(n as isize) } +} + +impl Step { + #[inline] + fn fixed usize>(self, f: F) -> isize { + match self { + Self::Fixed(n) => n, + Self::Percent(0) => 0, + Self::Percent(n) => n as isize * f() as isize / 100, + } + } + + #[inline] + pub fn add usize>(self, pos: usize, f: F) -> usize { + let fixed = self.fixed(f); + if fixed > 0 { pos + fixed as usize } else { pos.saturating_sub(fixed.unsigned_abs()) } + } + + #[inline] + pub fn is_positive(&self) -> bool { + match *self { + Self::Fixed(n) => n > 0, + Self::Percent(n) => n > 0, + } + } +}