feat: new input DDS event watching changes in input content (#4043)

This commit is contained in:
三咲雅 misaki masa 2026-06-14 19:58:23 +08:00 committed by GitHub
parent b37be0529a
commit f1e93d7f52
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 220 additions and 50 deletions

5
Cargo.lock generated
View file

@ -5565,6 +5565,7 @@ dependencies = [
"indexmap 2.14.0", "indexmap 2.14.0",
"libc", "libc",
"mlua", "mlua",
"parking_lot",
"paste", "paste",
"ratatui", "ratatui",
"scopeguard", "scopeguard",
@ -6001,8 +6002,10 @@ version = "26.5.6"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"tokio", "tokio",
"tracing",
"yazi-config", "yazi-config",
"yazi-core", "yazi-core",
"yazi-dds",
"yazi-macro", "yazi-macro",
"yazi-scheduler", "yazi-scheduler",
"yazi-shared", "yazi-shared",
@ -6227,11 +6230,13 @@ version = "26.5.6"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clipboard-win", "clipboard-win",
"dyn-clone",
"futures", "futures",
"mlua", "mlua",
"parking_lot", "parking_lot",
"ratatui", "ratatui",
"serde", "serde",
"strum",
"tokio", "tokio",
"unicode-width", "unicode-width",
"yazi-adapter", "yazi-adapter",

View file

@ -46,6 +46,7 @@ futures = { workspace = true }
hashbrown = { workspace = true } hashbrown = { workspace = true }
indexmap = { workspace = true } indexmap = { workspace = true }
mlua = { workspace = true } mlua = { workspace = true }
parking_lot = { workspace = true }
paste = { workspace = true } paste = { workspace = true }
ratatui = { workspace = true } ratatui = { workspace = true }
scopeguard = { workspace = true } scopeguard = { workspace = true }

View file

@ -19,9 +19,9 @@ impl Actor for Close {
}; };
guard.ticket.next(); guard.ticket.next();
if let Some(tx) = guard.tx.take() { if let Some(cb) = guard.cb.take() {
let value = guard.snap().value.clone(); let value = guard.snap().value.clone();
_ = tx.send(if form.submit { InputEvent::Submit(value) } else { InputEvent::Cancel(value) }); cb(if form.submit { InputEvent::Submit(value) } else { InputEvent::Cancel(value) });
} }
drop(guard); drop(guard);

View file

@ -14,8 +14,9 @@ pub(super) struct Core {
c_tabs: Option<Value>, c_tabs: Option<Value>,
c_tasks: Option<Value>, c_tasks: Option<Value>,
c_yanked: Option<Value>, c_yanked: Option<Value>,
c_layer: Option<Value>, c_input: Option<Value>,
c_which: Option<Value>, c_which: Option<Value>,
c_layer: Option<Value>,
} }
impl Deref for Core { impl Deref for Core {
@ -33,8 +34,9 @@ impl Core {
c_tabs: None, c_tabs: None,
c_tasks: None, c_tasks: None,
c_yanked: None, c_yanked: None,
c_layer: None, c_input: None,
c_which: None, c_which: None,
c_layer: None,
}) })
} }
} }
@ -59,10 +61,11 @@ impl UserData for Core {
b"tabs" => reuse!(tabs, super::Tabs::make(&me.mgr.tabs)), b"tabs" => reuse!(tabs, super::Tabs::make(&me.mgr.tabs)),
b"tasks" => reuse!(tasks, super::Tasks::make(&me.tasks)), b"tasks" => reuse!(tasks, super::Tasks::make(&me.tasks)),
b"yanked" => reuse!(yanked, super::Yanked::make(&me.mgr.yanked)), b"yanked" => reuse!(yanked, super::Yanked::make(&me.mgr.yanked)),
b"input" => reuse!(input, super::Input::make(&me.input)),
b"which" => reuse!(which, super::Which::make(&me.which)),
b"layer" => { b"layer" => {
reuse!(layer, Ok::<_, mlua::Error>(yazi_binding::Layer::from(me.layer()))) reuse!(layer, Ok::<_, mlua::Error>(yazi_binding::Layer::from(me.layer())))
} }
b"which" => reuse!(which, super::Which::make(&me.which)),
_ => Value::Nil, _ => Value::Nil,
}) })
}); });

View file

@ -0,0 +1,29 @@
use std::ops::Deref;
use mlua::{AnyUserData, UserData, UserDataFields};
use yazi_shim::mlua::UserDataFieldsExt;
use super::{Lives, PtrCell};
use crate::lives::InputAlt;
pub(super) struct Input {
inner: PtrCell<yazi_core::input::Input>,
}
impl Deref for Input {
type Target = yazi_core::input::Input;
fn deref(&self) -> &Self::Target { &self.inner }
}
impl Input {
pub(super) fn make(inner: &yazi_core::input::Input) -> mlua::Result<AnyUserData> {
Lives::scoped_userdata(Self { inner: inner.into() })
}
}
impl UserData for Input {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_cached_field("alt", |_, me| me.alt.as_ref().map(InputAlt::make).transpose());
}
}

View file

@ -0,0 +1,27 @@
use std::{ops::Deref, sync::Arc};
use mlua::{AnyUserData, UserData, UserDataFields};
use parking_lot::Mutex;
use yazi_shim::mlua::UserDataFieldsExt;
use super::{Lives, PtrCell};
pub(super) struct InputAlt(PtrCell<Arc<Mutex<yazi_widgets::input::Input>>>);
impl Deref for InputAlt {
type Target = Arc<Mutex<yazi_widgets::input::Input>>;
fn deref(&self) -> &Self::Target { &self.0 }
}
impl InputAlt {
pub(super) fn make(inner: &Arc<Mutex<yazi_widgets::input::Input>>) -> mlua::Result<AnyUserData> {
Lives::scoped_userdata(Self(inner.into()))
}
}
impl UserData for InputAlt {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_cached_field("value", |lua, me| lua.create_string(me.lock().value()));
}
}

View file

@ -1,4 +1,4 @@
yazi_macro::mod_flat!(behavior core file files filter finder folder lives mode mut_cell preference preview ptr selected tab tabs task tasks which yanked); yazi_macro::mod_flat!(behavior core file files filter finder folder input input_alt lives mode mut_cell preference preview ptr selected tab tabs task tasks which yanked);
pub(super) fn init() { pub(super) fn init() {
unsafe { FILE_CACHE.get().write(std::mem::MaybeUninit::new(<_>::default())) }; unsafe { FILE_CACHE.get().write(std::mem::MaybeUninit::new(<_>::default())) };

View file

@ -19,6 +19,7 @@ pub struct Border {
pub edge: Edge, pub edge: Edge,
pub r#type: ratatui::widgets::BorderType, pub r#type: ratatui::widgets::BorderType,
pub style: ratatui::style::Style, pub style: ratatui::style::Style,
pub merge: ratatui::symbols::merge::MergeStrategy,
pub titles: Vec<(ratatui::widgets::TitlePosition, ratatui::text::Line<'static>)>, pub titles: Vec<(ratatui::widgets::TitlePosition, ratatui::text::Line<'static>)>,
} }
@ -64,7 +65,8 @@ impl Widget for Border {
let mut block = ratatui::widgets::Block::default() let mut block = ratatui::widgets::Block::default()
.borders(self.edge.0) .borders(self.edge.0)
.border_type(self.r#type) .border_type(self.r#type)
.border_style(self.style); .border_style(self.style)
.merge_borders(self.merge);
for title in self.titles { for title in self.titles {
block = match title { block = match title {
@ -116,5 +118,13 @@ impl UserData for Border {
ud.borrow_mut::<Self>()?.edge = edge; ud.borrow_mut::<Self>()?.edge = edge;
Ok(ud) Ok(ud)
}); });
methods.add_function("merge", |_, (ud, exact): (AnyUserData, bool)| {
ud.borrow_mut::<Self>()?.merge = if exact {
ratatui::symbols::merge::MergeStrategy::Exact
} else {
ratatui::symbols::merge::MergeStrategy::Fuzzy
};
Ok(ud)
});
} }
} }

