mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 14:51:03 +00:00
feat: subscribe to and intercept app-level events through DDS (#3632)
This commit is contained in:
parent
583345296f
commit
6d942ea011
82 changed files with 815 additions and 537 deletions
5
Cargo.lock
generated
5
Cargo.lock
generated
|
|
@ -5542,6 +5542,7 @@ dependencies = [
|
|||
"libc",
|
||||
"mlua",
|
||||
"paste",
|
||||
"ratatui",
|
||||
"scopeguard",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
|
|
@ -5551,6 +5552,7 @@ dependencies = [
|
|||
"yazi-config",
|
||||
"yazi-core",
|
||||
"yazi-dds",
|
||||
"yazi-emulator",
|
||||
"yazi-fs",
|
||||
"yazi-macro",
|
||||
"yazi-parser",
|
||||
|
|
@ -5558,6 +5560,7 @@ dependencies = [
|
|||
"yazi-proxy",
|
||||
"yazi-scheduler",
|
||||
"yazi-shared",
|
||||
"yazi-term",
|
||||
"yazi-tty",
|
||||
"yazi-vfs",
|
||||
"yazi-watcher",
|
||||
|
|
@ -5704,6 +5707,7 @@ dependencies = [
|
|||
"indexmap 2.13.0",
|
||||
"parking_lot",
|
||||
"ratatui",
|
||||
"serde",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tokio-util",
|
||||
|
|
@ -5868,6 +5872,7 @@ dependencies = [
|
|||
"hashbrown 0.16.1",
|
||||
"mlua",
|
||||
"ordered-float 5.1.0",
|
||||
"ratatui",
|
||||
"serde",
|
||||
"serde_with",
|
||||
"tokio",
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ function bugReportBody(creator, content, hash) {
|
|||
return null
|
||||
}
|
||||
|
||||
return `Hey @${creator}, thank you for opening this issue to help us improve Yazi, appreciate it!
|
||||
return `Hey @${creator}, thank you for opening the issue to help improve Yazi, appreciate it!
|
||||
|
||||
I noticed that you did not correctly follow the issue template. Please ensure that:
|
||||
|
||||
|
|
@ -18,7 +18,7 @@ I noticed that you did not correctly follow the issue template. Please ensure th
|
|||
|
||||
Issues with \`${LABEL_NAME}\` will be marked ready once edited with the proper content, or closed after 2 days of inactivity.
|
||||
|
||||
Our maintainers work on Yazi in a personal capacity, and debug info helps them work efficiently, understand your setup quickly, and find a more appropriate solution. Thank you for the understanding!
|
||||
Our maintainers work on Yazi in their free time, this helps them work efficiently, understand your setup quickly, and find a more appropriate solution. Thanks for your understanding!
|
||||
`
|
||||
}
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ function featureRequestBody(creator, content) {
|
|||
return null
|
||||
}
|
||||
|
||||
return `Hey @${creator}, thank you for opening this issue to help us improve Yazi, appreciate it!
|
||||
return `Hey @${creator}, thank you for opening the issue to help improve Yazi, appreciate it!
|
||||
|
||||
I noticed that you did not correctly follow the issue template. Please ensure that:
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ I noticed that you did not correctly follow the issue template. Please ensure th
|
|||
|
||||
Issues with \`${LABEL_NAME}\` will be marked ready once edited with the proper content, or closed after 2 days of inactivity.
|
||||
|
||||
Our maintainers work on Yazi in a personal capacity, and debug info helps them work efficiently, understand your setup quickly, and find a more appropriate solution. Thank you for the understanding!
|
||||
Our maintainers work on Yazi in their free time, this helps them work efficiently, understand your setup quickly, and find a more appropriate solution. Thanks for your understanding!
|
||||
`
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ yazi-boot = { path = "../yazi-boot", version = "26.1.22" }
|
|||
yazi-config = { path = "../yazi-config", version = "26.1.22" }
|
||||
yazi-core = { path = "../yazi-core", version = "26.1.22" }
|
||||
yazi-dds = { path = "../yazi-dds", version = "26.1.22" }
|
||||
yazi-emulator = { path = "../yazi-emulator", version = "26.1.22" }
|
||||
yazi-fs = { path = "../yazi-fs", version = "26.1.22" }
|
||||
yazi-macro = { path = "../yazi-macro", version = "26.1.22" }
|
||||
yazi-parser = { path = "../yazi-parser", version = "26.1.22" }
|
||||
|
|
@ -28,6 +29,7 @@ yazi-plugin = { path = "../yazi-plugin", version = "26.1.22" }
|
|||
yazi-proxy = { path = "../yazi-proxy", version = "26.1.22" }
|
||||
yazi-scheduler = { path = "../yazi-scheduler", version = "26.1.22" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "26.1.22" }
|
||||
yazi-term = { path = "../yazi-term", version = "26.1.22" }
|
||||
yazi-tty = { path = "../yazi-tty", version = "26.1.22" }
|
||||
yazi-vfs = { path = "../yazi-vfs", version = "26.1.22" }
|
||||
yazi-watcher = { path = "../yazi-watcher", version = "26.1.22" }
|
||||
|
|
@ -40,6 +42,7 @@ futures = { workspace = true }
|
|||
hashbrown = { workspace = true }
|
||||
mlua = { workspace = true }
|
||||
paste = { workspace = true }
|
||||
ratatui = { workspace = true }
|
||||
scopeguard = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tokio-stream = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use anyhow::{Result, bail};
|
||||
use anyhow::Result;
|
||||
use mlua::IntoLua;
|
||||
use tracing::error;
|
||||
use yazi_actor::lives::Lives;
|
||||
|
|
@ -6,16 +6,18 @@ use yazi_binding::runtime_mut;
|
|||
use yazi_dds::{LOCAL, Payload, REMOTE};
|
||||
use yazi_macro::succ;
|
||||
use yazi_plugin::LUA;
|
||||
use yazi_shared::{data::Data, event::CmdCow};
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::app::App;
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
impl App {
|
||||
pub(crate) fn accept_payload(&self, mut c: CmdCow) -> Result<Data> {
|
||||
let Some(payload) = c.take_any2::<Payload>("payload").transpose()? else {
|
||||
bail!("'payload' is required for accept_payload");
|
||||
};
|
||||
pub struct AcceptPayload;
|
||||
|
||||
impl Actor for AcceptPayload {
|
||||
type Options = Payload<'static>;
|
||||
|
||||
const NAME: &str = "accept_payload";
|
||||
|
||||
fn act(cx: &mut Ctx, payload: Payload) -> Result<Data> {
|
||||
let kind = payload.body.kind().to_owned();
|
||||
let lock = if payload.receiver == 0 || payload.receiver != payload.sender {
|
||||
REMOTE.read()
|
||||
|
|
@ -26,7 +28,7 @@ impl App {
|
|||
let Some(handlers) = lock.get(&kind).filter(|&m| !m.is_empty()).cloned() else { succ!() };
|
||||
drop(lock);
|
||||
|
||||
succ!(Lives::scope(&self.core, || {
|
||||
succ!(Lives::scope(&cx.core, || {
|
||||
let body = payload.body.into_lua(&LUA)?;
|
||||
for (id, cb) in handlers {
|
||||
let blocking = runtime_mut!(LUA)?.critical_push(&id, true);
|
||||
|
|
@ -1,30 +1,33 @@
|
|||
use anyhow::Result;
|
||||
use yazi_actor::Ctx;
|
||||
use yazi_boot::BOOT;
|
||||
use yazi_macro::act;
|
||||
use yazi_macro::{act, succ};
|
||||
use yazi_parser::{VoidOpt, mgr::CdSource};
|
||||
use yazi_shared::{data::Data, strand::StrandLike, url::UrlLike};
|
||||
|
||||
use crate::app::App;
|
||||
use crate::Actor;
|
||||
|
||||
impl App {
|
||||
pub fn bootstrap(&mut self, _: VoidOpt) -> Result<Data> {
|
||||
pub struct Bootstrap;
|
||||
|
||||
impl Actor for Bootstrap {
|
||||
type Options = VoidOpt;
|
||||
|
||||
const NAME: &str = "bootstrap";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
|
||||
for (i, file) in BOOT.files.iter().enumerate() {
|
||||
let tabs = &mut self.core.mgr.tabs;
|
||||
let tabs = &mut cx.core.mgr.tabs;
|
||||
if tabs.len() <= i {
|
||||
tabs.push(Default::default());
|
||||
}
|
||||
|
||||
let cx = &mut Ctx::active(&mut self.core);
|
||||
cx.tab = i;
|
||||
|
||||
if file.is_empty() {
|
||||
act!(mgr:cd, cx, (BOOT.cwds[i].clone(), CdSource::Tab))?;
|
||||
} else if let Ok(u) = BOOT.cwds[i].try_join(file) {
|
||||
act!(mgr:reveal, cx, (u, CdSource::Tab))?;
|
||||
}
|
||||
}
|
||||
|
||||
act!(render, self)
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
23
yazi-actor/src/app/deprecate.rs
Normal file
23
yazi-actor/src/app/deprecate.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::act;
|
||||
use yazi_parser::{app::DeprecateOpt, notify::{PushLevel, PushOpt}};
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Deprecate;
|
||||
|
||||
impl Actor for Deprecate {
|
||||
type Options = DeprecateOpt;
|
||||
|
||||
const NAME: &str = "deprecate";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
act!(notify:push, cx, PushOpt {
|
||||
title: "Deprecated API".to_owned(),
|
||||
content: opt.content.into_owned(),
|
||||
level: PushLevel::Warn,
|
||||
timeout: std::time::Duration::from_secs(20),
|
||||
})
|
||||
}
|
||||
}
|
||||
17
yazi-actor/src/app/focus.rs
Normal file
17
yazi-actor/src/app/focus.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
use anyhow::Result;
|
||||
use yazi_actor::Ctx;
|
||||
use yazi_macro::act;
|
||||
use yazi_parser::VoidOpt;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::Actor;
|
||||
|
||||
pub struct Focus;
|
||||
|
||||
impl Actor for Focus {
|
||||
type Options = VoidOpt;
|
||||
|
||||
const NAME: &str = "focus";
|
||||
|
||||
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> { act!(mgr:refresh, cx) }
|
||||
}
|
||||
|
|
@ -4,14 +4,12 @@ yazi_macro::mod_flat!(
|
|||
deprecate
|
||||
focus
|
||||
mouse
|
||||
notify
|
||||
plugin
|
||||
plugin_do
|
||||
quit
|
||||
reflow
|
||||
render
|
||||
resize
|
||||
resume
|
||||
stop
|
||||
update_notify
|
||||
update_progress
|
||||
);
|
||||
|
|
@ -9,14 +9,20 @@ use yazi_parser::app::MouseOpt;
|
|||
use yazi_plugin::LUA;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::app::App;
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
impl App {
|
||||
pub fn mouse(&mut self, opt: MouseOpt) -> Result<Data> {
|
||||
pub struct Mouse;
|
||||
|
||||
impl Actor for Mouse {
|
||||
type Options = MouseOpt;
|
||||
|
||||
const NAME: &str = "mouse";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let event = yazi_plugin::bindings::MouseEvent::from(opt.event);
|
||||
let Some(size) = self.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 result = Lives::scope(&self.core, move || {
|
||||
let result = Lives::scope(&cx.core, move || {
|
||||
let area = yazi_binding::elements::Rect::from(size);
|
||||
let root = LUA.globals().raw_get::<Table>("Root")?.call_method::<Table>("new", area)?;
|
||||
|
||||
38
yazi-actor/src/app/plugin.rs
Normal file
38
yazi-actor/src/app/plugin.rs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{act, succ};
|
||||
use yazi_parser::app::{PluginMode, PluginOpt};
|
||||
use yazi_plugin::loader::LOADER;
|
||||
use yazi_proxy::{AppProxy, NotifyProxy};
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Plugin;
|
||||
|
||||
impl Actor for Plugin {
|
||||
type Options = PluginOpt;
|
||||
|
||||
const NAME: &str = "plugin";
|
||||
|
||||
fn act(cx: &mut Ctx, mut opt: Self::Options) -> Result<Data> {
|
||||
let mut hits = false;
|
||||
if let Some(chunk) = LOADER.read().get(&*opt.id) {
|
||||
hits = true;
|
||||
opt.mode = opt.mode.auto_then(chunk.sync_entry);
|
||||
}
|
||||
|
||||
if opt.mode == PluginMode::Async {
|
||||
succ!(cx.core.tasks.scheduler.plugin_entry(opt));
|
||||
} else if opt.mode == PluginMode::Sync && hits {
|
||||
return act!(app:plugin_do, cx, opt);
|
||||
}
|
||||
|
||||
tokio::spawn(async move {
|
||||
match LOADER.ensure(&opt.id, |_| ()).await {
|
||||
Ok(()) => AppProxy::plugin_do(opt),
|
||||
Err(e) => NotifyProxy::push_error("Plugin load failed", e),
|
||||
}
|
||||
});
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
|
|
@ -2,52 +2,35 @@ use anyhow::Result;
|
|||
use mlua::ObjectLike;
|
||||
use scopeguard::defer;
|
||||
use tracing::{error, warn};
|
||||
use yazi_actor::lives::Lives;
|
||||
use yazi_binding::runtime_mut;
|
||||
use yazi_dds::Sendable;
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::app::{PluginMode, PluginOpt};
|
||||
use yazi_plugin::{LUA, loader::{LOADER, Loader}};
|
||||
use yazi_proxy::AppProxy;
|
||||
use yazi_proxy::NotifyProxy;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::app::App;
|
||||
use crate::{Actor, Ctx, lives::Lives};
|
||||
|
||||
impl App {
|
||||
pub(crate) fn plugin(&mut self, mut opt: PluginOpt) -> Result<Data> {
|
||||
let mut hits = false;
|
||||
if let Some(chunk) = LOADER.read().get(&*opt.id) {
|
||||
hits = true;
|
||||
opt.mode = opt.mode.auto_then(chunk.sync_entry);
|
||||
}
|
||||
pub struct PluginDo;
|
||||
|
||||
if opt.mode == PluginMode::Async {
|
||||
succ!(self.core.tasks.plugin_entry(opt));
|
||||
} else if opt.mode == PluginMode::Sync && hits {
|
||||
return self.plugin_do(opt);
|
||||
}
|
||||
impl Actor for PluginDo {
|
||||
type Options = PluginOpt;
|
||||
|
||||
tokio::spawn(async move {
|
||||
match LOADER.ensure(&opt.id, |_| ()).await {
|
||||
Ok(()) => AppProxy::plugin_do(opt),
|
||||
Err(e) => AppProxy::notify_error("Plugin load failed", e),
|
||||
}
|
||||
});
|
||||
succ!();
|
||||
}
|
||||
const NAME: &str = "plugin_do";
|
||||
|
||||
pub(crate) fn plugin_do(&mut self, opt: PluginOpt) -> Result<Data> {
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let loader = LOADER.read();
|
||||
let Some(chunk) = loader.get(&*opt.id) else {
|
||||
succ!(warn!("plugin `{}` not found", opt.id));
|
||||
};
|
||||
|
||||
if let Err(e) = Loader::compatible_or_error(&opt.id, chunk) {
|
||||
succ!(AppProxy::notify_error("Incompatible plugin", e));
|
||||
succ!(NotifyProxy::push_error("Incompatible plugin", e));
|
||||
}
|
||||
|
||||
if opt.mode.auto_then(chunk.sync_entry) != PluginMode::Sync {
|
||||
succ!(self.core.tasks.plugin_entry(opt));
|
||||
succ!(cx.core.tasks.scheduler.plugin_entry(opt));
|
||||
}
|
||||
|
||||
let blocking = runtime_mut!(LUA)?.critical_push(&opt.id, true);
|
||||
|
|
@ -59,7 +42,7 @@ impl App {
|
|||
};
|
||||
drop(loader);
|
||||
|
||||
let result = Lives::scope(&self.core, || {
|
||||
let result = Lives::scope(&cx.core, || {
|
||||
if let Some(cb) = opt.callback {
|
||||
cb(&LUA, plugin)
|
||||
} else {
|
||||
47
yazi-actor/src/app/quit.rs
Normal file
47
yazi-actor/src/app/quit.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
use anyhow::Result;
|
||||
use yazi_boot::ARGS;
|
||||
use yazi_fs::provider::{Provider, local::Local};
|
||||
use yazi_parser::app::QuitOpt;
|
||||
use yazi_shared::{data::Data, strand::{StrandBuf, StrandLike, ToStrand}};
|
||||
use yazi_term::Term;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Quit;
|
||||
|
||||
impl Actor for Quit {
|
||||
type Options = QuitOpt;
|
||||
|
||||
const NAME: &str = "quit";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
cx.core.tasks.shutdown();
|
||||
cx.core.mgr.shutdown();
|
||||
|
||||
futures::executor::block_on(async {
|
||||
_ = futures::join!(
|
||||
yazi_dds::shutdown(),
|
||||
yazi_dds::STATE.drain(),
|
||||
Self::cwd_to_file(cx, opt.no_cwd_file),
|
||||
Self::selected_to_file(opt.selected)
|
||||
);
|
||||
});
|
||||
|
||||
Term::goodbye(|| opt.code);
|
||||
}
|
||||
}
|
||||
|
||||
impl Quit {
|
||||
async fn cwd_to_file(cx: &Ctx<'_>, no: bool) {
|
||||
if let Some(p) = ARGS.cwd_file.as_ref().filter(|_| !no) {
|
||||
let cwd = cx.core.mgr.cwd().to_strand();
|
||||
Local::regular(p).write(cwd.encoded_bytes()).await.ok();
|
||||
}
|
||||
}
|
||||
|
||||
async fn selected_to_file(selected: Option<StrandBuf>) {
|
||||
if let (Some(s), Some(p)) = (selected, &ARGS.chooser_file) {
|
||||
Local::regular(p).write(s.encoded_bytes()).await.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,18 +5,24 @@ use tracing::error;
|
|||
use yazi_actor::lives::Lives;
|
||||
use yazi_config::LAYOUT;
|
||||
use yazi_macro::{render, succ};
|
||||
use yazi_parser::VoidOpt;
|
||||
use yazi_parser::app::ReflowOpt;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{Root, app::App};
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
impl App {
|
||||
pub fn reflow(&mut self, _: VoidOpt) -> Result<Data> {
|
||||
let Some(size) = self.term.as_ref().and_then(|t| t.size().ok()) else { succ!() };
|
||||
pub struct Reflow;
|
||||
|
||||
impl Actor for Reflow {
|
||||
type Options = ReflowOpt;
|
||||
|
||||
const NAME: &str = "reflow";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let Some(size) = cx.term.as_ref().and_then(|t| t.size().ok()) else { succ!() };
|
||||
let mut layout = LAYOUT.get();
|
||||
|
||||
let result = Lives::scope(&self.core, || {
|
||||
let comps = Root::reflow((Position::ORIGIN, size).into())?;
|
||||
let result = Lives::scope(&cx.core, || {
|
||||
let comps = (opt.reflow)((Position::ORIGIN, size).into())?;
|
||||
|
||||
for v in comps.sequence_values::<Value>() {
|
||||
let Value::Table(t) = v? else {
|
||||
25
yazi-actor/src/app/resize.rs
Normal file
25
yazi-actor/src/app/resize.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
use anyhow::Result;
|
||||
use yazi_actor::Ctx;
|
||||
use yazi_macro::act;
|
||||
use yazi_parser::app::ReflowOpt;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::Actor;
|
||||
|
||||
pub struct Resize;
|
||||
|
||||
impl Actor for Resize {
|
||||
type Options = ReflowOpt;
|
||||
|
||||
const NAME: &str = "resize";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
act!(app:reflow, cx, opt)?;
|
||||
|
||||
cx.core.current_mut().arrow(0);
|
||||
cx.core.parent_mut().map(|f| f.arrow(0));
|
||||
cx.core.current_mut().sync_page(true);
|
||||
|
||||
act!(mgr:peek, cx)
|
||||
}
|
||||
}
|
||||
28
yazi-actor/src/app/resume.rs
Normal file
28
yazi-actor/src/app/resume.rs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::app::ResumeOpt;
|
||||
use yazi_shared::data::Data;
|
||||
use yazi_term::Term;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Resume;
|
||||
|
||||
impl Actor for Resume {
|
||||
type Options = ResumeOpt;
|
||||
|
||||
const NAME: &str = "resume";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
cx.core.active_mut().preview.reset();
|
||||
*cx.term = Some(Term::start().unwrap());
|
||||
|
||||
// While the app resumes, it's possible that the terminal size has changed.
|
||||
// We need to trigger a resize, and render the UI based on the resized area.
|
||||
act!(app:resize, cx, opt.reflow)?;
|
||||
|
||||
opt.tx.send((true, opt.token))?;
|
||||
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
|
|
@ -3,18 +3,24 @@ use yazi_macro::succ;
|
|||
use yazi_parser::app::StopOpt;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::app::App;
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
impl App {
|
||||
pub fn stop(&mut self, opt: StopOpt) -> Result<Data> {
|
||||
self.core.active_mut().preview.reset_image();
|
||||
pub struct Stop;
|
||||
|
||||
impl Actor for Stop {
|
||||
type Options = StopOpt;
|
||||
|
||||
const NAME: &str = "stop";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
cx.core.active_mut().preview.reset_image();
|
||||
|
||||
// We need to destroy the `term` first before stopping the `signals`
|
||||
// to prevent any signal from triggering the term to render again
|
||||
// while the app is being suspended.
|
||||
self.term = None;
|
||||
*cx.term = None;
|
||||
|
||||
self.signals.stop(opt.token);
|
||||
opt.tx.send((false, opt.token))?;
|
||||
|
||||
succ!();
|
||||
}
|
||||
|
|
@ -1,15 +1,21 @@
|
|||
use anyhow::Result;
|
||||
use yazi_actor::Ctx;
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_macro::{act, render, render_partial, succ};
|
||||
use yazi_parser::app::UpdateProgressOpt;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::app::App;
|
||||
use crate::Actor;
|
||||
|
||||
impl App {
|
||||
pub(crate) fn update_progress(&mut self, opt: UpdateProgressOpt) -> Result<Data> {
|
||||
pub struct UpdateProgress;
|
||||
|
||||
impl Actor for UpdateProgress {
|
||||
type Options = UpdateProgressOpt;
|
||||
|
||||
const NAME: &str = "update_progress";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
// Update the progress of all tasks.
|
||||
let tasks = &mut self.core.tasks;
|
||||
let tasks = &mut cx.core.tasks;
|
||||
let progressed = tasks.summary != opt.summary;
|
||||
tasks.summary = opt.summary;
|
||||
|
||||
|
|
@ -18,7 +24,6 @@ impl App {
|
|||
let new = tasks.paginate();
|
||||
if tasks.snaps != new {
|
||||
tasks.snaps = new;
|
||||
let cx = &mut Ctx::active(&mut self.core);
|
||||
act!(tasks:arrow, cx)?;
|
||||
succ!(render!());
|
||||
}
|
||||
|
|
@ -29,7 +34,7 @@ impl App {
|
|||
} else if tasks.summary.total == 0 {
|
||||
succ!(render!())
|
||||
} else {
|
||||
act!(render_partially, self)
|
||||
succ!(render_partial!())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,9 +4,11 @@ use anyhow::{Result, anyhow};
|
|||
use yazi_core::{Core, mgr::Tabs, tab::{Folder, Tab}, tasks::Tasks};
|
||||
use yazi_fs::File;
|
||||
use yazi_shared::{Id, Source, event::Cmd, url::UrlBuf};
|
||||
use yazi_term::Term;
|
||||
|
||||
pub struct Ctx<'a> {
|
||||
pub core: &'a mut Core,
|
||||
pub term: &'a mut Option<Term>,
|
||||
pub tab: usize,
|
||||
pub level: usize,
|
||||
pub source: Source,
|
||||
|
|
@ -26,7 +28,7 @@ impl DerefMut for Ctx<'_> {
|
|||
|
||||
impl<'a> Ctx<'a> {
|
||||
#[inline]
|
||||
pub fn new(core: &'a mut Core, cmd: &Cmd) -> Result<Self> {
|
||||
pub fn new(cmd: &Cmd, core: &'a mut Core, term: &'a mut Option<Term>) -> Result<Self> {
|
||||
let tab = if let Ok(id) = cmd.get::<Id>("tab") {
|
||||
core
|
||||
.mgr
|
||||
|
|
@ -40,6 +42,7 @@ impl<'a> Ctx<'a> {
|
|||
|
||||
Ok(Self {
|
||||
core,
|
||||
term,
|
||||
tab,
|
||||
level: 0,
|
||||
source: cmd.source,
|
||||
|
|
@ -53,6 +56,7 @@ impl<'a> Ctx<'a> {
|
|||
let tab = cx.core.mgr.tabs.cursor;
|
||||
Self {
|
||||
core: cx.core,
|
||||
term: cx.term,
|
||||
tab,
|
||||
level: cx.level,
|
||||
source: cx.source,
|
||||
|
|
@ -62,10 +66,11 @@ impl<'a> Ctx<'a> {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub fn active(core: &'a mut Core) -> Self {
|
||||
pub fn active(core: &'a mut Core, term: &'a mut Option<Term>) -> Self {
|
||||
let tab = core.mgr.tabs.cursor;
|
||||
Self {
|
||||
core,
|
||||
term,
|
||||
tab,
|
||||
level: 0,
|
||||
source: Source::Unknown,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
extern crate self as yazi_actor;
|
||||
|
||||
yazi_macro::mod_pub!(cmp confirm core help input lives mgr pick spot tasks which);
|
||||
yazi_macro::mod_pub!(app cmp confirm core help input lives mgr notify pick spot tasks which);
|
||||
|
||||
yazi_macro::mod_flat!(actor context);
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use yazi_dds::Pubsub;
|
|||
use yazi_fs::{File, FilesOp, Splatter, max_common_root, path::skip_url, provider::{FileBuilder, Provider, local::{Gate, Local}}};
|
||||
use yazi_macro::{err, succ};
|
||||
use yazi_parser::VoidOpt;
|
||||
use yazi_proxy::{AppProxy, HIDER, TasksProxy};
|
||||
use yazi_proxy::{AppProxy, HIDER, NotifyProxy, TasksProxy};
|
||||
use yazi_shared::{data::Data, path::PathDyn, strand::{AsStrand, AsStrandJoin, Strand, StrandBuf, StrandLike}, terminal_clear, url::{AsUrl, UrlBuf, UrlCow, UrlLike}};
|
||||
use yazi_tty::TTY;
|
||||
use yazi_vfs::{VfsFile, maybe_exists, provider};
|
||||
|
|
@ -28,12 +28,12 @@ impl Actor for BulkRename {
|
|||
|
||||
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
|
||||
let Some(opener) = Self::opener() else {
|
||||
succ!(AppProxy::notify_warn("Bulk rename", "No text opener found"));
|
||||
succ!(NotifyProxy::push_warn("Bulk rename", "No text opener found"));
|
||||
};
|
||||
|
||||
let selected: Vec<_> = cx.tab().selected_or_hovered().cloned().collect();
|
||||
if selected.is_empty() {
|
||||
succ!(AppProxy::notify_warn("Bulk rename", "No files selected"));
|
||||
succ!(NotifyProxy::push_warn("Bulk rename", "No files selected"));
|
||||
}
|
||||
|
||||
let root = max_common_root(&selected);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use anyhow::{Result, bail};
|
||||
use yazi_macro::{act, render, render_and, succ};
|
||||
use yazi_parser::{VoidOpt, mgr::EscapeOpt};
|
||||
use yazi_proxy::AppProxy;
|
||||
use yazi_proxy::NotifyProxy;
|
||||
use yazi_shared::{data::Data, url::UrlLike};
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
|
@ -76,7 +76,7 @@ impl Actor for EscapeVisual {
|
|||
if !select {
|
||||
tab.selected.remove_many(urls);
|
||||
} else if urls.len() != tab.selected.add_many(urls) {
|
||||
AppProxy::notify_warn(
|
||||
NotifyProxy::push_warn(
|
||||
"Escape visual mode",
|
||||
"Some files cannot be selected, due to path nesting conflict.",
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ use anyhow::Result;
|
|||
use tokio::{select, time};
|
||||
use yazi_config::popup::ConfirmCfg;
|
||||
use yazi_dds::spark::SparkKind;
|
||||
use yazi_macro::{emit, succ};
|
||||
use yazi_parser::mgr::QuitOpt;
|
||||
use yazi_proxy::ConfirmProxy;
|
||||
use yazi_shared::{data::Data, event::EventQuit, strand::{Strand, StrandLike, ToStrandJoin}, url::AsUrl};
|
||||
use yazi_macro::{act, succ};
|
||||
use yazi_parser::app::QuitOpt;
|
||||
use yazi_proxy::{AppProxy, ConfirmProxy};
|
||||
use yazi_shared::{data::Data, strand::{Strand, StrandLike, ToStrandJoin}, url::AsUrl};
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
|
|
@ -19,8 +19,6 @@ impl Actor for Quit {
|
|||
const NAME: &str = "quit";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let event = opt.into();
|
||||
|
||||
let ongoing = cx.tasks().ongoing().clone();
|
||||
let (left, left_names) = {
|
||||
let ongoing = ongoing.lock();
|
||||
|
|
@ -28,7 +26,7 @@ impl Actor for Quit {
|
|||
};
|
||||
|
||||
if left == 0 {
|
||||
succ!(emit!(Quit(event)));
|
||||
return act!(app:quit, cx, opt);
|
||||
}
|
||||
|
||||
tokio::spawn(async move {
|
||||
|
|
@ -40,13 +38,13 @@ impl Actor for Quit {
|
|||
i += 1;
|
||||
if i > 40 { break }
|
||||
else if ongoing.lock().is_empty() {
|
||||
emit!(Quit(event));
|
||||
AppProxy::quit(opt);
|
||||
return;
|
||||
}
|
||||
}
|
||||
b = token.future() => {
|
||||
if b {
|
||||
emit!(Quit(event));
|
||||
AppProxy::quit(opt);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -54,7 +52,7 @@ impl Actor for Quit {
|
|||
}
|
||||
|
||||
if token.future().await {
|
||||
emit!(Quit(event));
|
||||
AppProxy::quit(opt);
|
||||
}
|
||||
});
|
||||
succ!();
|
||||
|
|
@ -73,7 +71,7 @@ impl Quit {
|
|||
{
|
||||
let paths = selected.into_iter().join(Strand::Utf8("\n"));
|
||||
if !paths.is_empty() {
|
||||
emit!(Quit(EventQuit { selected: Some(paths), ..Default::default() }));
|
||||
AppProxy::quit(QuitOpt { selected: Some(paths), ..Default::default() });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use yazi_fs::{FilesOp, cha::Cha};
|
|||
use yazi_macro::{act, succ};
|
||||
use yazi_parser::{VoidOpt, mgr::{CdSource, SearchOpt, SearchOptVia}};
|
||||
use yazi_plugin::external;
|
||||
use yazi_proxy::{AppProxy, InputProxy, MgrProxy};
|
||||
use yazi_proxy::{InputProxy, MgrProxy, NotifyProxy};
|
||||
use yazi_shared::{data::Data, url::UrlLike};
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
|
@ -54,7 +54,7 @@ impl Actor for SearchDo {
|
|||
|
||||
let hidden = tab.pref.show_hidden;
|
||||
let Ok(cwd) = tab.cwd().to_search(&opt.subject) else {
|
||||
succ!(AppProxy::notify_warn("Search", "Only local filesystem searches are supported"));
|
||||
succ!(NotifyProxy::push_warn("Search", "Only local filesystem searches are supported"));
|
||||
};
|
||||
|
||||
tab.search = Some(tokio::spawn(async move {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use anyhow::Result;
|
|||
use yazi_core::tab::Tab;
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::mgr::{CdSource, TabCreateOpt};
|
||||
use yazi_proxy::AppProxy;
|
||||
use yazi_proxy::NotifyProxy;
|
||||
use yazi_shared::{data::Data, url::UrlLike};
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
|
@ -18,7 +18,7 @@ impl Actor for TabCreate {
|
|||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
if cx.tabs().len() >= MAX_TABS {
|
||||
succ!(AppProxy::notify_warn(
|
||||
succ!(NotifyProxy::push_warn(
|
||||
"Too many tabs",
|
||||
"You can only open up to 9 tabs at the same time."
|
||||
));
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{render_and, succ};
|
||||
use yazi_parser::mgr::ToggleOpt;
|
||||
use yazi_proxy::AppProxy;
|
||||
use yazi_proxy::NotifyProxy;
|
||||
use yazi_shared::{data::Data, url::UrlCow};
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
|
@ -26,7 +26,7 @@ impl Actor for Toggle {
|
|||
};
|
||||
|
||||
if !b {
|
||||
AppProxy::notify_warn(
|
||||
NotifyProxy::push_warn(
|
||||
"Toggle",
|
||||
"This file cannot be selected, due to path nesting conflict.",
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{render, succ};
|
||||
use yazi_parser::mgr::ToggleAllOpt;
|
||||
use yazi_proxy::AppProxy;
|
||||
use yazi_proxy::NotifyProxy;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
|
@ -39,7 +39,7 @@ impl Actor for ToggleAll {
|
|||
};
|
||||
|
||||
if warn {
|
||||
AppProxy::notify_warn(
|
||||
NotifyProxy::push_warn(
|
||||
"Toggle all",
|
||||
"Some files cannot be selected, due to path nesting conflict.",
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use yazi_macro::{act, render, succ};
|
|||
use yazi_parser::VoidOpt;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::Actor;
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Unyank;
|
||||
|
||||
|
|
@ -12,7 +12,7 @@ impl Actor for Unyank {
|
|||
|
||||
const NAME: &str = "unyank";
|
||||
|
||||
fn act(cx: &mut crate::Ctx, _: Self::Options) -> Result<Data> {
|
||||
fn act(cx: &mut Ctx, _: Self::Options) -> Result<Data> {
|
||||
let repeek = cx.hovered().is_some_and(|f| f.is_dir() && cx.mgr.yanked.contains_in(&f.url));
|
||||
cx.mgr.yanked.clear();
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use yazi_macro::{render, succ};
|
|||
use yazi_parser::mgr::UpdatePeekedOpt;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::Actor;
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct UpdatePeeked;
|
||||
|
||||
|
|
@ -12,7 +12,7 @@ impl Actor for UpdatePeeked {
|
|||
|
||||
const NAME: &str = "update_peeked";
|
||||
|
||||
fn act(cx: &mut crate::Ctx, opt: Self::Options) -> Result<Data> {
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let Some(hovered) = cx.hovered().map(|h| &h.url) else {
|
||||
succ!(cx.tab_mut().preview.reset());
|
||||
};
|
||||
|
|
|
|||
31
yazi-actor/src/notify/push.rs
Normal file
31
yazi-actor/src/notify/push.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
use std::time::{Duration, Instant};
|
||||
|
||||
use anyhow::Result;
|
||||
use yazi_core::notify::Message;
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::notify::PushOpt;
|
||||
use yazi_proxy::NotifyProxy;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Push;
|
||||
|
||||
impl Actor for Push {
|
||||
type Options = PushOpt;
|
||||
|
||||
const NAME: &str = "push";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
let instant = Instant::now();
|
||||
|
||||
let mut msg = Message::from(opt);
|
||||
msg.timeout += instant - cx.notify.messages.first().map_or(instant, |m| m.instant);
|
||||
|
||||
if cx.notify.messages.iter().all(|m| m != &msg) {
|
||||
cx.notify.messages.push(msg);
|
||||
NotifyProxy::tick(Duration::ZERO);
|
||||
}
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
64
yazi-actor/src/notify/tick.rs
Normal file
64
yazi-actor/src/notify/tick.rs
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use ratatui::layout::Rect;
|
||||
use yazi_core::notify::Notify;
|
||||
use yazi_emulator::Dimension;
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::notify::TickOpt;
|
||||
use yazi_proxy::NotifyProxy;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Tick;
|
||||
|
||||
impl Actor for Tick {
|
||||
type Options = TickOpt;
|
||||
|
||||
const NAME: &str = "tick";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
cx.notify.tick_handle.take().map(|h| h.abort());
|
||||
|
||||
let Dimension { rows, columns, .. } = Dimension::available();
|
||||
let area = Notify::available(Rect { x: 0, y: 0, width: columns, height: rows });
|
||||
|
||||
let limit = cx.notify.limit(area);
|
||||
if limit == 0 {
|
||||
succ!();
|
||||
}
|
||||
|
||||
for m in &mut cx.notify.messages[..limit] {
|
||||
if m.timeout.is_zero() {
|
||||
m.percent = m.percent.saturating_sub(20);
|
||||
} else if m.percent < 100 {
|
||||
m.percent += 20;
|
||||
} else {
|
||||
m.timeout = m.timeout.saturating_sub(opt.interval);
|
||||
}
|
||||
}
|
||||
|
||||
cx.notify.messages.retain(|m| m.percent > 0 || !m.timeout.is_zero());
|
||||
let limit = cx.notify.limit(area);
|
||||
let timeouts: Vec<_> = cx.notify.messages[..limit]
|
||||
.iter()
|
||||
.filter(|&m| m.percent == 100 && !m.timeout.is_zero())
|
||||
.map(|m| m.timeout)
|
||||
.collect();
|
||||
|
||||
let interval = if timeouts.len() != limit {
|
||||
Duration::from_millis(50)
|
||||
} else if let Some(min) = timeouts.iter().min() {
|
||||
*min
|
||||
} else {
|
||||
succ!();
|
||||
};
|
||||
|
||||
cx.notify.tick_handle = Some(tokio::spawn(async move {
|
||||
tokio::time::sleep(interval).await;
|
||||
NotifyProxy::tick(interval);
|
||||
}));
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@ impl Keymap {
|
|||
Layer::Help => &self.help,
|
||||
Layer::Cmp => &self.cmp,
|
||||
Layer::Which => &[],
|
||||
Layer::Notify => &[],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ hashbrown = { workspace = true }
|
|||
indexmap = { workspace = true }
|
||||
parking_lot = { workspace = true }
|
||||
ratatui = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tokio-stream = { workspace = true }
|
||||
tokio-util = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
use std::time::{Duration, Instant};
|
||||
|
||||
use yazi_parser::app::NotifyOpt;
|
||||
use yazi_proxy::AppProxy;
|
||||
|
||||
use crate::notify::{Message, Notify};
|
||||
|
||||
impl Notify {
|
||||
pub fn push(&mut self, opt: NotifyOpt) {
|
||||
let instant = Instant::now();
|
||||
|
||||
let mut msg = Message::from(opt);
|
||||
msg.timeout += instant - self.messages.first().map_or(instant, |m| m.instant);
|
||||
|
||||
if self.messages.iter().all(|m| m != &msg) {
|
||||
self.messages.push(msg);
|
||||
AppProxy::update_notify(Duration::ZERO);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use ratatui::layout::Rect;
|
||||
use yazi_parser::notify::TickOpt;
|
||||
use yazi_proxy::AppProxy;
|
||||
|
||||
use crate::notify::Notify;
|
||||
|
||||
impl Notify {
|
||||
pub fn tick(&mut self, opt: TickOpt, area: Rect) {
|
||||
self.tick_handle.take().map(|h| h.abort());
|
||||
|
||||
let limit = self.limit(area);
|
||||
if limit == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
for m in &mut self.messages[..limit] {
|
||||
if m.timeout.is_zero() {
|
||||
m.percent = m.percent.saturating_sub(20);
|
||||
} else if m.percent < 100 {
|
||||
m.percent += 20;
|
||||
} else {
|
||||
m.timeout = m.timeout.saturating_sub(opt.interval);
|
||||
}
|
||||
}
|
||||
|
||||
self.messages.retain(|m| m.percent > 0 || !m.timeout.is_zero());
|
||||
let limit = self.limit(area);
|
||||
let timeouts: Vec<_> = self.messages[..limit]
|
||||
.iter()
|
||||
.filter(|&m| m.percent == 100 && !m.timeout.is_zero())
|
||||
.map(|m| m.timeout)
|
||||
.collect();
|
||||
|
||||
let interval = if timeouts.len() != limit {
|
||||
Duration::from_millis(50)
|
||||
} else if let Some(min) = timeouts.iter().min() {
|
||||
*min
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
self.tick_handle = Some(tokio::spawn(async move {
|
||||
tokio::time::sleep(interval).await;
|
||||
AppProxy::update_notify(interval);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
use std::time::{Duration, Instant};
|
||||
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
use yazi_parser::app::{NotifyLevel, NotifyOpt};
|
||||
use yazi_parser::notify::{PushLevel, PushOpt};
|
||||
|
||||
use super::NOTIFY_BORDER;
|
||||
|
||||
pub struct Message {
|
||||
pub title: String,
|
||||
pub content: String,
|
||||
pub level: NotifyLevel,
|
||||
pub level: PushLevel,
|
||||
pub timeout: Duration,
|
||||
|
||||
pub instant: Instant,
|
||||
|
|
@ -16,8 +16,8 @@ pub struct Message {
|
|||
pub max_width: usize,
|
||||
}
|
||||
|
||||
impl From<NotifyOpt> for Message {
|
||||
fn from(opt: NotifyOpt) -> Self {
|
||||
impl From<PushOpt> for Message {
|
||||
fn from(opt: PushOpt) -> Self {
|
||||
let title = opt.title.lines().next().unwrap_or_default();
|
||||
let title_width = title.width() + (opt.level.icon().width() + /* Space */ 1);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
yazi_macro::mod_pub!(commands);
|
||||
|
||||
yazi_macro::mod_flat!(message notify);
|
||||
|
||||
pub const NOTIFY_BORDER: u16 = 2;
|
||||
|
|
|
|||
|
|
@ -1,17 +1,23 @@
|
|||
use std::ops::ControlFlow;
|
||||
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::layout::{Constraint, Layout, Rect};
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
use super::{Message, NOTIFY_SPACING};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Notify {
|
||||
pub(super) tick_handle: Option<JoinHandle<()>>,
|
||||
pub tick_handle: Option<JoinHandle<()>>,
|
||||
pub messages: Vec<Message>,
|
||||
}
|
||||
|
||||
impl Notify {
|
||||
pub fn available(area: Rect) -> Rect {
|
||||
let chunks = Layout::horizontal([Constraint::Fill(1), Constraint::Min(80)]).split(area);
|
||||
let chunks = Layout::vertical([Constraint::Fill(1), Constraint::Max(1)]).split(chunks[1]);
|
||||
chunks[0]
|
||||
}
|
||||
|
||||
pub fn limit(&self, area: Rect) -> usize {
|
||||
if self.messages.is_empty() {
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
yazi_macro::mod_flat!(file plugin prework process tasks);
|
||||
yazi_macro::mod_flat!(file prework process tasks);
|
||||
|
||||
pub const TASKS_BORDER: u16 = 2;
|
||||
pub const TASKS_PADDING: u16 = 2;
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
use yazi_parser::app::PluginOpt;
|
||||
|
||||
use super::Tasks;
|
||||
|
||||
impl Tasks {
|
||||
pub fn plugin_entry(&self, opt: PluginOpt) { self.scheduler.plugin_entry(opt); }
|
||||
}
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
use std::{fmt::Display, io::Write, str::FromStr};
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
use mlua::{IntoLua, Lua, Value};
|
||||
use yazi_boot::BOOT;
|
||||
use yazi_macro::{emit, relay};
|
||||
use yazi_shared::Id;
|
||||
use yazi_shared::{Id, event::CmdCow};
|
||||
|
||||
use crate::{ID, ember::Ember};
|
||||
use crate::{ID, ember::Ember, spark::Spark};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Payload<'a> {
|
||||
|
|
@ -76,6 +77,25 @@ impl<'a> From<Ember<'a>> for Payload<'a> {
|
|||
fn from(value: Ember<'a>) -> Self { Self::new(value) }
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<Spark<'a>> for Payload<'a> {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: Spark<'a>) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
Spark::AppAcceptPayload(payload) => Ok(payload),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<CmdCow> for Payload<'_> {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> {
|
||||
c.take_any2("payload").ok_or_else(|| anyhow!("Missing 'payload' in Payload"))?
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Payload<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let result = match &self.body {
|
||||
|
|
@ -104,3 +124,15 @@ impl Display for Payload<'_> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoLua for Payload<'a> {
|
||||
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
|
||||
lua
|
||||
.create_table_from([
|
||||
("receiver", yazi_binding::Id(self.receiver).into_lua(lua)?),
|
||||
("sender", yazi_binding::Id(self.sender).into_lua(lua)?),
|
||||
("body", self.body.into_lua(lua)?),
|
||||
])?
|
||||
.into_lua(lua)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,21 @@ pub enum Spark<'a> {
|
|||
// Void
|
||||
Void(yazi_parser::VoidOpt),
|
||||
|
||||
// App
|
||||
AppAcceptPayload(crate::Payload<'a>),
|
||||
AppBootstrap(yazi_parser::VoidOpt),
|
||||
AppDeprecate(yazi_parser::app::DeprecateOpt),
|
||||
AppFocus(yazi_parser::VoidOpt),
|
||||
AppMouse(yazi_parser::app::MouseOpt),
|
||||
AppPlugin(yazi_parser::app::PluginOpt),
|
||||
AppPluginDo(yazi_parser::app::PluginOpt),
|
||||
AppQuit(yazi_parser::app::QuitOpt),
|
||||
AppReflow(yazi_parser::app::ReflowOpt),
|
||||
AppResize(yazi_parser::app::ReflowOpt),
|
||||
AppResume(yazi_parser::app::ResumeOpt),
|
||||
AppStop(yazi_parser::app::StopOpt),
|
||||
AppUpdateProgress(yazi_parser::app::UpdateProgressOpt),
|
||||
|
||||
// Mgr
|
||||
Arrow(yazi_parser::ArrowOpt),
|
||||
Back(yazi_parser::VoidOpt),
|
||||
|
|
@ -42,7 +57,7 @@ pub enum Spark<'a> {
|
|||
OpenDo(yazi_parser::mgr::OpenDoOpt),
|
||||
Paste(yazi_parser::mgr::PasteOpt),
|
||||
Peek(yazi_parser::mgr::PeekOpt),
|
||||
Quit(yazi_parser::mgr::QuitOpt),
|
||||
Quit(yazi_parser::app::QuitOpt),
|
||||
Refresh(yazi_parser::VoidOpt),
|
||||
Remove(yazi_parser::mgr::RemoveOpt),
|
||||
RemoveDo(yazi_parser::mgr::RemoveOpt),
|
||||
|
|
@ -107,6 +122,7 @@ pub enum Spark<'a> {
|
|||
InputShow(yazi_parser::input::ShowOpt),
|
||||
|
||||
// Notify
|
||||
NotifyPush(yazi_parser::notify::PushOpt),
|
||||
NotifyTick(yazi_parser::notify::TickOpt),
|
||||
|
||||
// Pick
|
||||
|
|
@ -160,6 +176,21 @@ impl<'a> IntoLua for Spark<'a> {
|
|||
// Void
|
||||
Self::Void(b) => b.into_lua(lua),
|
||||
|
||||
// App
|
||||
Self::AppAcceptPayload(b) => b.into_lua(lua),
|
||||
Self::AppBootstrap(b) => b.into_lua(lua),
|
||||
Self::AppDeprecate(b) => b.into_lua(lua),
|
||||
Self::AppFocus(b) => b.into_lua(lua),
|
||||
Self::AppMouse(b) => b.into_lua(lua),
|
||||
Self::AppPlugin(b) => b.into_lua(lua),
|
||||
Self::AppPluginDo(b) => b.into_lua(lua),
|
||||
Self::AppQuit(b) => b.into_lua(lua),
|
||||
Self::AppReflow(b) => b.into_lua(lua),
|
||||
Self::AppResize(b) => b.into_lua(lua),
|
||||
Self::AppResume(b) => b.into_lua(lua),
|
||||
Self::AppStop(b) => b.into_lua(lua),
|
||||
Self::AppUpdateProgress(b) => b.into_lua(lua),
|
||||
|
||||
// Mgr
|
||||
Self::Arrow(b) => b.into_lua(lua),
|
||||
Self::Back(b) => b.into_lua(lua),
|
||||
|
|
@ -260,6 +291,7 @@ impl<'a> IntoLua for Spark<'a> {
|
|||
Self::InputShow(b) => b.into_lua(lua),
|
||||
|
||||
// Notify
|
||||
Self::NotifyPush(b) => b.into_lua(lua),
|
||||
Self::NotifyTick(b) => b.into_lua(lua),
|
||||
|
||||
// Pick
|
||||
|
|
@ -292,6 +324,8 @@ impl<'a> IntoLua for Spark<'a> {
|
|||
|
||||
try_from_spark!(
|
||||
VoidOpt,
|
||||
app:bootstrap,
|
||||
app:focus,
|
||||
mgr:back,
|
||||
mgr:bulk_rename,
|
||||
mgr:enter,
|
||||
|
|
@ -309,7 +343,17 @@ try_from_spark!(
|
|||
mgr:unyank,
|
||||
mgr:watch
|
||||
);
|
||||
|
||||
// App
|
||||
try_from_spark!(ArrowOpt, mgr:arrow, mgr:tab_swap);
|
||||
try_from_spark!(app::DeprecateOpt, app:deprecate);
|
||||
try_from_spark!(app::MouseOpt, app:mouse);
|
||||
try_from_spark!(app::PluginOpt, app:plugin, app:plugin_do);
|
||||
try_from_spark!(app::QuitOpt, app:quit, mgr:quit);
|
||||
try_from_spark!(app::ReflowOpt, app:reflow, app:resize);
|
||||
try_from_spark!(app::ResumeOpt, app:resume);
|
||||
try_from_spark!(app::StopOpt, app:stop);
|
||||
try_from_spark!(app::UpdateProgressOpt, app:update_progress);
|
||||
try_from_spark!(cmp::CloseOpt, cmp:close);
|
||||
try_from_spark!(cmp::ShowOpt, cmp:show);
|
||||
try_from_spark!(cmp::TriggerOpt, cmp:trigger);
|
||||
|
|
@ -347,7 +391,6 @@ try_from_spark!(mgr::OpenDoOpt, mgr:open_do);
|
|||
try_from_spark!(mgr::OpenOpt, mgr:open);
|
||||
try_from_spark!(mgr::PasteOpt, mgr:paste);
|
||||
try_from_spark!(mgr::PeekOpt, mgr:peek);
|
||||
try_from_spark!(mgr::QuitOpt, mgr:quit);
|
||||
try_from_spark!(mgr::RemoveOpt, mgr:remove, mgr:remove_do);
|
||||
try_from_spark!(mgr::RenameOpt, mgr:rename);
|
||||
try_from_spark!(mgr::RevealOpt, mgr:reveal);
|
||||
|
|
@ -371,11 +414,12 @@ try_from_spark!(mgr::UpdateYankedOpt<'a>, mgr:update_yanked);
|
|||
try_from_spark!(mgr::UploadOpt, mgr:upload);
|
||||
try_from_spark!(mgr::VisualModeOpt, mgr:visual_mode);
|
||||
try_from_spark!(mgr::YankOpt, mgr:yank);
|
||||
try_from_spark!(notify::PushOpt, notify:push);
|
||||
try_from_spark!(notify::TickOpt, notify:tick);
|
||||
try_from_spark!(pick::CloseOpt, pick:close);
|
||||
try_from_spark!(pick::ShowOpt, pick:show);
|
||||
try_from_spark!(spot::CopyOpt, spot:copy);
|
||||
try_from_spark!(tasks::ProcessOpenOpt, tasks:process_open);
|
||||
try_from_spark!(tasks::UpdateSucceedOpt, tasks:update_succeed);
|
||||
try_from_spark!(which::CallbackOpt, which:callback);
|
||||
try_from_spark!(which::ActivateOpt, which:activate);
|
||||
try_from_spark!(which::CallbackOpt, which:callback);
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@ use std::{sync::atomic::Ordering, time::{Duration, Instant}};
|
|||
|
||||
use anyhow::Result;
|
||||
use tokio::{select, time::sleep};
|
||||
use yazi_actor::Ctx;
|
||||
use yazi_core::Core;
|
||||
use yazi_macro::act;
|
||||
use yazi_shared::event::{Event, NEED_RENDER};
|
||||
use yazi_shared::{data::Data, event::{Event, NEED_RENDER}};
|
||||
use yazi_term::Term;
|
||||
|
||||
use crate::{Dispatcher, Signals};
|
||||
|
|
@ -21,21 +22,23 @@ impl App {
|
|||
let (mut rx, signals) = (Event::take(), Signals::start()?);
|
||||
|
||||
let mut app = Self { core: Core::make(), term: Some(term), signals };
|
||||
act!(bootstrap, app)?;
|
||||
app.bootstrap()?;
|
||||
|
||||
let mut events = Vec::with_capacity(50);
|
||||
let (mut timeout, mut last_render) = (None, Instant::now());
|
||||
let (mut timeout, mut need_render, mut last_render) = (None, 0, Instant::now());
|
||||
macro_rules! drain_events {
|
||||
() => {
|
||||
for event in events.drain(..) {
|
||||
Dispatcher::new(&mut app).dispatch(event)?;
|
||||
if !NEED_RENDER.load(Ordering::Relaxed) {
|
||||
|
||||
need_render = NEED_RENDER.load(Ordering::Relaxed);
|
||||
if need_render == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
timeout = Duration::from_millis(10).checked_sub(last_render.elapsed());
|
||||
if timeout.is_none() {
|
||||
act!(render, app)?;
|
||||
app.render(need_render == 2)?;
|
||||
last_render = Instant::now();
|
||||
}
|
||||
}
|
||||
|
|
@ -46,7 +49,7 @@ impl App {
|
|||
if let Some(t) = timeout.take() {
|
||||
select! {
|
||||
_ = sleep(t) => {
|
||||
act!(render, app)?;
|
||||
app.render(need_render == 2)?;
|
||||
last_render = Instant::now();
|
||||
}
|
||||
n = rx.recv_many(&mut events, 50) => {
|
||||
|
|
@ -62,4 +65,11 @@ impl App {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn bootstrap(&mut self) -> anyhow::Result<Data> {
|
||||
let cx = &mut Ctx::active(&mut self.core, &mut self.term);
|
||||
act!(app:bootstrap, cx)?;
|
||||
|
||||
self.render(false)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::app::{DeprecateOpt, NotifyLevel, NotifyOpt};
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::app::App;
|
||||
|
||||
impl App {
|
||||
pub(crate) fn deprecate(&mut self, opt: DeprecateOpt) -> Result<Data> {
|
||||
succ!(self.core.notify.push(NotifyOpt {
|
||||
title: "Deprecated API".to_owned(),
|
||||
content: opt.content.into_owned(),
|
||||
level: NotifyLevel::Warn,
|
||||
timeout: std::time::Duration::from_secs(20),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
use anyhow::Result;
|
||||
use yazi_actor::Ctx;
|
||||
use yazi_macro::act;
|
||||
use yazi_parser::VoidOpt;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::app::App;
|
||||
|
||||
impl App {
|
||||
pub fn focus(&mut self, _: VoidOpt) -> Result<Data> {
|
||||
let cx = &mut Ctx::active(&mut self.core);
|
||||
|
||||
act!(mgr:refresh, cx)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::app::NotifyOpt;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::app::App;
|
||||
|
||||
impl App {
|
||||
pub(crate) fn notify(&mut self, opt: NotifyOpt) -> Result<Data> {
|
||||
succ!(self.core.notify.push(opt));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
use yazi_boot::ARGS;
|
||||
use yazi_fs::provider::{Provider, local::Local};
|
||||
use yazi_shared::{event::EventQuit, strand::{StrandBuf, StrandLike, ToStrand}};
|
||||
use yazi_term::Term;
|
||||
|
||||
use crate::app::App;
|
||||
|
||||
impl App {
|
||||
pub(crate) fn quit(&mut self, opt: EventQuit) -> ! {
|
||||
self.core.tasks.shutdown();
|
||||
self.core.mgr.shutdown();
|
||||
|
||||
futures::executor::block_on(async {
|
||||
_ = futures::join!(
|
||||
yazi_dds::shutdown(),
|
||||
yazi_dds::STATE.drain(),
|
||||
self.cwd_to_file(opt.no_cwd_file),
|
||||
self.selected_to_file(opt.selected)
|
||||
);
|
||||
});
|
||||
|
||||
Term::goodbye(|| opt.code);
|
||||
}
|
||||
|
||||
async fn cwd_to_file(&self, no: bool) {
|
||||
if let Some(p) = ARGS.cwd_file.as_ref().filter(|_| !no) {
|
||||
let cwd = self.core.mgr.cwd().to_strand();
|
||||
Local::regular(p).write(cwd.encoded_bytes()).await.ok();
|
||||
}
|
||||
}
|
||||
|
||||
async fn selected_to_file(&self, selected: Option<StrandBuf>) {
|
||||
if let (Some(s), Some(p)) = (selected, &ARGS.chooser_file) {
|
||||
Local::regular(p).write(s.encoded_bytes()).await.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
use anyhow::Result;
|
||||
use yazi_actor::Ctx;
|
||||
use yazi_macro::act;
|
||||
use yazi_parser::VoidOpt;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::app::App;
|
||||
|
||||
impl App {
|
||||
pub fn resize(&mut self, _: VoidOpt) -> Result<Data> {
|
||||
act!(reflow, self)?;
|
||||
|
||||
self.core.current_mut().arrow(0);
|
||||
self.core.parent_mut().map(|f| f.arrow(0));
|
||||
self.core.current_mut().sync_page(true);
|
||||
|
||||
let cx = &mut Ctx::active(&mut self.core);
|
||||
act!(mgr:peek, cx)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::app::ResumeOpt;
|
||||
use yazi_shared::data::Data;
|
||||
use yazi_term::Term;
|
||||
|
||||
use crate::app::App;
|
||||
|
||||
impl App {
|
||||
pub(crate) fn resume(&mut self, opt: ResumeOpt) -> Result<Data> {
|
||||
self.core.active_mut().preview.reset();
|
||||
self.term = Some(Term::start().unwrap());
|
||||
|
||||
// While the app resumes, it's possible that the terminal size has changed.
|
||||
// We need to trigger a resize, and render the UI based on the resized area.
|
||||
act!(resize, self)?;
|
||||
|
||||
self.signals.resume(opt.token);
|
||||
|
||||
succ!(render!());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
use anyhow::Result;
|
||||
use ratatui::layout::Rect;
|
||||
use yazi_emulator::Dimension;
|
||||
use yazi_macro::act;
|
||||
use yazi_parser::notify::TickOpt;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{app::App, notify};
|
||||
|
||||
impl App {
|
||||
pub(crate) fn update_notify(&mut self, opt: TickOpt) -> Result<Data> {
|
||||
let Dimension { rows, columns, .. } = Dimension::available();
|
||||
let area =
|
||||
notify::Notify::available(Rect { x: 0, y: 0, width: columns, height: rows });
|
||||
|
||||
self.core.notify.tick(opt, area);
|
||||
|
||||
if self.core.notify.messages.is_empty() {
|
||||
act!(render, self)
|
||||
} else {
|
||||
act!(render_partially, self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1 @@
|
|||
yazi_macro::mod_pub!(commands);
|
||||
|
||||
yazi_macro::mod_flat!(app);
|
||||
yazi_macro::mod_flat!(app render);
|
||||
|
|
|
|||
|
|
@ -6,17 +6,20 @@ use ratatui::{CompletedFrame, backend::{Backend, CrosstermBackend}, buffer::Buff
|
|||
use yazi_actor::{Ctx, lives::Lives};
|
||||
use yazi_binding::elements::COLLISION;
|
||||
use yazi_macro::{act, succ};
|
||||
use yazi_parser::VoidOpt;
|
||||
use yazi_shared::{data::Data, event::NEED_RENDER};
|
||||
use yazi_tty::TTY;
|
||||
|
||||
use crate::{app::App, root::Root};
|
||||
|
||||
impl App {
|
||||
pub(crate) fn render(&mut self, _: VoidOpt) -> Result<Data> {
|
||||
NEED_RENDER.store(false, Ordering::Relaxed);
|
||||
pub(crate) fn render(&mut self, partial: bool) -> Result<Data> {
|
||||
NEED_RENDER.store(0, Ordering::Relaxed);
|
||||
let Some(term) = &mut self.term else { succ!() };
|
||||
|
||||
if partial {
|
||||
return self.render_partially();
|
||||
}
|
||||
|
||||
Self::routine(true, None);
|
||||
let _guard = scopeguard::guard(self.core.cursor(), |c| Self::routine(false, c));
|
||||
|
||||
|
|
@ -31,21 +34,21 @@ impl App {
|
|||
Self::patch(frame);
|
||||
}
|
||||
if !self.core.notify.messages.is_empty() {
|
||||
act!(render_partially, self)?;
|
||||
self.render_partially()?;
|
||||
}
|
||||
|
||||
// Reload preview if collision is resolved
|
||||
if collision && !COLLISION.load(Ordering::Relaxed) {
|
||||
let cx = &mut Ctx::active(&mut self.core);
|
||||
let cx = &mut Ctx::active(&mut self.core, &mut self.term);
|
||||
act!(mgr:peek, cx, true)?;
|
||||
}
|
||||
succ!();
|
||||
}
|
||||
|
||||
pub(crate) fn render_partially(&mut self, _: VoidOpt) -> Result<Data> {
|
||||
pub(crate) fn render_partially(&mut self) -> Result<Data> {
|
||||
let Some(term) = &mut self.term else { succ!() };
|
||||
if !term.can_partial() {
|
||||
return act!(render, self);
|
||||
return self.render(false);
|
||||
}
|
||||
|
||||
Self::routine(true, None);
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
use std::sync::atomic::Ordering;
|
||||
|
||||
use anyhow::Result;
|
||||
use crossterm::event::KeyEvent;
|
||||
use crossterm::event::{KeyEvent, MouseEvent};
|
||||
use tracing::warn;
|
||||
use yazi_actor::Ctx;
|
||||
use yazi_config::keymap::Key;
|
||||
use yazi_macro::{act, emit, succ};
|
||||
use yazi_shared::{data::Data, event::{CmdCow, Event, NEED_RENDER}};
|
||||
|
|
@ -20,17 +21,15 @@ impl<'a> Dispatcher<'a> {
|
|||
|
||||
#[inline]
|
||||
pub(super) fn dispatch(&mut self, event: Event) -> Result<()> {
|
||||
// FIXME: handle errors
|
||||
let result = match event {
|
||||
Event::Call(cmd) => self.dispatch_call(cmd),
|
||||
Event::Seq(cmds) => self.dispatch_seq(cmds),
|
||||
Event::Render => self.dispatch_render(),
|
||||
Event::Render(partial) => self.dispatch_render(partial),
|
||||
Event::Key(key) => self.dispatch_key(key),
|
||||
Event::Mouse(mouse) => act!(mouse, self.app, mouse),
|
||||
Event::Resize => act!(resize, self.app),
|
||||
Event::Focus => act!(focus, self.app),
|
||||
Event::Mouse(mouse) => self.dispatch_mouse(mouse),
|
||||
Event::Resize => self.dispatch_resize(),
|
||||
Event::Focus => self.dispatch_focus(),
|
||||
Event::Paste(str) => self.dispatch_paste(str),
|
||||
Event::Quit(opt) => act!(quit, self.app, opt),
|
||||
};
|
||||
|
||||
if let Err(err) = result {
|
||||
|
|
@ -54,8 +53,13 @@ impl<'a> Dispatcher<'a> {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn dispatch_render(&mut self) -> Result<Data> {
|
||||
succ!(NEED_RENDER.store(true, Ordering::Relaxed))
|
||||
fn dispatch_render(&mut self, partial: bool) -> Result<Data> {
|
||||
if partial {
|
||||
_ = NEED_RENDER.compare_exchange(0, 2, Ordering::Relaxed, Ordering::Relaxed);
|
||||
} else {
|
||||
NEED_RENDER.store(1, Ordering::Relaxed);
|
||||
}
|
||||
succ!()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
@ -64,6 +68,24 @@ impl<'a> Dispatcher<'a> {
|
|||
succ!();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn dispatch_mouse(&mut self, mouse: MouseEvent) -> Result<Data> {
|
||||
let cx = &mut Ctx::active(&mut self.app.core, &mut self.app.term);
|
||||
act!(app:mouse, cx, mouse)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn dispatch_resize(&mut self) -> Result<Data> {
|
||||
let cx = &mut Ctx::active(&mut self.app.core, &mut self.app.term);
|
||||
act!(app:resize, cx, crate::Root::reflow as fn(_) -> _)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn dispatch_focus(&mut self) -> Result<Data> {
|
||||
let cx = &mut Ctx::active(&mut self.app.core, &mut self.app.term);
|
||||
act!(app:focus, cx)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn dispatch_paste(&mut self, str: String) -> Result<Data> {
|
||||
if self.app.core.input.visible {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use anyhow::Result;
|
||||
use anyhow::{Context, Result};
|
||||
use yazi_actor::Ctx;
|
||||
use yazi_macro::{act, succ};
|
||||
use yazi_shared::{Layer, data::Data, event::CmdCow};
|
||||
|
|
@ -27,34 +27,45 @@ impl<'a> Executor<'a> {
|
|||
Layer::Help => self.help(cmd),
|
||||
Layer::Cmp => self.cmp(cmd),
|
||||
Layer::Which => self.which(cmd),
|
||||
Layer::Notify => self.notify(cmd),
|
||||
}
|
||||
}
|
||||
|
||||
fn app(&mut self, cmd: CmdCow) -> Result<Data> {
|
||||
fn app(&mut self, mut cmd: CmdCow) -> Result<Data> {
|
||||
let cx = &mut Ctx::new(&cmd, &mut self.app.core, &mut self.app.term)?;
|
||||
|
||||
macro_rules! on {
|
||||
($name:ident) => {
|
||||
if cmd.name == stringify!($name) {
|
||||
return act!($name, self.app, cmd);
|
||||
return act!(app:$name, cx, cmd);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
on!(accept_payload);
|
||||
on!(notify);
|
||||
on!(plugin);
|
||||
on!(plugin_do);
|
||||
on!(update_notify);
|
||||
on!(update_progress);
|
||||
on!(resize);
|
||||
on!(stop);
|
||||
on!(resume);
|
||||
on!(deprecate);
|
||||
on!(quit);
|
||||
|
||||
succ!();
|
||||
match &*cmd.name {
|
||||
"resize" => act!(app:resize, cx, crate::Root::reflow as fn(_) -> _),
|
||||
"resume" => act!(app:resume, cx, yazi_parser::app::ResumeOpt {
|
||||
tx: self.app.signals.tx.clone(),
|
||||
token: cmd.take_any("token").context("Invalid 'token' in ResumeOpt")?,
|
||||
reflow: crate::Root::reflow,
|
||||
}),
|
||||
"stop" => act!(app:stop, cx, yazi_parser::app::StopOpt {
|
||||
tx: self.app.signals.tx.clone(),
|
||||
token: cmd.take_any("token").context("Invalid 'token' in StopOpt")?,
|
||||
}),
|
||||
_ => succ!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn mgr(&mut self, cmd: CmdCow) -> Result<Data> {
|
||||
let cx = &mut Ctx::new(&mut self.app.core, &cmd)?;
|
||||
let cx = &mut Ctx::new(&cmd, &mut self.app.core, &mut self.app.term)?;
|
||||
|
||||
macro_rules! on {
|
||||
($name:ident) => {
|
||||
|
|
@ -143,13 +154,13 @@ impl<'a> Executor<'a> {
|
|||
// Help
|
||||
"help" => act!(help:toggle, cx, Layer::Mgr),
|
||||
// Plugin
|
||||
"plugin" => act!(plugin, self.app, cmd),
|
||||
"plugin" => act!(app:plugin, cx, cmd),
|
||||
_ => succ!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn tasks(&mut self, cmd: CmdCow) -> Result<Data> {
|
||||
let cx = &mut Ctx::new(&mut self.app.core, &cmd)?;
|
||||
let cx = &mut Ctx::new(&cmd, &mut self.app.core, &mut self.app.term)?;
|
||||
|
||||
macro_rules! on {
|
||||
($name:ident) => {
|
||||
|
|
@ -173,13 +184,13 @@ impl<'a> Executor<'a> {
|
|||
// Help
|
||||
"help" => act!(help:toggle, cx, Layer::Tasks),
|
||||
// Plugin
|
||||
"plugin" => act!(plugin, self.app, cmd),
|
||||
"plugin" => act!(app:plugin, cx, cmd),
|
||||
_ => succ!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn spot(&mut self, cmd: CmdCow) -> Result<Data> {
|
||||
let cx = &mut Ctx::new(&mut self.app.core, &cmd)?;
|
||||
let cx = &mut Ctx::new(&cmd, &mut self.app.core, &mut self.app.term)?;
|
||||
|
||||
macro_rules! on {
|
||||
($name:ident) => {
|
||||
|
|
@ -198,13 +209,13 @@ impl<'a> Executor<'a> {
|
|||
// Help
|
||||
"help" => act!(help:toggle, cx, Layer::Spot),
|
||||
// Plugin
|
||||
"plugin" => act!(plugin, self.app, cmd),
|
||||
"plugin" => act!(app:plugin, cx, cmd),
|
||||
_ => succ!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn pick(&mut self, cmd: CmdCow) -> Result<Data> {
|
||||
let cx = &mut Ctx::new(&mut self.app.core, &cmd)?;
|
||||
let cx = &mut Ctx::new(&cmd, &mut self.app.core, &mut self.app.term)?;
|
||||
|
||||
macro_rules! on {
|
||||
($name:ident) => {
|
||||
|
|
@ -222,14 +233,14 @@ impl<'a> Executor<'a> {
|
|||
// Help
|
||||
"help" => act!(help:toggle, cx, Layer::Pick),
|
||||
// Plugin
|
||||
"plugin" => act!(plugin, self.app, cmd),
|
||||
"plugin" => act!(app:plugin, cx, cmd),
|
||||
_ => succ!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn input(&mut self, cmd: CmdCow) -> Result<Data> {
|
||||
let mode = self.app.core.input.mode();
|
||||
let cx = &mut Ctx::new(&mut self.app.core, &cmd)?;
|
||||
let cx = &mut Ctx::new(&cmd, &mut self.app.core, &mut self.app.term)?;
|
||||
|
||||
macro_rules! on {
|
||||
($name:ident) => {
|
||||
|
|
@ -249,7 +260,7 @@ impl<'a> Executor<'a> {
|
|||
// Help
|
||||
"help" => return act!(help:toggle, cx, Layer::Input),
|
||||
// Plugin
|
||||
"plugin" => return act!(plugin, self.app, cmd),
|
||||
"plugin" => return act!(app:plugin, cx, cmd),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
@ -263,7 +274,7 @@ impl<'a> Executor<'a> {
|
|||
}
|
||||
|
||||
fn confirm(&mut self, cmd: CmdCow) -> Result<Data> {
|
||||
let cx = &mut Ctx::new(&mut self.app.core, &cmd)?;
|
||||
let cx = &mut Ctx::new(&cmd, &mut self.app.core, &mut self.app.term)?;
|
||||
|
||||
macro_rules! on {
|
||||
($name:ident) => {
|
||||
|
|
@ -281,7 +292,7 @@ impl<'a> Executor<'a> {
|
|||
}
|
||||
|
||||
fn help(&mut self, cmd: CmdCow) -> Result<Data> {
|
||||
let cx = &mut Ctx::new(&mut self.app.core, &cmd)?;
|
||||
let cx = &mut Ctx::new(&cmd, &mut self.app.core, &mut self.app.term)?;
|
||||
|
||||
macro_rules! on {
|
||||
($name:ident) => {
|
||||
|
|
@ -299,13 +310,13 @@ impl<'a> Executor<'a> {
|
|||
// Help
|
||||
"close" => act!(help:toggle, cx, Layer::Help),
|
||||
// Plugin
|
||||
"plugin" => act!(plugin, self.app, cmd),
|
||||
"plugin" => act!(app:plugin, cx, cmd),
|
||||
_ => succ!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn cmp(&mut self, cmd: CmdCow) -> Result<Data> {
|
||||
let cx = &mut Ctx::new(&mut self.app.core, &cmd)?;
|
||||
let cx = &mut Ctx::new(&cmd, &mut self.app.core, &mut self.app.term)?;
|
||||
|
||||
macro_rules! on {
|
||||
($name:ident) => {
|
||||
|
|
@ -324,13 +335,13 @@ impl<'a> Executor<'a> {
|
|||
// Help
|
||||
"help" => act!(help:toggle, cx, Layer::Cmp),
|
||||
// Plugin
|
||||
"plugin" => act!(plugin, self.app, cmd),
|
||||
"plugin" => act!(app:plugin, cx, cmd),
|
||||
_ => succ!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn which(&mut self, cmd: CmdCow) -> Result<Data> {
|
||||
let cx = &mut Ctx::new(&mut self.app.core, &cmd)?;
|
||||
let cx = &mut Ctx::new(&cmd, &mut self.app.core, &mut self.app.term)?;
|
||||
|
||||
macro_rules! on {
|
||||
($name:ident) => {
|
||||
|
|
@ -345,4 +356,21 @@ impl<'a> Executor<'a> {
|
|||
|
||||
succ!();
|
||||
}
|
||||
|
||||
fn notify(&mut self, cmd: CmdCow) -> Result<Data> {
|
||||
let cx = &mut Ctx::new(&cmd, &mut self.app.core, &mut self.app.term)?;
|
||||
|
||||
macro_rules! on {
|
||||
($name:ident) => {
|
||||
if cmd.name == stringify!($name) {
|
||||
return act!(notify:$name, cx, cmd);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
on!(push);
|
||||
on!(tick);
|
||||
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,15 +8,6 @@ pub(crate) struct Notify<'a> {
|
|||
impl<'a> Notify<'a> {
|
||||
pub(crate) fn new(core: &'a Core) -> Self { Self { core } }
|
||||
|
||||
pub(crate) fn available(area: Rect) -> Rect {
|
||||
let chunks = layout::Layout::horizontal([Constraint::Fill(1), Constraint::Min(80)]).split(area);
|
||||
|
||||
let chunks =
|
||||
layout::Layout::vertical([Constraint::Fill(1), Constraint::Max(1)]).split(chunks[1]);
|
||||
|
||||
chunks[0]
|
||||
}
|
||||
|
||||
fn tiles<'m>(area: Rect, messages: impl Iterator<Item = &'m Message> + Clone) -> Vec<Rect> {
|
||||
layout::Layout::vertical(
|
||||
[Constraint::Fill(1)]
|
||||
|
|
@ -42,7 +33,7 @@ impl<'a> Notify<'a> {
|
|||
impl Widget for Notify<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let notify = &self.core.notify;
|
||||
let available = Self::available(area);
|
||||
let available = yazi_core::notify::Notify::available(area);
|
||||
|
||||
let messages = notify.messages.iter().take(notify.limit(available)).rev();
|
||||
let tiles = Self::tiles(available, messages.clone());
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ impl<'a> Router<'a> {
|
|||
|
||||
use Layer as L;
|
||||
Ok(match layer {
|
||||
L::App => unreachable!(),
|
||||
L::App | L::Notify => unreachable!(),
|
||||
L::Mgr | L::Tasks | L::Spot | L::Pick | L::Input | L::Confirm | L::Help => {
|
||||
self.matches(layer, key)
|
||||
}
|
||||
|
|
@ -42,7 +42,7 @@ impl<'a> Router<'a> {
|
|||
}
|
||||
|
||||
if on.len() > 1 {
|
||||
let cx = &mut Ctx::active(&mut self.app.core);
|
||||
let cx = &mut Ctx::active(&mut self.app.core, &mut self.app.term);
|
||||
act!(which:activate, cx, (layer, key)).ok();
|
||||
} else {
|
||||
emit!(Seq(ChordCow::from(chord).into_seq()));
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use yazi_config::YAZI;
|
|||
use yazi_shared::{CompletionToken, event::Event};
|
||||
|
||||
pub(super) struct Signals {
|
||||
tx: mpsc::UnboundedSender<(bool, CompletionToken)>,
|
||||
pub(super) tx: mpsc::UnboundedSender<(bool, CompletionToken)>,
|
||||
}
|
||||
|
||||
impl Signals {
|
||||
|
|
@ -17,10 +17,6 @@ impl Signals {
|
|||
Ok(Self { tx })
|
||||
}
|
||||
|
||||
pub(super) fn stop(&mut self, token: CompletionToken) { self.tx.send((false, token)).ok(); }
|
||||
|
||||
pub(super) fn resume(&mut self, token: CompletionToken) { self.tx.send((true, token)).ok(); }
|
||||
|
||||
#[cfg(unix)]
|
||||
fn handle_sys(n: libc::c_int) -> bool {
|
||||
use libc::{SIGCONT, SIGHUP, SIGINT, SIGQUIT, SIGSTOP, SIGTERM, SIGTSTP};
|
||||
|
|
@ -30,7 +26,7 @@ impl Signals {
|
|||
match n {
|
||||
SIGINT => { /* ignored */ }
|
||||
SIGQUIT | SIGHUP | SIGTERM => {
|
||||
Event::Quit(Default::default()).emit();
|
||||
AppProxy::quit(Default::default());
|
||||
return false;
|
||||
}
|
||||
SIGTSTP => {
|
||||
|
|
@ -38,7 +34,7 @@ impl Signals {
|
|||
AppProxy::stop().await;
|
||||
if unsafe { libc::kill(0, SIGSTOP) } != 0 {
|
||||
error!("Failed to stop the process:\n{}", std::io::Error::last_os_error());
|
||||
Event::Quit(Default::default()).emit();
|
||||
AppProxy::quit(Default::default());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
#[macro_export]
|
||||
macro_rules! emit {
|
||||
(Quit($opt:expr)) => {
|
||||
yazi_shared::event::Event::Quit($opt).emit();
|
||||
};
|
||||
(Call($cmd:expr)) => {
|
||||
yazi_shared::event::Event::Call(yazi_shared::event::CmdCow::from($cmd)).emit();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#[macro_export]
|
||||
macro_rules! render {
|
||||
() => {
|
||||
yazi_shared::event::NEED_RENDER.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
yazi_shared::event::NEED_RENDER.store(1, std::sync::atomic::Ordering::Relaxed);
|
||||
};
|
||||
($cond:expr) => {
|
||||
if $cond {
|
||||
|
|
@ -28,3 +28,15 @@ macro_rules! render_and {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! render_partial {
|
||||
() => {{
|
||||
_ = yazi_shared::event::NEED_RENDER.compare_exchange(
|
||||
0,
|
||||
2,
|
||||
std::sync::atomic::Ordering::Relaxed,
|
||||
std::sync::atomic::Ordering::Relaxed,
|
||||
);
|
||||
}};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ dyn-clone = { workspace = true }
|
|||
hashbrown = { workspace = true }
|
||||
mlua = { workspace = true }
|
||||
ordered-float = { workspace = true }
|
||||
ratatui = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_with = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
use anyhow::bail;
|
||||
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
|
||||
use yazi_shared::{SStr, event::CmdCow};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DeprecateOpt {
|
||||
pub content: SStr,
|
||||
}
|
||||
|
|
@ -16,3 +18,11 @@ impl TryFrom<CmdCow> for DeprecateOpt {
|
|||
Ok(Self { content })
|
||||
}
|
||||
}
|
||||
|
||||
impl FromLua for DeprecateOpt {
|
||||
fn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err("unsupported".into_lua_err()) }
|
||||
}
|
||||
|
||||
impl IntoLua for DeprecateOpt {
|
||||
fn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err("unsupported".into_lua_err()) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
yazi_macro::mod_flat!(deprecate mouse notify plugin resume stop update_progress);
|
||||
yazi_macro::mod_flat!(deprecate mouse plugin quit reflow resume stop update_progress);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
use crossterm::event::MouseEvent;
|
||||
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MouseOpt {
|
||||
pub event: MouseEvent,
|
||||
}
|
||||
|
|
@ -7,3 +9,11 @@ pub struct MouseOpt {
|
|||
impl From<MouseEvent> for MouseOpt {
|
||||
fn from(event: MouseEvent) -> Self { Self { event } }
|
||||
}
|
||||
|
||||
impl FromLua for MouseOpt {
|
||||
fn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err("unsupported".into_lua_err()) }
|
||||
}
|
||||
|
||||
impl IntoLua for MouseOpt {
|
||||
fn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err("unsupported".into_lua_err()) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use std::{borrow::Cow, fmt::{self, Debug}, str::FromStr};
|
|||
use anyhow::bail;
|
||||
use dyn_clone::DynClone;
|
||||
use hashbrown::HashMap;
|
||||
use mlua::{Lua, Table};
|
||||
use mlua::{ExternalError, FromLua, IntoLua, Lua, Table, Value};
|
||||
use serde::Deserialize;
|
||||
use yazi_shared::{SStr, data::{Data, DataKey}, event::{Cmd, CmdCow}};
|
||||
|
||||
|
|
@ -39,6 +39,14 @@ impl TryFrom<CmdCow> for PluginOpt {
|
|||
}
|
||||
}
|
||||
|
||||
impl FromLua for PluginOpt {
|
||||
fn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err("unsupported".into_lua_err()) }
|
||||
}
|
||||
|
||||
impl IntoLua for PluginOpt {
|
||||
fn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err("unsupported".into_lua_err()) }
|
||||
}
|
||||
|
||||
impl PluginOpt {
|
||||
pub fn new_callback(id: impl Into<SStr>, f: impl PluginCallback) -> Self {
|
||||
Self {
|
||||
|
|
|
|||
35
yazi-parser/src/app/quit.rs
Normal file
35
yazi-parser/src/app/quit.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
use mlua::{FromLua, IntoLua, Lua, LuaSerdeExt, Value};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use yazi_shared::{event::CmdCow, strand::StrandBuf};
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
pub struct QuitOpt {
|
||||
pub code: i32,
|
||||
#[serde(skip)] // FIXME
|
||||
pub selected: Option<StrandBuf>,
|
||||
pub no_cwd_file: bool,
|
||||
}
|
||||
|
||||
impl TryFrom<CmdCow> for QuitOpt {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> {
|
||||
if let Some(opt) = c.take_any2("opt") {
|
||||
return opt;
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
code: c.get("code").unwrap_or_default(),
|
||||
selected: None, // FIXME
|
||||
no_cwd_file: c.bool("no-cwd-file"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FromLua for QuitOpt {
|
||||
fn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> { lua.from_value(value) }
|
||||
}
|
||||
|
||||
impl IntoLua for QuitOpt {
|
||||
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> { lua.to_value(&self) }
|
||||
}
|
||||
19
yazi-parser/src/app/reflow.rs
Normal file
19
yazi-parser/src/app/reflow.rs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
use mlua::{ExternalError, FromLua, IntoLua, Lua, Table, Value};
|
||||
use ratatui::layout::Rect;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ReflowOpt {
|
||||
pub reflow: fn(Rect) -> mlua::Result<Table>,
|
||||
}
|
||||
|
||||
impl From<fn(Rect) -> mlua::Result<Table>> for ReflowOpt {
|
||||
fn from(f: fn(Rect) -> mlua::Result<Table>) -> Self { Self { reflow: f } }
|
||||
}
|
||||
|
||||
impl FromLua for ReflowOpt {
|
||||
fn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err("unsupported".into_lua_err()) }
|
||||
}
|
||||
|
||||
impl IntoLua for ReflowOpt {
|
||||
fn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err("unsupported".into_lua_err()) }
|
||||
}
|
||||
|
|
@ -1,18 +1,19 @@
|
|||
use anyhow::bail;
|
||||
use yazi_shared::{CompletionToken, event::CmdCow};
|
||||
use mlua::{ExternalError, FromLua, IntoLua, Lua, Table, Value};
|
||||
use ratatui::layout::Rect;
|
||||
use tokio::sync::mpsc;
|
||||
use yazi_shared::CompletionToken;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ResumeOpt {
|
||||
pub tx: mpsc::UnboundedSender<(bool, CompletionToken)>,
|
||||
pub token: CompletionToken,
|
||||
pub reflow: fn(Rect) -> mlua::Result<Table>,
|
||||
}
|
||||
|
||||
impl TryFrom<CmdCow> for ResumeOpt {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> {
|
||||
let Some(token) = c.take_any("token") else {
|
||||
bail!("Invalid 'token' in ResumeOpt");
|
||||
};
|
||||
|
||||
Ok(Self { token })
|
||||
impl FromLua for ResumeOpt {
|
||||
fn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err("unsupported".into_lua_err()) }
|
||||
}
|
||||
|
||||
impl IntoLua for ResumeOpt {
|
||||
fn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err("unsupported".into_lua_err()) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,17 @@
|
|||
use anyhow::bail;
|
||||
use yazi_shared::{CompletionToken, event::CmdCow};
|
||||
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
|
||||
use tokio::sync::mpsc;
|
||||
use yazi_shared::CompletionToken;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct StopOpt {
|
||||
pub tx: mpsc::UnboundedSender<(bool, CompletionToken)>,
|
||||
pub token: CompletionToken,
|
||||
}
|
||||
|
||||
impl TryFrom<CmdCow> for StopOpt {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> {
|
||||
let Some(token) = c.take_any("token") else {
|
||||
bail!("Invalid 'token' in StopOpt");
|
||||
};
|
||||
|
||||
Ok(Self { token })
|
||||
impl FromLua for StopOpt {
|
||||
fn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err("unsupported".into_lua_err()) }
|
||||
}
|
||||
|
||||
impl IntoLua for StopOpt {
|
||||
fn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err("unsupported".into_lua_err()) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
use anyhow::bail;
|
||||
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
|
||||
use ordered_float::OrderedFloat;
|
||||
use serde::Serialize;
|
||||
use yazi_shared::event::CmdCow;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UpdateProgressOpt {
|
||||
pub summary: TaskSummary,
|
||||
}
|
||||
|
|
@ -19,6 +21,14 @@ impl TryFrom<CmdCow> for UpdateProgressOpt {
|
|||
}
|
||||
}
|
||||
|
||||
impl FromLua for UpdateProgressOpt {
|
||||
fn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err("unsupported".into_lua_err()) }
|
||||
}
|
||||
|
||||
impl IntoLua for UpdateProgressOpt {
|
||||
fn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err("unsupported".into_lua_err()) }
|
||||
}
|
||||
|
||||
// --- Progress
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]
|
||||
pub struct TaskSummary {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
|
||||
use yazi_shared::event::CmdCow;
|
||||
|
||||
use crate::mgr::QuitOpt;
|
||||
use crate::app::QuitOpt;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct CloseOpt(pub QuitOpt);
|
||||
|
||||
impl From<CmdCow> for CloseOpt {
|
||||
fn from(c: CmdCow) -> Self { Self(c.into()) }
|
||||
impl TryFrom<CmdCow> for CloseOpt {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(c: CmdCow) -> Result<Self, Self::Error> { c.try_into().map(Self) }
|
||||
}
|
||||
|
||||
impl FromLua for CloseOpt {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ yazi_macro::mod_flat!(
|
|||
open_do
|
||||
paste
|
||||
peek
|
||||
quit
|
||||
remove
|
||||
rename
|
||||
reveal
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
use mlua::{FromLua, IntoLua, Lua, LuaSerdeExt, Value};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use yazi_shared::event::{CmdCow, EventQuit};
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||
pub struct QuitOpt {
|
||||
pub code: i32,
|
||||
pub no_cwd_file: bool,
|
||||
}
|
||||
|
||||
impl From<CmdCow> for QuitOpt {
|
||||
fn from(c: CmdCow) -> Self {
|
||||
Self { code: c.get("code").unwrap_or_default(), no_cwd_file: c.bool("no-cwd-file") }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<QuitOpt> for EventQuit {
|
||||
fn from(value: QuitOpt) -> Self {
|
||||
Self { code: value.code, no_cwd_file: value.no_cwd_file, ..Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
impl FromLua for QuitOpt {
|
||||
fn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> { lua.from_value(value) }
|
||||
}
|
||||
|
||||
impl IntoLua for QuitOpt {
|
||||
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> { lua.to_value(&self) }
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
yazi_macro::mod_flat!(tick);
|
||||
yazi_macro::mod_flat!(push tick);
|
||||
|
|
|
|||
|
|
@ -8,16 +8,16 @@ use yazi_config::{Style, THEME};
|
|||
use yazi_shared::event::CmdCow;
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
pub struct NotifyOpt {
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct PushOpt {
|
||||
pub title: String,
|
||||
pub content: String,
|
||||
pub level: NotifyLevel,
|
||||
pub level: PushLevel,
|
||||
#[serde_as(as = "DurationSeconds<f64>")] // FIXME
|
||||
pub timeout: Duration,
|
||||
}
|
||||
|
||||
impl TryFrom<CmdCow> for NotifyOpt {
|
||||
impl TryFrom<CmdCow> for PushOpt {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> {
|
||||
|
|
@ -25,25 +25,25 @@ impl TryFrom<CmdCow> for NotifyOpt {
|
|||
}
|
||||
}
|
||||
|
||||
impl FromLua for NotifyOpt {
|
||||
impl FromLua for PushOpt {
|
||||
fn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> { lua.from_value(value) }
|
||||
}
|
||||
|
||||
impl IntoLua for NotifyOpt {
|
||||
impl IntoLua for PushOpt {
|
||||
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> { lua.to_value(&self) }
|
||||
}
|
||||
|
||||
// --- Level
|
||||
#[derive(Clone, Copy, Default, Deserialize, Eq, PartialEq, Serialize)]
|
||||
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum NotifyLevel {
|
||||
pub enum PushLevel {
|
||||
#[default]
|
||||
Info,
|
||||
Warn,
|
||||
Error,
|
||||
}
|
||||
|
||||
impl NotifyLevel {
|
||||
impl PushLevel {
|
||||
pub fn icon(self) -> &'static str {
|
||||
match self {
|
||||
Self::Info => &THEME.notify.icon_info,
|
||||
|
|
@ -61,7 +61,7 @@ impl NotifyLevel {
|
|||
}
|
||||
}
|
||||
|
||||
impl FromStr for NotifyLevel {
|
||||
impl FromStr for PushLevel {
|
||||
type Err = serde::de::value::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
|
|
@ -6,8 +6,8 @@ use tokio_stream::wrappers::UnboundedReceiverStream;
|
|||
use yazi_binding::{elements::{Line, Pos, Text}, runtime};
|
||||
use yazi_config::{keymap::{Chord, ChordCow, Key}, popup::{ConfirmCfg, InputCfg}};
|
||||
use yazi_macro::relay;
|
||||
use yazi_parser::{app::NotifyOpt, which::ActivateOpt};
|
||||
use yazi_proxy::{AppProxy, ConfirmProxy, InputProxy, WhichProxy};
|
||||
use yazi_parser::{notify::PushOpt, which::ActivateOpt};
|
||||
use yazi_proxy::{ConfirmProxy, InputProxy, NotifyProxy, WhichProxy};
|
||||
use yazi_shared::Debounce;
|
||||
|
||||
use super::Utils;
|
||||
|
|
@ -100,7 +100,7 @@ impl Utils {
|
|||
}
|
||||
|
||||
pub(super) fn notify(lua: &Lua) -> mlua::Result<Function> {
|
||||
lua.create_function(|_, opt: NotifyOpt| Ok(AppProxy::notify(opt)))
|
||||
lua.create_function(|_, opt: PushOpt| Ok(NotifyProxy::push(opt)))
|
||||
}
|
||||
|
||||
fn parse_keys(value: Value) -> mlua::Result<Vec<Key>> {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use yazi_macro::{emit, relay};
|
||||
use yazi_parser::app::{NotifyLevel, NotifyOpt, PluginOpt, TaskSummary};
|
||||
use yazi_parser::app::{PluginOpt, QuitOpt, TaskSummary};
|
||||
use yazi_shared::CompletionToken;
|
||||
|
||||
pub struct AppProxy;
|
||||
|
||||
impl AppProxy {
|
||||
pub fn quit(opt: QuitOpt) {
|
||||
emit!(Call(relay!(app:quit).with_any("opt", opt)));
|
||||
}
|
||||
|
||||
pub async fn stop() {
|
||||
let token = CompletionToken::default();
|
||||
emit!(Call(relay!(app:stop).with_any("token", token.clone())));
|
||||
|
|
@ -19,32 +21,6 @@ impl AppProxy {
|
|||
token.future().await;
|
||||
}
|
||||
|
||||
pub fn notify(opt: NotifyOpt) {
|
||||
emit!(Call(relay!(app:notify).with_any("opt", opt)));
|
||||
}
|
||||
|
||||
pub fn update_notify(dur: Duration) {
|
||||
emit!(Call(relay!(app:update_notify, [dur.as_secs_f64()])));
|
||||
}
|
||||
|
||||
pub fn notify_warn(title: impl Into<String>, content: impl Into<String>) {
|
||||
Self::notify(NotifyOpt {
|
||||
title: title.into(),
|
||||
content: content.into(),
|
||||
level: NotifyLevel::Warn,
|
||||
timeout: Duration::from_secs(5),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn notify_error(title: &str, content: impl ToString) {
|
||||
Self::notify(NotifyOpt {
|
||||
title: title.to_owned(),
|
||||
content: content.to_string(),
|
||||
level: NotifyLevel::Error,
|
||||
timeout: Duration::from_secs(10),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn plugin(opt: PluginOpt) {
|
||||
emit!(Call(relay!(app:plugin).with_any("opt", opt)));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
mod macros;
|
||||
|
||||
yazi_macro::mod_flat!(app cmp confirm input mgr pick semaphore tasks which);
|
||||
yazi_macro::mod_flat!(app cmp confirm input mgr notify pick semaphore tasks which);
|
||||
|
||||
pub fn init() { crate::init_semaphore(); }
|
||||
|
|
|
|||
34
yazi-proxy/src/notify.rs
Normal file
34
yazi-proxy/src/notify.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use yazi_macro::{emit, relay};
|
||||
use yazi_parser::notify::{PushLevel, PushOpt};
|
||||
|
||||
pub struct NotifyProxy;
|
||||
|
||||
impl NotifyProxy {
|
||||
pub fn push(opt: PushOpt) {
|
||||
emit!(Call(relay!(notify:push).with_any("opt", opt)));
|
||||
}
|
||||
|
||||
pub fn push_warn(title: impl Into<String>, content: impl Into<String>) {
|
||||
Self::push(PushOpt {
|
||||
title: title.into(),
|
||||
content: content.into(),
|
||||
level: PushLevel::Warn,
|
||||
timeout: Duration::from_secs(5),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn push_error(title: &str, content: impl ToString) {
|
||||
Self::push(PushOpt {
|
||||
title: title.to_owned(),
|
||||
content: content.to_string(),
|
||||
level: PushLevel::Error,
|
||||
timeout: Duration::from_secs(10),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn tick(dur: Duration) {
|
||||
emit!(Call(relay!(notify:tick, [dur.as_secs_f64()])));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
use anyhow::{Result, anyhow};
|
||||
use tokio::{io::{AsyncBufReadExt, BufReader}, select, sync::mpsc};
|
||||
use yazi_binding::Permit;
|
||||
use yazi_proxy::{AppProxy, HIDER};
|
||||
use yazi_proxy::{AppProxy, HIDER, NotifyProxy};
|
||||
|
||||
use super::{ProcessInBg, ProcessInBlock, ProcessInOrphan, ShellOpt};
|
||||
use crate::{TaskOp, TaskOps, process::{ProcessOutBg, ProcessOutBlock, ProcessOutOrphan}};
|
||||
|
|
@ -20,7 +20,7 @@ impl Process {
|
|||
let (id, cmd) = (task.id, task.cmd.clone());
|
||||
let result = super::shell(task.into()).await;
|
||||
if let Err(e) = result {
|
||||
AppProxy::notify_warn(cmd.to_string_lossy(), format!("Failed to start process: {e}"));
|
||||
NotifyProxy::push_warn(cmd.to_string_lossy(), format!("Failed to start process: {e}"));
|
||||
return Ok(self.ops.out(id, ProcessOutBlock::Succ));
|
||||
}
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ impl Process {
|
|||
Some(code) => format!("Process exited with status code: {code}"),
|
||||
None => "Process terminated by signal".to_string(),
|
||||
};
|
||||
AppProxy::notify_warn(cmd.to_string_lossy(), content);
|
||||
NotifyProxy::push_warn(cmd.to_string_lossy(), content);
|
||||
}
|
||||
|
||||
Ok(self.ops.out(id, ProcessOutBlock::Succ))
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use crossterm::event::{KeyEvent, MouseEvent};
|
|||
use tokio::sync::mpsc;
|
||||
|
||||
use super::CmdCow;
|
||||
use crate::{RoCell, strand::StrandBuf};
|
||||
use crate::RoCell;
|
||||
|
||||
static TX: RoCell<mpsc::UnboundedSender<Event>> = RoCell::new();
|
||||
static RX: RoCell<mpsc::UnboundedReceiver<Event>> = RoCell::new();
|
||||
|
|
@ -11,20 +11,12 @@ static RX: RoCell<mpsc::UnboundedReceiver<Event>> = RoCell::new();
|
|||
pub enum Event {
|
||||
Call(CmdCow),
|
||||
Seq(Vec<CmdCow>),
|
||||
Render,
|
||||
Render(bool),
|
||||
Key(KeyEvent),
|
||||
Mouse(MouseEvent),
|
||||
Resize,
|
||||
Focus,
|
||||
Paste(String),
|
||||
Quit(EventQuit),
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct EventQuit {
|
||||
pub code: i32,
|
||||
pub no_cwd_file: bool,
|
||||
pub selected: Option<StrandBuf>,
|
||||
}
|
||||
|
||||
impl Event {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
yazi_macro::mod_flat!(cmd cow event);
|
||||
|
||||
pub static NEED_RENDER: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
|
||||
pub static NEED_RENDER: std::sync::atomic::AtomicU8 = std::sync::atomic::AtomicU8::new(0);
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ pub enum Layer {
|
|||
Help,
|
||||
Cmp,
|
||||
Which,
|
||||
Notify,
|
||||
}
|
||||
|
||||
impl Display for Layer {
|
||||
|
|
@ -31,6 +32,7 @@ impl Display for Layer {
|
|||
Self::Help => "help",
|
||||
Self::Cmp => "cmp",
|
||||
Self::Which => "which",
|
||||
Self::Notify => "notify",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue