feat: new experimental ya.async() API (#3422)

This commit is contained in:
三咲雅 misaki masa 2025-12-11 01:10:28 +08:00 committed by GitHub
parent 85a99937a1
commit ac7718c226
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 153 additions and 89 deletions

View file

@ -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

View file

@ -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<Value>,
v_cache: Option<Value>,
v_bare: Option<Value>,
}
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<F: UserDataFields<Self>>(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));

View file

@ -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<File>;
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<File> 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<yazi_fs::File>) -> 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>("url")?.into(),
cha: *t.raw_get::<Cha>("cha")?,
..Default::default()
}))
})?,
)
impl TryFrom<Value> for File {
type Error = mlua::Error;
fn try_from(value: Value) -> Result<Self, Self::Error> {
match value {
Value::Table(tbl) => Self::try_from(tbl),
Value::UserData(ud) => Self::try_from(ud),
_ => Err(EXPECTED.into_lua_err())?,
}
}
}
impl TryFrom<Table> for File {
type Error = mlua::Error;
fn try_from(value: Table) -> Result<Self, Self::Error> {
Ok(Self::new(yazi_fs::File {
url: value.raw_get::<Url>("url")?.into(),
cha: *value.raw_get::<Cha>("cha")?,
..Default::default()
}))
}
}
impl TryFrom<AnyUserData> for File {
type Error = mlua::Error;
fn try_from(value: AnyUserData) -> Result<Self, Self::Error> {
Ok(if let Ok(me) = value.borrow::<Self>() { me.clone() } else { value.get("bare")? })
}
}
@ -52,7 +83,7 @@ impl FromLua for File {
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
Ok(match value {
Value::UserData(ud) => Self::new(ud.take::<Self>()?.inner),
_ => Err("Expected a File".into_lua_err())?,
_ => Err("expected a File".into_lua_err())?,
})
}
}

View file

@ -0,0 +1,16 @@
use mlua::{MultiValue, UserData, UserDataMethods};
use tokio::task::JoinHandle;
pub enum Handle {
AsyncFn(JoinHandle<mlua::Result<MultiValue>>),
}
impl UserData for Handle {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("abort", |_, me, ()| {
Ok(match me {
Self::AsyncFn(h) => h.abort(),
})
});
}
}

View file

@ -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);

View file

@ -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 {} ({} {})",

View file

@ -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
}

View file

@ -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<UrlBuf>, r#type: Option<ChaType>) -> Self {

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -19,7 +19,7 @@ pub fn slim_lua(name: &str) -> mlua::Result<Lua> {
yazi_binding::Url::install(&lua)?;
yazi_binding::Error::install(&lua)?;
crate::loader::install_isolate(&lua)?;
crate::loader::install(&lua)?;
crate::process::install(&lua)?;
// Addons

View file

@ -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) }

View file

@ -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<Table> {
fn create_mt(lua: &Lua, id: String, r#mod: Table) -> mlua::Result<Table> {
let id: Arc<str> = 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::<Table>("__mod")?.raw_get::<Value>(&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<str>, f: &str, sync: bool) -> mlua::Result<Function> {
fn create_wrapper(lua: &Lua, id: Arc<str>, f: &str) -> mlua::Result<Function> {
let f: Arc<str> = 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::<MultiValue>(&f, args);
let result = r#mod.call_async_function::<MultiValue>(&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::<MultiValue>(&f, args).await;
runtime_mut!(lua)?.pop();
result
}
})
}
}
})
}
fn split_mod_and_args(

View file

@ -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(())

View file

@ -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<Function> {
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::<MultiValue>(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<Function> {
lua.create_function(|lua, (r#type, buffer): (mlua::String, Option<usize>)| {
match (&*r#type.as_bytes(), buffer) {

View file

@ -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)?,

View file

@ -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();
}

View file

@ -0,0 +1,5 @@
use tokio::task::LocalSet;
use crate::RoCell;
pub static LOCAL_SET: RoCell<LocalSet> = RoCell::new();