View file

@ -52,7 +52,7 @@ impl TryFrom<Table> for Input {
realtime: false, realtime: false,
completion: false, completion: false,
}, },
tx: None, cb: None,
})?; })?;
Ok(Self { inner: Arc::new(Mutex::new(input)), ..Default::default() }) Ok(Self { inner: Arc::new(Mutex::new(input)), ..Default::default() })

View file

@ -26,18 +26,18 @@ yazi-shim = { path = "../yazi-shim", version = "26.5.6" }
yazi-version = { path = "../yazi-version", version = "26.5.6" } yazi-version = { path = "../yazi-version", version = "26.5.6" }
# External dependencies # External dependencies
anyhow = { workspace = true } anyhow = { workspace = true }
hashbrown = { workspace = true } hashbrown = { workspace = true }
indexmap = { workspace = true } indexmap = { workspace = true }
inventory = { workspace = true } inventory = { workspace = true }
mlua = { workspace = true } mlua = { workspace = true }
parking_lot = { workspace = true } parking_lot = { workspace = true }
paste = { workspace = true } paste = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
tokio = { workspace = true } tokio = { workspace = true }
tokio-stream = { workspace = true } tokio-stream = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
uds_windows = "1.2.1" uds_windows = "1.2.1"

View file

@ -2,7 +2,7 @@ use anyhow::{Result, bail};
use mlua::{ExternalResult, IntoLua, Lua, Value}; use mlua::{ExternalResult, IntoLua, Lua, Value};
use yazi_shared::Id; use yazi_shared::Id;
use super::{EmberBulkRename, EmberBye, EmberCd, EmberCustom, EmberDelete, EmberDownload, EmberDuplicate, EmberHey, EmberHi, EmberHover, EmberLoad, EmberMount, EmberMove, EmberRename, EmberTab, EmberTrash, EmberYank}; use super::{EmberBulkRename, EmberBye, EmberCd, EmberCustom, EmberDelete, EmberDownload, EmberDuplicate, EmberHey, EmberHi, EmberHover, EmberInput, EmberLoad, EmberMount, EmberMove, EmberRename, EmberTab, EmberTrash, EmberYank};
use crate::Payload; use crate::Payload;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -22,6 +22,7 @@ pub enum Ember<'a> {
Trash(EmberTrash<'a>), Trash(EmberTrash<'a>),
Delete(EmberDelete<'a>), Delete(EmberDelete<'a>),
Download(EmberDownload<'a>), Download(EmberDownload<'a>),
Input(EmberInput<'a>),
Mount(EmberMount), Mount(EmberMount),
Custom(EmberCustom), Custom(EmberCustom),
} }
@ -44,6 +45,7 @@ impl Ember<'static> {
"trash" => Self::Trash(serde_json::from_str(body)?), "trash" => Self::Trash(serde_json::from_str(body)?),
"delete" => Self::Delete(serde_json::from_str(body)?), "delete" => Self::Delete(serde_json::from_str(body)?),
"download" => Self::Download(serde_json::from_str(body)?), "download" => Self::Download(serde_json::from_str(body)?),
"input" => Self::Input(serde_json::from_str(body)?),
"mount" => Self::Mount(serde_json::from_str(body)?), "mount" => Self::Mount(serde_json::from_str(body)?),
_ => EmberCustom::from_str(kind, body)?, _ => EmberCustom::from_str(kind, body)?,
}) })
@ -72,6 +74,7 @@ impl Ember<'static> {
| "trash" | "trash"
| "delete" | "delete"
| "download" | "download"
| "input"
| "mount" | "mount"
) || kind.starts_with("key-") ) || kind.starts_with("key-")
|| kind.starts_with("ind-") || kind.starts_with("ind-")
@ -111,6 +114,7 @@ impl<'a> Ember<'a> {
Self::Trash(_) => "trash", Self::Trash(_) => "trash",
Self::Delete(_) => "delete", Self::Delete(_) => "delete",
Self::Download(_) => "download", Self::Download(_) => "download",
Self::Input(_) => "input",
Self::Mount(_) => "mount", Self::Mount(_) => "mount",
Self::Custom(b) => b.kind.as_str(), Self::Custom(b) => b.kind.as_str(),
} }
@ -139,6 +143,7 @@ impl<'a> IntoLua for Ember<'a> {
Self::Trash(b) => b.into_lua(lua), Self::Trash(b) => b.into_lua(lua),
Self::Delete(b) => b.into_lua(lua), Self::Delete(b) => b.into_lua(lua),
Self::Download(b) => b.into_lua(lua), Self::Download(b) => b.into_lua(lua),
Self::Input(b) => b.into_lua(lua),
Self::Mount(b) => b.into_lua(lua), Self::Mount(b) => b.into_lua(lua),
Self::Custom(b) => b.into_lua(lua), Self::Custom(b) => b.into_lua(lua),
} }

