feat: new relay-notify-push DDS event to allow custom notification handlers (#3642)

This commit is contained in:
sxyazi 2026-01-31 09:25:17 +08:00
parent dfdb235d74
commit a015d4a7ef
No known key found for this signature in database
33 changed files with 117 additions and 143 deletions

View file

@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
### Added ### Added
- Allow using `ps.sub()` in `init.lua` directly without a plugin ([#3638]) - Allow using `ps.sub()` in `init.lua` directly without a plugin ([#3638])
- New `relay-notify-push` DDS event to allow custom notification handlers ([#3642])
- New `cx.which` API to access the which component state ([#3617]) - New `cx.which` API to access the which component state ([#3617])
- New `ind-which-activate` DDS event to change the which component behavior ([#3608]) - New `ind-which-activate` DDS event to change the which component behavior ([#3608])
@ -1636,3 +1637,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
[#3633]: https://github.com/sxyazi/yazi/pull/3633 [#3633]: https://github.com/sxyazi/yazi/pull/3633
[#3634]: https://github.com/sxyazi/yazi/pull/3634 [#3634]: https://github.com/sxyazi/yazi/pull/3634
[#3638]: https://github.com/sxyazi/yazi/pull/3638 [#3638]: https://github.com/sxyazi/yazi/pull/3638
[#3642]: https://github.com/sxyazi/yazi/pull/3642

4
Cargo.lock generated
View file

@ -5538,6 +5538,7 @@ version = "26.1.22"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"crossterm 0.29.0", "crossterm 0.29.0",
"either",
"futures", "futures",
"hashbrown 0.16.1", "hashbrown 0.16.1",
"libc", "libc",
@ -5765,6 +5766,7 @@ version = "26.1.22"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"crossterm 0.29.0", "crossterm 0.29.0",
"either",
"scopeguard", "scopeguard",
"tokio", "tokio",
"tracing", "tracing",
@ -5835,6 +5837,7 @@ dependencies = [
"bitflags 2.10.0", "bitflags 2.10.0",
"core-foundation-sys", "core-foundation-sys",
"dirs", "dirs",
"either",
"foldhash", "foldhash",
"hashbrown 0.16.1", "hashbrown 0.16.1",
"libc", "libc",
@ -6056,6 +6059,7 @@ version = "26.1.22"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"deadpool", "deadpool",
"either",
"futures", "futures",
"hashbrown 0.16.1", "hashbrown 0.16.1",
"parking_lot", "parking_lot",

View file

@ -35,6 +35,7 @@ core-foundation-sys = "0.8.7"
crossterm = { version = "0.29.0", features = [ "event-stream" ] } crossterm = { version = "0.29.0", features = [ "event-stream" ] }
dirs = "6.0.0" dirs = "6.0.0"
dyn-clone = "1.0.20" dyn-clone = "1.0.20"
either = { version = "1.15.0" }
foldhash = "0.2.0" foldhash = "0.2.0"
futures = "0.3.31" futures = "0.3.31"
globset = "0.4.18" globset = "0.4.18"

View file

@ -38,6 +38,7 @@ yazi-widgets = { path = "../yazi-widgets", version = "26.1.22" }
# External dependencies # External dependencies
anyhow = { workspace = true } anyhow = { workspace = true }
crossterm = { workspace = true } crossterm = { workspace = true }
either = { workspace = true }
futures = { workspace = true } futures = { workspace = true }
hashbrown = { workspace = true } hashbrown = { workspace = true }
mlua = { workspace = true } mlua = { workspace = true }

View file

@ -2,7 +2,7 @@ use anyhow::Result;
use mlua::IntoLua; use mlua::IntoLua;
use tracing::error; use tracing::error;
use yazi_actor::lives::Lives; use yazi_actor::lives::Lives;
use yazi_binding::runtime_mut; use yazi_binding::runtime_scope;
use yazi_dds::{LOCAL, Payload, REMOTE}; use yazi_dds::{LOCAL, Payload, REMOTE};
use yazi_macro::succ; use yazi_macro::succ;
use yazi_plugin::LUA; use yazi_plugin::LUA;
@ -31,11 +31,9 @@ impl Actor for AcceptPayload {
succ!(Lives::scope(&cx.core, || { succ!(Lives::scope(&cx.core, || {
let body = payload.body.into_lua(&LUA)?; let body = payload.body.into_lua(&LUA)?;
for (id, cb) in handlers { for (id, cb) in handlers {
let blocking = runtime_mut!(LUA)?.critical_push(&id, true); if let Err(e) = runtime_scope!(LUA, &id, cb.call::<()>(body.clone())) {
if let Err(e) = cb.call::<()>(body.clone()) {
error!("Failed to run `{kind}` event handler in your `{id}` plugin: {e}"); error!("Failed to run `{kind}` event handler in your `{id}` plugin: {e}");
} }
runtime_mut!(LUA)?.critical_pop(blocking)?;
} }
Ok(()) Ok(())
})?); })?);

View file

@ -3,6 +3,7 @@ use crossterm::event::MouseEventKind;
use mlua::{ObjectLike, Table}; use mlua::{ObjectLike, Table};
use tracing::error; use tracing::error;
use yazi_actor::lives::Lives; use yazi_actor::lives::Lives;
use yazi_binding::runtime_scope;
use yazi_config::YAZI; use yazi_config::YAZI;
use yazi_macro::succ; use yazi_macro::succ;
use yazi_parser::app::MouseOpt; use yazi_parser::app::MouseOpt;
@ -20,11 +21,14 @@ impl Actor for Mouse {
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> { fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
let event = yazi_binding::MouseEvent::from(opt.event); let event = yazi_binding::MouseEvent::from(opt.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 result = Lives::scope(&cx.core, move || { let result = Lives::scope(&cx.core, move || {
let area = yazi_binding::elements::Rect::from(size); let root = runtime_scope!(LUA, "root", {
let root = LUA.globals().raw_get::<Table>("Root")?.call_method::<Table>("new", area)?; LUA.globals().raw_get::<Table>("Root")?.call_method::<Table>("new", area)
})?;
if matches!(event.kind, MouseEventKind::Down(_) if YAZI.mgr.mouse_events.get().draggable()) { if matches!(event.kind, MouseEventKind::Down(_) if YAZI.mgr.mouse_events.get().draggable()) {
root.raw_set("_drag_start", event)?; root.raw_set("_drag_start", event)?;

View file

@ -1,6 +1,6 @@
use anyhow::Result; use anyhow::Result;
use mlua::{ErrorContext, ExternalError, IntoLua, Value}; use mlua::{ErrorContext, ExternalError, IntoLua, Value};
use yazi_binding::runtime_mut; use yazi_binding::runtime_scope;
use yazi_dds::{LOCAL, spark::{Spark, SparkKind}}; use yazi_dds::{LOCAL, spark::{Spark, SparkKind}};
use yazi_plugin::LUA; use yazi_plugin::LUA;
@ -18,11 +18,7 @@ impl Preflight {
Ok(Lives::scope(cx.core, || { Ok(Lives::scope(cx.core, || {
let mut body = opt.1.into_lua(&LUA)?; let mut body = opt.1.into_lua(&LUA)?;
for (id, cb) in handlers { for (id, cb) in handlers {
let blocking = runtime_mut!(LUA)?.critical_push(&id, true); match runtime_scope!(LUA, &id, cb.call::<Value>(&body)) {
let result = cb.call::<Value>(&body);
runtime_mut!(LUA)?.critical_pop(blocking)?;
match result {
Ok(Value::Nil) => { Ok(Value::Nil) => {
Err(format!("`{kind}` event cancelled by `{id}` plugin on preflight").into_lua_err())? Err(format!("`{kind}` event cancelled by `{id}` plugin on preflight").into_lua_err())?
} }

View file

@ -16,7 +16,10 @@ pub(super) static FILE_CACHE: RoCell<RefCell<HashMap<PtrCell<yazi_fs::File>, Any
pub struct Lives; pub struct Lives;
impl Lives { impl Lives {
pub fn scope<T>(core: &yazi_core::Core, f: impl FnOnce() -> mlua::Result<T>) -> mlua::Result<T> { pub fn scope<T, F>(core: &yazi_core::Core, f: F) -> mlua::Result<T>
where
F: FnOnce() -> mlua::Result<T>,
{
FILE_CACHE.init(Default::default()); FILE_CACHE.init(Default::default());
defer! { FILE_CACHE.drop(); } defer! { FILE_CACHE.drop(); }

View file

@ -14,7 +14,7 @@ impl Actor for ToggleAll {
const NAME: &str = "toggle_all"; const NAME: &str = "toggle_all";
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> { fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
use yazi_shared::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);

View file

@ -1,11 +1,11 @@
use std::time::{Duration, Instant}; use std::time::Instant;
use anyhow::Result; use anyhow::Result;
use yazi_core::notify::Message; use yazi_core::notify::Message;
use yazi_macro::succ; use yazi_dds::spark::SparkKind;
use yazi_macro::{act, succ};
use yazi_parser::notify::PushOpt; use yazi_parser::notify::PushOpt;
use yazi_proxy::NotifyProxy; use yazi_shared::{Source, data::Data};
use yazi_shared::data::Data;
use crate::{Actor, Ctx}; use crate::{Actor, Ctx};
@ -24,8 +24,15 @@ impl Actor for Push {
if cx.notify.messages.iter().all(|m| m != &msg) { if cx.notify.messages.iter().all(|m| m != &msg) {
cx.notify.messages.push(msg); cx.notify.messages.push(msg);
NotifyProxy::tick(Duration::ZERO); act!(notify:tick, cx)?;
} }
succ!(); succ!();
} }
fn hook(cx: &Ctx, _: &Self::Options) -> Option<SparkKind> {
match cx.source() {
Source::Relay => Some(SparkKind::RelayNotifyPush),
_ => None,
}
}
} }

View file

@ -4,7 +4,7 @@ use anyhow::Result;
use ratatui::layout::Rect; use ratatui::layout::Rect;
use yazi_core::notify::Notify; use yazi_core::notify::Notify;
use yazi_emulator::Dimension; use yazi_emulator::Dimension;
use yazi_macro::succ; use yazi_macro::{render, render_partial, succ};
use yazi_parser::notify::TickOpt; use yazi_parser::notify::TickOpt;
use yazi_proxy::NotifyProxy; use yazi_proxy::NotifyProxy;
use yazi_shared::data::Data; use yazi_shared::data::Data;
@ -52,13 +52,13 @@ impl Actor for Tick {
} else if let Some(min) = timeouts.iter().min() { } else if let Some(min) = timeouts.iter().min() {
*min *min
} else { } else {
succ!(); succ!(render!());
}; };
cx.notify.tick_handle = Some(tokio::spawn(async move { cx.notify.tick_handle = Some(tokio::spawn(async move {
tokio::time::sleep(interval).await; tokio::time::sleep(interval).await;
NotifyProxy::tick(interval); NotifyProxy::tick(interval);
})); }));
succ!(); succ!(render_partial!());
} }
} }

View file

@ -1,7 +1,5 @@
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use yazi_shared::Either;
use crate::Adapter; use crate::Adapter;
pub(super) struct Adapters(Vec<Adapter>); pub(super) struct Adapters(Vec<Adapter>);
@ -17,12 +15,7 @@ impl DerefMut for Adapters {
} }
impl From<&yazi_emulator::Emulator> for Adapters { impl From<&yazi_emulator::Emulator> for Adapters {
fn from(value: &yazi_emulator::Emulator) -> Self { fn from(value: &yazi_emulator::Emulator) -> Self { value.kind.either_into() }
match value.kind {
Either::Left(b) => b.into(),
Either::Right(u) => u.into(),
}
}
} }
impl From<yazi_emulator::Brand> for Adapters { impl From<yazi_emulator::Brand> for Adapters {

View file

@ -19,7 +19,7 @@ pub fn init() -> anyhow::Result<()> {
// Emulator detection // Emulator detection
let mut emulator = Emulator::detect().unwrap_or_default(); let mut emulator = Emulator::detect().unwrap_or_default();
TMUX.set(emulator.kind.is_left_and(|&b| b == Brand::Tmux)); TMUX.set(emulator.kind.left() == Some(Brand::Tmux));
// Tmux support // Tmux support
if TMUX.get() { if TMUX.get() {

View file

@ -14,6 +14,19 @@ macro_rules! runtime_mut {
}}; }};
} }
#[macro_export]
macro_rules! runtime_scope {
($lua:ident, $id:expr, $block:expr) => {{
let mut f = || {
let blocking = $crate::runtime_mut!($lua)?.critical_push($id, true);
let result = (|| $block)();
$crate::runtime_mut!($lua)?.critical_pop(blocking)?;
result
};
f()
}};
}
#[macro_export] #[macro_export]
macro_rules! deprecate { macro_rules! deprecate {
($lua:ident, $tt:tt) => {{ ($lua:ident, $tt:tt) => {{

View file

@ -164,7 +164,7 @@ impl Actions {
fn file1_output() -> String { fn file1_output() -> String {
use std::io::Write; use std::io::Write;
let p = env::temp_dir().join(format!("yazi-debug-{}", timestamp_us())); let p = env::temp_dir().join(format!(".yazi-debug-{}.tmp", timestamp_us()));
std::fs::File::create_new(&p).map(|mut f| f.write_all(b"Hello, World!")).ok(); std::fs::File::create_new(&p).map(|mut f| f.write_all(b"Hello, World!")).ok();
let cmd = env::var_os("YAZI_FILE_ONE").unwrap_or("file".into()); let cmd = env::var_os("YAZI_FILE_ONE").unwrap_or("file".into());

View file

@ -48,6 +48,7 @@ clap = { workspace = true }
clap_complete = "4.5.65" clap_complete = "4.5.65"
clap_complete_fig = "4.5.2" clap_complete_fig = "4.5.2"
clap_complete_nushell = "4.5.10" clap_complete_nushell = "4.5.10"
serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
vergen-gitcl = { version = "9.1.0", features = [ "build" ] } vergen-gitcl = { version = "9.1.0", features = [ "build" ] }

View file

@ -2,7 +2,7 @@ use std::{borrow::Cow, ffi::OsString};
use anyhow::{Result, bail}; use anyhow::{Result, bail};
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use yazi_shared::{Either, Id}; use yazi_shared::Id;
#[derive(Parser)] #[derive(Parser)]
#[command(name = "Ya", about, long_about = None)] #[command(name = "Ya", about, long_about = None)]
@ -139,9 +139,16 @@ macro_rules! impl_emit_body {
impl $name { impl $name {
#[allow(dead_code)] #[allow(dead_code)]
pub(super) fn body(self) -> Result<String> { pub(super) fn body(self) -> Result<String> {
let cmd: Vec<_> = [Either::Left(self.name)] #[derive(serde::Serialize)]
#[serde(untagged)]
enum Elem {
Name(String),
Arg(Vec<u8>),
}
let cmd: Vec<_> = [Elem::Name(self.name)]
.into_iter() .into_iter()
.chain(self.args.into_iter().map(|s| Either::Right(s.into_encoded_bytes()))) .chain(self.args.into_iter().map(|s| Elem::Arg(s.into_encoded_bytes())))
.collect(); .collect();
Ok(serde_json::to_string(&cmd)?) Ok(serde_json::to_string(&cmd)?)
} }

View file

@ -12,6 +12,8 @@ pub enum SparkKind {
KeyQuit, KeyQuit,
// which:activate // which:activate
IndWhichActivate, IndWhichActivate,
// notify:push
RelayNotifyPush,
} }
impl AsRef<str> for SparkKind { impl AsRef<str> for SparkKind {
@ -27,6 +29,8 @@ impl AsRef<str> for SparkKind {
Self::KeyQuit => "key-quit", Self::KeyQuit => "key-quit",
// which:activate // which:activate
Self::IndWhichActivate => "ind-which-activate", Self::IndWhichActivate => "ind-which-activate",
// notify:push
Self::RelayNotifyPush => "relay-notify-push",
} }
} }
} }

View file

@ -166,6 +166,8 @@ impl<'a> Spark<'a> {
KeyQuit => Self::Quit(<_>::from_lua(value, lua)?), KeyQuit => Self::Quit(<_>::from_lua(value, lua)?),
// which:activate // which:activate
IndWhichActivate => Self::WhichActivate(<_>::from_lua(value, lua)?), IndWhichActivate => Self::WhichActivate(<_>::from_lua(value, lua)?),
// notify:push
RelayNotifyPush => Self::NotifyPush(<_>::from_lua(value, lua)?),
}) })
} }
} }

View file

@ -19,6 +19,7 @@ yazi-tty = { path = "../yazi-tty", version = "26.1.22" }
# External dependencies # External dependencies
anyhow = { workspace = true } anyhow = { workspace = true }
crossterm = { workspace = true } crossterm = { workspace = true }
either = { workspace = true }
scopeguard = { workspace = true } scopeguard = { workspace = true }
tokio = { workspace = true } tokio = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }

View file

@ -2,10 +2,11 @@ use std::{io::BufWriter, time::Duration};
use anyhow::Result; use anyhow::Result;
use crossterm::{cursor::{RestorePosition, SavePosition}, execute, style::Print, terminal::{disable_raw_mode, enable_raw_mode}}; use crossterm::{cursor::{RestorePosition, SavePosition}, execute, style::Print, terminal::{disable_raw_mode, enable_raw_mode}};
use either::Either;
use scopeguard::defer; use scopeguard::defer;
use tokio::time::sleep; use tokio::time::sleep;
use tracing::{debug, error, warn}; use tracing::{debug, error, warn};
use yazi_shared::{Either, RoCell}; use yazi_shared::RoCell;
use yazi_tty::{Handle, TTY}; use yazi_tty::{Handle, TTY};
use crate::{Brand, Dimension, Mux, TMUX, Unknown}; use crate::{Brand, Dimension, Mux, TMUX, Unknown};

View file

@ -4,7 +4,9 @@ use anyhow::Result;
use crossterm::{cursor::{MoveTo, SetCursorStyle, Show}, execute, queue, terminal::{BeginSynchronizedUpdate, EndSynchronizedUpdate}}; use crossterm::{cursor::{MoveTo, SetCursorStyle, Show}, execute, queue, terminal::{BeginSynchronizedUpdate, EndSynchronizedUpdate}};
use ratatui::{CompletedFrame, backend::{Backend, CrosstermBackend}, buffer::Buffer, layout::Position}; use ratatui::{CompletedFrame, backend::{Backend, CrosstermBackend}, buffer::Buffer, layout::Position};
use yazi_actor::{Ctx, lives::Lives}; use yazi_actor::{Ctx, lives::Lives};
use yazi_binding::runtime_scope;
use yazi_macro::{act, succ}; use yazi_macro::{act, succ};
use yazi_plugin::LUA;
use yazi_shared::{data::Data, event::NEED_RENDER}; use yazi_shared::{data::Data, event::NEED_RENDER};
use yazi_tty::TTY; use yazi_tty::TTY;
use yazi_widgets::COLLISION; use yazi_widgets::COLLISION;
@ -26,7 +28,9 @@ impl App {
let collision = COLLISION.swap(false, Ordering::Relaxed); let collision = COLLISION.swap(false, Ordering::Relaxed);
let frame = term let frame = term
.draw(|f| { .draw(|f| {
_ = Lives::scope(&self.core, || Ok(f.render_widget(Root::new(&self.core), f.area()))); _ = Lives::scope(&self.core, || {
runtime_scope!(LUA, "root", Ok(f.render_widget(Root::new(&self.core), f.area())))
});
}) })
.unwrap(); .unwrap();
@ -57,9 +61,11 @@ impl App {
let frame = term let frame = term
.draw_partial(|f| { .draw_partial(|f| {
_ = Lives::scope(&self.core, || { _ = Lives::scope(&self.core, || {
f.render_widget(crate::tasks::Progress::new(&self.core), f.area()); runtime_scope!(LUA, "root", {
f.render_widget(crate::notify::Notify::new(&self.core), f.area()); f.render_widget(crate::tasks::Progress::new(&self.core), f.area());
Ok(()) f.render_widget(crate::notify::Notify::new(&self.core), f.area());
Ok(())
})
}); });
}) })
.unwrap(); .unwrap();

View file

@ -22,6 +22,7 @@ anyhow = { workspace = true }
arc-swap = "1.8.0" arc-swap = "1.8.0"
bitflags = { workspace = true } bitflags = { workspace = true }
dirs = { workspace = true } dirs = { workspace = true }
either = { workspace = true }
foldhash = { workspace = true } foldhash = { workspace = true }
hashbrown = { workspace = true } hashbrown = { workspace = true }
parking_lot = { workspace = true } parking_lot = { workspace = true }

View file

@ -1,7 +1,7 @@
use std::{collections::VecDeque, future::poll_fn, io, mem, path::{Path, PathBuf}, pin::Pin, task::{Poll, ready}, time::{Duration, Instant}}; use std::{collections::VecDeque, future::poll_fn, io, mem, path::{Path, PathBuf}, pin::Pin, task::{Poll, ready}, time::{Duration, Instant}};
use either::Either;
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
use yazi_shared::Either;
type Task = Either<PathBuf, std::fs::ReadDir>; type Task = Either<PathBuf, std::fs::ReadDir>;
@ -84,7 +84,7 @@ impl SizeCalculator {
}; };
} }
let Some(next) = front.right_mut()?.next() else { let Some(next) = front.as_mut().right()?.next() else {
pop_and_continue!(); pop_and_continue!();
}; };

View file

@ -182,8 +182,8 @@ fn final_path(path: &Path) -> io::Result<PathBuf> {
fn final_path(path: &Path) -> io::Result<PathBuf> { fn final_path(path: &Path) -> io::Result<PathBuf> {
use std::{ffi::OsString, fs::File, os::windows::{ffi::OsStringExt, fs::OpenOptionsExt, io::AsRawHandle}}; use std::{ffi::OsString, fs::File, os::windows::{ffi::OsStringExt, fs::OpenOptionsExt, io::AsRawHandle}};
use either::Either;
use windows_sys::Win32::{Foundation::HANDLE, Storage::FileSystem::{FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, GetFinalPathNameByHandleW, VOLUME_NAME_DOS}}; use windows_sys::Win32::{Foundation::HANDLE, Storage::FileSystem::{FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, GetFinalPathNameByHandleW, VOLUME_NAME_DOS}};
use yazi_shared::Either;
let file = std::fs::OpenOptions::new() let file = std::fs::OpenOptions::new()
.access_mode(0) .access_mode(0)
@ -214,7 +214,8 @@ fn final_path(path: &Path) -> io::Result<PathBuf> {
match inner(&file, &mut [0u16; 512])? { match inner(&file, &mut [0u16; 512])? {
Either::Left(path) => Ok(path), Either::Left(path) => Ok(path),
Either::Right(len) => inner(&file, &mut vec![0u16; len as usize])? Either::Right(len) => inner(&file, &mut vec![0u16; len as usize])?
.left_or_err(|| io::Error::new(io::ErrorKind::InvalidData, "path too long")), .left()
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "path too long")),
} }
} }

View file

@ -3,7 +3,7 @@ use std::{str::FromStr, time::Duration};
use anyhow::anyhow; use anyhow::anyhow;
use mlua::{FromLua, IntoLua, Lua, LuaSerdeExt, Value}; use mlua::{FromLua, IntoLua, Lua, LuaSerdeExt, Value};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_with::{DurationSeconds, serde_as}; use serde_with::{DurationSecondsWithFrac, serde_as};
use yazi_config::{Style, THEME}; use yazi_config::{Style, THEME};
use yazi_shared::event::CmdCow; use yazi_shared::event::CmdCow;
@ -13,7 +13,7 @@ pub struct PushOpt {
pub title: String, pub title: String,
pub content: String, pub content: String,
pub level: PushLevel, pub level: PushLevel,
#[serde_as(as = "DurationSeconds<f64>")] // FIXME #[serde_as(as = "DurationSecondsWithFrac<f64>")]
pub timeout: Duration, pub timeout: Duration,
} }

View file

@ -4,7 +4,7 @@ use anyhow::bail;
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use yazi_shared::event::CmdCow; use yazi_shared::event::CmdCow;
#[derive(Debug)] #[derive(Debug, Default)]
pub struct TickOpt { pub struct TickOpt {
pub interval: Duration, pub interval: Duration,
} }

View file

@ -26,6 +26,7 @@ impl Deref for Loader {
impl Default for Loader { impl Default for Loader {
fn default() -> Self { fn default() -> Self {
let cache = HashMap::from_iter([ let cache = HashMap::from_iter([
// Plugins
("archive".to_owned(), preset!("plugins/archive").into()), ("archive".to_owned(), preset!("plugins/archive").into()),
("code".to_owned(), preset!("plugins/code").into()), ("code".to_owned(), preset!("plugins/code").into()),
("dds".to_owned(), preset!("plugins/dds").into()), ("dds".to_owned(), preset!("plugins/dds").into()),
@ -51,6 +52,22 @@ impl Default for Loader {
("vfs".to_owned(), preset!("plugins/vfs").into()), ("vfs".to_owned(), preset!("plugins/vfs").into()),
("video".to_owned(), preset!("plugins/video").into()), ("video".to_owned(), preset!("plugins/video").into()),
("zoxide".to_owned(), preset!("plugins/zoxide").into()), ("zoxide".to_owned(), preset!("plugins/zoxide").into()),
// Components
("current".to_owned(), [][..].into()),
("entity".to_owned(), [][..].into()),
("header".to_owned(), [][..].into()),
("linemode".to_owned(), [][..].into()),
("marker".to_owned(), [][..].into()),
("modal".to_owned(), [][..].into()),
("parent".to_owned(), [][..].into()),
("preview".to_owned(), [][..].into()),
("progress".to_owned(), [][..].into()),
("rail".to_owned(), [][..].into()),
("root".to_owned(), [][..].into()),
("status".to_owned(), [][..].into()),
("tab".to_owned(), [][..].into()),
("tabs".to_owned(), [][..].into()),
("tasks".to_owned(), [][..].into()),
]); ]);
Self { cache: RwLock::new(cache) } Self { cache: RwLock::new(cache) }
} }

View file

@ -1,7 +1,7 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use futures::executor::block_on; use futures::executor::block_on;
use mlua::Lua; use mlua::Lua;
use yazi_binding::{Runtime, runtime_mut}; use yazi_binding::{Runtime, runtime_scope};
use yazi_boot::BOOT; use yazi_boot::BOOT;
use yazi_macro::plugin_preset as preset; use yazi_macro::plugin_preset as preset;
use yazi_shared::RoCell; use yazi_shared::RoCell;
@ -65,9 +65,7 @@ fn stage_2(lua: &'static Lua) -> mlua::Result<()> {
lua.load(preset!("compat")).set_name("compat.lua").exec()?; lua.load(preset!("compat")).set_name("compat.lua").exec()?;
if let Ok(b) = std::fs::read(BOOT.config_dir.join("init.lua")) { if let Ok(b) = std::fs::read(BOOT.config_dir.join("init.lua")) {
let blocking = runtime_mut!(lua)?.critical_push("init", true); runtime_scope!(lua, "init", block_on(lua.load(b).set_name("init.lua").exec_async()))?;
block_on(lua.load(b).set_name("init.lua").exec_async())?;
runtime_mut!(lua)?.critical_pop(blocking)?;
} }
Ok(()) Ok(())

View file

@ -1,89 +0,0 @@
use serde::Serialize;
#[derive(Clone, Copy, Debug)]
pub enum Either<L, R> {
Left(L),
Right(R),
}
impl<L, R> Either<L, R> {
pub fn left(&self) -> Option<&L> {
match self {
Self::Left(l) => Some(l),
_ => None,
}
}
pub fn right(&self) -> Option<&R> {
match self {
Self::Right(r) => Some(r),
_ => None,
}
}
pub fn left_mut(&mut self) -> Option<&mut L> {
match self {
Self::Left(l) => Some(l),
_ => None,
}
}
pub fn right_mut(&mut self) -> Option<&mut R> {
match self {
Self::Right(r) => Some(r),
_ => None,
}
}
pub fn is_left_and<F: FnOnce(&L) -> bool>(&self, f: F) -> bool {
self.left().map(f).unwrap_or(false)
}
pub fn is_right_and<F: FnOnce(&R) -> bool>(&self, f: F) -> bool {
self.right().map(f).unwrap_or(false)
}
pub fn into_left(self) -> Option<L> {
match self {
Self::Left(l) => Some(l),
_ => None,
}
}
pub fn into_right(self) -> Option<R> {
match self {
Self::Right(r) => Some(r),
_ => None,
}
}
pub fn left_or_err<E, F: FnOnce() -> E>(self, f: F) -> Result<L, E> {
match self {
Self::Left(l) => Ok(l),
_ => Err(f()),
}
}
pub fn right_or_err<E, F: FnOnce() -> E>(self, f: F) -> Result<R, E> {
match self {
Self::Right(r) => Ok(r),
_ => Err(f()),
}
}
}
impl<L, R> Serialize for Either<L, R>
where
L: Serialize,
R: Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
Self::Left(l) => l.serialize(serializer),
Self::Right(r) => r.serialize(serializer),
}
}
}

View file

@ -1,6 +1,6 @@
yazi_macro::mod_pub!(data errors event loc path pool scheme shell strand translit url wtf8); yazi_macro::mod_pub!(data errors event loc path pool scheme shell strand translit url wtf8);
yazi_macro::mod_flat!(alias bytes chars completion_token condition debounce either env id layer localset natsort os predictor ro_cell source sync_cell terminal tests throttle time utf8); yazi_macro::mod_flat!(alias bytes chars completion_token condition debounce env id layer localset natsort os predictor ro_cell source sync_cell terminal tests throttle time utf8);
pub fn init() { pub fn init() {
LOCAL_SET.with(tokio::task::LocalSet::new); LOCAL_SET.with(tokio::task::LocalSet::new);

View file

@ -21,6 +21,7 @@ yazi-shared = { path = "../yazi-shared", version = "26.1.22" }
# External dependencies # External dependencies
anyhow = { workspace = true } anyhow = { workspace = true }
deadpool = { version = "0.12.3", default-features = false, features = [ "managed", "rt_tokio_1" ] } deadpool = { version = "0.12.3", default-features = false, features = [ "managed", "rt_tokio_1" ] }
either = { workspace = true }
futures = { workspace = true } futures = { workspace = true }
hashbrown = { workspace = true } hashbrown = { workspace = true }
parking_lot = { workspace = true } parking_lot = { workspace = true }

View file

@ -1,7 +1,8 @@
use std::{collections::VecDeque, io, time::{Duration, Instant}}; use std::{collections::VecDeque, io, time::{Duration, Instant}};
use either::Either;
use yazi_fs::provider::{DirReader, FileHolder}; use yazi_fs::provider::{DirReader, FileHolder};
use yazi_shared::{Either, url::{AsUrl, UrlBuf}}; use yazi_shared::url::{AsUrl, UrlBuf};
use super::ReadDir; use super::ReadDir;
@ -66,7 +67,7 @@ impl SizeCalculator {
}; };
} }
let Ok(Some(ent)) = front.right_mut()?.next().await else { let Ok(Some(ent)) = front.as_mut().right()?.next().await else {
pop_and_continue!(); pop_and_continue!();
}; };