diff --git a/CHANGELOG.md b/CHANGELOG.md index b8c51214..6c57833b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/): - Zoom in or out of the preview image ([#2864]) - Improve the UX of the pick and input components ([#2906], [#2935]) - Show progress of each task in task manager ([#3121], [#3131], [#3134]) +- New experimental `ya.async()` API ([#3422]) - New `overall` option to set the overall background color ([#3317]) - Rounded corners for indicator bar ([#3419]) - New `bulk_rename` command always renames files with the editor ([#2984]) @@ -1554,3 +1555,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/): [#3393]: https://github.com/sxyazi/yazi/pull/3393 [#3396]: https://github.com/sxyazi/yazi/pull/3396 [#3419]: https://github.com/sxyazi/yazi/pull/3419 +[#3422]: https://github.com/sxyazi/yazi/pull/3422 diff --git a/yazi-actor/src/lives/file.rs b/yazi-actor/src/lives/file.rs index c981e750..df4f2e46 100644 --- a/yazi-actor/src/lives/file.rs +++ b/yazi-actor/src/lives/file.rs @@ -1,7 +1,7 @@ use std::ops::Deref; use mlua::{AnyUserData, IntoLua, UserData, UserDataFields, UserDataMethods, Value}; -use yazi_binding::Style; +use yazi_binding::{Style, cached_field}; use yazi_config::THEME; use yazi_plugin::bindings::Range; use yazi_shared::{path::AsPath, url::UrlLike}; @@ -20,6 +20,8 @@ pub(super) struct File { v_name: Option, v_cache: Option, + + v_bare: Option, } impl Deref for File { @@ -54,6 +56,8 @@ impl File { v_name: None, v_cache: None, + + v_bare: None, })?; ve.insert(ud.clone()); ud @@ -65,6 +69,7 @@ impl File { impl UserData for File { fn add_fields>(fields: &mut F) { yazi_binding::impl_file_fields!(fields); + cached_field!(fields, bare, |_, me| Ok(yazi_binding::File::new(&**me))); fields.add_field_method_get("idx", |_, me| Ok(me.idx + 1)); fields.add_field_method_get("is_hovered", |_, me| Ok(me.idx == me.folder.cursor)); diff --git a/yazi-binding/src/file.rs b/yazi-binding/src/file.rs index 9ef1b7a8..489252f3 100644 --- a/yazi-binding/src/file.rs +++ b/yazi-binding/src/file.rs @@ -1,11 +1,14 @@ use std::ops::Deref; -use mlua::{ExternalError, FromLua, Lua, Table, UserData, UserDataFields, UserDataMethods, UserDataRef, Value}; +use mlua::{AnyUserData, ExternalError, FromLua, Lua, ObjectLike, Table, UserData, UserDataFields, UserDataMethods, UserDataRef, Value}; use crate::{Cha, Url, impl_file_fields, impl_file_methods}; pub type FileRef = UserDataRef; +const EXPECTED: &str = "expected a table, File, or fs::File"; + +#[derive(Clone)] pub struct File { inner: yazi_fs::File, @@ -28,23 +31,51 @@ impl From for yazi_fs::File { } impl File { - pub fn new(inner: yazi_fs::File) -> Self { - Self { inner, v_cha: None, v_url: None, v_link_to: None, v_name: None, v_cache: None } + pub fn new(inner: impl Into) -> Self { + Self { + inner: inner.into(), + v_cha: None, + v_url: None, + v_link_to: None, + v_name: None, + v_cache: None, + } + } + + pub fn install(lua: &Lua) -> mlua::Result<()> { + lua.globals().raw_set("File", lua.create_function(|_, value: Value| Self::try_from(value))?) } } -impl File { - pub fn install(lua: &Lua) -> mlua::Result<()> { - lua.globals().raw_set( - "File", - lua.create_function(|_, t: Table| { - Ok(Self::new(yazi_fs::File { - url: t.raw_get::("url")?.into(), - cha: *t.raw_get::("cha")?, - ..Default::default() - })) - })?, - ) +impl TryFrom for File { + type Error = mlua::Error; + + fn try_from(value: Value) -> Result { + match value { + Value::Table(tbl) => Self::try_from(tbl), + Value::UserData(ud) => Self::try_from(ud), + _ => Err(EXPECTED.into_lua_err())?, + } + } +} + +impl TryFrom for File { + type Error = mlua::Error; + + fn try_from(value: Table) -> Result { + Ok(Self::new(yazi_fs::File { + url: value.raw_get::("url")?.into(), + cha: *value.raw_get::("cha")?, + ..Default::default() + })) + } +} + +impl TryFrom for File { + type Error = mlua::Error; + + fn try_from(value: AnyUserData) -> Result { + Ok(if let Ok(me) = value.borrow::() { me.clone() } else { value.get("bare")? }) } } @@ -52,7 +83,7 @@ impl FromLua for File { fn from_lua(value: Value, _: &Lua) -> mlua::Result { Ok(match value { Value::UserData(ud) => Self::new(ud.take::()?.inner), - _ => Err("Expected a File".into_lua_err())?, + _ => Err("expected a File".into_lua_err())?, }) } } diff --git a/yazi-binding/src/handle.rs b/yazi-binding/src/handle.rs new file mode 100644 index 00000000..c9a6f35e --- /dev/null +++ b/yazi-binding/src/handle.rs @@ -0,0 +1,16 @@ +use mlua::{MultiValue, UserData, UserDataMethods}; +use tokio::task::JoinHandle; + +pub enum Handle { + AsyncFn(JoinHandle>), +} + +impl UserData for Handle { + fn add_methods>(methods: &mut M) { + methods.add_method("abort", |_, me, ()| { + Ok(match me { + Self::AsyncFn(h) => h.abort(), + }) + }); + } +} diff --git a/yazi-binding/src/lib.rs b/yazi-binding/src/lib.rs index aa051c39..e23749be 100644 --- a/yazi-binding/src/lib.rs +++ b/yazi-binding/src/lib.rs @@ -2,4 +2,4 @@ mod macros; yazi_macro::mod_pub!(elements); -yazi_macro::mod_flat!(cha color composer error file icon id iter path permit runtime scheme stage style url utils); +yazi_macro::mod_flat!(cha color composer error file handle icon id iter path permit runtime scheme stage style url utils); diff --git a/yazi-cli/src/main.rs b/yazi-cli/src/main.rs index 06776ea7..a2a15be2 100644 --- a/yazi-cli/src/main.rs +++ b/yazi-cli/src/main.rs @@ -6,10 +6,14 @@ use std::process::ExitCode; use clap::Parser; use yazi_macro::{errln, outln}; +use yazi_shared::LOCAL_SET; #[tokio::main] async fn main() -> ExitCode { - match run().await { + yazi_shared::init(); + yazi_fs::init(); + + match LOCAL_SET.run_until(run()).await { Ok(()) => ExitCode::SUCCESS, Err(e) => { for cause in e.chain() { @@ -26,9 +30,6 @@ async fn main() -> ExitCode { } async fn run() -> anyhow::Result<()> { - yazi_shared::init(); - yazi_fs::init(); - if std::env::args_os().nth(1).is_some_and(|s| s == "-V" || s == "--version") { outln!( "Ya {} ({} {})", diff --git a/yazi-fm/src/main.rs b/yazi-fm/src/main.rs index 9033317f..6f7f0153 100644 --- a/yazi-fm/src/main.rs +++ b/yazi-fm/src/main.rs @@ -37,5 +37,6 @@ async fn main() -> anyhow::Result<()> { yazi_plugin::init()?; yazi_dds::serve(); - app::App::serve().await + + yazi_shared::LOCAL_SET.run_until(app::App::serve()).await } diff --git a/yazi-fs/src/file.rs b/yazi-fs/src/file.rs index dbf067b4..9914a9a5 100644 --- a/yazi-fs/src/file.rs +++ b/yazi-fs/src/file.rs @@ -17,6 +17,10 @@ impl Deref for File { fn deref(&self) -> &Self::Target { &self.cha } } +impl From<&File> for File { + fn from(value: &File) -> Self { value.clone() } +} + impl File { #[inline] pub fn from_dummy(url: impl Into, r#type: Option) -> Self { diff --git a/yazi-plugin/preset/components/entity.lua b/yazi-plugin/preset/components/entity.lua index aa3421cd..aad13b12 100644 --- a/yazi-plugin/preset/components/entity.lua +++ b/yazi-plugin/preset/components/entity.lua @@ -13,7 +13,7 @@ Entity = { function Entity:new(file) return setmetatable({ _file = file }, { __index = self }) end function Entity:padding() - if not self._file.is_hovered or self._file.in_preview then + if not self._file.is_hovered then return " " end diff --git a/yazi-plugin/preset/components/linemode.lua b/yazi-plugin/preset/components/linemode.lua index b59beab2..0cc5f2db 100644 --- a/yazi-plugin/preset/components/linemode.lua +++ b/yazi-plugin/preset/components/linemode.lua @@ -8,19 +8,6 @@ Linemode = { function Linemode:new(file) return setmetatable({ _file = file }, { __index = self }) end -function Linemode:padding() - if not self._file.is_hovered then - return " " - end - - local style = Entity:new(self._file):style_rev() - if style then - return ui.Span(th.indicator.padding.close):style(style) - else - return " " - end -end - function Linemode:solo() if not self._file.in_current then return "" @@ -77,6 +64,19 @@ function Linemode:owner() return string.format("%s:%s", user, group) end +function Linemode:padding() + if not self._file.is_hovered then + return " " + end + + local style = Entity:new(self._file):style_rev() + if style then + return ui.Span(th.indicator.padding.close):style(style) + else + return " " + end +end + function Linemode:redraw() local lines = {} for _, c in ipairs(self._children) do diff --git a/yazi-plugin/preset/plugins/folder.lua b/yazi-plugin/preset/plugins/folder.lua index 46284a72..e6c7e88d 100644 --- a/yazi-plugin/preset/plugins/folder.lua +++ b/yazi-plugin/preset/plugins/folder.lua @@ -21,17 +21,18 @@ function M:peek(job) return ya.preview_widget(job, ui.Text(s):area(job.area):align(ui.Align.CENTER):wrap(ui.Wrap.YES)) end - local items = {} + local left, right = {}, {} for _, f in ipairs(folder.window) do local entity = Entity:new(f) - items[#items + 1] = entity:redraw():truncate { - max = job.area.w, - ellipsis = entity:ellipsis(job.area.w), - } + left[#left + 1], right[#right + 1] = entity:redraw(), Linemode:new(f):redraw() + + local max = math.max(0, job.area.w - right[#right]:width()) + left[#left]:truncate { max = max, ellipsis = entity:ellipsis(max) } end ya.preview_widget(job, { - ui.List(items):area(job.area), + ui.List(left):area(job.area), + ui.Text(right):area(job.area):align(ui.Align.RIGHT), table.unpack(Marker:new(job.area, folder):redraw()), }) end diff --git a/yazi-plugin/src/isolate/isolate.rs b/yazi-plugin/src/isolate/isolate.rs index 10e20f73..c60098e3 100644 --- a/yazi-plugin/src/isolate/isolate.rs +++ b/yazi-plugin/src/isolate/isolate.rs @@ -19,7 +19,7 @@ pub fn slim_lua(name: &str) -> mlua::Result { yazi_binding::Url::install(&lua)?; yazi_binding::Error::install(&lua)?; - crate::loader::install_isolate(&lua)?; + crate::loader::install(&lua)?; crate::process::install(&lua)?; // Addons diff --git a/yazi-plugin/src/loader/mod.rs b/yazi-plugin/src/loader/mod.rs index a01533f9..187b8085 100644 --- a/yazi-plugin/src/loader/mod.rs +++ b/yazi-plugin/src/loader/mod.rs @@ -3,5 +3,3 @@ yazi_macro::mod_flat!(chunk loader require); pub(super) fn init() { LOADER.with(<_>::default); } pub(super) fn install(lua: &mlua::Lua) -> mlua::Result<()> { Require::install(lua) } - -pub(super) fn install_isolate(lua: &mlua::Lua) -> mlua::Result<()> { Require::install_isolate(lua) } diff --git a/yazi-plugin/src/loader/require.rs b/yazi-plugin/src/loader/require.rs index 89f1e20c..9480f9d3 100644 --- a/yazi-plugin/src/loader/require.rs +++ b/yazi-plugin/src/loader/require.rs @@ -9,23 +9,6 @@ pub(super) struct Require; impl Require { pub(super) fn install(lua: &Lua) -> mlua::Result<()> { - lua.globals().raw_set( - "require", - lua.create_function(|lua, id: mlua::String| { - let id = id.to_str()?; - let id = Self::absolute_id(lua, &id)?; - futures::executor::block_on(LOADER.ensure(&id, |_| ())).into_lua_err()?; - - runtime_mut!(lua)?.push(&id); - let mod_ = LOADER.load(lua, &id); - runtime_mut!(lua)?.pop(); - - Self::create_mt(lua, id.into_owned(), mod_?, true) - })?, - ) - } - - pub(super) fn install_isolate(lua: &Lua) -> mlua::Result<()> { lua.globals().raw_set( "require", lua.create_async_function(|lua, id: mlua::String| async move { @@ -37,12 +20,12 @@ impl Require { let mod_ = LOADER.load(&lua, &id); runtime_mut!(lua)?.pop(); - Self::create_mt(&lua, id.into_owned(), mod_?, false) + Self::create_mt(&lua, id.into_owned(), mod_?) })?, ) } - fn create_mt(lua: &Lua, id: String, r#mod: Table, sync: bool) -> mlua::Result
{ + fn create_mt(lua: &Lua, id: String, r#mod: Table) -> mlua::Result
{ let id: Arc = Arc::from(id); let mt = lua.create_table_from([ ( @@ -50,7 +33,7 @@ impl Require { lua.create_function(move |lua, (ts, key): (Table, mlua::String)| { match ts.raw_get::
("__mod")?.raw_get::(&key)? { Value::Function(_) => { - Self::create_wrapper(lua, id.clone(), &key.to_str()?, sync)?.into_lua(lua) + Self::create_wrapper(lua, id.clone(), &key.to_str()?)?.into_lua(lua) } v => Ok(v), } @@ -69,29 +52,19 @@ impl Require { Ok(ts) } - fn create_wrapper(lua: &Lua, id: Arc, f: &str, sync: bool) -> mlua::Result { + fn create_wrapper(lua: &Lua, id: Arc, f: &str) -> mlua::Result { let f: Arc = Arc::from(f); - if sync { - lua.create_function(move |lua, args: MultiValue| { - let (r#mod, args) = Self::split_mod_and_args(lua, &id, args)?; + lua.create_async_function(move |lua, args: MultiValue| { + let (id, f) = (id.clone(), f.clone()); + async move { + let (r#mod, args) = Self::split_mod_and_args(&lua, &id, args)?; runtime_mut!(lua)?.push(&id); - let result = r#mod.call_function::(&f, args); + let result = r#mod.call_async_function::(&f, args).await; runtime_mut!(lua)?.pop(); result - }) - } else { - lua.create_async_function(move |lua, args: MultiValue| { - let (id, f) = (id.clone(), f.clone()); - async move { - let (r#mod, args) = Self::split_mod_and_args(&lua, &id, args)?; - runtime_mut!(lua)?.push(&id); - let result = r#mod.call_async_function::(&f, args).await; - runtime_mut!(lua)?.pop(); - result - } - }) - } + } + }) } fn split_mod_and_args( diff --git a/yazi-plugin/src/lua.rs b/yazi-plugin/src/lua.rs index 44706033..42f40518 100644 --- a/yazi-plugin/src/lua.rs +++ b/yazi-plugin/src/lua.rs @@ -1,4 +1,5 @@ use anyhow::{Context, Result}; +use futures::executor::block_on; use mlua::Lua; use yazi_binding::Runtime; use yazi_boot::BOOT; @@ -30,6 +31,7 @@ fn stage_1(lua: &'static Lua) -> Result<()> { yazi_binding::Error::install(lua)?; yazi_binding::Cha::install(lua)?; crate::loader::install(lua)?; + crate::process::install(lua)?; yazi_binding::File::install(lua)?; yazi_binding::Url::install(lua)?; @@ -62,7 +64,7 @@ fn stage_2(lua: &'static Lua) -> mlua::Result<()> { lua.load(preset!("compat")).set_name("compat.lua").exec()?; if let Ok(b) = std::fs::read(BOOT.config_dir.join("init.lua")) { - lua.load(b).set_name("init.lua").exec()?; + block_on(lua.load(b).set_name("init.lua").exec_async())?; } Ok(()) diff --git a/yazi-plugin/src/utils/sync.rs b/yazi-plugin/src/utils/sync.rs index ec689459..36aec1ce 100644 --- a/yazi-plugin/src/utils/sync.rs +++ b/yazi-plugin/src/utils/sync.rs @@ -2,11 +2,11 @@ use anyhow::Context; use futures::future::join_all; use mlua::{ExternalError, ExternalResult, Function, IntoLuaMulti, Lua, MultiValue, Value, Variadic}; use tokio::sync::oneshot; -use yazi_binding::{runtime, runtime_mut}; +use yazi_binding::{Handle, runtime, runtime_mut}; use yazi_dds::Sendable; use yazi_parser::app::{PluginCallback, PluginOpt}; use yazi_proxy::AppProxy; -use yazi_shared::data::Data; +use yazi_shared::{LOCAL_SET, data::Data}; use super::Utils; use crate::{bindings::{MpscRx, MpscTx, MpscUnboundedRx, MpscUnboundedTx, OneshotRx, OneshotTx}, loader::LOADER}; @@ -47,6 +47,29 @@ impl Utils { } } + pub(super) fn r#async(lua: &Lua, isolate: bool) -> mlua::Result { + if isolate { + lua.create_function(|_, _: Function| { + Err::<(), _>("`ya.async()` can only be used in sync context at the moment".into_lua_err()) + }) + } else { + lua.create_function(|lua, (f, args): (Function, MultiValue)| { + let name = runtime!(lua)?.current_owned(); + + Ok(Handle::AsyncFn(LOCAL_SET.spawn_local(async move { + let result = f.call_async::(args).await; + if let Err(ref e) = result { + match name { + Some(s) => tracing::error!("Failed to execute async block in `{s}` plugin: {e}"), + None => tracing::error!("Failed to execute async block in `init.lua`: {e}"), + } + } + result + }))) + }) + } + } + pub(super) fn chan(lua: &Lua) -> mlua::Result { lua.create_function(|lua, (r#type, buffer): (mlua::String, Option)| { match (&*r#type.as_bytes(), buffer) { diff --git a/yazi-plugin/src/utils/utils.rs b/yazi-plugin/src/utils/utils.rs index ca4e2083..a6ffe9d0 100644 --- a/yazi-plugin/src/utils/utils.rs +++ b/yazi-plugin/src/utils/utils.rs @@ -53,6 +53,7 @@ pub fn compose( // Sync b"sync" => Utils::sync(lua, isolate)?, + b"async" => Utils::r#async(lua, isolate)?, b"chan" => Utils::chan(lua)?, b"join" => Utils::join(lua)?, b"select" => Utils::select(lua)?, diff --git a/yazi-shared/src/lib.rs b/yazi-shared/src/lib.rs index 6471e522..f0d71cb6 100644 --- a/yazi-shared/src/lib.rs +++ b/yazi-shared/src/lib.rs @@ -1,14 +1,15 @@ yazi_macro::mod_pub!(data errors event loc path pool scheme shell strand translit url wtf8); -yazi_macro::mod_flat!(alias bytes chars condition debounce either env id layer natsort os predictor ro_cell source sync_cell terminal tests throttle time utf8); +yazi_macro::mod_flat!(alias bytes chars condition debounce either env id layer localset natsort os predictor ro_cell source sync_cell terminal tests throttle time utf8); pub fn init() { - pool::init(); + LOCAL_SET.with(tokio::task::LocalSet::new); LOG_LEVEL.replace(<_>::from(std::env::var("YAZI_LOG").unwrap_or_default())); #[cfg(unix)] USERS_CACHE.with(<_>::default); + pool::init(); event::Event::init(); } diff --git a/yazi-shared/src/localset.rs b/yazi-shared/src/localset.rs new file mode 100644 index 00000000..33ae9e47 --- /dev/null +++ b/yazi-shared/src/localset.rs @@ -0,0 +1,5 @@ +use tokio::task::LocalSet; + +use crate::RoCell; + +pub static LOCAL_SET: RoCell = RoCell::new();