From 311830b23427df85b69938dcbb90fba8d8d7085e Mon Sep 17 00:00:00 2001 From: OliverGuy Date: Thu, 7 May 2026 00:31:13 +0200 Subject: [PATCH] feat(input): history up/down --- yazi-actor/src/input/close.rs | 7 ++- yazi-config/preset/keymap-default.toml | 6 +++ yazi-widgets/src/input/actor/actor.rs | 1 + yazi-widgets/src/input/actor/history.rs | 23 +++++++++ yazi-widgets/src/input/actor/mod.rs | 2 +- yazi-widgets/src/input/history.rs | 63 ++++++++++++++++++++++++ yazi-widgets/src/input/mod.rs | 2 +- yazi-widgets/src/input/parser/history.rs | 19 +++++++ yazi-widgets/src/input/parser/mod.rs | 2 +- 9 files changed, 121 insertions(+), 4 deletions(-) create mode 100644 yazi-widgets/src/input/actor/history.rs create mode 100644 yazi-widgets/src/input/history.rs create mode 100644 yazi-widgets/src/input/parser/history.rs diff --git a/yazi-actor/src/input/close.rs b/yazi-actor/src/input/close.rs index 0c0a8eb5..f5529726 100644 --- a/yazi-actor/src/input/close.rs +++ b/yazi-actor/src/input/close.rs @@ -20,7 +20,12 @@ impl Actor for Close { if let Some(tx) = input.tx.take() { let value = input.snap().value.clone(); - _ = tx.send(if form.submit { InputEvent::Submit(value) } else { InputEvent::Cancel(value) }); + if form.submit { + yazi_widgets::input::INPUT_HISTORY.lock().unwrap().push(value.clone()); + _ = tx.send(InputEvent::Submit(value)); + } else { + _ = tx.send(InputEvent::Cancel(value)); + } } act!(cmp:close, cx)?; diff --git a/yazi-config/preset/keymap-default.toml b/yazi-config/preset/keymap-default.toml index 7d621ec0..d2384505 100644 --- a/yazi-config/preset/keymap-default.toml +++ b/yazi-config/preset/keymap-default.toml @@ -304,6 +304,12 @@ keymap = [ { on = "p", run = "paste", desc = "Paste copied characters after the cursor" }, { on = "P", run = "paste --before", desc = "Paste copied characters before the cursor" }, + # History + { on = "", run = "history up", desc = "Navigate to the previous history entry" }, + { on = "", run = "history down", desc = "Navigate to the next history entry" }, + { on = "", run = "history up", desc = "Navigate to the previous history entry" }, + { on = "", run = "history down", desc = "Navigate to the next history entry" }, + # Undo/Redo/Casefy { on = "u", run = [ "undo", "casefy lower" ], desc = "Undo, or lowercase if in visual mode" }, { on = "U", run = "casefy upper", desc = "Uppercase" }, diff --git a/yazi-widgets/src/input/actor/actor.rs b/yazi-widgets/src/input/actor/actor.rs index a029417d..a0fbdd1a 100644 --- a/yazi-widgets/src/input/actor/actor.rs +++ b/yazi-widgets/src/input/actor/actor.rs @@ -22,6 +22,7 @@ impl Input { on!(r#move, "move"); on!(backward); on!(forward); + on!(history); match self.mode() { InputMode::Normal => { diff --git a/yazi-widgets/src/input/actor/history.rs b/yazi-widgets/src/input/actor/history.rs new file mode 100644 index 00000000..9f7f786f --- /dev/null +++ b/yazi-widgets/src/input/actor/history.rs @@ -0,0 +1,23 @@ +use anyhow::Result; +use yazi_macro::{act, render, succ}; +use yazi_shared::data::Data; + +use crate::input::{Input, InputOp, INPUT_HISTORY, parser::HistoryOpt}; + +impl Input { + pub fn history(&mut self, opt: HistoryOpt) -> Result { + if self.snap().op != InputOp::None { + succ!(); + } + + let new_value = INPUT_HISTORY.lock().unwrap().navigate(opt.up, &self.snap().value); + let Some(value) = new_value else { succ!() }; + + let snap = self.snap_mut(); + snap.value = value; + snap.cursor = snap.count(); + + act!(r#move, self)?; + succ!(render!()); + } +} diff --git a/yazi-widgets/src/input/actor/mod.rs b/yazi-widgets/src/input/actor/mod.rs index 7913df7d..3e891aa2 100644 --- a/yazi-widgets/src/input/actor/mod.rs +++ b/yazi-widgets/src/input/actor/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(actor backspace backward casefy complete delete escape forward insert kill paste r#move r#type redo replace undo visual yank); +yazi_macro::mod_flat!(actor backspace backward casefy complete delete escape forward history insert kill paste r#move r#type redo replace undo visual yank); diff --git a/yazi-widgets/src/input/history.rs b/yazi-widgets/src/input/history.rs new file mode 100644 index 00000000..3752e0e4 --- /dev/null +++ b/yazi-widgets/src/input/history.rs @@ -0,0 +1,63 @@ +use std::sync::Mutex; + +pub static INPUT_HISTORY: Mutex = Mutex::new(InputHistory::new()); + +#[derive(Debug, Default)] +pub struct InputHistory { + entries: Vec, + idx: Option, + draft: String, +} + +impl InputHistory { + pub const fn new() -> Self { + Self { entries: Vec::new(), idx: None, draft: String::new() } + } + + pub fn push(&mut self, value: String) { + if value.is_empty() { + return; + } + if self.entries.last().map(String::as_str) != Some(&value) { + self.entries.push(value); + } + self.reset(); + } + + pub fn reset(&mut self) { + self.idx = None; + self.draft.clear(); + } + + pub fn navigate(&mut self, up: bool, current: &str) -> Option { + if self.entries.is_empty() { + return None; + } + + if up { + let new_idx = match self.idx { + None => { + self.draft = current.to_owned(); + self.entries.len() - 1 + } + Some(0) => return None, + Some(i) => i - 1, + }; + self.idx = Some(new_idx); + Some(self.entries[new_idx].clone()) + } else { + match self.idx { + None => None, + Some(i) if i + 1 >= self.entries.len() => { + self.idx = None; + let draft = std::mem::take(&mut self.draft); + Some(draft) + } + Some(i) => { + self.idx = Some(i + 1); + Some(self.entries[i + 1].clone()) + } + } + } + } +} diff --git a/yazi-widgets/src/input/mod.rs b/yazi-widgets/src/input/mod.rs index dde2b4e8..265e2eea 100644 --- a/yazi-widgets/src/input/mod.rs +++ b/yazi-widgets/src/input/mod.rs @@ -1,3 +1,3 @@ yazi_macro::mod_pub!(actor parser); -yazi_macro::mod_flat!(event input mode op option snap snaps widget); +yazi_macro::mod_flat!(event history input mode op option snap snaps widget); diff --git a/yazi-widgets/src/input/parser/history.rs b/yazi-widgets/src/input/parser/history.rs new file mode 100644 index 00000000..57f39d0b --- /dev/null +++ b/yazi-widgets/src/input/parser/history.rs @@ -0,0 +1,19 @@ +use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; +use yazi_shared::event::ActionCow; + +#[derive(Debug)] +pub struct HistoryOpt { + pub up: bool, +} + +impl From for HistoryOpt { + fn from(a: ActionCow) -> Self { Self { up: a.str(0) == "up" } } +} + +impl FromLua for HistoryOpt { + fn from_lua(_: Value, _: &Lua) -> mlua::Result { Err("unsupported".into_lua_err()) } +} + +impl IntoLua for HistoryOpt { + fn into_lua(self, _: &Lua) -> mlua::Result { Err("unsupported".into_lua_err()) } +} diff --git a/yazi-widgets/src/input/parser/mod.rs b/yazi-widgets/src/input/parser/mod.rs index 924c5be3..17b0fb13 100644 --- a/yazi-widgets/src/input/parser/mod.rs +++ b/yazi-widgets/src/input/parser/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(backspace backward casefy complete delete forward insert kill paste r#move); +yazi_macro::mod_flat!(backspace backward casefy complete delete forward history insert kill paste r#move);