View file

@ -0,0 +1,34 @@
use std::borrow::Cow;
use mlua::{IntoLua, Lua, Value};
use serde::{Deserialize, Serialize};
use super::Ember;
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct EmberInput<'a> {
pub r#type: Cow<'a, str>,
pub value: Cow<'a, str>,
}
impl<'a> EmberInput<'a> {
pub fn borrowed(r#type: &'a str, value: &'a str) -> Ember<'a> {
Self { r#type: r#type.into(), value: value.into() }.into()
}
}
impl EmberInput<'static> {
pub fn owned(r#type: &'static str, value: &str) -> Ember<'static> {
Self { r#type: r#type.into(), value: value.to_owned().into() }.into()
}
}
impl<'a> From<EmberInput<'a>> for Ember<'a> {
fn from(value: EmberInput<'a>) -> Self { Self::Input(value) }
}
impl IntoLua for EmberInput<'_> {
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
lua.create_table_from([("type", self.r#type), ("value", self.value)])?.into_lua(lua)
}
}

View file

@ -1,3 +1,3 @@
yazi_macro::mod_flat!( yazi_macro::mod_flat!(
bulk_rename bye cd custom delete download duplicate ember hey hi hover load mount r#move rename tab trash yank bulk_rename bye cd custom delete download duplicate ember hey hi hover input load mount r#move rename tab trash yank
); );

View file

@ -103,6 +103,7 @@ impl Display for Payload<'_> {
Ember::Trash(b) => serde_json::to_string(b), Ember::Trash(b) => serde_json::to_string(b),
Ember::Delete(b) => serde_json::to_string(b), Ember::Delete(b) => serde_json::to_string(b),
Ember::Download(b) => serde_json::to_string(b), Ember::Download(b) => serde_json::to_string(b),
Ember::Input(b) => serde_json::to_string(b),
Ember::Mount(b) => serde_json::to_string(b), Ember::Mount(b) => serde_json::to_string(b),
Ember::Custom(b) => serde_json::to_string(b), Ember::Custom(b) => serde_json::to_string(b),
}; };

View file

