fix: make the input component sync and reactive (#3949)

This commit is contained in:
三咲雅 misaki masa 2026-05-09 09:26:07 +08:00 committed by GitHub
parent 3968c4799c
commit 92b9ea3794
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
41 changed files with 192 additions and 146 deletions

48
Cargo.lock generated
View file

@ -312,9 +312,9 @@ dependencies = [
[[package]] [[package]]
name = "avif-serialize" name = "avif-serialize"
version = "0.8.8" version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "375082f007bd67184fb9c0374614b29f9aaa604ec301635f72338bb65386a53d" checksum = "e7178fe5f7d460b13895ebb9dcb28a3a6216d2df2574a0806cb51b555d297f38"
dependencies = [ dependencies = [
"arrayvec", "arrayvec",
] ]
@ -589,9 +589,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.61" version = "1.2.62"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98"
dependencies = [ dependencies = [
"find-msvc-tools", "find-msvc-tools",
"jobserver", "jobserver",
@ -2156,9 +2156,9 @@ dependencies = [
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.97" version = "0.3.98"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"futures-util", "futures-util",
@ -2564,9 +2564,9 @@ dependencies = [
[[package]] [[package]]
name = "no_std_io2" name = "no_std_io2"
version = "0.9.3" version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b51ed7824b6e07d354605f4abb3d9d300350701299da96642ee084f5ce631550" checksum = "418abd1b6d34fbf6cae440dc874771b0525a604428704c76e48b29a5e67b8003"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@ -3379,9 +3379,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]] [[package]]
name = "quick-xml" name = "quick-xml"
version = "0.39.3" version = "0.39.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "721da970c312655cde9b4ffe0547f20a8494866a4af5ff51f18b7c633d0c870b" checksum = "cdcc8dd4e2f670d309a5f0e83fe36dfdc05af317008fea29144da1a2ac858e5e"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@ -3732,9 +3732,9 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
[[package]] [[package]]
name = "rfc6979" name = "rfc6979"
version = "0.5.0-rc.5" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23a3127ee32baec36af75b4107082d9bd823501ec14a4e016be4b6b37faa74ae" checksum = "5236ce872cac07e0fb3969b0cbf468c7d2f37d432f1b627dcb7b8d34563fb0c3"
dependencies = [ dependencies = [
"hmac 0.13.0", "hmac 0.13.0",
"subtle", "subtle",
@ -4696,9 +4696,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.52.2" version = "1.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "110a78583f19d5cdb2c5ccf321d1290344e71313c6c37d43520d386027d18386" checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
dependencies = [ dependencies = [
"bytes", "bytes",
"libc", "libc",
@ -5113,9 +5113,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.120" version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"once_cell", "once_cell",
@ -5126,9 +5126,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-futures" name = "wasm-bindgen-futures"
version = "0.4.70" version = "0.4.71"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084" checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8"
dependencies = [ dependencies = [
"js-sys", "js-sys",
"wasm-bindgen", "wasm-bindgen",
@ -5136,9 +5136,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.120" version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578"
dependencies = [ dependencies = [
"quote", "quote",
"wasm-bindgen-macro-support", "wasm-bindgen-macro-support",
@ -5146,9 +5146,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro-support" name = "wasm-bindgen-macro-support"
version = "0.2.120" version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"proc-macro2", "proc-macro2",
@ -5159,9 +5159,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-shared" name = "wasm-bindgen-shared"
version = "0.2.120" version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]

View file

@ -71,7 +71,7 @@ serde_with = "3.19.0"
strum = { version = "0.28.0", features = [ "derive" ] } strum = { version = "0.28.0", features = [ "derive" ] }
syntect = { version = "5.3.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] } syntect = { version = "5.3.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
thiserror = "2.0.18" thiserror = "2.0.18"
tokio = { version = "1.52.2", features = [ "full" ] } tokio = { version = "1.52.3", features = [ "full" ] }
tokio-stream = "0.1.18" tokio-stream = "0.1.18"
tokio-util = "0.7.18" tokio-util = "0.7.18"
toml = { version = "1.1.2" } toml = { version = "1.1.2" }

View file

@ -65,7 +65,7 @@ impl Actor for BulkRename {
batcher.prime(&tmp); batcher.prime(&tmp);
TasksProxy::process_exec( TasksProxy::process_exec(
cwd.into(), cwd,
Splatter::new(&[UrlCow::default(), tmp.as_url().into()]).splat(&opener.run), Splatter::new(&[UrlCow::default(), tmp.as_url().into()]).splat(&opener.run),
vec![UrlCow::default(), UrlBuf::from(&tmp).into()], vec![UrlCow::default(), UrlBuf::from(&tmp).into()],
opener.block, opener.block,

View file

@ -7,9 +7,9 @@ use yazi_config::popup::InputCfg;
use yazi_core::mgr::CdSource; use yazi_core::mgr::CdSource;
use yazi_dds::Pubsub; use yazi_dds::Pubsub;
use yazi_fs::{File, FilesOp, path::{clean_url, expand_url}}; use yazi_fs::{File, FilesOp, path::{clean_url, expand_url}};
use yazi_macro::{act, err, render, succ}; use yazi_macro::{act, err, input, render, succ};
use yazi_parser::mgr::CdForm; use yazi_parser::mgr::CdForm;
use yazi_proxy::{CmpProxy, InputProxy, MgrProxy}; use yazi_proxy::{CmpProxy, MgrProxy};
use yazi_shared::{Debounce, data::Data, url::{AsUrl, UrlBuf, UrlLike}}; use yazi_shared::{Debounce, data::Data, url::{AsUrl, UrlBuf, UrlLike}};
use yazi_vfs::{VfsFile, provider}; use yazi_vfs::{VfsFile, provider};
use yazi_widgets::input::InputEvent; use yazi_widgets::input::InputEvent;
@ -62,8 +62,8 @@ impl Actor for Cd {
} }
impl Cd { impl Cd {
fn cd_interactive(cx: &Ctx) -> Result<Data> { fn cd_interactive(cx: &mut Ctx) -> Result<Data> {
let input = InputProxy::show(InputCfg::cd(cx.cwd().as_url())); let input = input!(cx, InputCfg::cd(cx.cwd().as_url()))?;
tokio::spawn(async move { tokio::spawn(async move {
let rx = Debounce::new(UnboundedReceiverStream::new(input), Duration::from_millis(50)); let rx = Debounce::new(UnboundedReceiverStream::new(input), Duration::from_millis(50));

View file

@ -1,9 +1,9 @@
use anyhow::{Result, bail}; use anyhow::{Result, bail};
use yazi_config::popup::{ConfirmCfg, InputCfg}; use yazi_config::popup::{ConfirmCfg, InputCfg};
use yazi_fs::{File, FilesOp}; use yazi_fs::{File, FilesOp};
use yazi_macro::{ok_or_not_found, succ}; use yazi_macro::{input, ok_or_not_found, succ};
use yazi_parser::mgr::CreateForm; use yazi_parser::mgr::CreateForm;
use yazi_proxy::{ConfirmProxy, InputProxy, MgrProxy}; use yazi_proxy::{ConfirmProxy, MgrProxy};
use yazi_shared::{data::Data, url::{UrlBuf, UrlLike}}; use yazi_shared::{data::Data, url::{UrlBuf, UrlLike}};
use yazi_vfs::{VfsFile, maybe_exists, provider}; use yazi_vfs::{VfsFile, maybe_exists, provider};
use yazi_watcher::WATCHER; use yazi_watcher::WATCHER;
@ -20,7 +20,7 @@ impl Actor for Create {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> { fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let cwd = cx.cwd().to_owned(); let cwd = cx.cwd().to_owned();
let mut input = InputProxy::show(InputCfg::create(form.dir)); let mut input = input!(cx, InputCfg::create(form.dir))?;
tokio::spawn(async move { tokio::spawn(async move {
let Some(InputEvent::Submit(name)) = input.recv().await else { return }; let Some(InputEvent::Submit(name)) = input.recv().await else { return };

View file

@ -5,9 +5,9 @@ use tokio::pin;
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream}; use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
use yazi_config::popup::InputCfg; use yazi_config::popup::InputCfg;
use yazi_core::mgr::FilterOpt; use yazi_core::mgr::FilterOpt;
use yazi_macro::succ; use yazi_macro::{input, succ};
use yazi_parser::mgr::FilterForm; use yazi_parser::mgr::FilterForm;
use yazi_proxy::{InputProxy, MgrProxy}; use yazi_proxy::MgrProxy;
use yazi_shared::{Debounce, data::Data}; use yazi_shared::{Debounce, data::Data};
use yazi_widgets::input::InputEvent; use yazi_widgets::input::InputEvent;
@ -20,8 +20,8 @@ impl Actor for Filter {
const NAME: &str = "filter"; const NAME: &str = "filter";
fn act(_: &mut Ctx, Self::Form { opt }: Self::Form) -> Result<Data> { fn act(cx: &mut Ctx, Self::Form { opt }: Self::Form) -> Result<Data> {
let input = InputProxy::show(InputCfg::filter()); let input = input!(cx, InputCfg::filter())?;
tokio::spawn(async move { tokio::spawn(async move {
let rx = Debounce::new(UnboundedReceiverStream::new(input), Duration::from_millis(50)); let rx = Debounce::new(UnboundedReceiverStream::new(input), Duration::from_millis(50));

View file

@ -5,9 +5,9 @@ use tokio::pin;
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream}; use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
use yazi_config::popup::InputCfg; use yazi_config::popup::InputCfg;
use yazi_core::mgr::FindDoOpt; use yazi_core::mgr::FindDoOpt;
use yazi_macro::succ; use yazi_macro::{input, succ};
use yazi_parser::mgr::FindForm; use yazi_parser::mgr::FindForm;
use yazi_proxy::{InputProxy, MgrProxy}; use yazi_proxy::MgrProxy;
use yazi_shared::{Debounce, data::Data}; use yazi_shared::{Debounce, data::Data};
use yazi_widgets::input::InputEvent; use yazi_widgets::input::InputEvent;
@ -20,8 +20,8 @@ impl Actor for Find {
const NAME: &str = "find"; const NAME: &str = "find";
fn act(_: &mut Ctx, form: Self::Form) -> Result<Data> { fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let input = InputProxy::show(InputCfg::find(form.prev)); let input = input!(cx, InputCfg::find(form.prev))?;
tokio::spawn(async move { tokio::spawn(async move {
let rx = Debounce::new(UnboundedReceiverStream::new(input), Duration::from_millis(50)); let rx = Debounce::new(UnboundedReceiverStream::new(input), Duration::from_millis(50));

View file

@ -1,8 +1,8 @@
use anyhow::Result; use anyhow::Result;
use yazi_config::popup::ConfirmCfg; use yazi_config::popup::ConfirmCfg;
use yazi_macro::{act, succ}; use yazi_macro::{act, confirm, succ};
use yazi_parser::mgr::RemoveForm; use yazi_parser::mgr::RemoveForm;
use yazi_proxy::{ConfirmProxy, MgrProxy}; use yazi_proxy::MgrProxy;
use yazi_shared::data::Data; use yazi_shared::data::Data;
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};
@ -29,14 +29,17 @@ impl Actor for Remove {
return act!(mgr:remove_do, cx, form); return act!(mgr:remove_do, cx, form);
} }
let confirm = ConfirmProxy::show(if form.permanently { let confirm = confirm!(
ConfirmCfg::delete(&form.targets) cx,
} else { if form.permanently {
ConfirmCfg::trash(&form.targets) ConfirmCfg::delete(&form.targets)
}); } else {
ConfirmCfg::trash(&form.targets)
}
)?;
tokio::spawn(async move { tokio::spawn(async move {
if confirm.await { if confirm.future().await {
MgrProxy::remove_do(form.targets, form.permanently); MgrProxy::remove_do(form.targets, form.permanently);
} }
}); });

View file

@ -2,9 +2,9 @@ use anyhow::Result;
use yazi_config::popup::{ConfirmCfg, InputCfg}; use yazi_config::popup::{ConfirmCfg, InputCfg};
use yazi_dds::Pubsub; use yazi_dds::Pubsub;
use yazi_fs::{File, FilesOp}; use yazi_fs::{File, FilesOp};
use yazi_macro::{act, err, ok_or_not_found, succ}; use yazi_macro::{act, err, input, ok_or_not_found, succ};
use yazi_parser::mgr::RenameForm; use yazi_parser::mgr::RenameForm;
use yazi_proxy::{ConfirmProxy, InputProxy, MgrProxy}; use yazi_proxy::{ConfirmProxy, MgrProxy};
use yazi_shared::{Id, data::Data, url::{UrlBuf, UrlLike}}; use yazi_shared::{Id, data::Data, url::{UrlBuf, UrlLike}};
use yazi_vfs::{VfsFile, maybe_exists, provider}; use yazi_vfs::{VfsFile, maybe_exists, provider};
use yazi_watcher::WATCHER; use yazi_watcher::WATCHER;
@ -42,7 +42,7 @@ impl Actor for Rename {
}; };
let (tab, old) = (cx.tab().id, hovered.url_owned()); let (tab, old) = (cx.tab().id, hovered.url_owned());
let mut input = InputProxy::show(InputCfg::rename().with_value(name).with_cursor(cursor)); let mut input = input!(cx, InputCfg::rename().with_value(name).with_cursor(cursor))?;
tokio::spawn(async move { tokio::spawn(async move {
let Some(InputEvent::Submit(name)) = input.recv().await else { return }; let Some(InputEvent::Submit(name)) = input.recv().await else { return };

View file

@ -6,10 +6,10 @@ use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
use yazi_config::popup::InputCfg; use yazi_config::popup::InputCfg;
use yazi_core::mgr::{CdSource, SearchVia}; use yazi_core::mgr::{CdSource, SearchVia};
use yazi_fs::{FilesOp, cha::Cha}; use yazi_fs::{FilesOp, cha::Cha};
use yazi_macro::{act, succ}; use yazi_macro::{act, input, succ};
use yazi_parser::{VoidForm, mgr::SearchForm}; use yazi_parser::{VoidForm, mgr::SearchForm};
use yazi_plugin::external; use yazi_plugin::external;
use yazi_proxy::{InputProxy, MgrProxy}; use yazi_proxy::MgrProxy;
use yazi_scheduler::NotifyProxy; use yazi_scheduler::NotifyProxy;
use yazi_shared::{data::Data, url::{AsUrl, UrlLike}}; use yazi_shared::{data::Data, url::{AsUrl, UrlLike}};
use yazi_widgets::input::InputEvent; use yazi_widgets::input::InputEvent;
@ -28,7 +28,7 @@ impl Actor for Search {
handle.abort(); handle.abort();
} }
let mut input = InputProxy::show(InputCfg::search(opt.via.into()).with_value(&*opt.subject)); let mut input = input!(cx, InputCfg::search(opt.via.into()).with_value(&*opt.subject))?;
tokio::spawn(async move { tokio::spawn(async move {
if let Some(InputEvent::Submit(subject)) = input.recv().await { if let Some(InputEvent::Submit(subject)) = input.recv().await {

View file

@ -2,9 +2,9 @@ use std::borrow::Cow;
use anyhow::Result; use anyhow::Result;
use yazi_config::popup::InputCfg; use yazi_config::popup::InputCfg;
use yazi_macro::{act, succ}; use yazi_macro::{act, input, succ};
use yazi_parser::mgr::ShellForm; use yazi_parser::mgr::ShellForm;
use yazi_proxy::{InputProxy, TasksProxy}; use yazi_proxy::TasksProxy;
use yazi_scheduler::process::ProcessOpt; use yazi_scheduler::process::ProcessOpt;
use yazi_shared::data::Data; use yazi_shared::data::Data;
use yazi_widgets::input::InputEvent; use yazi_widgets::input::InputEvent;
@ -24,9 +24,11 @@ impl Actor for Shell {
let cwd = form.cwd.take().unwrap_or_else(|| cx.cwd().clone()); let cwd = form.cwd.take().unwrap_or_else(|| cx.cwd().clone());
let selected: Vec<_> = cx.tab().hovered_and_selected().cloned().map(Into::into).collect(); let selected: Vec<_> = cx.tab().hovered_and_selected().cloned().map(Into::into).collect();
let input = form.interactive.then(|| { let input = if form.interactive {
InputProxy::show(InputCfg::shell(form.block).with_value(&*form.run).with_cursor(form.cursor)) Some(input!(cx, InputCfg::shell(form.block).with_value(&*form.run).with_cursor(form.cursor))?)
}); } else {
None
};
tokio::spawn(async move { tokio::spawn(async move {
if let Some(mut rx) = input { if let Some(mut rx) = input {

View file

@ -2,9 +2,9 @@ use std::borrow::Cow;
use anyhow::Result; use anyhow::Result;
use yazi_config::popup::InputCfg; use yazi_config::popup::InputCfg;
use yazi_macro::{act, render, succ}; use yazi_macro::{act, input, render, succ};
use yazi_parser::mgr::TabRenameForm; use yazi_parser::mgr::TabRenameForm;
use yazi_proxy::{InputProxy, MgrProxy}; use yazi_proxy::MgrProxy;
use yazi_shared::data::Data; use yazi_shared::data::Data;
use yazi_widgets::input::InputEvent; use yazi_widgets::input::InputEvent;
@ -27,9 +27,11 @@ impl Actor for TabRename {
succ!(render!()); succ!(render!());
} }
let mut input = InputProxy::show( let mut input = input!(
InputCfg::tab_rename().with_value(form.name.unwrap_or(Cow::Borrowed(&pref.name))), cx,
); InputCfg::tab_rename().with_value(form.name.unwrap_or(Cow::Borrowed(&pref.name)))
)?;
tokio::spawn(async move { tokio::spawn(async move {
if let Some(InputEvent::Submit(name)) = input.recv().await { if let Some(InputEvent::Submit(name)) = input.recv().await {
MgrProxy::tab_rename(tab, name); MgrProxy::tab_rename(tab, name);

View file

@ -63,13 +63,13 @@ impl FromLua for FetcherMatcher {
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> { fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
match value { match value {
Value::Table(t) => t.try_into(), Value::Table(t) => t.try_into(),
_ => return Err("expected a table of FetcherMatcher".into_lua_err()), _ => Err("expected a table of FetcherMatcher".into_lua_err()),
} }
} }
} }
impl IntoLua for FetcherMatcher { impl IntoLua for FetcherMatcher {
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> { fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
Iter::new(self.0.into_iter().map(Fetcher::new), None).into_lua(lua) Iter::new(self.0.map(Fetcher::new), None).into_lua(lua)
} }
} }

View file

@ -73,13 +73,13 @@ impl FromLua for OpenRuleMatcher {
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> { fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
match value { match value {
Value::Table(t) => t.try_into(), Value::Table(t) => t.try_into(),
_ => return Err("expected a table of OpenRuleMatcher".into_lua_err()), _ => Err("expected a table of OpenRuleMatcher".into_lua_err()),
} }
} }
} }
impl IntoLua for OpenRuleMatcher { impl IntoLua for OpenRuleMatcher {
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> { fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
Iter::new(self.0.into_iter().map(OpenRule::new), None).into_lua(lua) Iter::new(self.0.map(OpenRule::new), None).into_lua(lua)
} }
} }

View file

@ -20,7 +20,7 @@ impl UserData for OpenRules {
_ => index, _ => index,
}; };
YAZI.open.insert(index, rule.clone().into()).into_lua_err()?; YAZI.open.insert(index, rule.clone()).into_lua_err()?;
Ok(rule) Ok(rule)
}); });

View file

@ -76,6 +76,6 @@ impl FromLua for OpenerRuleMatcher {
impl IntoLua for OpenerRuleMatcher { impl IntoLua for OpenerRuleMatcher {
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> { fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
Iter::new(self.0.into_iter().map(OpenerRule::new), None).into_lua(lua) Iter::new(self.0.map(OpenerRule::new), None).into_lua(lua)
} }
} }

View file

@ -43,7 +43,7 @@ impl UserData for OpenerRules {
_ => index, _ => index,
}; };
me.inner.insert(index, rule.clone().into()).into_lua_err()?; me.inner.insert(index, rule.clone()).into_lua_err()?;
Ok(rule) Ok(rule)
}); });

View file

@ -63,13 +63,13 @@ impl FromLua for PreloaderMatcher {
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> { fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
match value { match value {
Value::Table(t) => t.try_into(), Value::Table(t) => t.try_into(),
_ => return Err("expected a table of PreloaderMatcher".into_lua_err()), _ => Err("expected a table of PreloaderMatcher".into_lua_err()),
} }
} }
} }
impl IntoLua for PreloaderMatcher { impl IntoLua for PreloaderMatcher {
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> { fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
Iter::new(self.0.into_iter().map(Preloader::new), None).into_lua(lua) Iter::new(self.0.map(Preloader::new), None).into_lua(lua)
} }
} }

View file

@ -73,13 +73,13 @@ impl FromLua for PreviewerMatcher {
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> { fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
match value { match value {
Value::Table(t) => t.try_into(), Value::Table(t) => t.try_into(),
_ => return Err("expected a table of PreviewerMatcher".into_lua_err()), _ => Err("expected a table of PreviewerMatcher".into_lua_err()),
} }
} }
} }
impl IntoLua for PreviewerMatcher { impl IntoLua for PreviewerMatcher {
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> { fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
Iter::new(self.0.into_iter().map(Previewer::new), None).into_lua(lua) Iter::new(self.0.map(Previewer::new), None).into_lua(lua)
} }
} }

View file

@ -63,13 +63,13 @@ impl FromLua for SpotterMatcher {
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> { fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
match value { match value {
Value::Table(t) => t.try_into(), Value::Table(t) => t.try_into(),
_ => return Err("expected a table of SpotterMatcher".into_lua_err()), _ => Err("expected a table of SpotterMatcher".into_lua_err()),
} }
} }
} }
impl IntoLua for SpotterMatcher { impl IntoLua for SpotterMatcher {
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> { fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
Iter::new(self.0.into_iter().map(Spotter::new), None).into_lua(lua) Iter::new(self.0.map(Spotter::new), None).into_lua(lua)
} }
} }

View file

@ -7,7 +7,7 @@ pub struct Color(pub ratatui::style::Color);
impl Color { impl Color {
pub fn compose(lua: &Lua) -> mlua::Result<Value> { pub fn compose(lua: &Lua) -> mlua::Result<Value> {
let new = lua.create_function(|_, (_, color): (Table, Color)| Ok(color))?; let new = lua.create_function(|_, (_, color): (Table, Self)| Ok(color))?;
let color = lua.create_table()?; let color = lua.create_table()?;
color.set_metatable(Some(lua.create_table_from([(MetaMethod::Call.name(), new)])?))?; color.set_metatable(Some(lua.create_table_from([(MetaMethod::Call.name(), new)])?))?;

View file

@ -20,7 +20,7 @@ impl From<yazi_config::Style> for Style {
} }
impl From<Style> for ratatui::style::Style { impl From<Style> for ratatui::style::Style {
fn from(value: Style) -> ratatui::style::Style { value.0 } fn from(value: Style) -> Self { value.0 }
} }
impl From<&SyncCell<yazi_config::Style>> for Style { impl From<&SyncCell<yazi_config::Style>> for Style {

View file

@ -35,7 +35,7 @@ impl Actions {
writeln!(s, " TERM_PROGRAM : {:?}", env::var_os("TERM_PROGRAM"))?; writeln!(s, " TERM_PROGRAM : {:?}", env::var_os("TERM_PROGRAM"))?;
writeln!(s, " TERM_PROGRAM_VERSION: {:?}", env::var_os("TERM_PROGRAM_VERSION"))?; writeln!(s, " TERM_PROGRAM_VERSION: {:?}", env::var_os("TERM_PROGRAM_VERSION"))?;
writeln!(s, " Brand.from_env : {:?}", yazi_emulator::Brand::from_env())?; writeln!(s, " Brand.from_env : {:?}", yazi_emulator::Brand::from_env())?;
writeln!(s, " Emulator.detect : {:?}", &*yazi_emulator::EMULATOR)?; writeln!(s, " Emulator.detect : {:?}", *yazi_emulator::EMULATOR)?;
writeln!(s, "\nAdapter")?; writeln!(s, "\nAdapter")?;
writeln!(s, " Adapter.matches : {:?}", yazi_adapter::ADAPTOR)?; writeln!(s, " Adapter.matches : {:?}", yazi_adapter::ADAPTOR)?;

View file

@ -9,7 +9,7 @@ pub(super) fn init() -> anyhow::Result<()> {
.with_context(|| format!("failed to create packages directory: {packages_dir:?}"))?; .with_context(|| format!("failed to create packages directory: {packages_dir:?}"))?;
let config_dir = Xdg::config_dir(); let config_dir = Xdg::config_dir();
std::fs::create_dir_all(&config_dir) std::fs::create_dir_all(config_dir)
.with_context(|| format!("failed to create config directory: {config_dir:?}"))?; .with_context(|| format!("failed to create config directory: {config_dir:?}"))?;
Ok(()) Ok(())

View file

@ -33,6 +33,6 @@ pub(super) fn has_serde_attr(attrs: &[Attribute], name: &str) -> bool {
a.path().is_ident("serde") a.path().is_ident("serde")
&& a && a
.parse_args_with(Punctuated::<Meta, Comma>::parse_terminated) .parse_args_with(Punctuated::<Meta, Comma>::parse_terminated)
.map_or(false, |n| n.iter().any(|m| m.path().is_ident(name))) .is_ok_and(|n| n.iter().any(|m| m.path().is_ident(name)))
}) })
} }

View file

@ -63,7 +63,7 @@ pub fn build_flavor(light: bool, merge: bool) -> anyhow::Result<theme::Theme> {
)?; )?;
} }
Ok(preset.reshape(light)?) preset.reshape(light)
} }
fn wait_for_key(e: anyhow::Error) -> anyhow::Result<()> { fn wait_for_key(e: anyhow::Error) -> anyhow::Result<()> {

View file

@ -67,8 +67,8 @@ impl Opener {
impl DeserializeOverHook for Opener { impl DeserializeOverHook for Opener {
fn deserialize_over_hook(self) -> Result<Self, toml::de::Error> { fn deserialize_over_hook(self) -> Result<Self, toml::de::Error> {
let mut inner = self.unwrap_unchecked(); let mut inner = self.unwrap_unchecked();
for mut rules in inner.values_mut() { for rules in inner.values_mut() {
*rules = Arc::try_unwrap(mem::take(&mut rules)) *rules = Arc::try_unwrap(mem::take(rules))
.expect("unique opener value arc") .expect("unique opener value arc")
.deserialize_over_hook()? .deserialize_over_hook()?
.into(); .into();

View file

@ -28,7 +28,7 @@ impl DeserializeOverWith for Selector {
self, self,
deserializer: D, deserializer: D,
) -> Result<Self, D::Error> { ) -> Result<Self, D::Error> {
let new = Selector::deserialize(deserializer)?; let new = Self::deserialize(deserializer)?;
Self::new(new.url.or(self.url), new.mime.or(self.mime)).map_err(de::Error::custom) Self::new(new.url.or(self.url), new.mime.or(self.mime)).map_err(de::Error::custom)
} }
} }

View file

@ -7,6 +7,6 @@ pub enum CustomField {
String(String), String(String),
} }
impl From<&CustomField> for CustomField { impl From<&Self> for CustomField {
fn from(value: &CustomField) -> Self { value.clone() } fn from(value: &Self) -> Self { value.clone() }
} }

View file

@ -1,7 +1,7 @@
use std::{sync::atomic::Ordering, time::{Duration, Instant}}; use std::{sync::atomic::Ordering, time::{Duration, Instant}};
use anyhow::Result; use anyhow::Result;
use tokio::{select, time::sleep}; use tokio::{select, sync::mpsc, time::sleep};
use yazi_actor::Ctx; use yazi_actor::Ctx;
use yazi_core::Core; use yazi_core::Core;
use yazi_macro::act; use yazi_macro::act;
@ -14,62 +14,82 @@ pub(crate) struct App {
pub(crate) core: Core, pub(crate) core: Core,
pub(crate) term: Option<Term>, pub(crate) term: Option<Term>,
pub(crate) signals: Signals, pub(crate) signals: Signals,
need_render: u8,
pub(crate) last_render: Instant,
next_render: Option<Duration>,
} }
impl App { impl App {
fn make(term: Term, signals: Signals) -> Result<Self> {
Ok(Self {
core: Core::make(),
term: Some(term),
signals,
need_render: 0,
last_render: Instant::now(),
next_render: None,
})
}
pub(crate) async fn serve() -> Result<()> { pub(crate) async fn serve() -> Result<()> {
let term = Term::start()?; let term = Term::start()?;
let (mut rx, signals) = (Event::take(), Signals::start()?); let (mut rx, signals) = (Event::take(), Signals::start()?);
let mut app = Self { core: Core::make(), term: Some(term), signals }; let mut app = Self::make(term, signals)?;
app.bootstrap()?; app.bootstrap()?;
let mut events = Vec::with_capacity(50);
let (mut timeout, mut need_render, mut last_render) = (None, 0, Instant::now());
macro_rules! drain_events {
() => {
for event in events.drain(..) {
Dispatcher::new(&mut app).dispatch(event);
need_render = NEED_RENDER.load(Ordering::Relaxed);
if need_render == 0 {
continue;
}
timeout = Duration::from_millis(10).checked_sub(last_render.elapsed());
if timeout.is_none() {
app.render(need_render == 2)?;
last_render = Instant::now();
}
}
};
}
loop { loop {
if let Some(t) = timeout.take() { if let Some(t) = app.next_render.take() {
select! { select! {
_ = sleep(t) => { _ = sleep(t) => {
app.render(need_render == 2)?; app.render(app.need_render == 2)?;
last_render = Instant::now();
} }
n = rx.recv_many(&mut events, 50) => { r = app.drain(&mut rx) => if !r? {
if n == 0 { break } break;
drain_events!();
} }
} }
} else if rx.recv_many(&mut events, 50).await != 0 { } else if !app.drain(&mut rx).await? {
drain_events!();
} else {
break; break;
} }
} }
Ok(()) Ok(())
} }
fn bootstrap(&mut self) -> anyhow::Result<Data> { fn bootstrap(&mut self) -> Result<Data> {
let cx = &mut Ctx::active(&mut self.core, &mut self.term); let cx = &mut Ctx::active(&mut self.core, &mut self.term);
act!(app:bootstrap, cx)?; act!(app:bootstrap, cx)?;
self.render(false) self.render(false)
} }
async fn drain(&mut self, rx: &mut mpsc::UnboundedReceiver<Event>) -> Result<bool> {
let Some(event) = rx.recv().await else {
return Ok(false);
};
self.dispatch(event)?;
while let Ok(e) = rx.try_recv() {
self.dispatch(e)?;
}
Ok(true)
}
fn dispatch(&mut self, event: Event) -> Result<()> {
Dispatcher::new(self).dispatch(event);
self.need_render = NEED_RENDER.load(Ordering::Relaxed);
if self.need_render == 0 {
return Ok(());
}
self.next_render = Duration::from_millis(10).checked_sub(self.last_render.elapsed());
if self.next_render.is_none() {
self.render(self.need_render == 2)?;
}
Ok(())
}
} }

View file

@ -1,4 +1,4 @@
use std::sync::atomic::{AtomicU8, Ordering}; use std::{sync::atomic::{AtomicU8, Ordering}, time::Instant};
use anyhow::Result; use anyhow::Result;
use crossterm::{cursor::{MoveTo, SetCursorStyle, Show}, execute, queue, terminal::{BeginSynchronizedUpdate, EndSynchronizedUpdate}}; use crossterm::{cursor::{MoveTo, SetCursorStyle, Show}, execute, queue, terminal::{BeginSynchronizedUpdate, EndSynchronizedUpdate}};
@ -16,6 +16,7 @@ use crate::{app::App, root::Root};
impl App { impl App {
pub(crate) fn render(&mut self, partial: bool) -> Result<Data> { pub(crate) fn render(&mut self, partial: bool) -> Result<Data> {
self.last_render = Instant::now();
NEED_RENDER.store(0, Ordering::Relaxed); NEED_RENDER.store(0, Ordering::Relaxed);
let Some(term) = &mut self.term else { succ!() }; let Some(term) = &mut self.term else { succ!() };

View file

@ -20,8 +20,8 @@ impl<'a> Dispatcher<'a> {
pub(super) fn dispatch(&mut self, event: Event) { pub(super) fn dispatch(&mut self, event: Event) {
let result = match event { let result = match event {
Event::Call(action) => self.dispatch_call(action), Event::Call(action) => Ok(self.dispatch_call(action)),
Event::Seq(actions) => self.dispatch_seq(actions), Event::Seq(actions) => Ok(self.dispatch_seq(actions)),
Event::Render(partial) => self.dispatch_render(partial), Event::Render(partial) => self.dispatch_render(partial),
Event::Key(key) => self.dispatch_key(key), Event::Key(key) => self.dispatch_key(key),
Event::Mouse(mouse) => self.dispatch_mouse(mouse), Event::Mouse(mouse) => self.dispatch_mouse(mouse),
@ -35,7 +35,7 @@ impl<'a> Dispatcher<'a> {
} }
} }
fn dispatch_call(&mut self, action: ActionCow) -> Result<()> { fn dispatch_call(&mut self, action: ActionCow) {
let tx = action.replier().cloned(); let tx = action.replier().cloned();
let result = Executor::new(self.app).execute(action); let result = Executor::new(self.app).execute(action);
@ -45,17 +45,15 @@ impl<'a> Dispatcher<'a> {
if let Some(tx) = tx { if let Some(tx) = tx {
tx.send(result).ok(); tx.send(result).ok();
} }
Ok(())
} }
fn dispatch_seq(&mut self, mut actions: Vec<ActionCow>) -> Result<()> { pub(super) fn dispatch_seq(&mut self, mut actions: Vec<ActionCow>) {
if let Some(last) = actions.pop() { if let Some(last) = actions.pop() {
self.dispatch_call(last)?; self.dispatch_call(last);
} }
if !actions.is_empty() { if !actions.is_empty() {
emit!(Seq(actions)); emit!(Seq(actions));
} }
Ok(())
} }
fn dispatch_render(&mut self, partial: bool) -> Result<()> { fn dispatch_render(&mut self, partial: bool) -> Result<()> {

View file

@ -20,7 +20,7 @@ impl Logs {
} }
let state_dir = Xdg::state_dir(); let state_dir = Xdg::state_dir();
std::fs::create_dir_all(&state_dir) std::fs::create_dir_all(state_dir)
.with_context(|| format!("failed to create state directory: {state_dir:?}"))?; .with_context(|| format!("failed to create state directory: {state_dir:?}"))?;
let log_path = state_dir.join("yazi.log"); let log_path = state_dir.join("yazi.log");

View file

@ -1,10 +1,10 @@
use anyhow::Result; use anyhow::Result;
use yazi_actor::Ctx; use yazi_actor::Ctx;
use yazi_config::{KEYMAP, keymap::{Chord, ChordCow, Key}}; use yazi_config::{KEYMAP, keymap::{Chord, ChordCow, Key}};
use yazi_macro::{act, emit}; use yazi_macro::act;
use yazi_shared::Layer; use yazi_shared::Layer;
use crate::app::App; use crate::{Dispatcher, app::App};
pub(super) struct Router<'a> { pub(super) struct Router<'a> {
app: &'a mut App, app: &'a mut App,
@ -45,7 +45,7 @@ impl<'a> Router<'a> {
let cx = &mut Ctx::active(&mut self.app.core, &mut self.app.term); let cx = &mut Ctx::active(&mut self.app.core, &mut self.app.term);
act!(which:activate, cx, (layer, key)).ok(); act!(which:activate, cx, (layer, key)).ok();
} else { } else {
emit!(Seq(ChordCow::from(chord).into_seq())); Dispatcher::new(self.app).dispatch_seq(ChordCow::from(chord).into_seq());
} }
return true; return true;
} }

View file

@ -91,7 +91,7 @@ impl Xdg {
let mut p = env::var_os("XDG_RUNTIME_DIR") let mut p = env::var_os("XDG_RUNTIME_DIR")
.map(PathBuf::from) .map(PathBuf::from)
.filter(|p| p.is_absolute()) .filter(|p| p.is_absolute())
.unwrap_or_else(|| env::temp_dir()); .unwrap_or_else(env::temp_dir);
let uid = unix_either!( let uid = unix_either!(
{ {

View file

@ -1,8 +1,8 @@
#[macro_export] #[macro_export]
macro_rules! act { macro_rules! act {
(@pre $layer:ident : $name:ident, $cx:ident, $opt:ident) => { (@pre $layer:ident : $name:ident, $cx:ident, $opt:ident) => {
if let Some(hook) = <act!($layer:$name) as yazi_actor::Actor>::hook($cx, &$opt) { if let Some(hook) = <$crate::act!($layer:$name) as yazi_actor::Actor>::hook($cx, &$opt) {
<act!(core:preflight)>::act($cx, (hook, yazi_parser::spark!($layer:$name, $opt))).map(|spark| spark.try_into().unwrap()) <$crate::act!(core:preflight)>::act($cx, (hook, yazi_parser::spark!($layer:$name, $opt))).map(|spark| spark.try_into().unwrap())
} else { } else {
Ok($opt) Ok($opt)
} }
@ -12,8 +12,8 @@ macro_rules! act {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
$cx.backtrace.push(concat!(stringify!($layer), ":", stringify!($name))); $cx.backtrace.push(concat!(stringify!($layer), ":", stringify!($name)));
let result = match act!(@pre $layer:$name, $cx, $opt) { let result = match $crate::act!(@pre $layer:$name, $cx, $opt) {
Ok(opt) => <act!($layer:$name) as yazi_actor::Actor>::act($cx, opt), Ok(opt) => <$crate::act!($layer:$name) as yazi_actor::Actor>::act($cx, opt),
Err(e) => Err(e), Err(e) => Err(e),
}; };
@ -25,12 +25,12 @@ macro_rules! act {
}}; }};
($layer:ident : $name:ident, $cx:ident, $action:expr) => { ($layer:ident : $name:ident, $cx:ident, $action:expr) => {
<act!($layer:$name) as yazi_actor::Actor>::Form::try_from($action) <$crate::act!($layer:$name) as yazi_actor::Actor>::Form::try_from($action)
.map_err(anyhow::Error::from) .map_err(anyhow::Error::from)
.and_then(|opt| act!(@impl $layer:$name, $cx, opt)) .and_then(|opt| $crate::act!(@impl $layer:$name, $cx, opt))
}; };
($layer:ident : $name:ident, $cx:ident) => { ($layer:ident : $name:ident, $cx:ident) => {
act!($layer:$name, $cx, <<act!($layer:$name) as yazi_actor::Actor>::Form as Default>::default()) $crate::act!($layer:$name, $cx, <<$crate::act!($layer:$name) as yazi_actor::Actor>::Form as Default>::default())
}; };
($layer:ident : $name:ident) => { ($layer:ident : $name:ident) => {
paste::paste! { yazi_actor::$layer::[<$name:camel>] } paste::paste! { yazi_actor::$layer::[<$name:camel>] }

10
yazi-macro/src/confirm.rs Normal file
View file

@ -0,0 +1,10 @@
#[macro_export]
macro_rules! confirm {
($cx:ident, $cfg:expr) => {{
let token = yazi_shared::CompletionToken::default();
match $crate::act!(confirm:show, $cx, yazi_parser::confirm::ShowForm { cfg: $cfg, token: token.clone() }) {
Ok(_) => Ok(token),
Err(e) => Err(e)
}
}};
}

10
yazi-macro/src/input.rs Normal file
View file

@ -0,0 +1,10 @@
#[macro_export]
macro_rules! input {
($cx:ident, $cfg:expr) => {{
let (tx, rx) = ::tokio::sync::mpsc::unbounded_channel();
match $crate::act!(input:show, $cx, yazi_widgets::input::InputOpt { cfg: $cfg, tx }) {
Ok(_) => Ok(rx),
Err(e) => Err(e)
}
}};
}

View file

@ -1,9 +1,11 @@
mod actor; mod actor;
mod asset; mod asset;
mod confirm;
mod context; mod context;
mod event; mod event;
mod fmt; mod fmt;
mod fs; mod fs;
mod input;
mod log; mod log;
mod module; mod module;
mod platform; mod platform;

View file

@ -83,7 +83,7 @@ impl From<&str> for Data {
impl<T> FromIterator<T> for Data impl<T> FromIterator<T> for Data
where where
T: Into<Data>, T: Into<Self>,
{ {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self { fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
Self::List(iter.into_iter().map(Into::into).collect()) Self::List(iter.into_iter().map(Into::into).collect())

View file

@ -8,9 +8,7 @@ pub struct NonEmptyString(String);
impl NonEmptyString { impl NonEmptyString {
#[inline] #[inline]
pub fn new(value: String) -> Option<Self> { pub fn new(value: String) -> Option<Self> { Some(Self(value)).filter(|s| !s.is_empty()) }
Some(NonEmptyString(value)).filter(|s| !s.is_empty())
}
} }
impl Deref for NonEmptyString { impl Deref for NonEmptyString {