feat: hover cursor over the new file after copying/cutting/linking/hardlinking (#3846)

This commit is contained in:
三咲雅 misaki masa 2026-04-03 20:45:34 +08:00 committed by GitHub
parent ad655eda52
commit 0cedbd9c7b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
96 changed files with 383 additions and 360 deletions

View file

@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
- Custom tab name ([#3666])
- New `--in` for `search` action to set search directory ([#3696])
- Hover cursor over the new file after copying/cutting/linking/hardlinking ([#3846])
- Multi-file spotter ([#3733])
- Vim-like `lua` action that runs an inline Lua snippet ([#3813])
- Certificate authentication for SFTP VFS provider ([#3716])
@ -1694,3 +1695,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
[#3792]: https://github.com/sxyazi/yazi/pull/3792
[#3804]: https://github.com/sxyazi/yazi/pull/3804
[#3813]: https://github.com/sxyazi/yazi/pull/3813
[#3846]: https://github.com/sxyazi/yazi/pull/3846

View file

@ -37,6 +37,7 @@ debug = false
[workspace.dependencies]
ansi-to-tui = "8.0.1"
anyhow = "1.0.102"
arc-swap = "1.9.0"
base64 = "0.22.1"
bitflags = { version = "2.11.0", features = [ "serde" ] }
chrono = "0.4.44"

View file

@ -13,10 +13,10 @@ impl Actor for Deprecate {
const NAME: &str = "deprecate";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
act!(notify:push, cx, MessageOpt {
title: "Deprecated API".to_owned(),
content: opt.content.into_owned(),
content: form.content.into_owned(),
level: MessageLevel::Warn,
timeout: std::time::Duration::from_secs(20),
})

View file

@ -15,8 +15,8 @@ impl Actor for Lua {
const NAME: &str = "lua";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
let chunk = LUA.load(&*opt.code).set_name("anonymous");
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let chunk = LUA.load(&*form.code).set_name("anonymous");
let result = Lives::scope(cx.core, || {
runtime_scope!(LUA, "inline", Sendable::value_to_data(&LUA, chunk.eval()?))
});

View file

@ -19,8 +19,8 @@ impl Actor for Mouse {
const NAME: &str = "mouse";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
let event = yazi_binding::MouseEvent::from(opt.event);
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let event = yazi_binding::MouseEvent::from(form.event);
let Some(size) = cx.term.as_ref().and_then(|t| t.size().ok()) else { succ!() };
let area = yazi_binding::elements::Rect::from(size);

View file

@ -17,12 +17,12 @@ impl Actor for Reflow {
const NAME: &str = "reflow";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let Some(size) = cx.term.as_ref().and_then(|t| t.size().ok()) else { succ!() };
let mut layout = LAYOUT.get();
let result = Lives::scope(cx.core, || {
let comps = (opt.reflow)((Position::ORIGIN, size).into())?;
let comps = (form.reflow)((Position::ORIGIN, size).into())?;
for v in comps.sequence_values::<Value>() {
let Value::Table(t) = v? else {

View file

@ -13,8 +13,8 @@ impl Actor for Resize {
const NAME: &str = "resize";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
act!(app:reflow, cx, opt)?;
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
act!(app:reflow, cx, form)?;
cx.current_mut().arrow(0);
cx.parent_mut().map(|f| f.arrow(0));

View file

@ -13,15 +13,15 @@ impl Actor for Resume {
const NAME: &str = "resume";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
cx.active_mut().preview.reset();
*cx.term = Some(Term::start()?);
// While the app resumes, it's possible that the terminal size has changed.
// We need to trigger a resize, and render the UI based on the resized area.
act!(app:resize, cx, opt.reflow)?;
act!(app:resize, cx, form.reflow)?;
opt.tx.send((true, opt.token))?;
form.tx.send((true, form.replier))?;
act!(app:title, cx).ok();
succ!(render!());

View file

@ -12,7 +12,7 @@ impl Actor for Stop {
const NAME: &str = "stop";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
cx.active_mut().preview.reset_image();
// We need to destroy the `term` first before stopping the `signals`
@ -20,7 +20,7 @@ impl Actor for Stop {
// while the app is being suspended.
*cx.term = None;
opt.tx.send((false, opt.token))?;
form.tx.send((false, form.replier))?;
succ!();
}

View file

@ -16,15 +16,15 @@ impl Actor for Title {
const NAME: &str = "title";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
let s = opt.value.unwrap_or_else(|| format!("Yazi: {}", cx.tab().name()).into());
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let s = form.value.unwrap_or_else(|| format!("Yazi: {}", cx.tab().name()).into());
execute!(TTY.writer(), SetTitle(&s))?;
yazi_term::STATE.set(TermState { title: !s.is_empty(), ..yazi_term::STATE.get() });
succ!()
}
fn hook(cx: &Ctx, _opt: &Self::Form) -> Option<SparkKind> {
fn hook(cx: &Ctx, _form: &Self::Form) -> Option<SparkKind> {
match cx.source() {
Source::Ind => Some(SparkKind::IndAppTitle),
_ => None,

View file

@ -13,11 +13,11 @@ impl Actor for UpdateProgress {
const NAME: &str = "update_progress";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
// Update the progress of all tasks.
let tasks = &mut cx.tasks;
let progressed = tasks.summary != opt.summary;
tasks.summary = opt.summary;
let progressed = tasks.summary != form.summary;
tasks.summary = form.summary;
// If the task manager is visible, update the snaps with a full render.
if tasks.visible {

View file

@ -13,7 +13,7 @@ impl Actor for Arrow {
const NAME: &str = "arrow";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
succ!(render!(cx.cmp.scroll(opt.step)));
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
succ!(render!(cx.cmp.scroll(form.step)));
}
}

View file

@ -15,9 +15,9 @@ impl Actor for Close {
const NAME: &str = "close";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let cmp = &mut cx.cmp;
if let Some(item) = cmp.selected().filter(|_| opt.submit).cloned() {
if let Some(item) = cmp.selected().filter(|_| form.submit).cloned() {
return act!(input:complete, cx, CompleteOpt { name: item.name, is_dir: item.is_dir, ticket: cmp.ticket });
}

View file

@ -18,15 +18,15 @@ impl Actor for Trigger {
const NAME: &str = "trigger";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
if opt.ticket.is_some_and(|t| t != cx.cmp.ticket) {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
if form.ticket.is_some_and(|t| t != cx.cmp.ticket) {
succ!();
} else if opt.ticket.is_none() {
} else if form.ticket.is_none() {
cx.cmp.ticket = cx.input.ticket.current();
}
cx.cmp.handle.take().map(|h| h.abort());
let Some((parent, word)) = Self::split_url(&opt.word) else {
let Some((parent, word)) = Self::split_url(&form.word) else {
return act!(cmp:close, cx, false);
};

View file

@ -12,14 +12,14 @@ impl Actor for Arrow {
const NAME: &str = "arrow";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let confirm = &mut cx.core.confirm;
let area = cx.core.mgr.area(confirm.position);
let len = confirm.list.line_count(area.width);
let old = confirm.offset;
confirm.offset = opt.step.add(confirm.offset, len, area.height as _);
confirm.offset = form.step.add(confirm.offset, len, area.height as _);
succ!(render!(old != confirm.offset));
}

View file

@ -12,8 +12,8 @@ impl Actor for Close {
const NAME: &str = "close";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
cx.confirm.token.complete(opt.submit);
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
cx.confirm.token.complete(form.submit);
cx.confirm.visible = false;
succ!(render!());
}

View file

@ -12,18 +12,18 @@ impl Actor for Show {
const NAME: &str = "show";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
act!(confirm:close, cx)?;
let confirm = &mut cx.confirm;
confirm.title = opt.cfg.title;
confirm.body = opt.cfg.body;
confirm.list = opt.cfg.list;
confirm.title = form.cfg.title;
confirm.body = form.cfg.body;
confirm.list = form.cfg.list;
confirm.position = opt.cfg.position;
confirm.position = form.cfg.position;
confirm.offset = 0;
confirm.token = opt.token;
confirm.token = form.token;
confirm.visible = true;
succ!(render!());

View file

@ -13,7 +13,7 @@ impl Actor for Arrow {
const NAME: &str = "arrow";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
succ!(render!(cx.help.scroll(opt.step)));
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
succ!(render!(cx.help.scroll(form.step)));
}
}

View file

@ -12,11 +12,11 @@ impl Actor for Toggle {
const NAME: &str = "toggle";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let help = &mut cx.help;
help.visible = !help.visible;
help.layer = opt.layer;
help.layer = form.layer;
help.keyword = String::new();
help.in_filter = None;

View file

@ -13,14 +13,14 @@ impl Actor for Close {
const NAME: &str = "close";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let input = &mut cx.input;
input.visible = false;
input.ticket.next();
if let Some(tx) = input.tx.take() {
let value = input.snap().value.clone();
_ = tx.send(if opt.submit { InputEvent::Submit(value) } else { InputEvent::Cancel(value) });
_ = tx.send(if form.submit { InputEvent::Submit(value) } else { InputEvent::Cancel(value) });
}
act!(cmp:close, cx)?;

View file

@ -12,12 +12,12 @@ impl Actor for Complete {
const NAME: &str = "complete";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let input = &mut cx.input;
if !input.visible || input.ticket.current() != opt.ticket {
if !input.visible || input.ticket.current() != form.ticket {
succ!();
}
act!(complete, input, opt)
act!(complete, input, form)
}
}

View file

@ -14,14 +14,14 @@ impl Actor for Show {
const NAME: &str = "show";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
act!(input:close, cx)?;
let input = &mut cx.input;
input.visible = true;
input.title = opt.cfg.title.clone();
input.position = opt.cfg.position;
*input.deref_mut() = yazi_widgets::input::Input::new(opt)?;
input.title = form.cfg.title.clone();
input.position = form.cfg.position;
*input.deref_mut() = yazi_widgets::input::Input::new(form)?;
succ!(render!());
}

View file

@ -12,12 +12,15 @@ impl Actor for Arrow {
const NAME: &str = "arrow";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let tab = cx.tab_mut();
if !tab.current.arrow(opt.step) {
if !tab.current.arrow(form.step) {
succ!();
}
// Retrace
tab.current.retrace();
// Visual selection
if let Some((start, items)) = tab.mode.visual_mut() {
let end = tab.current.cursor;
@ -28,7 +31,7 @@ impl Actor for Arrow {
act!(mgr:peek, cx)?;
act!(mgr:watch, cx)?;
// cx.tasks.scheduler.batch.next(); // FIXME: clear user action
cx.tasks.scheduler.behavior.reset();
succ!(render!());
}
}

View file

@ -12,8 +12,8 @@ impl Actor for BulkExit {
const NAME: &str = "bulk_exit";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
cx.mgr.batcher.decide(opt.target, opt.accept);
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
cx.mgr.batcher.decide(form.target, form.accept);
succ!();
}
}

View file

@ -22,14 +22,14 @@ impl Actor for Cd {
const NAME: &str = "cd";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
act!(mgr:escape_visual, cx)?;
if opt.interactive {
if form.interactive {
return Self::cd_interactive(cx);
}
let tab = cx.tab_mut();
if opt.target == *tab.cwd() {
if form.target == *tab.cwd() {
succ!();
}
@ -39,12 +39,12 @@ impl Actor for Cd {
}
// Current
let rep = tab.history.remove_or(&opt.target);
let rep = tab.history.remove_or(&form.target);
let rep = mem::replace(&mut tab.current, rep);
tab.history.insert(rep.url.clone(), rep);
// Parent
if let Some(parent) = opt.target.parent() {
if let Some(parent) = form.target.parent() {
tab.parent = Some(tab.history.remove_or(parent));
}
@ -54,7 +54,7 @@ impl Actor for Cd {
act!(mgr:sort, cx).ok();
act!(mgr:hover, cx)?;
act!(mgr:refresh, cx)?;
act!(mgr:stash, cx, opt).ok();
act!(mgr:stash, cx, form).ok();
act!(app:title, cx).ok();
succ!(render!());
}

View file

@ -13,11 +13,11 @@ impl Actor for Copy {
const NAME: &str = "copy";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
act!(mgr:escape_visual, cx)?;
let mut s = Vec::<u8>::new();
let mut it = if opt.hovered {
let mut it = if form.hovered {
Box::new(cx.hovered().map(|h| &h.url).into_iter())
} else {
cx.tab().selected_or_hovered()
@ -25,23 +25,23 @@ impl Actor for Copy {
.peekable();
while let Some(u) = it.next() {
match opt.r#type.as_ref() {
match form.r#type.as_ref() {
// TODO: rename to "url"
"path" => {
s.extend_from_slice(&opt.separator.transform(&u.to_strand()));
s.extend_from_slice(&form.separator.transform(&u.to_strand()));
}
"dirname" => {
if let Some(p) = u.parent() {
s.extend_from_slice(&opt.separator.transform(&p.to_strand()));
s.extend_from_slice(&form.separator.transform(&p.to_strand()));
}
}
"filename" => {
s.extend_from_slice(&opt.separator.transform(&u.name().unwrap_or_default()));
s.extend_from_slice(&form.separator.transform(&u.name().unwrap_or_default()));
}
"name_without_ext" => {
s.extend_from_slice(&opt.separator.transform(&u.stem().unwrap_or_default()));
s.extend_from_slice(&form.separator.transform(&u.stem().unwrap_or_default()));
}
_ => bail!("Unknown copy type: {}", opt.r#type),
_ => bail!("Unknown copy type: {}", form.r#type),
};
if it.peek().is_some() {
s.push(b'\n');
@ -49,8 +49,8 @@ impl Actor for Copy {
}
// Copy the CWD path regardless even if the directory is empty
if s.is_empty() && opt.r#type == "dirname" {
s.extend_from_slice(&opt.separator.transform(&cx.cwd().to_strand()));
if s.is_empty() && form.r#type == "dirname" {
s.extend_from_slice(&form.separator.transform(&cx.cwd().to_strand()));
}
futures::executor::block_on(CLIPBOARD.set(s));

View file

@ -18,9 +18,9 @@ impl Actor for Create {
const NAME: &str = "create";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let cwd = cx.cwd().to_owned();
let mut input = InputProxy::show(InputCfg::create(opt.dir));
let mut input = InputProxy::show(InputCfg::create(form.dir));
tokio::spawn(async move {
let Some(InputEvent::Submit(name)) = input.recv().await else { return };
@ -32,14 +32,14 @@ impl Actor for Create {
return;
};
if !opt.force
if !form.force
&& maybe_exists(&new).await
&& !ConfirmProxy::show(ConfirmCfg::overwrite(&new)).await
{
return;
}
_ = Self::r#do(new, opt.dir || name.ends_with('/') || name.ends_with('\\')).await;
_ = Self::r#do(new, form.dir || name.ends_with('/') || name.ends_with('\\')).await;
});
succ!();
}

View file

@ -20,15 +20,15 @@ impl Actor for Download {
const NAME: &str = "download";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let cwd = cx.cwd().clone();
let scheduler = cx.tasks.scheduler.clone();
tokio::spawn(async move {
Self::prepare(&opt.urls).await;
Self::prepare(&form.urls).await;
let mut wg1 = FuturesUnordered::new();
for url in opt.urls {
for url in form.urls {
let done = scheduler.file_download(url.to_owned());
wg1.push(async move { (done.future().await, url) });
}
@ -58,7 +58,7 @@ impl Actor for Download {
if futures::future::join_all(wg2).await.into_iter().any(|b| !b) {
return;
}
if opt.open && !urls.is_empty() {
if form.open && !urls.is_empty() {
MgrProxy::open(OpenOpt {
cwd: Some(cwd.into()),
targets: urls,

View file

@ -13,8 +13,8 @@ impl Actor for Escape {
const NAME: &str = "escape";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
if opt.is_empty() {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
if form.is_empty() {
_ = act!(mgr:escape_find, cx)? != false
|| act!(mgr:escape_visual, cx)? != false
|| act!(mgr:escape_filter, cx)? != false
@ -23,19 +23,19 @@ impl Actor for Escape {
succ!();
}
if opt.contains(EscapeForm::FIND) {
if form.contains(EscapeForm::FIND) {
act!(mgr:escape_find, cx)?;
}
if opt.contains(EscapeForm::VISUAL) {
if form.contains(EscapeForm::VISUAL) {
act!(mgr:escape_visual, cx)?;
}
if opt.contains(EscapeForm::FILTER) {
if form.contains(EscapeForm::FILTER) {
act!(mgr:escape_filter, cx)?;
}
if opt.contains(EscapeForm::SELECT) {
if form.contains(EscapeForm::SELECT) {
act!(mgr:escape_select, cx)?;
}
if opt.contains(EscapeForm::SEARCH) {
if form.contains(EscapeForm::SEARCH) {
act!(mgr:escape_search, cx)?;
}
succ!();

View file

@ -20,15 +20,15 @@ impl Actor for Find {
const NAME: &str = "find";
fn act(_: &mut Ctx, opt: Self::Form) -> Result<Data> {
let input = InputProxy::show(InputCfg::find(opt.prev));
fn act(_: &mut Ctx, form: Self::Form) -> Result<Data> {
let input = InputProxy::show(InputCfg::find(form.prev));
tokio::spawn(async move {
let rx = Debounce::new(UnboundedReceiverStream::new(input), Duration::from_millis(50));
pin!(rx);
while let Some(InputEvent::Submit(s) | InputEvent::Type(s)) = rx.next().await {
MgrProxy::find_do(FindDoOpt { query: s.into(), prev: opt.prev, case: opt.case });
MgrProxy::find_do(FindDoOpt { query: s.into(), prev: form.prev, case: form.case });
}
});
succ!();

View file

@ -12,12 +12,12 @@ impl Actor for FindArrow {
const NAME: &str = "find_arrow";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let tab = cx.tab_mut();
let Some(finder) = &mut tab.finder else { succ!() };
render!(finder.catchup(&tab.current));
let offset = if opt.prev {
let offset = if form.prev {
finder.prev(&tab.current.files, tab.current.cursor, false)
} else {
finder.next(&tab.current.files, tab.current.cursor, false)

View file

@ -12,12 +12,12 @@ impl Actor for Hardlink {
const NAME: &str = "hardlink";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let mgr = &mut cx.core.mgr;
let tab = &mgr.tabs[cx.tab];
if !mgr.yanked.cut {
cx.core.tasks.file_hardlink(&mgr.yanked, tab.cwd(), opt.force, opt.follow);
cx.core.tasks.file_hardlink(&mgr.yanked, tab.cwd(), form.force, form.follow);
}
succ!();

View file

@ -14,8 +14,8 @@ impl Actor for Hidden {
const NAME: &str = "hidden";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
let state = opt.state.bool(cx.tab().pref.show_hidden);
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let state = form.state.bool(cx.tab().pref.show_hidden);
cx.tab_mut().pref.show_hidden = state;
let hovered = cx.hovered().map(|f| f.urn().to_owned());

View file

@ -13,7 +13,7 @@ impl Actor for Hover {
const NAME: &str = "hover";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let tab = tab!(cx);
// Parent should always track CWD
@ -22,16 +22,16 @@ impl Actor for Hover {
}
// Repos CWD
render!(tab.current.repos(opt.urn.as_ref().map(Into::into)));
render!(tab.current.repos(form.urn.as_ref().map(Into::into)));
// Turn on tracing
if let (Some(h), Some(u)) = (tab.hovered(), opt.urn)
if let (Some(h), Some(u)) = (tab.hovered(), form.urn)
&& h.urn() == u
{
// `hover(Some)` occurs after user actions, such as create, rename, reveal, etc.
// At this point, it's intuitive to track the file location regardless.
tab.current.trace = Some(u.clone());
// cx.tasks.scheduler.batch.next();// FIXME: clear user action
cx.tasks.scheduler.behavior.reset();
}
// Publish through DDS

View file

@ -12,11 +12,11 @@ impl Actor for Linemode {
const NAME: &str = "linemode";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let tab = cx.tab_mut();
if opt.new != tab.pref.linemode {
tab.pref.linemode = opt.new.into_owned();
if form.new != tab.pref.linemode {
tab.pref.linemode = form.new.into_owned();
render!();
}

View file

@ -12,12 +12,12 @@ impl Actor for Link {
const NAME: &str = "link";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let mgr = &mut cx.core.mgr;
let tab = &mgr.tabs[cx.tab];
if !mgr.yanked.cut {
cx.core.tasks.file_link(&mgr.yanked, tab.cwd(), opt.relative, opt.force);
cx.core.tasks.file_link(&mgr.yanked, tab.cwd(), form.relative, form.force);
}
succ!();

View file

@ -12,18 +12,18 @@ impl Actor for Paste {
const NAME: &str = "paste";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let mgr = &mut cx.core.mgr;
let tab = &mgr.tabs[cx.tab];
let dest = tab.cwd();
if mgr.yanked.cut {
cx.core.tasks.file_cut(&mgr.yanked, dest, opt.force);
cx.core.tasks.file_cut(&mgr.yanked, dest, form.force);
mgr.tabs.iter_mut().for_each(|t| _ = t.selected.remove_many(&*mgr.yanked));
act!(mgr:unyank, cx)
} else {
succ!(cx.core.tasks.file_copy(&mgr.yanked, dest, opt.force, opt.follow));
succ!(cx.core.tasks.file_copy(&mgr.yanked, dest, form.force, form.follow));
}
}
}

View file

@ -12,7 +12,7 @@ impl Actor for Peek {
const NAME: &str = "peek";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let Some(hovered) = cx.hovered().cloned() else {
succ!(cx.tab_mut().preview.reset());
};
@ -33,13 +33,13 @@ impl Actor for Peek {
cx.tab_mut().preview.folder_lock = None;
}
if matches!(opt.only_if, Some(u) if u != hovered.url) {
if matches!(form.only_if, Some(u) if u != hovered.url) {
succ!();
}
if let Some(skip) = opt.skip {
if let Some(skip) = form.skip {
let preview = &mut cx.tab_mut().preview;
if opt.upper_bound {
if form.upper_bound {
preview.skip = preview.skip.min(skip);
} else {
preview.skip = skip;
@ -47,9 +47,9 @@ impl Actor for Peek {
}
if hovered.is_dir() {
cx.tab_mut().preview.go_folder(hovered, folder.map(|(_, cha)| cha), mime, opt.force);
cx.tab_mut().preview.go_folder(hovered, folder.map(|(_, cha)| cha), mime, form.force);
} else {
cx.tab_mut().preview.go(hovered, mime, opt.force);
cx.tab_mut().preview.go(hovered, mime, form.force);
}
succ!();
}

View file

@ -58,7 +58,7 @@ impl Actor for Quit {
succ!();
}
fn hook(cx: &Ctx, _opt: &Self::Form) -> Option<SparkKind> {
fn hook(cx: &Ctx, _form: &Self::Form) -> Option<SparkKind> {
Some(SparkKind::KeyQuit).filter(|_| cx.source().is_key())
}
}

View file

@ -14,30 +14,30 @@ impl Actor for Remove {
const NAME: &str = "remove";
fn act(cx: &mut Ctx, mut opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, mut form: Self::Form) -> Result<Data> {
act!(mgr:escape_visual, cx)?;
opt.targets = if opt.hovered {
form.targets = if form.hovered {
cx.hovered().map_or(vec![], |h| vec![h.url.clone()])
} else {
cx.tab().selected_or_hovered().cloned().collect()
};
if opt.targets.is_empty() {
if form.targets.is_empty() {
succ!();
} else if opt.force {
return act!(mgr:remove_do, cx, opt);
} else if form.force {
return act!(mgr:remove_do, cx, form);
}
let confirm = ConfirmProxy::show(if opt.permanently {
ConfirmCfg::delete(&opt.targets)
let confirm = ConfirmProxy::show(if form.permanently {
ConfirmCfg::delete(&form.targets)
} else {
ConfirmCfg::trash(&opt.targets)
ConfirmCfg::trash(&form.targets)
});
tokio::spawn(async move {
if confirm.await {
MgrProxy::remove_do(opt.targets, opt.permanently);
MgrProxy::remove_do(form.targets, form.permanently);
}
});
succ!();
@ -52,17 +52,17 @@ impl Actor for RemoveDo {
const NAME: &str = "remove_do";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let mgr = &mut cx.mgr;
mgr.tabs.iter_mut().for_each(|t| {
t.selected.remove_many(&opt.targets);
t.selected.remove_many(&form.targets);
});
mgr.yanked.remove_many(&opt.targets);
mgr.yanked.remove_many(&form.targets);
mgr.yanked.catchup_revision(false);
cx.tasks.file_remove(opt.targets, opt.permanently);
cx.tasks.file_remove(form.targets, form.permanently);
succ!();
}
}

View file

@ -19,17 +19,17 @@ impl Actor for Rename {
const NAME: &str = "rename";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
act!(mgr:escape_visual, cx)?;
if !opt.hovered && !cx.tab().selected.is_empty() {
if !form.hovered && !cx.tab().selected.is_empty() {
return act!(mgr:bulk_rename, cx);
}
let Some(hovered) = cx.hovered() else { succ!() };
let name = Self::empty_url_part(&hovered.url, &opt.empty);
let cursor = match opt.cursor.as_ref() {
let name = Self::empty_url_part(&hovered.url, &form.empty);
let cursor = match form.cursor.as_ref() {
"start" => Some(0),
"before_ext" => name
.chars()
@ -54,7 +54,7 @@ impl Actor for Rename {
return;
};
if opt.force || !maybe_exists(&new).await || provider::must_identical(&old, &new).await {
if form.force || !maybe_exists(&new).await || provider::must_identical(&old, &new).await {
Self::r#do(tab, old, new).await.ok();
} else if ConfirmProxy::show(ConfirmCfg::overwrite(&new)).await {
Self::r#do(tab, old, new).await.ok();

View file

@ -13,11 +13,11 @@ impl Actor for Reveal {
const NAME: &str = "reveal";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
let Some((parent, child)) = opt.target.pair() else { succ!() };
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let Some((parent, child)) = form.target.pair() else { succ!() };
// Cd to the parent directory
act!(mgr:cd, cx, (parent, opt.source))?;
act!(mgr:cd, cx, (parent, form.source))?;
// Try to hover over the child file
let tab = cx.tab_mut();
@ -25,8 +25,8 @@ impl Actor for Reveal {
// If the child is not hovered, which means it doesn't exist,
// create a dummy file
if !opt.no_dummy && tab.hovered().is_none_or(|f| child != f.urn()) {
let op = FilesOp::Creating(parent.into(), vec![File::from_dummy(&opt.target, None)]);
if !form.no_dummy && tab.hovered().is_none_or(|f| child != f.urn()) {
let op = FilesOp::Creating(parent.into(), vec![File::from_dummy(&form.target, None)]);
tab.current.update_pub(tab.id, op);
}

View file

@ -15,7 +15,7 @@ impl Actor for Seek {
const NAME: &str = "seek";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let Some(hovered) = cx.hovered() else {
succ!(cx.tab_mut().preview.reset());
};
@ -28,7 +28,7 @@ impl Actor for Seek {
succ!(cx.tab_mut().preview.reset());
};
let job = SeekJob { file: hovered.clone(), units: opt.units };
let job = SeekJob { file: hovered.clone(), units: form.units };
let opt = PluginOpt::new_callback(&*previewer.run.name, move |_, plugin| {
plugin.call_method("seek", job)
});

View file

@ -18,33 +18,33 @@ impl Actor for Shell {
const NAME: &str = "shell";
fn act(cx: &mut Ctx, mut opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, mut form: Self::Form) -> Result<Data> {
act!(mgr:escape_visual, cx)?;
let cwd = opt.cwd.take().unwrap_or(cx.cwd().into()).into_owned();
let cwd = form.cwd.take().unwrap_or(cx.cwd().into()).into_owned();
let selected: Vec<_> = cx.tab().hovered_and_selected().cloned().map(Into::into).collect();
let input = opt.interactive.then(|| {
InputProxy::show(InputCfg::shell(opt.block).with_value(&*opt.run).with_cursor(opt.cursor))
let input = form.interactive.then(|| {
InputProxy::show(InputCfg::shell(form.block).with_value(&*form.run).with_cursor(form.cursor))
});
tokio::spawn(async move {
if let Some(mut rx) = input {
match rx.recv().await {
Some(InputEvent::Submit(e)) => opt.run = Cow::Owned(e),
Some(InputEvent::Submit(e)) => form.run = Cow::Owned(e),
_ => return,
}
}
if opt.run.is_empty() {
if form.run.is_empty() {
return;
}
TasksProxy::open_shell_compat(ProcessOpt {
cwd: cwd.into(),
cmd: opt.run.to_string().into(),
cmd: form.run.to_string().into(),
args: selected,
block: opt.block,
orphan: opt.orphan,
block: form.block,
orphan: form.orphan,
spread: true,
});
});

View file

@ -14,14 +14,14 @@ impl Actor for Sort {
const NAME: &str = "sort";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let pref = &mut cx.tab_mut().pref;
pref.sort_by = opt.by.unwrap_or(pref.sort_by);
pref.sort_reverse = opt.reverse.unwrap_or(pref.sort_reverse);
pref.sort_dir_first = opt.dir_first.unwrap_or(pref.sort_dir_first);
pref.sort_sensitive = opt.sensitive.unwrap_or(pref.sort_sensitive);
pref.sort_translit = opt.translit.unwrap_or(pref.sort_translit);
pref.sort_fallback = opt.fallback.unwrap_or(pref.sort_fallback);
pref.sort_by = form.by.unwrap_or(pref.sort_by);
pref.sort_reverse = form.reverse.unwrap_or(pref.sort_reverse);
pref.sort_dir_first = form.dir_first.unwrap_or(pref.sort_dir_first);
pref.sort_sensitive = form.sensitive.unwrap_or(pref.sort_sensitive);
pref.sort_translit = form.translit.unwrap_or(pref.sort_translit);
pref.sort_fallback = form.fallback.unwrap_or(pref.sort_fallback);
let sorter = FilesSorter::from(&*pref);
let hovered = cx.hovered().map(|f| f.urn().to_owned());

View file

@ -12,7 +12,7 @@ impl Actor for Spot {
const NAME: &str = "spot";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
act!(mgr:escape_visual, cx)?;
let Some(hovered) = cx.hovered().cloned() else { succ!() };
@ -26,7 +26,7 @@ impl Actor for Spot {
// self.active_mut().spot.reset();
// }
if let Some(skip) = opt.skip {
if let Some(skip) = form.skip {
cx.tab_mut().spot.skip = skip;
} else if !cx.tab().spot.same_url(&hovered.url) {
cx.tab_mut().spot.skip = 0;

View file

@ -12,15 +12,15 @@ impl Actor for Stash {
const NAME: &str = "stash";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
if opt.target.is_absolute() && opt.target.is_internal() {
cx.tab_mut().backstack.push(opt.target.as_url());
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
if form.target.is_absolute() && form.target.is_internal() {
cx.tab_mut().backstack.push(form.target.as_url());
}
succ!()
}
fn hook(cx: &Ctx, _opt: &Self::Form) -> Option<SparkKind> {
fn hook(cx: &Ctx, _form: &Self::Form) -> Option<SparkKind> {
match cx.source() {
Source::Ind => Some(SparkKind::IndStash),
Source::Relay => Some(SparkKind::RelayStash),

View file

@ -12,16 +12,16 @@ impl Actor for TabClose {
const NAME: &str = "tab_close";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let len = cx.tabs().len();
if len < 2 || opt.idx >= len {
if len < 2 || form.idx >= len {
succ!();
}
let tabs = cx.tabs_mut();
tabs.remove(opt.idx).shutdown();
tabs.remove(form.idx).shutdown();
if opt.idx > tabs.cursor {
if form.idx > tabs.cursor {
tabs.set_idx(tabs.cursor);
} else {
tabs.set_idx(usize::min(tabs.cursor + 1, tabs.len() - 1));

View file

@ -16,7 +16,7 @@ impl Actor for TabCreate {
const NAME: &str = "tab_create";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
if cx.tabs().len() >= MAX_TABS {
succ!(NotifyProxy::push_warn(
"Too many tabs",
@ -25,7 +25,7 @@ impl Actor for TabCreate {
}
let mut tab = Tab::default();
let (cd, url) = if let Some(wd) = opt.url {
let (cd, url) = if let Some(wd) = form.url {
(true, wd.into_owned())
} else if let Some(h) = cx.hovered() {
tab.pref = cx.tab().pref.clone();

View file

@ -17,18 +17,18 @@ impl Actor for TabRename {
const NAME: &str = "tab_rename";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let tab = cx.tab().id;
let pref = &mut cx.tab_mut().pref;
if !opt.interactive {
pref.name = opt.name.unwrap_or_default().into_owned();
if !form.interactive {
pref.name = form.name.unwrap_or_default().into_owned();
act!(app:title, cx).ok();
succ!(render!());
}
let mut input = InputProxy::show(
InputCfg::tab_rename().with_value(opt.name.unwrap_or(Cow::Borrowed(&pref.name))),
InputCfg::tab_rename().with_value(form.name.unwrap_or(Cow::Borrowed(&pref.name))),
);
tokio::spawn(async move {
if let Some(InputEvent::Submit(name)) = input.recv().await {

View file

@ -13,10 +13,10 @@ impl Actor for TabSwap {
const NAME: &str = "tab_swap";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let tabs = cx.tabs_mut();
let new = opt.step.add(tabs.cursor, tabs.len(), 0);
let new = form.step.add(tabs.cursor, tabs.len(), 0);
if new == tabs.cursor {
succ!();
}

View file

@ -12,12 +12,12 @@ impl Actor for TabSwitch {
const NAME: &str = "tab_switch";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let tabs = cx.tabs_mut();
let idx = if opt.relative {
opt.step.saturating_add_unsigned(tabs.cursor).rem_euclid(tabs.len() as _) as _
let idx = if form.relative {
form.step.saturating_add_unsigned(tabs.cursor).rem_euclid(tabs.len() as _) as _
} else {
opt.step as usize
form.step as usize
};
if idx == tabs.cursor || idx >= tabs.len() {

View file

@ -13,13 +13,13 @@ impl Actor for Toggle {
const NAME: &str = "toggle";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let tab = cx.tab_mut();
let Some(url) = opt.url.or(tab.current.hovered().map(|h| UrlCow::from(&h.url))) else {
let Some(url) = form.url.or(tab.current.hovered().map(|h| UrlCow::from(&h.url))) else {
succ!();
};
let b = match opt.state {
let b = match form.state {
Some(true) => render_and!(tab.selected.add(&url)),
Some(false) => render_and!(tab.selected.remove(&url)) | true,
None => render_and!(tab.selected.remove(&url) || tab.selected.add(&url)),

View file

@ -13,18 +13,18 @@ impl Actor for ToggleAll {
const NAME: &str = "toggle_all";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
use either::Either::*;
let tab = cx.tab_mut();
let it = tab.current.files.iter().map(|f| &f.url);
let either = match opt.state {
Some(true) if opt.urls.is_empty() => Left((vec![], it.collect())),
Some(true) => Right((vec![], opt.urls)),
Some(false) if opt.urls.is_empty() => Left((it.collect(), vec![])),
Some(false) => Right((opt.urls, vec![])),
None if opt.urls.is_empty() => Left(it.partition(|&u| tab.selected.contains(u))),
None => Right(opt.urls.into_iter().partition(|u| tab.selected.contains(u))),
let either = match form.state {
Some(true) if form.urls.is_empty() => Left((vec![], it.collect())),
Some(true) => Right((vec![], form.urls)),
Some(false) if form.urls.is_empty() => Left((it.collect(), vec![])),
Some(false) => Right((form.urls, vec![])),
None if form.urls.is_empty() => Left(it.partition(|&u| tab.selected.contains(u))),
None => Right(form.urls.into_iter().partition(|u| tab.selected.contains(u))),
};
let warn = match either {

View file

@ -15,11 +15,11 @@ impl Actor for UpdateFiles {
const NAME: &str = "update_files";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let revision = cx.current().files.revision;
let linked: Vec<_> = LINKED.read().from_dir(opt.op.cwd()).map(|u| opt.op.chdir(u)).collect();
let linked: Vec<_> = LINKED.read().from_dir(form.op.cwd()).map(|u| form.op.chdir(u)).collect();
for op in [opt.op].into_iter().chain(linked) {
for op in [form.op].into_iter().chain(linked) {
cx.mgr.yanked.apply_op(&op);
Self::update_tab(cx, op).ok();
}

View file

@ -14,9 +14,9 @@ impl Actor for UpdateMimes {
const NAME: &str = "update_mimes";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let linked = LINKED.read();
let updates = opt
let updates = form
.updates
.into_iter()
.filter(|(url, mime)| cx.mgr.mimetype.get(url) != Some(mime))

View file

@ -12,12 +12,12 @@ impl Actor for UpdatePaged {
const NAME: &str = "update_paged";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
if opt.only_if.is_some_and(|u| u != *cx.cwd()) {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
if form.only_if.is_some_and(|u| u != *cx.cwd()) {
succ!();
}
let targets = cx.current().paginate(opt.page.unwrap_or(cx.current().page));
let targets = cx.current().paginate(form.page.unwrap_or(cx.current().page));
if !targets.is_empty() {
cx.tasks.fetch_paged(targets, &cx.mgr.mimetype);
cx.tasks.preload_paged(targets, &cx.mgr.mimetype);

View file

@ -13,12 +13,12 @@ impl Actor for UpdateYanked {
const NAME: &str = "update_yanked";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
if opt.urls.is_empty() && cx.mgr.yanked.is_empty() {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
if form.urls.is_empty() && cx.mgr.yanked.is_empty() {
succ!();
}
cx.mgr.yanked = Yanked::new(opt.cut, opt.0.urls.into_owned());
cx.mgr.yanked = Yanked::new(form.cut, form.0.urls.into_owned());
succ!(render!());
}
}

View file

@ -12,8 +12,8 @@ impl Actor for Upload {
const NAME: &str = "upload";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
for url in opt.urls {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
for url in form.urls {
cx.tasks.scheduler.file_upload(url.into_owned());
}
succ!();

View file

@ -15,11 +15,11 @@ impl Actor for VisualMode {
const NAME: &str = "visual_mode";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let tab = cx.tab_mut();
let idx = tab.current.cursor;
if opt.unset {
if form.unset {
tab.mode = Mode::Unset(idx, BTreeSet::from([idx]));
} else {
tab.mode = Mode::Select(idx, BTreeSet::from([idx]));

View file

@ -13,11 +13,11 @@ impl Actor for Yank {
const NAME: &str = "yank";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
act!(mgr:escape_visual, cx)?;
cx.mgr.yanked =
Yanked::new(opt.cut, cx.tab().selected_or_hovered().cloned().map(UrlBufCov).collect());
Yanked::new(form.cut, cx.tab().selected_or_hovered().cloned().map(UrlBufCov).collect());
render!(cx.mgr.yanked.catchup_revision(true));
act!(mgr:escape_select, cx)

View file

@ -18,7 +18,7 @@ impl Actor for Tick {
const NAME: &str = "tick";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
cx.notify.ticker.take().map(|h| h.abort());
let Dimension { rows, columns, .. } = Dimension::available();
@ -35,7 +35,7 @@ impl Actor for Tick {
} else if m.percent < 100 {
m.percent += 20;
} else {
m.timeout = m.timeout.saturating_sub(opt.interval);
m.timeout = m.timeout.saturating_sub(form.interval);
}
}

View file

@ -13,7 +13,7 @@ impl Actor for Arrow {
const NAME: &str = "arrow";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
succ!(render!(cx.pick.scroll(opt.step)));
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
succ!(render!(cx.pick.scroll(form.step)));
}
}

View file

@ -12,10 +12,10 @@ impl Actor for Close {
const NAME: &str = "close";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let pick = &mut cx.pick;
if let Some(cb) = pick.callback.take() {
_ = cb.send(if opt.submit { Some(pick.cursor) } else { None });
_ = cb.send(if form.submit { Some(pick.cursor) } else { None });
}
pick.cursor = 0;

View file

@ -12,15 +12,15 @@ impl Actor for Show {
const NAME: &str = "show";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
act!(pick:close, cx)?;
let pick = &mut cx.pick;
pick.title = opt.cfg.title;
pick.items = opt.cfg.items;
pick.position = opt.cfg.position;
pick.title = form.cfg.title;
pick.items = form.cfg.items;
pick.position = form.cfg.position;
pick.callback = Some(opt.tx);
pick.callback = Some(form.tx);
pick.visible = true;
succ!(render!());
}

View file

@ -12,11 +12,11 @@ impl Actor for Arrow {
const NAME: &str = "arrow";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let spot = &mut cx.tab_mut().spot;
let Some(lock) = &mut spot.lock else { succ!() };
let new = opt.step.add(spot.skip, lock.len().unwrap_or(u16::MAX as _), 0);
let new = form.step.add(spot.skip, lock.len().unwrap_or(u16::MAX as _), 0);
let Some(old) = lock.selected() else {
return act!(mgr:spot, cx, new);
};

View file

@ -13,13 +13,13 @@ impl Actor for Copy {
const NAME: &str = "copy";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let spot = &cx.tab().spot;
let Some(lock) = &spot.lock else { succ!() };
let Some(table) = lock.table() else { succ!() };
let mut s = String::new();
match opt.r#type.as_ref() {
match form.r#type.as_ref() {
"cell" => {
let Some(cell) = table.selected_cell() else { succ!() };
s = cell.to_string();

View file

@ -12,8 +12,8 @@ impl Actor for Swipe {
const NAME: &str = "swipe";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
act!(mgr:arrow, cx, opt)?;
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
act!(mgr:arrow, cx, form)?;
act!(mgr:spot, cx)
}
}

View file

@ -13,11 +13,11 @@ impl Actor for Arrow {
const NAME: &str = "arrow";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let tasks = &mut cx.tasks;
let old = tasks.cursor;
tasks.cursor = opt.step.add(tasks.cursor, tasks.snaps.len(), Tasks::limit());
tasks.cursor = form.step.add(tasks.cursor, tasks.snaps.len(), Tasks::limit());
succ!(render!(tasks.cursor != old));
}

View file

@ -1,7 +1,7 @@
use anyhow::Result;
use yazi_macro::succ;
use yazi_macro::{act, succ};
use yazi_parser::tasks::UpdateSucceedForm;
use yazi_shared::data::Data;
use yazi_shared::{data::Data, url::UrlLike};
use crate::{Actor, Ctx};
@ -12,23 +12,21 @@ impl Actor for UpdateSucceed {
const NAME: &str = "update_succeed";
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
if opt.urls.is_empty() {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
if form.urls.is_empty() {
succ!();
}
// FIXME: todo!();
// if opt.track
// && opt.batch == cx.tasks.scheduler.batch.current()
// && let Some((parent, urn)) = opt.urls[0].pair()
// && parent == *cx.cwd()
// {
// cx.tasks.scheduler.batch.next();
// cx.current_mut().trace = Some(urn.into());
// act!(mgr:hover, cx)?;
// }
if form.track
&& form.id == cx.tasks.scheduler.behavior.first_id()
&& let Some((parent, urn)) = form.urls[0].pair()
&& parent == *cx.cwd()
{
cx.current_mut().trace = Some(urn.into());
act!(mgr:hover, cx)?;
}
cx.mgr.watcher.report(opt.urls);
cx.mgr.watcher.report(form.urls);
succ!();
}
}

View file

@ -31,7 +31,7 @@ impl Actor for Activate {
succ!(render!());
}
fn hook(cx: &Ctx, _opt: &Self::Form) -> Option<SparkKind> {
fn hook(cx: &Ctx, _form: &Self::Form) -> Option<SparkKind> {
match cx.source() {
Source::Unknown => Some(SparkKind::IndWhichActivate),
_ => None,

View file

@ -63,6 +63,7 @@ impl Folder {
_ => {}
}
let mut deleted = vec![];
match op {
FilesOp::Full(_, files, _) => self.files.update_full(files),
FilesOp::Part(_, files, ticket) => self.files.update_part(files, ticket),
@ -71,16 +72,13 @@ impl Folder {
FilesOp::IOErr(..) => self.files.update_ioerr(),
FilesOp::Creating(_, files) => self.files.update_creating(files),
FilesOp::Deleting(_, urns) => {
let deleted = self.files.update_deleting(urns);
let delta = deleted.into_iter().filter(|&i| i < self.cursor).count() as isize;
self.arrow(-delta);
}
FilesOp::Deleting(_, urns) => deleted = self.files.update_deleting(urns),
FilesOp::Updating(_, files) => _ = self.files.update_updating(files),
FilesOp::Upserting(_, files) => self.files.update_upserting(files),
}
};
self.trace.take_if(|_| self.files.is_empty() && !self.stage.is_loading());
self.arrow(-(deleted.into_iter().filter(|&i| i < self.cursor).count() as isize));
self.repos(None);
(&stage, revision) != (&self.stage, self.files.revision)
@ -101,9 +99,7 @@ impl Folder {
self.scroll(step)
};
self.trace = self.hovered().filter(|_| b).map(|h| h.urn().into()).or(self.trace.take());
b |= self.squeeze_offset();
self.sync_page(false);
b
}
@ -114,19 +110,28 @@ impl Folder {
}
let new = self.files.position(urn).unwrap_or(self.cursor) as isize;
self.arrow(new - self.cursor as isize)
let b = self.arrow(new - self.cursor as isize);
self.retrace();
b
}
pub fn repos(&mut self, urn: Option<PathDyn>) -> bool {
if let Some(u) = urn {
self.hover(u)
} else if let Some(u) = &self.trace {
self.hover(u.clone().as_path())
} else if let Some(u) = self.trace.take() {
let b = self.hover(u.as_path());
self.trace = Some(u);
b
} else {
self.arrow(0)
}
}
pub fn retrace(&mut self) {
self.trace = self.hovered().map(|h| h.urn().into()).or(self.trace.take());
}
pub fn sync_page(&mut self, force: bool) {
let limit = LAYOUT.get().folder_limit();
if limit == 0 {

View file

@ -7,6 +7,8 @@ use crate::mgr::Yanked;
impl Tasks {
pub fn file_cut(&self, src: &Yanked, dest: &UrlBuf, force: bool) {
self.scheduler.behavior.reset();
for u in src.iter() {
let Some(Ok(to)) = u.name().map(|n| dest.try_join(n)) else {
debug!("file_cut: cannot join {u:?} with {dest:?}");
@ -21,6 +23,8 @@ impl Tasks {
}
pub fn file_copy(&self, src: &Yanked, dest: &UrlBuf, force: bool, follow: bool) {
self.scheduler.behavior.reset();
for u in src.iter() {
let Some(Ok(to)) = u.name().map(|n| dest.try_join(n)) else {
debug!("file_copy: cannot join {u:?} with {dest:?}");
@ -35,6 +39,8 @@ impl Tasks {
}
pub fn file_link(&self, src: &IndexSet<UrlBufCov>, dest: &UrlBuf, relative: bool, force: bool) {
self.scheduler.behavior.reset();
for u in src {
let Some(Ok(to)) = u.name().map(|n| dest.try_join(n)) else {
debug!("file_link: cannot join {u:?} with {dest:?}");
@ -49,6 +55,8 @@ impl Tasks {
}
pub fn file_hardlink(&self, src: &IndexSet<UrlBufCov>, dest: &UrlBuf, force: bool, follow: bool) {
self.scheduler.behavior.reset();
for u in src {
let Some(Ok(to)) = u.name().map(|n| dest.try_join(n)) else {
debug!("file_hardlink: cannot join {u:?} with {dest:?}");

View file

@ -53,12 +53,12 @@ impl<'a> Executor<'a> {
"resize" => act!(app:resize, cx, crate::Root::reflow as fn(_) -> _),
"resume" => act!(app:resume, cx, yazi_parser::app::ResumeForm {
tx: self.app.signals.tx.clone(),
token: action.take_any("token").context("Invalid 'token' in ResumeForm")?,
reflow: crate::Root::reflow,
replier: action.take_replier().context("Missing replier in ResumeForm")?,
}),
"stop" => act!(app:stop, cx, yazi_parser::app::StopForm {
tx: self.app.signals.tx.clone(),
token: action.take_any("token").context("Invalid 'token' in StopForm")?,
replier: action.take_replier().context("Missing replier in StopForm")?,
}),
_ => succ!(),
}

View file

@ -3,10 +3,10 @@ use crossterm::event::{Event as CrosstermEvent, EventStream, KeyEvent, KeyEventK
use futures::StreamExt;
use tokio::{select, sync::mpsc};
use yazi_config::YAZI;
use yazi_shared::{CompletionToken, event::Event};
use yazi_shared::event::{Event, Replier};
pub(super) struct Signals {
pub(super) tx: mpsc::UnboundedSender<(bool, CompletionToken)>,
pub(super) tx: mpsc::UnboundedSender<(bool, Replier)>,
}
impl Signals {
@ -65,7 +65,7 @@ impl Signals {
}
}
fn spawn(mut rx: mpsc::UnboundedReceiver<(bool, CompletionToken)>) -> Result<()> {
fn spawn(mut rx: mpsc::UnboundedReceiver<(bool, Replier)>) -> Result<()> {
#[cfg(unix)]
use libc::{SIGCONT, SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGTSTP};
@ -90,9 +90,9 @@ impl Signals {
if let Some(t) = &mut term {
select! {
biased;
Some((state, token)) = rx.recv() => {
Some((state, replier)) = rx.recv() => {
term = term.filter(|_| state);
token.complete(true);
replier.send(Ok(().into())).ok();
},
Some(n) = sys.next() => if !Self::handle_sys(n) { return },
Some(Ok(e)) = t.next() => Self::handle_term(e)
@ -100,9 +100,9 @@ impl Signals {
} else {
select! {
biased;
Some((state, token)) = rx.recv() => {
Some((state, replier)) = rx.recv() => {
term = state.then(EventStream::new);
token.complete(true);
replier.send(Ok(().into())).ok();
},
Some(n) = sys.next() => if !Self::handle_sys(n) { return },
}

View file

@ -20,7 +20,7 @@ yazi-shim = { path = "../yazi-shim", version = "26.2.2" }
# External dependencies
anyhow = { workspace = true }
arc-swap = "1.9.0"
arc-swap = { workspace = true }
bitflags = { workspace = true }
dirs = { workspace = true }
either = { workspace = true }

View file

@ -1,13 +1,13 @@
use mlua::{ExternalError, FromLua, IntoLua, Lua, Table, Value};
use ratatui::layout::Rect;
use tokio::sync::mpsc;
use yazi_shared::CompletionToken;
use yazi_shared::event::Replier;
#[derive(Debug)]
pub struct ResumeForm {
pub tx: mpsc::UnboundedSender<(bool, CompletionToken)>,
pub token: CompletionToken,
pub reflow: fn(Rect) -> mlua::Result<Table>,
pub tx: mpsc::UnboundedSender<(bool, Replier)>,
pub reflow: fn(Rect) -> mlua::Result<Table>,
pub replier: Replier,
}
impl FromLua for ResumeForm {

View file

@ -1,11 +1,11 @@
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use tokio::sync::mpsc;
use yazi_shared::CompletionToken;
use yazi_shared::event::Replier;
#[derive(Debug)]
pub struct StopForm {
pub tx: mpsc::UnboundedSender<(bool, CompletionToken)>,
pub token: CompletionToken,
pub tx: mpsc::UnboundedSender<(bool, Replier)>,
pub replier: Replier,
}
impl FromLua for StopForm {

View file

@ -1,4 +1,4 @@
use anyhow::bail;
use anyhow::anyhow;
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use yazi_core::cmp::CmpOpt;
use yazi_shared::event::ActionCow;
@ -16,11 +16,7 @@ impl TryFrom<ActionCow> for ShowForm {
type Error = anyhow::Error;
fn try_from(mut a: ActionCow) -> Result<Self, Self::Error> {
if let Some(opt) = a.take_any2("opt") {
opt
} else {
bail!("Invalid 'opt' in ShowForm");
}
Ok(Self { opt: a.take_any("opt").ok_or_else(|| anyhow!("Invalid 'opt' in ShowForm"))? })
}
}

View file

@ -19,7 +19,7 @@ impl TryFrom<ActionCow> for StashForm {
}
impl From<CdForm> for StashForm {
fn from(opt: CdForm) -> Self { Self { target: opt.target, source: opt.source } }
fn from(form: CdForm) -> Self { Self { target: form.target, source: form.source } }
}
impl FromLua for StashForm {

View file

@ -1,13 +1,12 @@
use anyhow::anyhow;
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use tokio::sync::mpsc;
use yazi_scheduler::process::ProcessOpt;
use yazi_shared::{data::Data, event::ActionCow};
use yazi_shared::event::{ActionCow, Replier};
#[derive(Clone, Debug)]
pub struct ProcessOpenForm {
pub opt: ProcessOpt,
pub replier: Option<mpsc::UnboundedSender<anyhow::Result<Data>>>,
pub replier: Option<Replier>,
}
impl TryFrom<ActionCow> for ProcessOpenForm {

View file

@ -78,7 +78,7 @@ pub(super) fn lines(lua: &Lua) -> mlua::Result<Value> {
parsed = s.to_text().into_lua_err()?.lines;
LineIter::parsed(&parsed, tab_size)
} else {
LineIter::source(&s, tab_size)
LineIter::source(s, tab_size)
};
if let Some(wrap) = *opts.raw_get::<Wrap>("wrap")? {

View file

@ -8,16 +8,16 @@ pub fn slim_lua(lua: &Lua) -> mlua::Result<()> {
globals.raw_set("ya", crate::utils::compose(true))?;
globals.raw_set("fs", crate::fs::compose())?;
globals.raw_set("rt", crate::runtime::compose())?;
globals.raw_set("th", crate::theme::compose().into_lua(&lua)?)?;
globals.raw_set("th", crate::theme::compose().into_lua(lua)?)?;
yazi_binding::Cha::install(&lua)?;
yazi_binding::File::install(&lua)?;
yazi_binding::Url::install(&lua)?;
yazi_binding::Path::install(&lua)?;
yazi_binding::Cha::install(lua)?;
yazi_binding::File::install(lua)?;
yazi_binding::Url::install(lua)?;
yazi_binding::Path::install(lua)?;
yazi_binding::Error::install(&lua)?;
yazi_binding::process::install(&lua)?;
yazi_runner::loader::install(&lua)?;
yazi_binding::Error::install(lua)?;
yazi_binding::process::install(lua)?;
yazi_runner::loader::install(lua)?;
// Addons
lua.load(preset!("ya")).set_name("ya.lua").exec()?;

View file

@ -12,8 +12,8 @@ impl IntoLua for FetchJob {
fn into_lua(self, lua: &mlua::Lua) -> mlua::Result<Value> {
lua
.create_table_from([
("args", Sendable::args_to_table_ref(&lua, &self.action.args)?.into_lua(&lua)?),
("files", lua.create_sequence_from(self.files.into_iter().map(File::new))?.into_lua(&lua)?),
("args", Sendable::args_to_table_ref(lua, &self.action.args)?.into_lua(lua)?),
("files", lua.create_sequence_from(self.files.into_iter().map(File::new))?.into_lua(lua)?),
])?
.into_lua(lua)
}

View file

@ -16,11 +16,11 @@ impl IntoLua for PeekJob {
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
lua
.create_table_from([
("area", Rect::from(LAYOUT.get().preview).into_lua(&lua)?),
("args", Sendable::args_to_table_ref(&lua, &self.action.args)?.into_lua(&lua)?),
("file", File::new(self.file).into_lua(&lua)?),
("mime", self.mime.into_lua(&lua)?),
("skip", self.skip.into_lua(&lua)?),
("area", Rect::from(LAYOUT.get().preview).into_lua(lua)?),
("args", Sendable::args_to_table_ref(lua, &self.action.args)?.into_lua(lua)?),
("file", File::new(self.file).into_lua(lua)?),
("mime", self.mime.into_lua(lua)?),
("skip", self.skip.into_lua(lua)?),
])?
.into_lua(lua)
}
@ -37,9 +37,9 @@ impl IntoLua for SeekJob {
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
lua
.create_table_from([
("area", Rect::from(LAYOUT.get().preview).into_lua(&lua)?),
("file", File::new(self.file).into_lua(&lua)?),
("units", self.units.into_lua(&lua)?),
("area", Rect::from(LAYOUT.get().preview).into_lua(lua)?),
("file", File::new(self.file).into_lua(lua)?),
("units", self.units.into_lua(lua)?),
])?
.into_lua(lua)
}

View file

@ -0,0 +1,19 @@
use std::sync::atomic::{AtomicU64, Ordering};
use yazi_shared::Id;
pub struct Behavior {
first_id: AtomicU64,
}
impl Behavior {
pub(super) fn new() -> Self { Self { first_id: AtomicU64::new(0) } }
pub(super) fn update(&self, id: Id) {
self.first_id.compare_exchange(0, id.get(), Ordering::Relaxed, Ordering::Relaxed).ok();
}
pub fn reset(&self) { self.first_id.store(0, Ordering::Relaxed); }
pub fn first_id(&self) -> Id { self.first_id.load(Ordering::Relaxed).into() }
}

View file

@ -10,7 +10,7 @@ pub(crate) struct FetchIn {
}
impl From<FetchIn> for FetchJob {
fn from(value: FetchIn) -> Self { FetchJob { action: &value.plugin.run, files: value.targets } }
fn from(value: FetchIn) -> Self { Self { action: &value.plugin.run, files: value.targets } }
}
impl FetchIn {

View file

@ -30,7 +30,7 @@ impl Hook {
}
let result = ok_or_not_found(provider::remove_dir_clean(&task.from).await);
TasksProxy::update_succeed(task.id, [&task.to, &task.from], false);
TasksProxy::update_succeed(task.id, [&task.to, &task.from], true);
Pump::push_move(task.from, task.to);
self.ops.out(task.id, FileOutCut::Clean(result));
@ -38,7 +38,7 @@ impl Hook {
pub(crate) async fn copy(&self, task: HookInOutCopy) {
if self.ongoing.lock().intact(task.id) {
TasksProxy::update_succeed(task.id, [&task.to], false);
TasksProxy::update_succeed(task.id, [&task.to], true);
Pump::push_duplicate(task.from, task.to);
}

View file

@ -2,7 +2,7 @@ mod macros;
yazi_macro::mod_pub!(fetch file hook plugin preload process size);
yazi_macro::mod_flat!(ongoing op out progress proxy scheduler snap summary task worker);
yazi_macro::mod_flat!(behavior ongoing op out progress proxy scheduler snap summary task worker);
const LOW: u8 = yazi_config::Priority::Low as u8;
const NORMAL: u8 = yazi_config::Priority::Normal as u8;

View file

@ -1,21 +1,20 @@
use tokio::sync::mpsc;
use yazi_macro::{emit, relay};
use yazi_shared::{CompletionToken, Id, SStr, url::UrlBuf};
use yazi_shared::{Id, SStr, url::UrlBuf};
pub struct AppProxy;
impl AppProxy {
// FIXME: use replier instead of token
pub async fn stop() {
let token = CompletionToken::default();
emit!(Call(relay!(app:stop).with_any("token", token.clone())));
token.future().await;
let (tx, mut rx) = mpsc::unbounded_channel();
emit!(Call(relay!(app:stop).with_replier(tx)));
rx.recv().await;
}
// FIXME: use replier instead of token
pub async fn resume() {
let token = CompletionToken::default();
emit!(Call(relay!(app:resume).with_any("token", token.clone())));
token.future().await;
let (tx, mut rx) = mpsc::unbounded_channel();
emit!(Call(relay!(app:resume).with_replier(tx)));
rx.recv().await;
}
}

View file

@ -1,16 +1,16 @@
use std::{ops::Deref, sync::{Arc, atomic::{AtomicU64, Ordering}}, time::Duration};
use std::{ops::Deref, sync::Arc, time::Duration};
use tokio::task::JoinHandle;
use yazi_config::{YAZI, plugin::{Fetcher, Preloader}};
use yazi_runner::plugin::PluginOpt;
use yazi_shared::{CompletionToken, Id, Throttle, url::{UrlBuf, UrlLike}};
use crate::{HIGH, LOW, NORMAL, Worker, Task, TaskProg, fetch::{FetchIn, FetchProg}, file::{FileInCopy, FileInCut, FileInDelete, FileInDownload, FileInHardlink, FileInLink, FileInTrash, FileInUpload, FileOutCopy, FileOutCut, FileOutDownload, FileOutHardlink, FileOutUpload, FileProgCopy, FileProgCut, FileProgDelete, FileProgDownload, FileProgHardlink, FileProgLink, FileProgTrash, FileProgUpload}, hook::{HookIn, HookInDelete, HookInDownload, HookInTrash, HookInUpload}, plugin::{PluginInEntry, PluginProgEntry}, preload::{PreloadIn, PreloadProg}, process::{ProcessInBg, ProcessInBlock, ProcessInOrphan, ProcessOpt, ProcessProgBg, ProcessProgBlock, ProcessProgOrphan}, size::{SizeIn, SizeProg}};
use crate::{Behavior, HIGH, LOW, NORMAL, Task, TaskProg, Worker, fetch::{FetchIn, FetchProg}, file::{FileInCopy, FileInCut, FileInDelete, FileInDownload, FileInHardlink, FileInLink, FileInTrash, FileInUpload, FileOutCopy, FileOutCut, FileOutDownload, FileOutHardlink, FileOutUpload, FileProgCopy, FileProgCut, FileProgDelete, FileProgDownload, FileProgHardlink, FileProgLink, FileProgTrash, FileProgUpload}, hook::{HookIn, HookInDelete, HookInDownload, HookInTrash, HookInUpload}, plugin::{PluginInEntry, PluginProgEntry}, preload::{PreloadIn, PreloadProg}, process::{ProcessInBg, ProcessInBlock, ProcessInOrphan, ProcessOpt, ProcessProgBg, ProcessProgBlock, ProcessProgOrphan}, size::{SizeIn, SizeProg}};
pub struct Scheduler {
pub worker: Worker,
pub user_action: AtomicU64,
handles: Vec<JoinHandle<()>>,
pub worker: Worker,
pub behavior: Behavior,
handles: Vec<JoinHandle<()>>,
}
impl Deref for Scheduler {
@ -22,7 +22,7 @@ impl Deref for Scheduler {
impl Scheduler {
pub fn serve() -> Self {
let (worker, handles) = Worker::make();
Self { worker, user_action: AtomicU64::new(0), handles }
Self { worker, behavior: Behavior::new(), handles }
}
fn add<T, R>(&self, name: String, map: impl FnOnce(&mut Task) -> R) -> R
@ -30,14 +30,11 @@ impl Scheduler {
T: Into<TaskProg> + Default,
{
let prog = T::default().into();
let is_user = prog.is_user();
let mut ongoing = self.ongoing.lock();
let task = ongoing.add(name, prog);
if is_user {
self.user_action.store(task.id.get(), Ordering::Relaxed);
}
self.behavior.update(task.id);
map(task)
}

View file

@ -18,7 +18,7 @@ impl From<&Ongoing> for TaskSummary {
let mut percent_count = 0;
for task in value.values() {
let s: TaskSummary = task.prog.into();
let s: Self = task.prog.into();
if s.total == 0 && !task.prog.failed() {
continue;
}

View file

@ -12,19 +12,19 @@ impl<'de> Deserializer<'de> for Data {
V: de::Visitor<'de>,
{
match self {
Data::Nil => visitor.visit_unit(),
Data::Boolean(b) => visitor.visit_bool(b),
Data::Integer(i) => visitor.visit_i64(i),
Data::Number(n) => visitor.visit_f64(n),
Data::String(Cow::Borrowed(s)) => visitor.visit_borrowed_str(s),
Data::String(Cow::Owned(s)) => visitor.visit_string(s),
Data::List(l) => visitor.visit_seq(SeqDeserializer { iter: l.into_iter() }),
Data::Dict(d) => visitor.visit_map(MapDeserializer { iter: d.into_iter(), value: None }),
Data::Id(i) => visitor.visit_u64(i.get()),
Data::Url(u) => u.into_deserializer().deserialize_any(visitor),
Data::Path(_) => Err(Error::custom("path not supported")),
Data::Bytes(b) => BytesDeserializer(b.into()).deserialize_any(visitor),
Data::Any(_) => Err(Error::custom("any not supported")),
Self::Nil => visitor.visit_unit(),
Self::Boolean(b) => visitor.visit_bool(b),
Self::Integer(i) => visitor.visit_i64(i),
Self::Number(n) => visitor.visit_f64(n),
Self::String(Cow::Borrowed(s)) => visitor.visit_borrowed_str(s),
Self::String(Cow::Owned(s)) => visitor.visit_string(s),
Self::List(l) => visitor.visit_seq(SeqDeserializer { iter: l.into_iter() }),
Self::Dict(d) => visitor.visit_map(MapDeserializer { iter: d.into_iter(), value: None }),
Self::Id(i) => visitor.visit_u64(i.get()),
Self::Url(u) => u.into_deserializer().deserialize_any(visitor),
Self::Path(_) => Err(Error::custom("path not supported")),
Self::Bytes(b) => BytesDeserializer(b.into()).deserialize_any(visitor),
Self::Any(_) => Err(Error::custom("any not supported")),
}
}
@ -122,9 +122,9 @@ impl<'de> Deserializer<'de> for Data {
V: de::Visitor<'de>,
{
match self {
Data::String(Cow::Borrowed(s)) => visitor.visit_borrowed_str(s),
Data::String(Cow::Owned(s)) => visitor.visit_string(s),
Data::Url(u) => visitor.visit_newtype_struct(u.into_deserializer()),
Self::String(Cow::Borrowed(s)) => visitor.visit_borrowed_str(s),
Self::String(Cow::Owned(s)) => visitor.visit_string(s),
Self::Url(u) => visitor.visit_newtype_struct(u.into_deserializer()),
_ => Err(Error::custom("not a string")),
}
}
@ -141,7 +141,7 @@ impl<'de> Deserializer<'de> for Data {
V: de::Visitor<'de>,
{
match self {
Data::Bytes(b) => BytesDeserializer(b.into()).deserialize_bytes(visitor),
Self::Bytes(b) => BytesDeserializer(b.into()).deserialize_bytes(visitor),
_ => Err(Error::custom("not bytes")),
}
}
@ -158,7 +158,7 @@ impl<'de> Deserializer<'de> for Data {
V: de::Visitor<'de>,
{
match self {
Data::Nil => visitor.visit_none(),
Self::Nil => visitor.visit_none(),
other => visitor.visit_some(other),
}
}
@ -168,7 +168,7 @@ impl<'de> Deserializer<'de> for Data {
V: de::Visitor<'de>,
{
match self {
Data::Nil => visitor.visit_unit(),
Self::Nil => visitor.visit_unit(),
_ => Err(Error::custom("expected unit")),
}
}
@ -182,7 +182,7 @@ impl<'de> Deserializer<'de> for Data {
V: de::Visitor<'de>,
{
match self {
Data::Nil => visitor.visit_unit(),
Self::Nil => visitor.visit_unit(),
_ => Err(Error::custom("expected unit struct")),
}
}
@ -203,8 +203,8 @@ impl<'de> Deserializer<'de> for Data {
V: de::Visitor<'de>,
{
match self {
Data::List(l) => visitor.visit_seq(SeqDeserializer { iter: l.into_iter() }),
Data::Bytes(b) => BytesDeserializer(b.into()).deserialize_seq(visitor),
Self::List(l) => visitor.visit_seq(SeqDeserializer { iter: l.into_iter() }),
Self::Bytes(b) => BytesDeserializer(b.into()).deserialize_seq(visitor),
_ => Err(Error::custom("not a sequence")),
}
}
@ -233,8 +233,8 @@ impl<'de> Deserializer<'de> for Data {
V: de::Visitor<'de>,
{
match self {
Data::Dict(d) => visitor.visit_map(MapDeserializer { iter: d.into_iter(), value: None }),
Data::Url(u) => Deserializer::deserialize_map(u.into_deserializer(), visitor),
Self::Dict(d) => visitor.visit_map(MapDeserializer { iter: d.into_iter(), value: None }),
Self::Url(u) => Deserializer::deserialize_map(u.into_deserializer(), visitor),
_ => Err(Error::custom("not a map")),
}
}
@ -261,7 +261,7 @@ impl<'de> Deserializer<'de> for Data {
V: de::Visitor<'de>,
{
match self {
Data::String(s) => visitor.visit_enum(s.into_deserializer()),
Self::String(s) => visitor.visit_enum(s.into_deserializer()),
_ => Err(Error::custom("not an enum")),
}
}

View file

@ -3,9 +3,8 @@ use std::{borrow::Cow, fmt::{self, Display}, mem, str::FromStr};
use anyhow::{Result, anyhow, bail};
use hashbrown::HashMap;
use serde::{Deserialize, de};
use tokio::sync::mpsc;
use crate::{Layer, SStr, Source, data::{Data, DataAny, DataKey}};
use crate::{Layer, SStr, Source, data::{Data, DataAny, DataKey}, event::Replier};
#[derive(Clone, Debug, Default)]
pub struct Action {
@ -89,7 +88,7 @@ impl Action {
self
}
pub fn with_replier(mut self, tx: mpsc::UnboundedSender<anyhow::Result<Data>>) -> Self {
pub fn with_replier(mut self, tx: Replier) -> Self {
self.args.insert("replier".into(), Data::Any(Box::new(tx)));
self
}
@ -115,9 +114,7 @@ impl Action {
self.args.get(&name.into())?.as_any()
}
pub fn replier(&self) -> Option<&mpsc::UnboundedSender<anyhow::Result<Data>>> {
self.any("replier")
}
pub fn replier(&self) -> Option<&Replier> { self.any("replier") }
pub fn first<'a, T>(&'a self) -> Result<T>
where
@ -210,9 +207,7 @@ impl Action {
(0..self.len()).filter_map(|i| self.args.remove(&DataKey::from(i))?.into_any())
}
pub fn take_replier(&mut self) -> Option<mpsc::UnboundedSender<anyhow::Result<Data>>> {
self.take_any("replier")
}
pub fn take_replier(&mut self) -> Option<Replier> { self.take_any("replier") }
// Parse
pub fn parse_args<I>(words: I, last: Option<String>) -> Result<HashMap<DataKey, Data>>

View file

@ -2,10 +2,9 @@ use std::{iter, ops::Deref};
use anyhow::Result;
use serde::de::DeserializeOwned;
use tokio::sync::mpsc;
use super::Action;
use crate::data::{Data, DataKey};
use crate::{data::{Data, DataKey}, event::Replier};
#[derive(Debug)]
pub enum ActionCow {
@ -116,7 +115,7 @@ impl ActionCow {
}
}
pub fn take_replier(&mut self) -> Option<mpsc::UnboundedSender<anyhow::Result<Data>>> {
pub fn take_replier(&mut self) -> Option<Replier> {
match self {
Self::Owned(c) => c.take_replier(),
Self::Borrowed(_) => None,

View file

@ -1,3 +1,5 @@
yazi_macro::mod_flat!(action cow de de_owned event);
pub type Replier = tokio::sync::mpsc::UnboundedSender<anyhow::Result<crate::data::Data>>;
pub static NEED_RENDER: std::sync::atomic::AtomicU8 = std::sync::atomic::AtomicU8::new(0);