@ -174,5 +174,7 @@ impl Pubsub {
pub_after!(download(urls: Vec<UrlBuf>), (&urls), (urls)); pub_after!(download(urls: Vec<UrlBuf>), (&urls), (urls));
pub_after!(input(r#type: &'static str, value: &str), (r#type, value));
pub_after!(mount(), ()); pub_after!(mount(), ());
} }

View file

@ -249,12 +249,12 @@ impl<'a> Executor<'a> {
} }
fn input(&mut self, action: ActionCow) -> Result<Data> { fn input(&mut self, action: ActionCow) -> Result<Data> {
let Some(mut guard) = self.app.core.input.lock_mut() else { succ!() }; let mut guard;
macro_rules! on { macro_rules! on {
($name:ident) => { ($name:ident) => {
if action.name == stringify!($name) { if action.name == stringify!($name) {
on!(input:$name, action); let cx = &mut Ctx::new(&action, &mut self.app.core, &mut self.app.term)?;
return act!(input:$name, cx, action);
} }
}; };
($layer:ident : $name:ident, $opt:expr) => {{ ($layer:ident : $name:ident, $opt:expr) => {{
@ -268,6 +268,11 @@ impl<'a> Executor<'a> {
on!(show); on!(show);
on!(close); on!(close);
guard = match self.app.core.input.lock_mut() {
Some(g) => g,
None => succ!(),
};
match guard.mode() { match guard.mode() {
InputMode::Normal => match action.name.as_ref() { InputMode::Normal => match action.name.as_ref() {
"help" => on!(help:toggle, Layer::Input), "help" => on!(help:toggle, Layer::Input),
@ -275,9 +280,10 @@ impl<'a> Executor<'a> {
"lua" => on!(app:lua, action), "lua" => on!(app:lua, action),
_ => {} _ => {}
}, },
InputMode::Insert => { InputMode::Insert => match action.name.as_ref() {
on!(complete); "complete" => on!(input:complete, action),
} _ => {}
},
InputMode::Replace => {} InputMode::Replace => {}
}; };

View file

@ -1,8 +1,17 @@
#[macro_export] #[macro_export]
macro_rules! input { macro_rules! input {
($cx:ident, $cfg:expr) => {{ ($cx:ident, $cfg:expr) => {{
use yazi_dds::Pubsub;
use yazi_shim::strum::IntoStr;
use yazi_widgets::input::{InputCallback, InputEvent, InputOpt};
let (tx, rx) = ::tokio::sync::mpsc::unbounded_channel(); let (tx, rx) = ::tokio::sync::mpsc::unbounded_channel();
match $crate::act!(input:show, $cx, yazi_widgets::input::InputOpt { cfg: $cfg, tx: Some(tx) }) { let cb: Box<dyn InputCallback> = Box::new(move |event| {
$crate::err!(Pubsub::pub_after_input((&event).into_str(), event.value()));
tx.send(event).ok();
});
match $crate::act!(input:show, $cx, InputOpt { cfg: $cfg, cb: Some(cb) }) {
Ok(_) => Ok(rx), Ok(_) => Ok(rx),
Err(e) => Err(e) Err(e) => Err(e)
} }

View file

@ -22,6 +22,7 @@ impl Utils {
edge: Edge(ratatui::widgets::Borders::ALL), edge: Edge(ratatui::widgets::Borders::ALL),
r#type: ratatui::widgets::BorderType::Rounded, r#type: ratatui::widgets::BorderType::Rounded,
style: THEME.spot.border.get().into(), style: THEME.spot.border.get().into(),
merge: Default::default(),
titles: vec![( titles: vec![(
ratatui::widgets::TitlePosition::Top, ratatui::widgets::TitlePosition::Top,
ratatui::text::Line::raw("Spot").centered().style(THEME.spot.title.get()), ratatui::text::Line::raw("Spot").centered().style(THEME.spot.title.get()),

View file

@ -17,10 +17,12 @@ yazi-config = { path = "../yazi-config", version = "26.5.6" }
yazi-core = { path = "../yazi-core", version = "26.5.6" } yazi-core = { path = "../yazi-core", version = "26.5.6" }
yazi-macro = { path = "../yazi-macro", version = "26.5.6" } yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
yazi-scheduler = { path = "../yazi-scheduler", version = "26.5.6" } yazi-scheduler = { path = "../yazi-scheduler", version = "26.5.6" }
yazi-dds = { path = "../yazi-dds", version = "26.5.6" }
yazi-shared = { path = "../yazi-shared", version = "26.5.6" } yazi-shared = { path = "../yazi-shared", version = "26.5.6" }
yazi-shim = { path = "../yazi-shim", version = "26.5.6" } yazi-shim = { path = "../yazi-shim", version = "26.5.6" }
yazi-widgets = { path = "../yazi-widgets", version = "26.5.6" } yazi-widgets = { path = "../yazi-widgets", version = "26.5.6" }
# External dependencies # External dependencies
anyhow = { workspace = true } anyhow = { workspace = true }
tokio = { workspace = true } tokio = { workspace = true }
tracing = { workspace = true }

View file

@ -1,14 +1,21 @@
use tokio::sync::mpsc; use tokio::sync::mpsc;
use yazi_config::popup::InputCfg; use yazi_config::popup::InputCfg;
use yazi_macro::{emit, relay}; use yazi_dds::Pubsub;
use yazi_widgets::input::InputEvent; use yazi_macro::{emit, err, relay};
use yazi_shim::strum::IntoStr;
use yazi_widgets::input::{InputCallback, InputEvent};
pub struct InputProxy; pub struct InputProxy;
impl InputProxy { impl InputProxy {
pub fn show(cfg: InputCfg) -> mpsc::UnboundedReceiver<InputEvent> { pub fn show(cfg: InputCfg) -> mpsc::UnboundedReceiver<InputEvent> {
let (tx, rx) = mpsc::unbounded_channel(); let (tx, rx) = mpsc::unbounded_channel();
emit!(Call(relay!(input:show).with_any("tx", tx).with_any("cfg", cfg))); let cb: Box<dyn InputCallback> = Box::new(move |event| {
err!(Pubsub::pub_after_input((&event).into_str(), event.value()));
tx.send(event).ok();
});
emit!(Call(relay!(input:show).with_any("cb", cb).with_any("cfg", cfg)));
rx rx
} }
} }

View file

@ -27,11 +27,13 @@ yazi-tty = { path = "../yazi-tty", version = "26.5.9" }
# External dependencies # External dependencies
anyhow = { workspace = true } anyhow = { workspace = true }
dyn-clone = { workspace = true }
futures = { workspace = true } futures = { workspace = true }
mlua = { workspace = true } mlua = { workspace = true }
parking_lot = { workspace = true } parking_lot = { workspace = true }
ratatui = { workspace = true } ratatui = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
strum = { workspace = true }
tokio = { workspace = true } tokio = { workspace = true }
unicode-width = { workspace = true } unicode-width = { workspace = true }

View file

@ -0,0 +1,22 @@
use std::fmt;
use dyn_clone::DynClone;
use yazi_macro::impl_data_any;
use crate::input::InputEvent;
pub trait InputCallback: Fn(InputEvent) + Send + Sync + DynClone + 'static {}
impl<T: Fn(InputEvent) + Send + Sync + Clone + 'static> InputCallback for T {}
impl Clone for Box<dyn InputCallback> {
fn clone(&self) -> Self { dyn_clone::clone_box(&**self) }
}
impl fmt::Debug for dyn InputCallback {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("InputCallback").finish_non_exhaustive()
}
}
impl_data_any!(Box<dyn InputCallback>);

View file

@ -1,6 +1,7 @@
use strum::IntoStaticStr;
use yazi_shared::Id; use yazi_shared::Id;
#[derive(Debug)] #[derive(Debug, IntoStaticStr)]
pub enum InputEvent { pub enum InputEvent {
Submit(String), Submit(String),
Cancel(String), Cancel(String),
@ -10,5 +11,11 @@ pub enum InputEvent {
} }
impl InputEvent { impl InputEvent {
pub fn value(&self) -> &str {
match self {
Self::Submit(v) | Self::Cancel(v) | Self::Type(v) | Self::Trigger(v, _) => v.as_str(),
}
}
pub fn is_submit(&self) -> bool { matches!(self, Self::Submit(_)) } pub fn is_submit(&self) -> bool { matches!(self, Self::Submit(_)) }
} }

View file

@ -1,7 +1,6 @@
use std::{borrow::Cow, ops::Range}; use std::{borrow::Cow, ops::Range};
use anyhow::Result; use anyhow::Result;
use tokio::sync::mpsc;
use yazi_config::{YAZI, popup::Position}; use yazi_config::{YAZI, popup::Position};
use yazi_macro::act; use yazi_macro::act;
use yazi_shared::Ids; use yazi_shared::Ids;
@ -9,7 +8,7 @@ use yazi_shim::path::CROSS_SEPARATOR;
use yazi_term::CursorStyle; use yazi_term::CursorStyle;
use super::{InputSnap, InputSnaps, mode::InputMode, op::InputOp}; use super::{InputSnap, InputSnaps, mode::InputMode, op::InputOp};
use crate::{CLIPBOARD, input::{InputEvent, InputOpt}}; use crate::{CLIPBOARD, input::{InputCallback, InputEvent, InputOpt}};
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct Input { pub struct Input {
@ -19,7 +18,7 @@ pub struct Input {
pub realtime: bool, pub realtime: bool,
pub completion: bool, pub completion: bool,
pub tx: Option<mpsc::UnboundedSender<InputEvent>>, pub cb: Option<Box<dyn InputCallback>>,
pub ticket: Ids, pub ticket: Ids,
} }
@ -33,7 +32,7 @@ impl Input {
realtime: opt.cfg.realtime, realtime: opt.cfg.realtime,
completion: opt.cfg.completion, completion: opt.cfg.completion,
tx: opt.tx, cb: opt.cb,
..Default::default() ..Default::default()
}; };
@ -93,20 +92,19 @@ impl Input {
pub(super) fn flush_type(&mut self) { pub(super) fn flush_type(&mut self) {
self.ticket.next(); self.ticket.next();
if let Some(tx) = self.tx.as_ref().filter(|_| self.realtime) { if let Some(cb) = self.cb.as_ref().filter(|_| self.realtime) {
tx.send(InputEvent::Type(self.value().to_owned())).ok(); cb(InputEvent::Type(self.value().to_owned()));
} }
self.flush_trigger(true); self.flush_trigger(true);
} }
pub(super) fn flush_trigger(&self, force: bool) { pub(super) fn flush_trigger(&self, force: bool) {
if let Some(tx) = self.tx.as_ref().filter(|_| self.completion) { if let Some(cb) = self.cb.as_ref().filter(|_| self.completion) {
tx.send(InputEvent::Trigger( cb(InputEvent::Trigger(
self.partition().0.to_owned(), self.partition().0.to_owned(),
(!force).then_some(self.ticket.current()), (!force).then_some(self.ticket.current()),
)) ));
.ok();
} }
} }
} }

View file

@ -1,3 +1,3 @@
yazi_macro::mod_pub!(actor parser); yazi_macro::mod_pub!(actor parser);
yazi_macro::mod_flat!(chars event gait input mode op option snap snaps widget); yazi_macro::mod_flat!(callback chars event gait input mode op option snap snaps widget);

View file

@ -1,15 +1,14 @@
use anyhow::bail; use anyhow::bail;
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use tokio::sync::mpsc;
use yazi_config::popup::InputCfg; use yazi_config::popup::InputCfg;
use yazi_shared::event::ActionCow; use yazi_shared::event::ActionCow;
use crate::input::InputEvent; use crate::input::InputCallback;
#[derive(Debug)] #[derive(Debug)]
pub struct InputOpt { pub struct InputOpt {
pub cfg: InputCfg, pub cfg: InputCfg,
pub tx: Option<mpsc::UnboundedSender<InputEvent>>, pub cb: Option<Box<dyn InputCallback>>,
} }
impl TryFrom<ActionCow> for InputOpt { impl TryFrom<ActionCow> for InputOpt {
@ -20,7 +19,7 @@ impl TryFrom<ActionCow> for InputOpt {
bail!("invalid 'cfg' in InputOpt"); bail!("invalid 'cfg' in InputOpt");
}; };
Ok(Self { cfg, tx: a.take_any("tx") }) Ok(Self { cfg, cb: a.take_any("cb") })
} }
} }