mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
feat: hover cursor over the new file after copying/cutting/linking/hardlinking (#3846)
This commit is contained in:
parent
ad655eda52
commit
0cedbd9c7b
96 changed files with 383 additions and 360 deletions
|
|
@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
||||||
|
|
||||||
- Custom tab name ([#3666])
|
- Custom tab name ([#3666])
|
||||||
- New `--in` for `search` action to set search directory ([#3696])
|
- 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])
|
- Multi-file spotter ([#3733])
|
||||||
- Vim-like `lua` action that runs an inline Lua snippet ([#3813])
|
- Vim-like `lua` action that runs an inline Lua snippet ([#3813])
|
||||||
- Certificate authentication for SFTP VFS provider ([#3716])
|
- 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
|
[#3792]: https://github.com/sxyazi/yazi/pull/3792
|
||||||
[#3804]: https://github.com/sxyazi/yazi/pull/3804
|
[#3804]: https://github.com/sxyazi/yazi/pull/3804
|
||||||
[#3813]: https://github.com/sxyazi/yazi/pull/3813
|
[#3813]: https://github.com/sxyazi/yazi/pull/3813
|
||||||
|
[#3846]: https://github.com/sxyazi/yazi/pull/3846
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ debug = false
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
ansi-to-tui = "8.0.1"
|
ansi-to-tui = "8.0.1"
|
||||||
anyhow = "1.0.102"
|
anyhow = "1.0.102"
|
||||||
|
arc-swap = "1.9.0"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
bitflags = { version = "2.11.0", features = [ "serde" ] }
|
bitflags = { version = "2.11.0", features = [ "serde" ] }
|
||||||
chrono = "0.4.44"
|
chrono = "0.4.44"
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,10 @@ impl Actor for Deprecate {
|
||||||
|
|
||||||
const NAME: &str = "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 {
|
act!(notify:push, cx, MessageOpt {
|
||||||
title: "Deprecated API".to_owned(),
|
title: "Deprecated API".to_owned(),
|
||||||
content: opt.content.into_owned(),
|
content: form.content.into_owned(),
|
||||||
level: MessageLevel::Warn,
|
level: MessageLevel::Warn,
|
||||||
timeout: std::time::Duration::from_secs(20),
|
timeout: std::time::Duration::from_secs(20),
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,8 @@ impl Actor for Lua {
|
||||||
|
|
||||||
const NAME: &str = "lua";
|
const NAME: &str = "lua";
|
||||||
|
|
||||||
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
|
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||||
let chunk = LUA.load(&*opt.code).set_name("anonymous");
|
let chunk = LUA.load(&*form.code).set_name("anonymous");
|
||||||
let result = Lives::scope(cx.core, || {
|
let result = Lives::scope(cx.core, || {
|
||||||
runtime_scope!(LUA, "inline", Sendable::value_to_data(&LUA, chunk.eval()?))
|
runtime_scope!(LUA, "inline", Sendable::value_to_data(&LUA, chunk.eval()?))
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@ impl Actor for Mouse {
|
||||||
|
|
||||||
const NAME: &str = "mouse";
|
const NAME: &str = "mouse";
|
||||||
|
|
||||||
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
|
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||||
let event = yazi_binding::MouseEvent::from(opt.event);
|
let event = yazi_binding::MouseEvent::from(form.event);
|
||||||
|
|
||||||
let Some(size) = cx.term.as_ref().and_then(|t| t.size().ok()) else { succ!() };
|
let Some(size) = cx.term.as_ref().and_then(|t| t.size().ok()) else { succ!() };
|
||||||
let area = yazi_binding::elements::Rect::from(size);
|
let area = yazi_binding::elements::Rect::from(size);
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,12 @@ impl Actor for Reflow {
|
||||||
|
|
||||||
const NAME: &str = "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 Some(size) = cx.term.as_ref().and_then(|t| t.size().ok()) else { succ!() };
|
||||||
let mut layout = LAYOUT.get();
|
let mut layout = LAYOUT.get();
|
||||||
|
|
||||||
let result = Lives::scope(cx.core, || {
|
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>() {
|
for v in comps.sequence_values::<Value>() {
|
||||||
let Value::Table(t) = v? else {
|
let Value::Table(t) = v? else {
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@ impl Actor for Resize {
|
||||||
|
|
||||||
const NAME: &str = "resize";
|
const NAME: &str = "resize";
|
||||||
|
|
||||||
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
|
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||||
act!(app:reflow, cx, opt)?;
|
act!(app:reflow, cx, form)?;
|
||||||
|
|
||||||
cx.current_mut().arrow(0);
|
cx.current_mut().arrow(0);
|
||||||
cx.parent_mut().map(|f| f.arrow(0));
|
cx.parent_mut().map(|f| f.arrow(0));
|
||||||
|
|
|
||||||
|
|
@ -13,15 +13,15 @@ impl Actor for Resume {
|
||||||
|
|
||||||
const NAME: &str = "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.active_mut().preview.reset();
|
||||||
*cx.term = Some(Term::start()?);
|
*cx.term = Some(Term::start()?);
|
||||||
|
|
||||||
// While the app resumes, it's possible that the terminal size has changed.
|
// 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.
|
// 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();
|
act!(app:title, cx).ok();
|
||||||
succ!(render!());
|
succ!(render!());
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ impl Actor for Stop {
|
||||||
|
|
||||||
const NAME: &str = "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();
|
cx.active_mut().preview.reset_image();
|
||||||
|
|
||||||
// We need to destroy the `term` first before stopping the `signals`
|
// 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.
|
// while the app is being suspended.
|
||||||
*cx.term = None;
|
*cx.term = None;
|
||||||
|
|
||||||
opt.tx.send((false, opt.token))?;
|
form.tx.send((false, form.replier))?;
|
||||||
|
|
||||||
succ!();
|
succ!();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,15 +16,15 @@ impl Actor for Title {
|
||||||
|
|
||||||
const NAME: &str = "title";
|
const NAME: &str = "title";
|
||||||
|
|
||||||
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
|
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||||
let s = opt.value.unwrap_or_else(|| format!("Yazi: {}", cx.tab().name()).into());
|
let s = form.value.unwrap_or_else(|| format!("Yazi: {}", cx.tab().name()).into());
|
||||||
execute!(TTY.writer(), SetTitle(&s))?;
|
execute!(TTY.writer(), SetTitle(&s))?;
|
||||||
|
|
||||||
yazi_term::STATE.set(TermState { title: !s.is_empty(), ..yazi_term::STATE.get() });
|
yazi_term::STATE.set(TermState { title: !s.is_empty(), ..yazi_term::STATE.get() });
|
||||||
succ!()
|
succ!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hook(cx: &Ctx, _opt: &Self::Form) -> Option<SparkKind> {
|
fn hook(cx: &Ctx, _form: &Self::Form) -> Option<SparkKind> {
|
||||||
match cx.source() {
|
match cx.source() {
|
||||||
Source::Ind => Some(SparkKind::IndAppTitle),
|
Source::Ind => Some(SparkKind::IndAppTitle),
|
||||||
_ => None,
|
_ => None,
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,11 @@ impl Actor for UpdateProgress {
|
||||||
|
|
||||||
const NAME: &str = "update_progress";
|
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.
|
// Update the progress of all tasks.
|
||||||
let tasks = &mut cx.tasks;
|
let tasks = &mut cx.tasks;
|
||||||
let progressed = tasks.summary != opt.summary;
|
let progressed = tasks.summary != form.summary;
|
||||||
tasks.summary = opt.summary;
|
tasks.summary = form.summary;
|
||||||
|
|
||||||
// If the task manager is visible, update the snaps with a full render.
|
// If the task manager is visible, update the snaps with a full render.
|
||||||
if tasks.visible {
|
if tasks.visible {
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ impl Actor for Arrow {
|
||||||
|
|
||||||
const NAME: &str = "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> {
|
||||||
succ!(render!(cx.cmp.scroll(opt.step)));
|
succ!(render!(cx.cmp.scroll(form.step)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,9 @@ impl Actor for Close {
|
||||||
|
|
||||||
const NAME: &str = "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;
|
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 });
|
return act!(input:complete, cx, CompleteOpt { name: item.name, is_dir: item.is_dir, ticket: cmp.ticket });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,15 +18,15 @@ impl Actor for Trigger {
|
||||||
|
|
||||||
const NAME: &str = "trigger";
|
const NAME: &str = "trigger";
|
||||||
|
|
||||||
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
|
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||||
if opt.ticket.is_some_and(|t| t != cx.cmp.ticket) {
|
if form.ticket.is_some_and(|t| t != cx.cmp.ticket) {
|
||||||
succ!();
|
succ!();
|
||||||
} else if opt.ticket.is_none() {
|
} else if form.ticket.is_none() {
|
||||||
cx.cmp.ticket = cx.input.ticket.current();
|
cx.cmp.ticket = cx.input.ticket.current();
|
||||||
}
|
}
|
||||||
|
|
||||||
cx.cmp.handle.take().map(|h| h.abort());
|
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);
|
return act!(cmp:close, cx, false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,14 +12,14 @@ impl Actor for Arrow {
|
||||||
|
|
||||||
const NAME: &str = "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 confirm = &mut cx.core.confirm;
|
||||||
|
|
||||||
let area = cx.core.mgr.area(confirm.position);
|
let area = cx.core.mgr.area(confirm.position);
|
||||||
let len = confirm.list.line_count(area.width);
|
let len = confirm.list.line_count(area.width);
|
||||||
|
|
||||||
let old = confirm.offset;
|
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));
|
succ!(render!(old != confirm.offset));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ impl Actor for Close {
|
||||||
|
|
||||||
const NAME: &str = "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> {
|
||||||
cx.confirm.token.complete(opt.submit);
|
cx.confirm.token.complete(form.submit);
|
||||||
cx.confirm.visible = false;
|
cx.confirm.visible = false;
|
||||||
succ!(render!());
|
succ!(render!());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,18 +12,18 @@ impl Actor for Show {
|
||||||
|
|
||||||
const NAME: &str = "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)?;
|
act!(confirm:close, cx)?;
|
||||||
|
|
||||||
let confirm = &mut cx.confirm;
|
let confirm = &mut cx.confirm;
|
||||||
confirm.title = opt.cfg.title;
|
confirm.title = form.cfg.title;
|
||||||
confirm.body = opt.cfg.body;
|
confirm.body = form.cfg.body;
|
||||||
confirm.list = opt.cfg.list;
|
confirm.list = form.cfg.list;
|
||||||
|
|
||||||
confirm.position = opt.cfg.position;
|
confirm.position = form.cfg.position;
|
||||||
confirm.offset = 0;
|
confirm.offset = 0;
|
||||||
|
|
||||||
confirm.token = opt.token;
|
confirm.token = form.token;
|
||||||
confirm.visible = true;
|
confirm.visible = true;
|
||||||
|
|
||||||
succ!(render!());
|
succ!(render!());
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ impl Actor for Arrow {
|
||||||
|
|
||||||
const NAME: &str = "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> {
|
||||||
succ!(render!(cx.help.scroll(opt.step)));
|
succ!(render!(cx.help.scroll(form.step)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,11 @@ impl Actor for Toggle {
|
||||||
|
|
||||||
const NAME: &str = "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;
|
let help = &mut cx.help;
|
||||||
|
|
||||||
help.visible = !help.visible;
|
help.visible = !help.visible;
|
||||||
help.layer = opt.layer;
|
help.layer = form.layer;
|
||||||
|
|
||||||
help.keyword = String::new();
|
help.keyword = String::new();
|
||||||
help.in_filter = None;
|
help.in_filter = None;
|
||||||
|
|
|
||||||
|
|
@ -13,14 +13,14 @@ impl Actor for Close {
|
||||||
|
|
||||||
const NAME: &str = "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;
|
let input = &mut cx.input;
|
||||||
input.visible = false;
|
input.visible = false;
|
||||||
input.ticket.next();
|
input.ticket.next();
|
||||||
|
|
||||||
if let Some(tx) = input.tx.take() {
|
if let Some(tx) = input.tx.take() {
|
||||||
let value = input.snap().value.clone();
|
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)?;
|
act!(cmp:close, cx)?;
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,12 @@ impl Actor for Complete {
|
||||||
|
|
||||||
const NAME: &str = "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;
|
let input = &mut cx.input;
|
||||||
if !input.visible || input.ticket.current() != opt.ticket {
|
if !input.visible || input.ticket.current() != form.ticket {
|
||||||
succ!();
|
succ!();
|
||||||
}
|
}
|
||||||
|
|
||||||
act!(complete, input, opt)
|
act!(complete, input, form)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,14 +14,14 @@ impl Actor for Show {
|
||||||
|
|
||||||
const NAME: &str = "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)?;
|
act!(input:close, cx)?;
|
||||||
|
|
||||||
let input = &mut cx.input;
|
let input = &mut cx.input;
|
||||||
input.visible = true;
|
input.visible = true;
|
||||||
input.title = opt.cfg.title.clone();
|
input.title = form.cfg.title.clone();
|
||||||
input.position = opt.cfg.position;
|
input.position = form.cfg.position;
|
||||||
*input.deref_mut() = yazi_widgets::input::Input::new(opt)?;
|
*input.deref_mut() = yazi_widgets::input::Input::new(form)?;
|
||||||
|
|
||||||
succ!(render!());
|
succ!(render!());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,15 @@ impl Actor for Arrow {
|
||||||
|
|
||||||
const NAME: &str = "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();
|
let tab = cx.tab_mut();
|
||||||
if !tab.current.arrow(opt.step) {
|
if !tab.current.arrow(form.step) {
|
||||||
succ!();
|
succ!();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Retrace
|
||||||
|
tab.current.retrace();
|
||||||
|
|
||||||
// Visual selection
|
// Visual selection
|
||||||
if let Some((start, items)) = tab.mode.visual_mut() {
|
if let Some((start, items)) = tab.mode.visual_mut() {
|
||||||
let end = tab.current.cursor;
|
let end = tab.current.cursor;
|
||||||
|
|
@ -28,7 +31,7 @@ impl Actor for Arrow {
|
||||||
act!(mgr:peek, cx)?;
|
act!(mgr:peek, cx)?;
|
||||||
act!(mgr:watch, cx)?;
|
act!(mgr:watch, cx)?;
|
||||||
|
|
||||||
// cx.tasks.scheduler.batch.next(); // FIXME: clear user action
|
cx.tasks.scheduler.behavior.reset();
|
||||||
succ!(render!());
|
succ!(render!());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ impl Actor for BulkExit {
|
||||||
|
|
||||||
const NAME: &str = "bulk_exit";
|
const NAME: &str = "bulk_exit";
|
||||||
|
|
||||||
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
|
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||||
cx.mgr.batcher.decide(opt.target, opt.accept);
|
cx.mgr.batcher.decide(form.target, form.accept);
|
||||||
succ!();
|
succ!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,14 +22,14 @@ impl Actor for Cd {
|
||||||
|
|
||||||
const NAME: &str = "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)?;
|
act!(mgr:escape_visual, cx)?;
|
||||||
if opt.interactive {
|
if form.interactive {
|
||||||
return Self::cd_interactive(cx);
|
return Self::cd_interactive(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
let tab = cx.tab_mut();
|
let tab = cx.tab_mut();
|
||||||
if opt.target == *tab.cwd() {
|
if form.target == *tab.cwd() {
|
||||||
succ!();
|
succ!();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,12 +39,12 @@ impl Actor for Cd {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Current
|
// 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);
|
let rep = mem::replace(&mut tab.current, rep);
|
||||||
tab.history.insert(rep.url.clone(), rep);
|
tab.history.insert(rep.url.clone(), rep);
|
||||||
|
|
||||||
// Parent
|
// Parent
|
||||||
if let Some(parent) = opt.target.parent() {
|
if let Some(parent) = form.target.parent() {
|
||||||
tab.parent = Some(tab.history.remove_or(parent));
|
tab.parent = Some(tab.history.remove_or(parent));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,7 +54,7 @@ impl Actor for Cd {
|
||||||
act!(mgr:sort, cx).ok();
|
act!(mgr:sort, cx).ok();
|
||||||
act!(mgr:hover, cx)?;
|
act!(mgr:hover, cx)?;
|
||||||
act!(mgr:refresh, cx)?;
|
act!(mgr:refresh, cx)?;
|
||||||
act!(mgr:stash, cx, opt).ok();
|
act!(mgr:stash, cx, form).ok();
|
||||||
act!(app:title, cx).ok();
|
act!(app:title, cx).ok();
|
||||||
succ!(render!());
|
succ!(render!());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,11 @@ impl Actor for Copy {
|
||||||
|
|
||||||
const NAME: &str = "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)?;
|
act!(mgr:escape_visual, cx)?;
|
||||||
|
|
||||||
let mut s = Vec::<u8>::new();
|
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())
|
Box::new(cx.hovered().map(|h| &h.url).into_iter())
|
||||||
} else {
|
} else {
|
||||||
cx.tab().selected_or_hovered()
|
cx.tab().selected_or_hovered()
|
||||||
|
|
@ -25,23 +25,23 @@ impl Actor for Copy {
|
||||||
.peekable();
|
.peekable();
|
||||||
|
|
||||||
while let Some(u) = it.next() {
|
while let Some(u) = it.next() {
|
||||||
match opt.r#type.as_ref() {
|
match form.r#type.as_ref() {
|
||||||
// TODO: rename to "url"
|
// TODO: rename to "url"
|
||||||
"path" => {
|
"path" => {
|
||||||
s.extend_from_slice(&opt.separator.transform(&u.to_strand()));
|
s.extend_from_slice(&form.separator.transform(&u.to_strand()));
|
||||||
}
|
}
|
||||||
"dirname" => {
|
"dirname" => {
|
||||||
if let Some(p) = u.parent() {
|
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" => {
|
"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" => {
|
"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() {
|
if it.peek().is_some() {
|
||||||
s.push(b'\n');
|
s.push(b'\n');
|
||||||
|
|
@ -49,8 +49,8 @@ impl Actor for Copy {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy the CWD path regardless even if the directory is empty
|
// Copy the CWD path regardless even if the directory is empty
|
||||||
if s.is_empty() && opt.r#type == "dirname" {
|
if s.is_empty() && form.r#type == "dirname" {
|
||||||
s.extend_from_slice(&opt.separator.transform(&cx.cwd().to_strand()));
|
s.extend_from_slice(&form.separator.transform(&cx.cwd().to_strand()));
|
||||||
}
|
}
|
||||||
|
|
||||||
futures::executor::block_on(CLIPBOARD.set(s));
|
futures::executor::block_on(CLIPBOARD.set(s));
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,9 @@ impl Actor for Create {
|
||||||
|
|
||||||
const NAME: &str = "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 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 {
|
tokio::spawn(async move {
|
||||||
let Some(InputEvent::Submit(name)) = input.recv().await else { return };
|
let Some(InputEvent::Submit(name)) = input.recv().await else { return };
|
||||||
|
|
@ -32,14 +32,14 @@ impl Actor for Create {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
if !opt.force
|
if !form.force
|
||||||
&& maybe_exists(&new).await
|
&& maybe_exists(&new).await
|
||||||
&& !ConfirmProxy::show(ConfirmCfg::overwrite(&new)).await
|
&& !ConfirmProxy::show(ConfirmCfg::overwrite(&new)).await
|
||||||
{
|
{
|
||||||
return;
|
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!();
|
succ!();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,15 +20,15 @@ impl Actor for Download {
|
||||||
|
|
||||||
const NAME: &str = "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 cwd = cx.cwd().clone();
|
||||||
let scheduler = cx.tasks.scheduler.clone();
|
let scheduler = cx.tasks.scheduler.clone();
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
Self::prepare(&opt.urls).await;
|
Self::prepare(&form.urls).await;
|
||||||
|
|
||||||
let mut wg1 = FuturesUnordered::new();
|
let mut wg1 = FuturesUnordered::new();
|
||||||
for url in opt.urls {
|
for url in form.urls {
|
||||||
let done = scheduler.file_download(url.to_owned());
|
let done = scheduler.file_download(url.to_owned());
|
||||||
wg1.push(async move { (done.future().await, url) });
|
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) {
|
if futures::future::join_all(wg2).await.into_iter().any(|b| !b) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if opt.open && !urls.is_empty() {
|
if form.open && !urls.is_empty() {
|
||||||
MgrProxy::open(OpenOpt {
|
MgrProxy::open(OpenOpt {
|
||||||
cwd: Some(cwd.into()),
|
cwd: Some(cwd.into()),
|
||||||
targets: urls,
|
targets: urls,
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@ impl Actor for Escape {
|
||||||
|
|
||||||
const NAME: &str = "escape";
|
const NAME: &str = "escape";
|
||||||
|
|
||||||
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
|
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||||
if opt.is_empty() {
|
if form.is_empty() {
|
||||||
_ = act!(mgr:escape_find, cx)? != false
|
_ = act!(mgr:escape_find, cx)? != false
|
||||||
|| act!(mgr:escape_visual, cx)? != false
|
|| act!(mgr:escape_visual, cx)? != false
|
||||||
|| act!(mgr:escape_filter, cx)? != false
|
|| act!(mgr:escape_filter, cx)? != false
|
||||||
|
|
@ -23,19 +23,19 @@ impl Actor for Escape {
|
||||||
succ!();
|
succ!();
|
||||||
}
|
}
|
||||||
|
|
||||||
if opt.contains(EscapeForm::FIND) {
|
if form.contains(EscapeForm::FIND) {
|
||||||
act!(mgr:escape_find, cx)?;
|
act!(mgr:escape_find, cx)?;
|
||||||
}
|
}
|
||||||
if opt.contains(EscapeForm::VISUAL) {
|
if form.contains(EscapeForm::VISUAL) {
|
||||||
act!(mgr:escape_visual, cx)?;
|
act!(mgr:escape_visual, cx)?;
|
||||||
}
|
}
|
||||||
if opt.contains(EscapeForm::FILTER) {
|
if form.contains(EscapeForm::FILTER) {
|
||||||
act!(mgr:escape_filter, cx)?;
|
act!(mgr:escape_filter, cx)?;
|
||||||
}
|
}
|
||||||
if opt.contains(EscapeForm::SELECT) {
|
if form.contains(EscapeForm::SELECT) {
|
||||||
act!(mgr:escape_select, cx)?;
|
act!(mgr:escape_select, cx)?;
|
||||||
}
|
}
|
||||||
if opt.contains(EscapeForm::SEARCH) {
|
if form.contains(EscapeForm::SEARCH) {
|
||||||
act!(mgr:escape_search, cx)?;
|
act!(mgr:escape_search, cx)?;
|
||||||
}
|
}
|
||||||
succ!();
|
succ!();
|
||||||
|
|
|
||||||
|
|
@ -20,15 +20,15 @@ impl Actor for Find {
|
||||||
|
|
||||||
const NAME: &str = "find";
|
const NAME: &str = "find";
|
||||||
|
|
||||||
fn act(_: &mut Ctx, opt: Self::Form) -> Result<Data> {
|
fn act(_: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||||
let input = InputProxy::show(InputCfg::find(opt.prev));
|
let input = InputProxy::show(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));
|
||||||
pin!(rx);
|
pin!(rx);
|
||||||
|
|
||||||
while let Some(InputEvent::Submit(s) | InputEvent::Type(s)) = rx.next().await {
|
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!();
|
succ!();
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,12 @@ impl Actor for FindArrow {
|
||||||
|
|
||||||
const NAME: &str = "find_arrow";
|
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 tab = cx.tab_mut();
|
||||||
let Some(finder) = &mut tab.finder else { succ!() };
|
let Some(finder) = &mut tab.finder else { succ!() };
|
||||||
|
|
||||||
render!(finder.catchup(&tab.current));
|
render!(finder.catchup(&tab.current));
|
||||||
let offset = if opt.prev {
|
let offset = if form.prev {
|
||||||
finder.prev(&tab.current.files, tab.current.cursor, false)
|
finder.prev(&tab.current.files, tab.current.cursor, false)
|
||||||
} else {
|
} else {
|
||||||
finder.next(&tab.current.files, tab.current.cursor, false)
|
finder.next(&tab.current.files, tab.current.cursor, false)
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,12 @@ impl Actor for Hardlink {
|
||||||
|
|
||||||
const NAME: &str = "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 mgr = &mut cx.core.mgr;
|
||||||
let tab = &mgr.tabs[cx.tab];
|
let tab = &mgr.tabs[cx.tab];
|
||||||
|
|
||||||
if !mgr.yanked.cut {
|
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!();
|
succ!();
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,8 @@ impl Actor for Hidden {
|
||||||
|
|
||||||
const NAME: &str = "hidden";
|
const NAME: &str = "hidden";
|
||||||
|
|
||||||
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
|
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||||
let state = opt.state.bool(cx.tab().pref.show_hidden);
|
let state = form.state.bool(cx.tab().pref.show_hidden);
|
||||||
cx.tab_mut().pref.show_hidden = state;
|
cx.tab_mut().pref.show_hidden = state;
|
||||||
|
|
||||||
let hovered = cx.hovered().map(|f| f.urn().to_owned());
|
let hovered = cx.hovered().map(|f| f.urn().to_owned());
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ impl Actor for Hover {
|
||||||
|
|
||||||
const NAME: &str = "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);
|
let tab = tab!(cx);
|
||||||
|
|
||||||
// Parent should always track CWD
|
// Parent should always track CWD
|
||||||
|
|
@ -22,16 +22,16 @@ impl Actor for Hover {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Repos CWD
|
// 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
|
// 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
|
&& h.urn() == u
|
||||||
{
|
{
|
||||||
// `hover(Some)` occurs after user actions, such as create, rename, reveal, etc.
|
// `hover(Some)` occurs after user actions, such as create, rename, reveal, etc.
|
||||||
// At this point, it's intuitive to track the file location regardless.
|
// At this point, it's intuitive to track the file location regardless.
|
||||||
tab.current.trace = Some(u.clone());
|
tab.current.trace = Some(u.clone());
|
||||||
// cx.tasks.scheduler.batch.next();// FIXME: clear user action
|
cx.tasks.scheduler.behavior.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Publish through DDS
|
// Publish through DDS
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,11 @@ impl Actor for Linemode {
|
||||||
|
|
||||||
const NAME: &str = "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();
|
let tab = cx.tab_mut();
|
||||||
|
|
||||||
if opt.new != tab.pref.linemode {
|
if form.new != tab.pref.linemode {
|
||||||
tab.pref.linemode = opt.new.into_owned();
|
tab.pref.linemode = form.new.into_owned();
|
||||||
render!();
|
render!();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,12 @@ impl Actor for Link {
|
||||||
|
|
||||||
const NAME: &str = "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 mgr = &mut cx.core.mgr;
|
||||||
let tab = &mgr.tabs[cx.tab];
|
let tab = &mgr.tabs[cx.tab];
|
||||||
|
|
||||||
if !mgr.yanked.cut {
|
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!();
|
succ!();
|
||||||
|
|
|
||||||
|
|
@ -12,18 +12,18 @@ impl Actor for Paste {
|
||||||
|
|
||||||
const NAME: &str = "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 mgr = &mut cx.core.mgr;
|
||||||
let tab = &mgr.tabs[cx.tab];
|
let tab = &mgr.tabs[cx.tab];
|
||||||
|
|
||||||
let dest = tab.cwd();
|
let dest = tab.cwd();
|
||||||
if mgr.yanked.cut {
|
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));
|
mgr.tabs.iter_mut().for_each(|t| _ = t.selected.remove_many(&*mgr.yanked));
|
||||||
act!(mgr:unyank, cx)
|
act!(mgr:unyank, cx)
|
||||||
} else {
|
} 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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ impl Actor for Peek {
|
||||||
|
|
||||||
const NAME: &str = "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 {
|
let Some(hovered) = cx.hovered().cloned() else {
|
||||||
succ!(cx.tab_mut().preview.reset());
|
succ!(cx.tab_mut().preview.reset());
|
||||||
};
|
};
|
||||||
|
|
@ -33,13 +33,13 @@ impl Actor for Peek {
|
||||||
cx.tab_mut().preview.folder_lock = None;
|
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!();
|
succ!();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(skip) = opt.skip {
|
if let Some(skip) = form.skip {
|
||||||
let preview = &mut cx.tab_mut().preview;
|
let preview = &mut cx.tab_mut().preview;
|
||||||
if opt.upper_bound {
|
if form.upper_bound {
|
||||||
preview.skip = preview.skip.min(skip);
|
preview.skip = preview.skip.min(skip);
|
||||||
} else {
|
} else {
|
||||||
preview.skip = skip;
|
preview.skip = skip;
|
||||||
|
|
@ -47,9 +47,9 @@ impl Actor for Peek {
|
||||||
}
|
}
|
||||||
|
|
||||||
if hovered.is_dir() {
|
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 {
|
} else {
|
||||||
cx.tab_mut().preview.go(hovered, mime, opt.force);
|
cx.tab_mut().preview.go(hovered, mime, form.force);
|
||||||
}
|
}
|
||||||
succ!();
|
succ!();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ impl Actor for Quit {
|
||||||
succ!();
|
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())
|
Some(SparkKind::KeyQuit).filter(|_| cx.source().is_key())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,30 +14,30 @@ impl Actor for Remove {
|
||||||
|
|
||||||
const NAME: &str = "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)?;
|
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()])
|
cx.hovered().map_or(vec![], |h| vec![h.url.clone()])
|
||||||
} else {
|
} else {
|
||||||
cx.tab().selected_or_hovered().cloned().collect()
|
cx.tab().selected_or_hovered().cloned().collect()
|
||||||
};
|
};
|
||||||
|
|
||||||
if opt.targets.is_empty() {
|
if form.targets.is_empty() {
|
||||||
succ!();
|
succ!();
|
||||||
} else if opt.force {
|
} else if form.force {
|
||||||
return act!(mgr:remove_do, cx, opt);
|
return act!(mgr:remove_do, cx, form);
|
||||||
}
|
}
|
||||||
|
|
||||||
let confirm = ConfirmProxy::show(if opt.permanently {
|
let confirm = ConfirmProxy::show(if form.permanently {
|
||||||
ConfirmCfg::delete(&opt.targets)
|
ConfirmCfg::delete(&form.targets)
|
||||||
} else {
|
} else {
|
||||||
ConfirmCfg::trash(&opt.targets)
|
ConfirmCfg::trash(&form.targets)
|
||||||
});
|
});
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if confirm.await {
|
if confirm.await {
|
||||||
MgrProxy::remove_do(opt.targets, opt.permanently);
|
MgrProxy::remove_do(form.targets, form.permanently);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
succ!();
|
succ!();
|
||||||
|
|
@ -52,17 +52,17 @@ impl Actor for RemoveDo {
|
||||||
|
|
||||||
const NAME: &str = "remove_do";
|
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;
|
let mgr = &mut cx.mgr;
|
||||||
|
|
||||||
mgr.tabs.iter_mut().for_each(|t| {
|
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);
|
mgr.yanked.catchup_revision(false);
|
||||||
|
|
||||||
cx.tasks.file_remove(opt.targets, opt.permanently);
|
cx.tasks.file_remove(form.targets, form.permanently);
|
||||||
succ!();
|
succ!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,17 +19,17 @@ impl Actor for Rename {
|
||||||
|
|
||||||
const NAME: &str = "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)?;
|
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);
|
return act!(mgr:bulk_rename, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(hovered) = cx.hovered() else { succ!() };
|
let Some(hovered) = cx.hovered() else { succ!() };
|
||||||
|
|
||||||
let name = Self::empty_url_part(&hovered.url, &opt.empty);
|
let name = Self::empty_url_part(&hovered.url, &form.empty);
|
||||||
let cursor = match opt.cursor.as_ref() {
|
let cursor = match form.cursor.as_ref() {
|
||||||
"start" => Some(0),
|
"start" => Some(0),
|
||||||
"before_ext" => name
|
"before_ext" => name
|
||||||
.chars()
|
.chars()
|
||||||
|
|
@ -54,7 +54,7 @@ impl Actor for Rename {
|
||||||
return;
|
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();
|
Self::r#do(tab, old, new).await.ok();
|
||||||
} else if ConfirmProxy::show(ConfirmCfg::overwrite(&new)).await {
|
} else if ConfirmProxy::show(ConfirmCfg::overwrite(&new)).await {
|
||||||
Self::r#do(tab, old, new).await.ok();
|
Self::r#do(tab, old, new).await.ok();
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,11 @@ impl Actor for Reveal {
|
||||||
|
|
||||||
const NAME: &str = "reveal";
|
const NAME: &str = "reveal";
|
||||||
|
|
||||||
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
|
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||||
let Some((parent, child)) = opt.target.pair() else { succ!() };
|
let Some((parent, child)) = form.target.pair() else { succ!() };
|
||||||
|
|
||||||
// Cd to the parent directory
|
// 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
|
// Try to hover over the child file
|
||||||
let tab = cx.tab_mut();
|
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,
|
// If the child is not hovered, which means it doesn't exist,
|
||||||
// create a dummy file
|
// create a dummy file
|
||||||
if !opt.no_dummy && tab.hovered().is_none_or(|f| child != f.urn()) {
|
if !form.no_dummy && tab.hovered().is_none_or(|f| child != f.urn()) {
|
||||||
let op = FilesOp::Creating(parent.into(), vec![File::from_dummy(&opt.target, None)]);
|
let op = FilesOp::Creating(parent.into(), vec![File::from_dummy(&form.target, None)]);
|
||||||
tab.current.update_pub(tab.id, op);
|
tab.current.update_pub(tab.id, op);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ impl Actor for Seek {
|
||||||
|
|
||||||
const NAME: &str = "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 {
|
let Some(hovered) = cx.hovered() else {
|
||||||
succ!(cx.tab_mut().preview.reset());
|
succ!(cx.tab_mut().preview.reset());
|
||||||
};
|
};
|
||||||
|
|
@ -28,7 +28,7 @@ impl Actor for Seek {
|
||||||
succ!(cx.tab_mut().preview.reset());
|
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| {
|
let opt = PluginOpt::new_callback(&*previewer.run.name, move |_, plugin| {
|
||||||
plugin.call_method("seek", job)
|
plugin.call_method("seek", job)
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -18,33 +18,33 @@ impl Actor for Shell {
|
||||||
|
|
||||||
const NAME: &str = "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)?;
|
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 selected: Vec<_> = cx.tab().hovered_and_selected().cloned().map(Into::into).collect();
|
||||||
|
|
||||||
let input = opt.interactive.then(|| {
|
let input = form.interactive.then(|| {
|
||||||
InputProxy::show(InputCfg::shell(opt.block).with_value(&*opt.run).with_cursor(opt.cursor))
|
InputProxy::show(InputCfg::shell(form.block).with_value(&*form.run).with_cursor(form.cursor))
|
||||||
});
|
});
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if let Some(mut rx) = input {
|
if let Some(mut rx) = input {
|
||||||
match rx.recv().await {
|
match rx.recv().await {
|
||||||
Some(InputEvent::Submit(e)) => opt.run = Cow::Owned(e),
|
Some(InputEvent::Submit(e)) => form.run = Cow::Owned(e),
|
||||||
_ => return,
|
_ => return,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if opt.run.is_empty() {
|
if form.run.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
TasksProxy::open_shell_compat(ProcessOpt {
|
TasksProxy::open_shell_compat(ProcessOpt {
|
||||||
cwd: cwd.into(),
|
cwd: cwd.into(),
|
||||||
cmd: opt.run.to_string().into(),
|
cmd: form.run.to_string().into(),
|
||||||
args: selected,
|
args: selected,
|
||||||
block: opt.block,
|
block: form.block,
|
||||||
orphan: opt.orphan,
|
orphan: form.orphan,
|
||||||
spread: true,
|
spread: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -14,14 +14,14 @@ impl Actor for Sort {
|
||||||
|
|
||||||
const NAME: &str = "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;
|
let pref = &mut cx.tab_mut().pref;
|
||||||
pref.sort_by = opt.by.unwrap_or(pref.sort_by);
|
pref.sort_by = form.by.unwrap_or(pref.sort_by);
|
||||||
pref.sort_reverse = opt.reverse.unwrap_or(pref.sort_reverse);
|
pref.sort_reverse = form.reverse.unwrap_or(pref.sort_reverse);
|
||||||
pref.sort_dir_first = opt.dir_first.unwrap_or(pref.sort_dir_first);
|
pref.sort_dir_first = form.dir_first.unwrap_or(pref.sort_dir_first);
|
||||||
pref.sort_sensitive = opt.sensitive.unwrap_or(pref.sort_sensitive);
|
pref.sort_sensitive = form.sensitive.unwrap_or(pref.sort_sensitive);
|
||||||
pref.sort_translit = opt.translit.unwrap_or(pref.sort_translit);
|
pref.sort_translit = form.translit.unwrap_or(pref.sort_translit);
|
||||||
pref.sort_fallback = opt.fallback.unwrap_or(pref.sort_fallback);
|
pref.sort_fallback = form.fallback.unwrap_or(pref.sort_fallback);
|
||||||
|
|
||||||
let sorter = FilesSorter::from(&*pref);
|
let sorter = FilesSorter::from(&*pref);
|
||||||
let hovered = cx.hovered().map(|f| f.urn().to_owned());
|
let hovered = cx.hovered().map(|f| f.urn().to_owned());
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ impl Actor for Spot {
|
||||||
|
|
||||||
const NAME: &str = "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)?;
|
act!(mgr:escape_visual, cx)?;
|
||||||
let Some(hovered) = cx.hovered().cloned() else { succ!() };
|
let Some(hovered) = cx.hovered().cloned() else { succ!() };
|
||||||
|
|
||||||
|
|
@ -26,7 +26,7 @@ impl Actor for Spot {
|
||||||
// self.active_mut().spot.reset();
|
// self.active_mut().spot.reset();
|
||||||
// }
|
// }
|
||||||
|
|
||||||
if let Some(skip) = opt.skip {
|
if let Some(skip) = form.skip {
|
||||||
cx.tab_mut().spot.skip = skip;
|
cx.tab_mut().spot.skip = skip;
|
||||||
} else if !cx.tab().spot.same_url(&hovered.url) {
|
} else if !cx.tab().spot.same_url(&hovered.url) {
|
||||||
cx.tab_mut().spot.skip = 0;
|
cx.tab_mut().spot.skip = 0;
|
||||||
|
|
|
||||||
|
|
@ -12,15 +12,15 @@ impl Actor for Stash {
|
||||||
|
|
||||||
const NAME: &str = "stash";
|
const NAME: &str = "stash";
|
||||||
|
|
||||||
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
|
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||||
if opt.target.is_absolute() && opt.target.is_internal() {
|
if form.target.is_absolute() && form.target.is_internal() {
|
||||||
cx.tab_mut().backstack.push(opt.target.as_url());
|
cx.tab_mut().backstack.push(form.target.as_url());
|
||||||
}
|
}
|
||||||
|
|
||||||
succ!()
|
succ!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hook(cx: &Ctx, _opt: &Self::Form) -> Option<SparkKind> {
|
fn hook(cx: &Ctx, _form: &Self::Form) -> Option<SparkKind> {
|
||||||
match cx.source() {
|
match cx.source() {
|
||||||
Source::Ind => Some(SparkKind::IndStash),
|
Source::Ind => Some(SparkKind::IndStash),
|
||||||
Source::Relay => Some(SparkKind::RelayStash),
|
Source::Relay => Some(SparkKind::RelayStash),
|
||||||
|
|
|
||||||
|
|
@ -12,16 +12,16 @@ impl Actor for TabClose {
|
||||||
|
|
||||||
const NAME: &str = "tab_close";
|
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();
|
let len = cx.tabs().len();
|
||||||
if len < 2 || opt.idx >= len {
|
if len < 2 || form.idx >= len {
|
||||||
succ!();
|
succ!();
|
||||||
}
|
}
|
||||||
|
|
||||||
let tabs = cx.tabs_mut();
|
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);
|
tabs.set_idx(tabs.cursor);
|
||||||
} else {
|
} else {
|
||||||
tabs.set_idx(usize::min(tabs.cursor + 1, tabs.len() - 1));
|
tabs.set_idx(usize::min(tabs.cursor + 1, tabs.len() - 1));
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ impl Actor for TabCreate {
|
||||||
|
|
||||||
const NAME: &str = "tab_create";
|
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 {
|
if cx.tabs().len() >= MAX_TABS {
|
||||||
succ!(NotifyProxy::push_warn(
|
succ!(NotifyProxy::push_warn(
|
||||||
"Too many tabs",
|
"Too many tabs",
|
||||||
|
|
@ -25,7 +25,7 @@ impl Actor for TabCreate {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut tab = Tab::default();
|
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())
|
(true, wd.into_owned())
|
||||||
} else if let Some(h) = cx.hovered() {
|
} else if let Some(h) = cx.hovered() {
|
||||||
tab.pref = cx.tab().pref.clone();
|
tab.pref = cx.tab().pref.clone();
|
||||||
|
|
|
||||||
|
|
@ -17,18 +17,18 @@ impl Actor for TabRename {
|
||||||
|
|
||||||
const NAME: &str = "tab_rename";
|
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 tab = cx.tab().id;
|
||||||
let pref = &mut cx.tab_mut().pref;
|
let pref = &mut cx.tab_mut().pref;
|
||||||
|
|
||||||
if !opt.interactive {
|
if !form.interactive {
|
||||||
pref.name = opt.name.unwrap_or_default().into_owned();
|
pref.name = form.name.unwrap_or_default().into_owned();
|
||||||
act!(app:title, cx).ok();
|
act!(app:title, cx).ok();
|
||||||
succ!(render!());
|
succ!(render!());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut input = InputProxy::show(
|
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 {
|
tokio::spawn(async move {
|
||||||
if let Some(InputEvent::Submit(name)) = input.recv().await {
|
if let Some(InputEvent::Submit(name)) = input.recv().await {
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,10 @@ impl Actor for TabSwap {
|
||||||
|
|
||||||
const NAME: &str = "tab_swap";
|
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 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 {
|
if new == tabs.cursor {
|
||||||
succ!();
|
succ!();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,12 @@ impl Actor for TabSwitch {
|
||||||
|
|
||||||
const NAME: &str = "tab_switch";
|
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 tabs = cx.tabs_mut();
|
||||||
let idx = if opt.relative {
|
let idx = if form.relative {
|
||||||
opt.step.saturating_add_unsigned(tabs.cursor).rem_euclid(tabs.len() as _) as _
|
form.step.saturating_add_unsigned(tabs.cursor).rem_euclid(tabs.len() as _) as _
|
||||||
} else {
|
} else {
|
||||||
opt.step as usize
|
form.step as usize
|
||||||
};
|
};
|
||||||
|
|
||||||
if idx == tabs.cursor || idx >= tabs.len() {
|
if idx == tabs.cursor || idx >= tabs.len() {
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,13 @@ impl Actor for Toggle {
|
||||||
|
|
||||||
const NAME: &str = "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 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!();
|
succ!();
|
||||||
};
|
};
|
||||||
|
|
||||||
let b = match opt.state {
|
let b = match form.state {
|
||||||
Some(true) => render_and!(tab.selected.add(&url)),
|
Some(true) => render_and!(tab.selected.add(&url)),
|
||||||
Some(false) => render_and!(tab.selected.remove(&url)) | true,
|
Some(false) => render_and!(tab.selected.remove(&url)) | true,
|
||||||
None => render_and!(tab.selected.remove(&url) || tab.selected.add(&url)),
|
None => render_and!(tab.selected.remove(&url) || tab.selected.add(&url)),
|
||||||
|
|
|
||||||
|
|
@ -13,18 +13,18 @@ impl Actor for ToggleAll {
|
||||||
|
|
||||||
const NAME: &str = "toggle_all";
|
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::*;
|
use either::Either::*;
|
||||||
let tab = cx.tab_mut();
|
let tab = cx.tab_mut();
|
||||||
|
|
||||||
let it = tab.current.files.iter().map(|f| &f.url);
|
let it = tab.current.files.iter().map(|f| &f.url);
|
||||||
let either = match opt.state {
|
let either = match form.state {
|
||||||
Some(true) if opt.urls.is_empty() => Left((vec![], it.collect())),
|
Some(true) if form.urls.is_empty() => Left((vec![], it.collect())),
|
||||||
Some(true) => Right((vec![], opt.urls)),
|
Some(true) => Right((vec![], form.urls)),
|
||||||
Some(false) if opt.urls.is_empty() => Left((it.collect(), vec![])),
|
Some(false) if form.urls.is_empty() => Left((it.collect(), vec![])),
|
||||||
Some(false) => Right((opt.urls, vec![])),
|
Some(false) => Right((form.urls, vec![])),
|
||||||
None if opt.urls.is_empty() => Left(it.partition(|&u| tab.selected.contains(u))),
|
None if form.urls.is_empty() => Left(it.partition(|&u| tab.selected.contains(u))),
|
||||||
None => Right(opt.urls.into_iter().partition(|u| tab.selected.contains(u))),
|
None => Right(form.urls.into_iter().partition(|u| tab.selected.contains(u))),
|
||||||
};
|
};
|
||||||
|
|
||||||
let warn = match either {
|
let warn = match either {
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,11 @@ impl Actor for UpdateFiles {
|
||||||
|
|
||||||
const NAME: &str = "update_files";
|
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 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);
|
cx.mgr.yanked.apply_op(&op);
|
||||||
Self::update_tab(cx, op).ok();
|
Self::update_tab(cx, op).ok();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,9 @@ impl Actor for UpdateMimes {
|
||||||
|
|
||||||
const NAME: &str = "update_mimes";
|
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 linked = LINKED.read();
|
||||||
let updates = opt
|
let updates = form
|
||||||
.updates
|
.updates
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|(url, mime)| cx.mgr.mimetype.get(url) != Some(mime))
|
.filter(|(url, mime)| cx.mgr.mimetype.get(url) != Some(mime))
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,12 @@ impl Actor for UpdatePaged {
|
||||||
|
|
||||||
const NAME: &str = "update_paged";
|
const NAME: &str = "update_paged";
|
||||||
|
|
||||||
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
|
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||||
if opt.only_if.is_some_and(|u| u != *cx.cwd()) {
|
if form.only_if.is_some_and(|u| u != *cx.cwd()) {
|
||||||
succ!();
|
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() {
|
if !targets.is_empty() {
|
||||||
cx.tasks.fetch_paged(targets, &cx.mgr.mimetype);
|
cx.tasks.fetch_paged(targets, &cx.mgr.mimetype);
|
||||||
cx.tasks.preload_paged(targets, &cx.mgr.mimetype);
|
cx.tasks.preload_paged(targets, &cx.mgr.mimetype);
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,12 @@ impl Actor for UpdateYanked {
|
||||||
|
|
||||||
const NAME: &str = "update_yanked";
|
const NAME: &str = "update_yanked";
|
||||||
|
|
||||||
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
|
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||||
if opt.urls.is_empty() && cx.mgr.yanked.is_empty() {
|
if form.urls.is_empty() && cx.mgr.yanked.is_empty() {
|
||||||
succ!();
|
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!());
|
succ!(render!());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ impl Actor for Upload {
|
||||||
|
|
||||||
const NAME: &str = "upload";
|
const NAME: &str = "upload";
|
||||||
|
|
||||||
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
|
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||||
for url in opt.urls {
|
for url in form.urls {
|
||||||
cx.tasks.scheduler.file_upload(url.into_owned());
|
cx.tasks.scheduler.file_upload(url.into_owned());
|
||||||
}
|
}
|
||||||
succ!();
|
succ!();
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,11 @@ impl Actor for VisualMode {
|
||||||
|
|
||||||
const NAME: &str = "visual_mode";
|
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 tab = cx.tab_mut();
|
||||||
|
|
||||||
let idx = tab.current.cursor;
|
let idx = tab.current.cursor;
|
||||||
if opt.unset {
|
if form.unset {
|
||||||
tab.mode = Mode::Unset(idx, BTreeSet::from([idx]));
|
tab.mode = Mode::Unset(idx, BTreeSet::from([idx]));
|
||||||
} else {
|
} else {
|
||||||
tab.mode = Mode::Select(idx, BTreeSet::from([idx]));
|
tab.mode = Mode::Select(idx, BTreeSet::from([idx]));
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,11 @@ impl Actor for Yank {
|
||||||
|
|
||||||
const NAME: &str = "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)?;
|
act!(mgr:escape_visual, cx)?;
|
||||||
|
|
||||||
cx.mgr.yanked =
|
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));
|
render!(cx.mgr.yanked.catchup_revision(true));
|
||||||
|
|
||||||
act!(mgr:escape_select, cx)
|
act!(mgr:escape_select, cx)
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ impl Actor for Tick {
|
||||||
|
|
||||||
const NAME: &str = "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());
|
cx.notify.ticker.take().map(|h| h.abort());
|
||||||
|
|
||||||
let Dimension { rows, columns, .. } = Dimension::available();
|
let Dimension { rows, columns, .. } = Dimension::available();
|
||||||
|
|
@ -35,7 +35,7 @@ impl Actor for Tick {
|
||||||
} else if m.percent < 100 {
|
} else if m.percent < 100 {
|
||||||
m.percent += 20;
|
m.percent += 20;
|
||||||
} else {
|
} else {
|
||||||
m.timeout = m.timeout.saturating_sub(opt.interval);
|
m.timeout = m.timeout.saturating_sub(form.interval);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ impl Actor for Arrow {
|
||||||
|
|
||||||
const NAME: &str = "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> {
|
||||||
succ!(render!(cx.pick.scroll(opt.step)));
|
succ!(render!(cx.pick.scroll(form.step)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,10 @@ impl Actor for Close {
|
||||||
|
|
||||||
const NAME: &str = "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;
|
let pick = &mut cx.pick;
|
||||||
if let Some(cb) = pick.callback.take() {
|
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;
|
pick.cursor = 0;
|
||||||
|
|
|
||||||
|
|
@ -12,15 +12,15 @@ impl Actor for Show {
|
||||||
|
|
||||||
const NAME: &str = "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)?;
|
act!(pick:close, cx)?;
|
||||||
|
|
||||||
let pick = &mut cx.pick;
|
let pick = &mut cx.pick;
|
||||||
pick.title = opt.cfg.title;
|
pick.title = form.cfg.title;
|
||||||
pick.items = opt.cfg.items;
|
pick.items = form.cfg.items;
|
||||||
pick.position = opt.cfg.position;
|
pick.position = form.cfg.position;
|
||||||
|
|
||||||
pick.callback = Some(opt.tx);
|
pick.callback = Some(form.tx);
|
||||||
pick.visible = true;
|
pick.visible = true;
|
||||||
succ!(render!());
|
succ!(render!());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,11 @@ impl Actor for Arrow {
|
||||||
|
|
||||||
const NAME: &str = "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 spot = &mut cx.tab_mut().spot;
|
||||||
let Some(lock) = &mut spot.lock else { succ!() };
|
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 {
|
let Some(old) = lock.selected() else {
|
||||||
return act!(mgr:spot, cx, new);
|
return act!(mgr:spot, cx, new);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,13 @@ impl Actor for Copy {
|
||||||
|
|
||||||
const NAME: &str = "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 spot = &cx.tab().spot;
|
||||||
let Some(lock) = &spot.lock else { succ!() };
|
let Some(lock) = &spot.lock else { succ!() };
|
||||||
let Some(table) = lock.table() else { succ!() };
|
let Some(table) = lock.table() else { succ!() };
|
||||||
|
|
||||||
let mut s = String::new();
|
let mut s = String::new();
|
||||||
match opt.r#type.as_ref() {
|
match form.r#type.as_ref() {
|
||||||
"cell" => {
|
"cell" => {
|
||||||
let Some(cell) = table.selected_cell() else { succ!() };
|
let Some(cell) = table.selected_cell() else { succ!() };
|
||||||
s = cell.to_string();
|
s = cell.to_string();
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ impl Actor for Swipe {
|
||||||
|
|
||||||
const NAME: &str = "swipe";
|
const NAME: &str = "swipe";
|
||||||
|
|
||||||
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
|
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||||
act!(mgr:arrow, cx, opt)?;
|
act!(mgr:arrow, cx, form)?;
|
||||||
act!(mgr:spot, cx)
|
act!(mgr:spot, cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,11 @@ impl Actor for Arrow {
|
||||||
|
|
||||||
const NAME: &str = "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 tasks = &mut cx.tasks;
|
||||||
|
|
||||||
let old = tasks.cursor;
|
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));
|
succ!(render!(tasks.cursor != old));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use yazi_macro::succ;
|
use yazi_macro::{act, succ};
|
||||||
use yazi_parser::tasks::UpdateSucceedForm;
|
use yazi_parser::tasks::UpdateSucceedForm;
|
||||||
use yazi_shared::data::Data;
|
use yazi_shared::{data::Data, url::UrlLike};
|
||||||
|
|
||||||
use crate::{Actor, Ctx};
|
use crate::{Actor, Ctx};
|
||||||
|
|
||||||
|
|
@ -12,23 +12,21 @@ impl Actor for UpdateSucceed {
|
||||||
|
|
||||||
const NAME: &str = "update_succeed";
|
const NAME: &str = "update_succeed";
|
||||||
|
|
||||||
fn act(cx: &mut Ctx, opt: Self::Form) -> Result<Data> {
|
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||||
if opt.urls.is_empty() {
|
if form.urls.is_empty() {
|
||||||
succ!();
|
succ!();
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: todo!();
|
if form.track
|
||||||
// if opt.track
|
&& form.id == cx.tasks.scheduler.behavior.first_id()
|
||||||
// && opt.batch == cx.tasks.scheduler.batch.current()
|
&& let Some((parent, urn)) = form.urls[0].pair()
|
||||||
// && let Some((parent, urn)) = opt.urls[0].pair()
|
&& parent == *cx.cwd()
|
||||||
// && parent == *cx.cwd()
|
{
|
||||||
// {
|
cx.current_mut().trace = Some(urn.into());
|
||||||
// cx.tasks.scheduler.batch.next();
|
act!(mgr:hover, cx)?;
|
||||||
// cx.current_mut().trace = Some(urn.into());
|
}
|
||||||
// act!(mgr:hover, cx)?;
|
|
||||||
// }
|
|
||||||
|
|
||||||
cx.mgr.watcher.report(opt.urls);
|
cx.mgr.watcher.report(form.urls);
|
||||||
succ!();
|
succ!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ impl Actor for Activate {
|
||||||
succ!(render!());
|
succ!(render!());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hook(cx: &Ctx, _opt: &Self::Form) -> Option<SparkKind> {
|
fn hook(cx: &Ctx, _form: &Self::Form) -> Option<SparkKind> {
|
||||||
match cx.source() {
|
match cx.source() {
|
||||||
Source::Unknown => Some(SparkKind::IndWhichActivate),
|
Source::Unknown => Some(SparkKind::IndWhichActivate),
|
||||||
_ => None,
|
_ => None,
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,7 @@ impl Folder {
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut deleted = vec![];
|
||||||
match op {
|
match op {
|
||||||
FilesOp::Full(_, files, _) => self.files.update_full(files),
|
FilesOp::Full(_, files, _) => self.files.update_full(files),
|
||||||
FilesOp::Part(_, files, ticket) => self.files.update_part(files, ticket),
|
FilesOp::Part(_, files, ticket) => self.files.update_part(files, ticket),
|
||||||
|
|
@ -71,16 +72,13 @@ impl Folder {
|
||||||
FilesOp::IOErr(..) => self.files.update_ioerr(),
|
FilesOp::IOErr(..) => self.files.update_ioerr(),
|
||||||
|
|
||||||
FilesOp::Creating(_, files) => self.files.update_creating(files),
|
FilesOp::Creating(_, files) => self.files.update_creating(files),
|
||||||
FilesOp::Deleting(_, urns) => {
|
FilesOp::Deleting(_, urns) => deleted = self.files.update_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::Updating(_, files) => _ = self.files.update_updating(files),
|
FilesOp::Updating(_, files) => _ = self.files.update_updating(files),
|
||||||
FilesOp::Upserting(_, files) => self.files.update_upserting(files),
|
FilesOp::Upserting(_, files) => self.files.update_upserting(files),
|
||||||
}
|
};
|
||||||
|
|
||||||
self.trace.take_if(|_| self.files.is_empty() && !self.stage.is_loading());
|
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);
|
self.repos(None);
|
||||||
|
|
||||||
(&stage, revision) != (&self.stage, self.files.revision)
|
(&stage, revision) != (&self.stage, self.files.revision)
|
||||||
|
|
@ -101,9 +99,7 @@ impl Folder {
|
||||||
self.scroll(step)
|
self.scroll(step)
|
||||||
};
|
};
|
||||||
|
|
||||||
self.trace = self.hovered().filter(|_| b).map(|h| h.urn().into()).or(self.trace.take());
|
|
||||||
b |= self.squeeze_offset();
|
b |= self.squeeze_offset();
|
||||||
|
|
||||||
self.sync_page(false);
|
self.sync_page(false);
|
||||||
b
|
b
|
||||||
}
|
}
|
||||||
|
|
@ -114,19 +110,28 @@ impl Folder {
|
||||||
}
|
}
|
||||||
|
|
||||||
let new = self.files.position(urn).unwrap_or(self.cursor) as isize;
|
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 {
|
pub fn repos(&mut self, urn: Option<PathDyn>) -> bool {
|
||||||
if let Some(u) = urn {
|
if let Some(u) = urn {
|
||||||
self.hover(u)
|
self.hover(u)
|
||||||
} else if let Some(u) = &self.trace {
|
} else if let Some(u) = self.trace.take() {
|
||||||
self.hover(u.clone().as_path())
|
let b = self.hover(u.as_path());
|
||||||
|
self.trace = Some(u);
|
||||||
|
b
|
||||||
} else {
|
} else {
|
||||||
self.arrow(0)
|
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) {
|
pub fn sync_page(&mut self, force: bool) {
|
||||||
let limit = LAYOUT.get().folder_limit();
|
let limit = LAYOUT.get().folder_limit();
|
||||||
if limit == 0 {
|
if limit == 0 {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ use crate::mgr::Yanked;
|
||||||
|
|
||||||
impl Tasks {
|
impl Tasks {
|
||||||
pub fn file_cut(&self, src: &Yanked, dest: &UrlBuf, force: bool) {
|
pub fn file_cut(&self, src: &Yanked, dest: &UrlBuf, force: bool) {
|
||||||
|
self.scheduler.behavior.reset();
|
||||||
|
|
||||||
for u in src.iter() {
|
for u in src.iter() {
|
||||||
let Some(Ok(to)) = u.name().map(|n| dest.try_join(n)) else {
|
let Some(Ok(to)) = u.name().map(|n| dest.try_join(n)) else {
|
||||||
debug!("file_cut: cannot join {u:?} with {dest:?}");
|
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) {
|
pub fn file_copy(&self, src: &Yanked, dest: &UrlBuf, force: bool, follow: bool) {
|
||||||
|
self.scheduler.behavior.reset();
|
||||||
|
|
||||||
for u in src.iter() {
|
for u in src.iter() {
|
||||||
let Some(Ok(to)) = u.name().map(|n| dest.try_join(n)) else {
|
let Some(Ok(to)) = u.name().map(|n| dest.try_join(n)) else {
|
||||||
debug!("file_copy: cannot join {u:?} with {dest:?}");
|
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) {
|
pub fn file_link(&self, src: &IndexSet<UrlBufCov>, dest: &UrlBuf, relative: bool, force: bool) {
|
||||||
|
self.scheduler.behavior.reset();
|
||||||
|
|
||||||
for u in src {
|
for u in src {
|
||||||
let Some(Ok(to)) = u.name().map(|n| dest.try_join(n)) else {
|
let Some(Ok(to)) = u.name().map(|n| dest.try_join(n)) else {
|
||||||
debug!("file_link: cannot join {u:?} with {dest:?}");
|
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) {
|
pub fn file_hardlink(&self, src: &IndexSet<UrlBufCov>, dest: &UrlBuf, force: bool, follow: bool) {
|
||||||
|
self.scheduler.behavior.reset();
|
||||||
|
|
||||||
for u in src {
|
for u in src {
|
||||||
let Some(Ok(to)) = u.name().map(|n| dest.try_join(n)) else {
|
let Some(Ok(to)) = u.name().map(|n| dest.try_join(n)) else {
|
||||||
debug!("file_hardlink: cannot join {u:?} with {dest:?}");
|
debug!("file_hardlink: cannot join {u:?} with {dest:?}");
|
||||||
|
|
|
||||||
|
|
@ -53,12 +53,12 @@ impl<'a> Executor<'a> {
|
||||||
"resize" => act!(app:resize, cx, crate::Root::reflow as fn(_) -> _),
|
"resize" => act!(app:resize, cx, crate::Root::reflow as fn(_) -> _),
|
||||||
"resume" => act!(app:resume, cx, yazi_parser::app::ResumeForm {
|
"resume" => act!(app:resume, cx, yazi_parser::app::ResumeForm {
|
||||||
tx: self.app.signals.tx.clone(),
|
tx: self.app.signals.tx.clone(),
|
||||||
token: action.take_any("token").context("Invalid 'token' in ResumeForm")?,
|
|
||||||
reflow: crate::Root::reflow,
|
reflow: crate::Root::reflow,
|
||||||
|
replier: action.take_replier().context("Missing replier in ResumeForm")?,
|
||||||
}),
|
}),
|
||||||
"stop" => act!(app:stop, cx, yazi_parser::app::StopForm {
|
"stop" => act!(app:stop, cx, yazi_parser::app::StopForm {
|
||||||
tx: self.app.signals.tx.clone(),
|
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!(),
|
_ => succ!(),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@ use crossterm::event::{Event as CrosstermEvent, EventStream, KeyEvent, KeyEventK
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use tokio::{select, sync::mpsc};
|
use tokio::{select, sync::mpsc};
|
||||||
use yazi_config::YAZI;
|
use yazi_config::YAZI;
|
||||||
use yazi_shared::{CompletionToken, event::Event};
|
use yazi_shared::event::{Event, Replier};
|
||||||
|
|
||||||
pub(super) struct Signals {
|
pub(super) struct Signals {
|
||||||
pub(super) tx: mpsc::UnboundedSender<(bool, CompletionToken)>,
|
pub(super) tx: mpsc::UnboundedSender<(bool, Replier)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Signals {
|
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)]
|
#[cfg(unix)]
|
||||||
use libc::{SIGCONT, SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGTSTP};
|
use libc::{SIGCONT, SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGTSTP};
|
||||||
|
|
||||||
|
|
@ -90,9 +90,9 @@ impl Signals {
|
||||||
if let Some(t) = &mut term {
|
if let Some(t) = &mut term {
|
||||||
select! {
|
select! {
|
||||||
biased;
|
biased;
|
||||||
Some((state, token)) = rx.recv() => {
|
Some((state, replier)) = rx.recv() => {
|
||||||
term = term.filter(|_| state);
|
term = term.filter(|_| state);
|
||||||
token.complete(true);
|
replier.send(Ok(().into())).ok();
|
||||||
},
|
},
|
||||||
Some(n) = sys.next() => if !Self::handle_sys(n) { return },
|
Some(n) = sys.next() => if !Self::handle_sys(n) { return },
|
||||||
Some(Ok(e)) = t.next() => Self::handle_term(e)
|
Some(Ok(e)) = t.next() => Self::handle_term(e)
|
||||||
|
|
@ -100,9 +100,9 @@ impl Signals {
|
||||||
} else {
|
} else {
|
||||||
select! {
|
select! {
|
||||||
biased;
|
biased;
|
||||||
Some((state, token)) = rx.recv() => {
|
Some((state, replier)) = rx.recv() => {
|
||||||
term = state.then(EventStream::new);
|
term = state.then(EventStream::new);
|
||||||
token.complete(true);
|
replier.send(Ok(().into())).ok();
|
||||||
},
|
},
|
||||||
Some(n) = sys.next() => if !Self::handle_sys(n) { return },
|
Some(n) = sys.next() => if !Self::handle_sys(n) { return },
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ yazi-shim = { path = "../yazi-shim", version = "26.2.2" }
|
||||||
|
|
||||||
# External dependencies
|
# External dependencies
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
arc-swap = "1.9.0"
|
arc-swap = { workspace = true }
|
||||||
bitflags = { workspace = true }
|
bitflags = { workspace = true }
|
||||||
dirs = { workspace = true }
|
dirs = { workspace = true }
|
||||||
either = { workspace = true }
|
either = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
use mlua::{ExternalError, FromLua, IntoLua, Lua, Table, Value};
|
use mlua::{ExternalError, FromLua, IntoLua, Lua, Table, Value};
|
||||||
use ratatui::layout::Rect;
|
use ratatui::layout::Rect;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use yazi_shared::CompletionToken;
|
use yazi_shared::event::Replier;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ResumeForm {
|
pub struct ResumeForm {
|
||||||
pub tx: mpsc::UnboundedSender<(bool, CompletionToken)>,
|
pub tx: mpsc::UnboundedSender<(bool, Replier)>,
|
||||||
pub token: CompletionToken,
|
pub reflow: fn(Rect) -> mlua::Result<Table>,
|
||||||
pub reflow: fn(Rect) -> mlua::Result<Table>,
|
pub replier: Replier,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromLua for ResumeForm {
|
impl FromLua for ResumeForm {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
|
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use yazi_shared::CompletionToken;
|
use yazi_shared::event::Replier;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct StopForm {
|
pub struct StopForm {
|
||||||
pub tx: mpsc::UnboundedSender<(bool, CompletionToken)>,
|
pub tx: mpsc::UnboundedSender<(bool, Replier)>,
|
||||||
pub token: CompletionToken,
|
pub replier: Replier,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromLua for StopForm {
|
impl FromLua for StopForm {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use anyhow::bail;
|
use anyhow::anyhow;
|
||||||
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
|
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
|
||||||
use yazi_core::cmp::CmpOpt;
|
use yazi_core::cmp::CmpOpt;
|
||||||
use yazi_shared::event::ActionCow;
|
use yazi_shared::event::ActionCow;
|
||||||
|
|
@ -16,11 +16,7 @@ impl TryFrom<ActionCow> for ShowForm {
|
||||||
type Error = anyhow::Error;
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
fn try_from(mut a: ActionCow) -> Result<Self, Self::Error> {
|
fn try_from(mut a: ActionCow) -> Result<Self, Self::Error> {
|
||||||
if let Some(opt) = a.take_any2("opt") {
|
Ok(Self { opt: a.take_any("opt").ok_or_else(|| anyhow!("Invalid 'opt' in ShowForm"))? })
|
||||||
opt
|
|
||||||
} else {
|
|
||||||
bail!("Invalid 'opt' in ShowForm");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ impl TryFrom<ActionCow> for StashForm {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<CdForm> 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 {
|
impl FromLua for StashForm {
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
|
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
|
||||||
use tokio::sync::mpsc;
|
|
||||||
use yazi_scheduler::process::ProcessOpt;
|
use yazi_scheduler::process::ProcessOpt;
|
||||||
use yazi_shared::{data::Data, event::ActionCow};
|
use yazi_shared::event::{ActionCow, Replier};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ProcessOpenForm {
|
pub struct ProcessOpenForm {
|
||||||
pub opt: ProcessOpt,
|
pub opt: ProcessOpt,
|
||||||
pub replier: Option<mpsc::UnboundedSender<anyhow::Result<Data>>>,
|
pub replier: Option<Replier>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<ActionCow> for ProcessOpenForm {
|
impl TryFrom<ActionCow> for ProcessOpenForm {
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ pub(super) fn lines(lua: &Lua) -> mlua::Result<Value> {
|
||||||
parsed = s.to_text().into_lua_err()?.lines;
|
parsed = s.to_text().into_lua_err()?.lines;
|
||||||
LineIter::parsed(&parsed, tab_size)
|
LineIter::parsed(&parsed, tab_size)
|
||||||
} else {
|
} else {
|
||||||
LineIter::source(&s, tab_size)
|
LineIter::source(s, tab_size)
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(wrap) = *opts.raw_get::<Wrap>("wrap")? {
|
if let Some(wrap) = *opts.raw_get::<Wrap>("wrap")? {
|
||||||
|
|
|
||||||
|
|
@ -8,16 +8,16 @@ pub fn slim_lua(lua: &Lua) -> mlua::Result<()> {
|
||||||
globals.raw_set("ya", crate::utils::compose(true))?;
|
globals.raw_set("ya", crate::utils::compose(true))?;
|
||||||
globals.raw_set("fs", crate::fs::compose())?;
|
globals.raw_set("fs", crate::fs::compose())?;
|
||||||
globals.raw_set("rt", crate::runtime::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::Cha::install(lua)?;
|
||||||
yazi_binding::File::install(&lua)?;
|
yazi_binding::File::install(lua)?;
|
||||||
yazi_binding::Url::install(&lua)?;
|
yazi_binding::Url::install(lua)?;
|
||||||
yazi_binding::Path::install(&lua)?;
|
yazi_binding::Path::install(lua)?;
|
||||||
|
|
||||||
yazi_binding::Error::install(&lua)?;
|
yazi_binding::Error::install(lua)?;
|
||||||
yazi_binding::process::install(&lua)?;
|
yazi_binding::process::install(lua)?;
|
||||||
yazi_runner::loader::install(&lua)?;
|
yazi_runner::loader::install(lua)?;
|
||||||
|
|
||||||
// Addons
|
// Addons
|
||||||
lua.load(preset!("ya")).set_name("ya.lua").exec()?;
|
lua.load(preset!("ya")).set_name("ya.lua").exec()?;
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ impl IntoLua for FetchJob {
|
||||||
fn into_lua(self, lua: &mlua::Lua) -> mlua::Result<Value> {
|
fn into_lua(self, lua: &mlua::Lua) -> mlua::Result<Value> {
|
||||||
lua
|
lua
|
||||||
.create_table_from([
|
.create_table_from([
|
||||||
("args", Sendable::args_to_table_ref(&lua, &self.action.args)?.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)?),
|
("files", lua.create_sequence_from(self.files.into_iter().map(File::new))?.into_lua(lua)?),
|
||||||
])?
|
])?
|
||||||
.into_lua(lua)
|
.into_lua(lua)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,11 @@ impl IntoLua for PeekJob {
|
||||||
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
|
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
|
||||||
lua
|
lua
|
||||||
.create_table_from([
|
.create_table_from([
|
||||||
("area", Rect::from(LAYOUT.get().preview).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)?),
|
("args", Sendable::args_to_table_ref(lua, &self.action.args)?.into_lua(lua)?),
|
||||||
("file", File::new(self.file).into_lua(&lua)?),
|
("file", File::new(self.file).into_lua(lua)?),
|
||||||
("mime", self.mime.into_lua(&lua)?),
|
("mime", self.mime.into_lua(lua)?),
|
||||||
("skip", self.skip.into_lua(&lua)?),
|
("skip", self.skip.into_lua(lua)?),
|
||||||
])?
|
])?
|
||||||
.into_lua(lua)
|
.into_lua(lua)
|
||||||
}
|
}
|
||||||
|
|
@ -37,9 +37,9 @@ impl IntoLua for SeekJob {
|
||||||
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
|
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
|
||||||
lua
|
lua
|
||||||
.create_table_from([
|
.create_table_from([
|
||||||
("area", Rect::from(LAYOUT.get().preview).into_lua(&lua)?),
|
("area", Rect::from(LAYOUT.get().preview).into_lua(lua)?),
|
||||||
("file", File::new(self.file).into_lua(&lua)?),
|
("file", File::new(self.file).into_lua(lua)?),
|
||||||
("units", self.units.into_lua(&lua)?),
|
("units", self.units.into_lua(lua)?),
|
||||||
])?
|
])?
|
||||||
.into_lua(lua)
|
.into_lua(lua)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
19
yazi-scheduler/src/behavior.rs
Normal file
19
yazi-scheduler/src/behavior.rs
Normal 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() }
|
||||||
|
}
|
||||||
|
|
@ -10,7 +10,7 @@ pub(crate) struct FetchIn {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<FetchIn> for FetchJob {
|
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 {
|
impl FetchIn {
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ impl Hook {
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = ok_or_not_found(provider::remove_dir_clean(&task.from).await);
|
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);
|
Pump::push_move(task.from, task.to);
|
||||||
|
|
||||||
self.ops.out(task.id, FileOutCut::Clean(result));
|
self.ops.out(task.id, FileOutCut::Clean(result));
|
||||||
|
|
@ -38,7 +38,7 @@ impl Hook {
|
||||||
|
|
||||||
pub(crate) async fn copy(&self, task: HookInOutCopy) {
|
pub(crate) async fn copy(&self, task: HookInOutCopy) {
|
||||||
if self.ongoing.lock().intact(task.id) {
|
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);
|
Pump::push_duplicate(task.from, task.to);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ mod macros;
|
||||||
|
|
||||||
yazi_macro::mod_pub!(fetch file hook plugin preload process size);
|
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 LOW: u8 = yazi_config::Priority::Low as u8;
|
||||||
const NORMAL: u8 = yazi_config::Priority::Normal as u8;
|
const NORMAL: u8 = yazi_config::Priority::Normal as u8;
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,20 @@
|
||||||
|
use tokio::sync::mpsc;
|
||||||
use yazi_macro::{emit, relay};
|
use yazi_macro::{emit, relay};
|
||||||
use yazi_shared::{CompletionToken, Id, SStr, url::UrlBuf};
|
use yazi_shared::{Id, SStr, url::UrlBuf};
|
||||||
|
|
||||||
pub struct AppProxy;
|
pub struct AppProxy;
|
||||||
|
|
||||||
impl AppProxy {
|
impl AppProxy {
|
||||||
// FIXME: use replier instead of token
|
|
||||||
pub async fn stop() {
|
pub async fn stop() {
|
||||||
let token = CompletionToken::default();
|
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||||
emit!(Call(relay!(app:stop).with_any("token", token.clone())));
|
emit!(Call(relay!(app:stop).with_replier(tx)));
|
||||||
token.future().await;
|
rx.recv().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: use replier instead of token
|
|
||||||
pub async fn resume() {
|
pub async fn resume() {
|
||||||
let token = CompletionToken::default();
|
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||||
emit!(Call(relay!(app:resume).with_any("token", token.clone())));
|
emit!(Call(relay!(app:resume).with_replier(tx)));
|
||||||
token.future().await;
|
rx.recv().await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 tokio::task::JoinHandle;
|
||||||
use yazi_config::{YAZI, plugin::{Fetcher, Preloader}};
|
use yazi_config::{YAZI, plugin::{Fetcher, Preloader}};
|
||||||
use yazi_runner::plugin::PluginOpt;
|
use yazi_runner::plugin::PluginOpt;
|
||||||
use yazi_shared::{CompletionToken, Id, Throttle, url::{UrlBuf, UrlLike}};
|
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 struct Scheduler {
|
||||||
pub worker: Worker,
|
pub worker: Worker,
|
||||||
pub user_action: AtomicU64,
|
pub behavior: Behavior,
|
||||||
handles: Vec<JoinHandle<()>>,
|
handles: Vec<JoinHandle<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for Scheduler {
|
impl Deref for Scheduler {
|
||||||
|
|
@ -22,7 +22,7 @@ impl Deref for Scheduler {
|
||||||
impl Scheduler {
|
impl Scheduler {
|
||||||
pub fn serve() -> Self {
|
pub fn serve() -> Self {
|
||||||
let (worker, handles) = Worker::make();
|
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
|
fn add<T, R>(&self, name: String, map: impl FnOnce(&mut Task) -> R) -> R
|
||||||
|
|
@ -30,14 +30,11 @@ impl Scheduler {
|
||||||
T: Into<TaskProg> + Default,
|
T: Into<TaskProg> + Default,
|
||||||
{
|
{
|
||||||
let prog = T::default().into();
|
let prog = T::default().into();
|
||||||
let is_user = prog.is_user();
|
|
||||||
|
|
||||||
let mut ongoing = self.ongoing.lock();
|
let mut ongoing = self.ongoing.lock();
|
||||||
let task = ongoing.add(name, prog);
|
let task = ongoing.add(name, prog);
|
||||||
|
|
||||||
if is_user {
|
self.behavior.update(task.id);
|
||||||
self.user_action.store(task.id.get(), Ordering::Relaxed);
|
|
||||||
}
|
|
||||||
map(task)
|
map(task)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ impl From<&Ongoing> for TaskSummary {
|
||||||
let mut percent_count = 0;
|
let mut percent_count = 0;
|
||||||
|
|
||||||
for task in value.values() {
|
for task in value.values() {
|
||||||
let s: TaskSummary = task.prog.into();
|
let s: Self = task.prog.into();
|
||||||
if s.total == 0 && !task.prog.failed() {
|
if s.total == 0 && !task.prog.failed() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,19 +12,19 @@ impl<'de> Deserializer<'de> for Data {
|
||||||
V: de::Visitor<'de>,
|
V: de::Visitor<'de>,
|
||||||
{
|
{
|
||||||
match self {
|
match self {
|
||||||
Data::Nil => visitor.visit_unit(),
|
Self::Nil => visitor.visit_unit(),
|
||||||
Data::Boolean(b) => visitor.visit_bool(b),
|
Self::Boolean(b) => visitor.visit_bool(b),
|
||||||
Data::Integer(i) => visitor.visit_i64(i),
|
Self::Integer(i) => visitor.visit_i64(i),
|
||||||
Data::Number(n) => visitor.visit_f64(n),
|
Self::Number(n) => visitor.visit_f64(n),
|
||||||
Data::String(Cow::Borrowed(s)) => visitor.visit_borrowed_str(s),
|
Self::String(Cow::Borrowed(s)) => visitor.visit_borrowed_str(s),
|
||||||
Data::String(Cow::Owned(s)) => visitor.visit_string(s),
|
Self::String(Cow::Owned(s)) => visitor.visit_string(s),
|
||||||
Data::List(l) => visitor.visit_seq(SeqDeserializer { iter: l.into_iter() }),
|
Self::List(l) => visitor.visit_seq(SeqDeserializer { iter: l.into_iter() }),
|
||||||
Data::Dict(d) => visitor.visit_map(MapDeserializer { iter: d.into_iter(), value: None }),
|
Self::Dict(d) => visitor.visit_map(MapDeserializer { iter: d.into_iter(), value: None }),
|
||||||
Data::Id(i) => visitor.visit_u64(i.get()),
|
Self::Id(i) => visitor.visit_u64(i.get()),
|
||||||
Data::Url(u) => u.into_deserializer().deserialize_any(visitor),
|
Self::Url(u) => u.into_deserializer().deserialize_any(visitor),
|
||||||
Data::Path(_) => Err(Error::custom("path not supported")),
|
Self::Path(_) => Err(Error::custom("path not supported")),
|
||||||
Data::Bytes(b) => BytesDeserializer(b.into()).deserialize_any(visitor),
|
Self::Bytes(b) => BytesDeserializer(b.into()).deserialize_any(visitor),
|
||||||
Data::Any(_) => Err(Error::custom("any not supported")),
|
Self::Any(_) => Err(Error::custom("any not supported")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,9 +122,9 @@ impl<'de> Deserializer<'de> for Data {
|
||||||
V: de::Visitor<'de>,
|
V: de::Visitor<'de>,
|
||||||
{
|
{
|
||||||
match self {
|
match self {
|
||||||
Data::String(Cow::Borrowed(s)) => visitor.visit_borrowed_str(s),
|
Self::String(Cow::Borrowed(s)) => visitor.visit_borrowed_str(s),
|
||||||
Data::String(Cow::Owned(s)) => visitor.visit_string(s),
|
Self::String(Cow::Owned(s)) => visitor.visit_string(s),
|
||||||
Data::Url(u) => visitor.visit_newtype_struct(u.into_deserializer()),
|
Self::Url(u) => visitor.visit_newtype_struct(u.into_deserializer()),
|
||||||
_ => Err(Error::custom("not a string")),
|
_ => Err(Error::custom("not a string")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -141,7 +141,7 @@ impl<'de> Deserializer<'de> for Data {
|
||||||
V: de::Visitor<'de>,
|
V: de::Visitor<'de>,
|
||||||
{
|
{
|
||||||
match self {
|
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")),
|
_ => Err(Error::custom("not bytes")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -158,7 +158,7 @@ impl<'de> Deserializer<'de> for Data {
|
||||||
V: de::Visitor<'de>,
|
V: de::Visitor<'de>,
|
||||||
{
|
{
|
||||||
match self {
|
match self {
|
||||||
Data::Nil => visitor.visit_none(),
|
Self::Nil => visitor.visit_none(),
|
||||||
other => visitor.visit_some(other),
|
other => visitor.visit_some(other),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -168,7 +168,7 @@ impl<'de> Deserializer<'de> for Data {
|
||||||
V: de::Visitor<'de>,
|
V: de::Visitor<'de>,
|
||||||
{
|
{
|
||||||
match self {
|
match self {
|
||||||
Data::Nil => visitor.visit_unit(),
|
Self::Nil => visitor.visit_unit(),
|
||||||
_ => Err(Error::custom("expected unit")),
|
_ => Err(Error::custom("expected unit")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -182,7 +182,7 @@ impl<'de> Deserializer<'de> for Data {
|
||||||
V: de::Visitor<'de>,
|
V: de::Visitor<'de>,
|
||||||
{
|
{
|
||||||
match self {
|
match self {
|
||||||
Data::Nil => visitor.visit_unit(),
|
Self::Nil => visitor.visit_unit(),
|
||||||
_ => Err(Error::custom("expected unit struct")),
|
_ => Err(Error::custom("expected unit struct")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -203,8 +203,8 @@ impl<'de> Deserializer<'de> for Data {
|
||||||
V: de::Visitor<'de>,
|
V: de::Visitor<'de>,
|
||||||
{
|
{
|
||||||
match self {
|
match self {
|
||||||
Data::List(l) => visitor.visit_seq(SeqDeserializer { iter: l.into_iter() }),
|
Self::List(l) => visitor.visit_seq(SeqDeserializer { iter: l.into_iter() }),
|
||||||
Data::Bytes(b) => BytesDeserializer(b.into()).deserialize_seq(visitor),
|
Self::Bytes(b) => BytesDeserializer(b.into()).deserialize_seq(visitor),
|
||||||
_ => Err(Error::custom("not a sequence")),
|
_ => Err(Error::custom("not a sequence")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -233,8 +233,8 @@ impl<'de> Deserializer<'de> for Data {
|
||||||
V: de::Visitor<'de>,
|
V: de::Visitor<'de>,
|
||||||
{
|
{
|
||||||
match self {
|
match self {
|
||||||
Data::Dict(d) => visitor.visit_map(MapDeserializer { iter: d.into_iter(), value: None }),
|
Self::Dict(d) => visitor.visit_map(MapDeserializer { iter: d.into_iter(), value: None }),
|
||||||
Data::Url(u) => Deserializer::deserialize_map(u.into_deserializer(), visitor),
|
Self::Url(u) => Deserializer::deserialize_map(u.into_deserializer(), visitor),
|
||||||
_ => Err(Error::custom("not a map")),
|
_ => Err(Error::custom("not a map")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -261,7 +261,7 @@ impl<'de> Deserializer<'de> for Data {
|
||||||
V: de::Visitor<'de>,
|
V: de::Visitor<'de>,
|
||||||
{
|
{
|
||||||
match self {
|
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")),
|
_ => Err(Error::custom("not an enum")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,8 @@ use std::{borrow::Cow, fmt::{self, Display}, mem, str::FromStr};
|
||||||
use anyhow::{Result, anyhow, bail};
|
use anyhow::{Result, anyhow, bail};
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use serde::{Deserialize, de};
|
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)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct Action {
|
pub struct Action {
|
||||||
|
|
@ -89,7 +88,7 @@ impl Action {
|
||||||
self
|
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.args.insert("replier".into(), Data::Any(Box::new(tx)));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
@ -115,9 +114,7 @@ impl Action {
|
||||||
self.args.get(&name.into())?.as_any()
|
self.args.get(&name.into())?.as_any()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn replier(&self) -> Option<&mpsc::UnboundedSender<anyhow::Result<Data>>> {
|
pub fn replier(&self) -> Option<&Replier> { self.any("replier") }
|
||||||
self.any("replier")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn first<'a, T>(&'a self) -> Result<T>
|
pub fn first<'a, T>(&'a self) -> Result<T>
|
||||||
where
|
where
|
||||||
|
|
@ -210,9 +207,7 @@ impl Action {
|
||||||
(0..self.len()).filter_map(|i| self.args.remove(&DataKey::from(i))?.into_any())
|
(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>>> {
|
pub fn take_replier(&mut self) -> Option<Replier> { self.take_any("replier") }
|
||||||
self.take_any("replier")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse
|
// Parse
|
||||||
pub fn parse_args<I>(words: I, last: Option<String>) -> Result<HashMap<DataKey, Data>>
|
pub fn parse_args<I>(words: I, last: Option<String>) -> Result<HashMap<DataKey, Data>>
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,9 @@ use std::{iter, ops::Deref};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use tokio::sync::mpsc;
|
|
||||||
|
|
||||||
use super::Action;
|
use super::Action;
|
||||||
use crate::data::{Data, DataKey};
|
use crate::{data::{Data, DataKey}, event::Replier};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ActionCow {
|
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 {
|
match self {
|
||||||
Self::Owned(c) => c.take_replier(),
|
Self::Owned(c) => c.take_replier(),
|
||||||
Self::Borrowed(_) => None,
|
Self::Borrowed(_) => None,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
yazi_macro::mod_flat!(action cow de de_owned event);
|
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);
|
pub static NEED_RENDER: std::sync::atomic::AtomicU8 = std::sync::atomic::AtomicU8::new(0);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue