diff --git a/Cargo.lock b/Cargo.lock index fee83de1..99986894 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -836,9 +836,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "form_urlencoded" diff --git a/Cargo.toml b/Cargo.toml index 49551b05..5d82d069 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ clap = { version = "4.5.32", features = [ "derive" ] } core-foundation-sys = "0.8.7" crossterm = { version = "0.28.1", features = [ "event-stream" ] } dirs = "6.0.0" -foldhash = "0.1.4" +foldhash = "0.1.5" futures = "0.3.31" globset = "0.4.16" indexmap = { version = "2.8.0", features = [ "serde" ] } diff --git a/yazi-config/preset/keymap-default.toml b/yazi-config/preset/keymap-default.toml index e9f00f81..a6cd44e1 100644 --- a/yazi-config/preset/keymap-default.toml +++ b/yazi-config/preset/keymap-default.toml @@ -30,13 +30,13 @@ keymap = [ { on = "", run = "arrow 100%", desc = "Move cursor down one page" }, { on = [ "g", "g" ], run = "arrow top", desc = "Move cursor to the top" }, - { on = "G", run = "arrow bot", desc = "Move cursor to the bottom" }, + { on = "G", run = "arrow bot", desc = "Move cursor to the bottom" }, # Navigation - { on = "h", run = "leave", desc = "Go back to the parent directory" }, + { on = "h", run = "leave", desc = "Back to the parent directory" }, { on = "l", run = "enter", desc = "Enter the child directory" }, - { on = "", run = "leave", desc = "Go back to the parent directory" }, + { on = "", run = "leave", desc = "Back to the parent directory" }, { on = "", run = "enter", desc = "Enter the child directory" }, { on = "H", run = "back", desc = "Back to previous directory" }, diff --git a/yazi-config/preset/yazi-default.toml b/yazi-config/preset/yazi-default.toml index 53cfce92..1f6f2b8b 100644 --- a/yazi-config/preset/yazi-default.toml +++ b/yazi-config/preset/yazi-default.toml @@ -118,7 +118,7 @@ preloaders = [ { mime = "application/ms-opentype", run = "font" }, ] previewers = [ - { name = "*/", run = "folder", sync = true }, + { name = "*/", run = "folder" }, # Code { mime = "text/*", run = "code" }, { mime = "application/{mbox,javascript,wine-extension-ini}", run = "code" }, diff --git a/yazi-config/src/plugin/previewer.rs b/yazi-config/src/plugin/previewer.rs index 0e4d573e..a84b213d 100644 --- a/yazi-config/src/plugin/previewer.rs +++ b/yazi-config/src/plugin/previewer.rs @@ -10,8 +10,6 @@ pub struct Previewer { pub name: Option, pub mime: Option, pub run: Cmd, - #[serde(default)] - pub sync: bool, } impl Previewer { diff --git a/yazi-core/src/tab/preview.rs b/yazi-core/src/tab/preview.rs index 65eccd6a..f8cf4274 100644 --- a/yazi-core/src/tab/preview.rs +++ b/yazi-core/src/tab/preview.rs @@ -32,11 +32,7 @@ impl Preview { }; self.abort(); - if previewer.sync { - isolate::peek_sync(&previewer.run, file, mime, self.skip); - } else { - self.previewer_ct = Some(isolate::peek(&previewer.run, file, mime, self.skip)); - } + self.previewer_ct = isolate::peek(&previewer.run, file, mime, self.skip); } pub fn go_folder(&mut self, file: File, dir: Option, force: bool) { diff --git a/yazi-fm/src/app/commands/plugin.rs b/yazi-fm/src/app/commands/plugin.rs index daba45e2..cd5aaef2 100644 --- a/yazi-fm/src/app/commands/plugin.rs +++ b/yazi-fm/src/app/commands/plugin.rs @@ -29,7 +29,7 @@ impl App { } tokio::spawn(async move { - match LOADER.ensure(&opt.id).await { + match LOADER.ensure(&opt.id, |_| ()).await { Ok(()) => AppProxy::plugin_do(opt), Err(e) => AppProxy::notify_error("Plugin load failed", e), } diff --git a/yazi-fm/src/term.rs b/yazi-fm/src/term.rs index 78a63602..182ee1af 100644 --- a/yazi-fm/src/term.rs +++ b/yazi-fm/src/term.rs @@ -35,10 +35,10 @@ impl Term { execute!( TTY.writer(), screen::SetScreen(true), - Print(Mux::csi("\x1bP$q q\x1b\\")), // Request cursor shape (DECRQSS query for DECSCUSR) - Print(Mux::csi("\x1b[?12$p")), // Request cursor blink status (DECSET) - Print("\x1b[?u"), // Request keyboard enhancement flags (CSI u) - Print(Mux::csi("\x1b[0c")), // Request device attributes + Print("\x1bP$q q\x1b\\"), // Request cursor shape (DECRQSS query for DECSCUSR) + Print(Mux::csi("\x1b[?12$p")), // Request cursor blink status (DECSET) + Print("\x1b[?u"), // Request keyboard enhancement flags (CSI u) + Print(Mux::csi("\x1b[0c")), // Request device attributes screen::SetScreen(false), EnableBracketedPaste, mouse::SetMouse(true), diff --git a/yazi-plugin/preset/plugins/folder.lua b/yazi-plugin/preset/plugins/folder.lua index 30a4a1f5..c2b8efa0 100644 --- a/yazi-plugin/preset/plugins/folder.lua +++ b/yazi-plugin/preset/plugins/folder.lua @@ -1,3 +1,5 @@ +--- @sync peek + local M = {} function M:peek(job) diff --git a/yazi-plugin/src/config/runtime.rs b/yazi-plugin/src/config/runtime.rs index 164293a3..f65f1a6f 100644 --- a/yazi-plugin/src/config/runtime.rs +++ b/yazi-plugin/src/config/runtime.rs @@ -29,16 +29,11 @@ impl<'a> Runtime<'a> { } fn args(lua: &Lua) -> mlua::Result { - Composer::make(lua, 5, |lua, key| { - match key { - b"entries" => { - lua.create_sequence_from(ARGS.entries.iter().map(Url::from))?.into_lua(lua)? - } - b"cwd_file" => ARGS.cwd_file.as_ref().map(Url::from).into_lua(lua)?, - b"chooser_file" => ARGS.chooser_file.as_ref().map(Url::from).into_lua(lua)?, - _ => return Ok(Value::Nil), - } - .into_lua(lua) + Composer::make(lua, 5, |lua, key| match key { + b"entries" => lua.create_sequence_from(ARGS.entries.iter().map(Url::from))?.into_lua(lua), + b"cwd_file" => ARGS.cwd_file.as_ref().map(Url::from).into_lua(lua), + b"chooser_file" => ARGS.chooser_file.as_ref().map(Url::from).into_lua(lua), + _ => Ok(Value::Nil), }) } diff --git a/yazi-plugin/src/isolate/entry.rs b/yazi-plugin/src/isolate/entry.rs index e39febea..6a112fbf 100644 --- a/yazi-plugin/src/isolate/entry.rs +++ b/yazi-plugin/src/isolate/entry.rs @@ -7,12 +7,12 @@ use super::slim_lua; use crate::loader::LOADER; pub async fn entry(opt: PluginOpt) -> mlua::Result<()> { - LOADER.ensure(&opt.id).await.into_lua_err()?; + LOADER.ensure(&opt.id, |_| ()).await.into_lua_err()?; tokio::task::spawn_blocking(move || { let lua = slim_lua(&opt.id)?; - let plugin: Table = if let Some(b) = LOADER.read().get(opt.id.as_ref()) { - lua.load(b.as_bytes()).set_name(opt.id.as_ref()).call(())? + let plugin: Table = if let Some(c) = LOADER.read().get(opt.id.as_ref()) { + lua.load(c.as_bytes()).set_name(opt.id.as_ref()).call(())? } else { return Err("unloaded plugin".into_lua_err()); }; diff --git a/yazi-plugin/src/isolate/fetch.rs b/yazi-plugin/src/isolate/fetch.rs index 3a82717b..923b9180 100644 --- a/yazi-plugin/src/isolate/fetch.rs +++ b/yazi-plugin/src/isolate/fetch.rs @@ -13,12 +13,12 @@ pub async fn fetch( if files.is_empty() { return Ok((FetchState::Bool(true), None)); } - LOADER.ensure(&cmd.name).await.into_lua_err()?; + LOADER.ensure(&cmd.name, |_| ()).await.into_lua_err()?; tokio::task::spawn_blocking(move || { let lua = slim_lua(&cmd.name)?; - let plugin: Table = if let Some(b) = LOADER.read().get(&cmd.name) { - lua.load(b.as_bytes()).set_name(&cmd.name).call(())? + let plugin: Table = if let Some(c) = LOADER.read().get(&cmd.name) { + lua.load(c.as_bytes()).set_name(&cmd.name).call(())? } else { return Err("unloaded plugin".into_lua_err()); }; diff --git a/yazi-plugin/src/isolate/peek.rs b/yazi-plugin/src/isolate/peek.rs index 5883d154..c922d3c1 100644 --- a/yazi-plugin/src/isolate/peek.rs +++ b/yazi-plugin/src/isolate/peek.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -use mlua::{ExternalError, ExternalResult, HookTriggers, IntoLua, ObjectLike, Table, VmState}; +use mlua::{ExternalError, HookTriggers, IntoLua, ObjectLike, Table, VmState}; use tokio::{runtime::Handle, select}; use tokio_util::sync::CancellationToken; use tracing::error; @@ -17,61 +17,36 @@ pub fn peek( file: yazi_fs::File, mime: Cow<'static, str>, skip: usize, -) -> CancellationToken { +) -> Option { let ct = CancellationToken::new(); - let (ct1, ct2) = (ct.clone(), ct.clone()); + if let Some(c) = LOADER.read().get(&cmd.name) { + if c.sync_peek { + peek_sync(cmd, file, mime, skip); + } else { + peek_async(cmd, file, mime, skip, ct.clone()); + } + return Some(ct).filter(|_| !c.sync_peek); + } - tokio::task::spawn_blocking(move || { - let future = async { - LOADER.ensure(&cmd.name).await.into_lua_err()?; - - let lua = slim_lua(&cmd.name)?; - lua.set_hook( - HookTriggers::new().on_calls().on_returns().every_nth_instruction(2000), - move |_, dbg| { - if ct1.is_cancelled() && dbg.source().what != "C" { - Err("Peek task cancelled".into_lua_err()) - } else { - Ok(VmState::Continue) - } - }, - ); - - let plugin: Table = if let Some(b) = LOADER.read().get(&cmd.name) { - lua.load(b.as_bytes()).set_name(&cmd.name).call(())? - } else { - return Err("unloaded plugin".into_lua_err()); - }; - - let job = lua.create_table_from([ - ("area", Rect::from(LAYOUT.get().preview).into_lua(&lua)?), - ("args", Sendable::args_to_table_ref(&lua, &cmd.args)?.into_lua(&lua)?), - ("file", File(file).into_lua(&lua)?), - ("mime", mime.into_lua(&lua)?), - ("skip", skip.into_lua(&lua)?), - ])?; - - if ct2.is_cancelled() { Ok(()) } else { plugin.call_async_method("peek", job).await } - }; - - let result = Handle::current().block_on(async { - select! { - _ = ct2.cancelled() => Ok(()), - r = future => r, - } - }); - - if let Err(e) = result { - if !e.to_string().contains("Peek task cancelled") { - error!("{e}"); - } + let ct_ = ct.clone(); + tokio::spawn(async move { + select! { + _ = ct_.cancelled() => {}, + Ok(b) = LOADER.ensure(&cmd.name, |c| c.sync_peek) => { + if b { + peek_sync(cmd, file, mime, skip); + } else { + peek_async(cmd, file, mime, skip, ct_); + } + }, + else => {} } }); - ct + Some(ct) } -pub fn peek_sync(cmd: &'static Cmd, file: yazi_fs::File, mime: Cow<'static, str>, skip: usize) { +fn peek_sync(cmd: &'static Cmd, file: yazi_fs::File, mime: Cow<'static, str>, skip: usize) { let cb: PluginCallback = Box::new(move |lua, plugin| { let job = lua.create_table_from([ ("area", Rect::from(LAYOUT.get().preview).into_lua(lua)?), @@ -86,3 +61,57 @@ pub fn peek_sync(cmd: &'static Cmd, file: yazi_fs::File, mime: Cow<'static, str> AppProxy::plugin(PluginOpt::new_callback(&cmd.name, cb)); } + +fn peek_async( + cmd: &'static Cmd, + file: yazi_fs::File, + mime: Cow<'static, str>, + skip: usize, + ct: CancellationToken, +) { + let ct_ = ct.clone(); + tokio::task::spawn_blocking(move || { + let future = async { + let lua = slim_lua(&cmd.name)?; + lua.set_hook( + HookTriggers::new().on_calls().on_returns().every_nth_instruction(2000), + move |_, dbg| { + if ct.is_cancelled() && dbg.source().what != "C" { + Err("Peek task cancelled".into_lua_err()) + } else { + Ok(VmState::Continue) + } + }, + ); + + let plugin: Table = if let Some(c) = LOADER.read().get(&cmd.name) { + lua.load(c.as_bytes()).set_name(&cmd.name).call(())? + } else { + return Err("unloaded plugin".into_lua_err()); + }; + + let job = lua.create_table_from([ + ("area", Rect::from(LAYOUT.get().preview).into_lua(&lua)?), + ("args", Sendable::args_to_table_ref(&lua, &cmd.args)?.into_lua(&lua)?), + ("file", File(file).into_lua(&lua)?), + ("mime", mime.into_lua(&lua)?), + ("skip", skip.into_lua(&lua)?), + ])?; + + if ct_.is_cancelled() { Ok(()) } else { plugin.call_async_method("peek", job).await } + }; + + let result = Handle::current().block_on(async { + select! { + _ = ct_.cancelled() => Ok(()), + r = future => r, + } + }); + + if let Err(e) = result { + if !e.to_string().contains("Peek task cancelled") { + error!("{e}"); + } + } + }); +} diff --git a/yazi-plugin/src/isolate/preload.rs b/yazi-plugin/src/isolate/preload.rs index 4461a365..c519bc08 100644 --- a/yazi-plugin/src/isolate/preload.rs +++ b/yazi-plugin/src/isolate/preload.rs @@ -11,7 +11,7 @@ pub async fn preload( cmd: &'static Cmd, file: yazi_fs::File, ) -> mlua::Result<(bool, Option)> { - LOADER.ensure(&cmd.name).await.into_lua_err()?; + LOADER.ensure(&cmd.name, |_| ()).await.into_lua_err()?; tokio::task::spawn_blocking(move || { let lua = slim_lua(&cmd.name)?; diff --git a/yazi-plugin/src/isolate/spot.rs b/yazi-plugin/src/isolate/spot.rs index 98c2d5f7..d7a2ba8e 100644 --- a/yazi-plugin/src/isolate/spot.rs +++ b/yazi-plugin/src/isolate/spot.rs @@ -21,7 +21,7 @@ pub fn spot( tokio::task::spawn_blocking(move || { let future = async { - LOADER.ensure(&cmd.name).await.into_lua_err()?; + LOADER.ensure(&cmd.name, |_| ()).await.into_lua_err()?; let lua = slim_lua(&cmd.name)?; lua.set_hook( diff --git a/yazi-plugin/src/loader/chunk.rs b/yazi-plugin/src/loader/chunk.rs index c53c0705..4c0b3718 100644 --- a/yazi-plugin/src/loader/chunk.rs +++ b/yazi-plugin/src/loader/chunk.rs @@ -5,6 +5,7 @@ use yazi_shared::natsort; pub struct Chunk { pub bytes: Cow<'static, [u8]>, pub since: String, + pub sync_peek: bool, pub sync_entry: bool, } @@ -29,6 +30,7 @@ impl Chunk { let Some(i) = rest.iter().position(|&b| b == b' ' || b == b'\t') else { break }; match (rest[..i].trim_ascii(), rest[i..].trim_ascii()) { + (b"@sync", b"peek") => self.sync_peek = true, (b"@sync", b"entry") => self.sync_entry = true, (b"@since", b"") => continue, @@ -44,7 +46,8 @@ impl Chunk { impl From> for Chunk { fn from(b: Cow<'static, [u8]>) -> Self { - let mut chunk = Self { bytes: b, since: String::new(), sync_entry: false }; + let mut chunk = + Self { bytes: b, since: String::new(), sync_entry: false, sync_peek: false }; chunk.analyze(); chunk } diff --git a/yazi-plugin/src/loader/loader.rs b/yazi-plugin/src/loader/loader.rs index d4cbbaae..a1d83c5d 100644 --- a/yazi-plugin/src/loader/loader.rs +++ b/yazi-plugin/src/loader/loader.rs @@ -50,47 +50,23 @@ impl Default for Loader { } impl Loader { - pub async fn ensure(&self, name: &str) -> Result<()> { - if let Some(chunk) = self.cache.read().get(name) { - return Self::compatible_or_error(name, chunk); - } - - // TODO: remove this - fn warn(name: &str) { - static WARNED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false); - if !WARNED.swap(true, std::sync::atomic::Ordering::Relaxed) { - yazi_proxy::AppProxy::notify(yazi_proxy::options::NotifyOpt { - title: "Deprecated entry file".to_owned(), - content: format!( - "The plugin's entry file `init.lua` has been deprecated in favor of the new `main.lua` (user's own `init.lua` remains unchanged). - -Please run `ya pack -m` to automatically migrate all plugins, or manually rename your `{name}.yazi/init.lua` to `{name}.yazi/main.lua`." - ), - level: yazi_proxy::options::NotifyLevel::Warn, - timeout: std::time::Duration::from_secs(25), - }); - } + pub async fn ensure(&self, name: &str, f: F) -> Result + where + F: FnOnce(&Chunk) -> T, + { + if let Some(c) = self.cache.read().get(name) { + return Self::compatible_or_error(name, c).map(|_| f(c)); } let p = BOOT.plugin_dir.join(format!("{name}.yazi/main.lua")); - let chunk = match fs::read(&p).await { - Ok(b) => b, - Err(e) if e.kind() == std::io::ErrorKind::NotFound => { - let p = BOOT.plugin_dir.join(format!("{name}.yazi/init.lua")); - match fs::read(&p).await { - Ok(b) => { - warn(name); - b - } - Err(e) => Err(e).with_context(|| format!("Failed to load plugin from {p:?}"))?, - } - } - Err(e) => Err(e).with_context(|| format!("Failed to load plugin from {p:?}"))?, - }; + let chunk = + fs::read(&p).await.with_context(|| format!("Failed to load plugin from {p:?}"))?.into(); - let mut cache = self.cache.write(); - cache.insert(name.to_owned(), chunk.into()); - Self::compatible_or_error(name, cache.get(name).unwrap()) + let result = Self::compatible_or_error(name, &chunk); + let inspect = f(&chunk); + + self.cache.write().insert(name.to_owned(), chunk); + result.map(|_| inspect) } pub fn load(&self, lua: &Lua, id: &str) -> mlua::Result { diff --git a/yazi-plugin/src/loader/require.rs b/yazi-plugin/src/loader/require.rs index fa410864..a8039eee 100644 --- a/yazi-plugin/src/loader/require.rs +++ b/yazi-plugin/src/loader/require.rs @@ -13,7 +13,7 @@ impl Require { "require", lua.create_function(|lua, id: mlua::String| { let s = &id.to_str()?; - futures::executor::block_on(LOADER.ensure(s)).into_lua_err()?; + futures::executor::block_on(LOADER.ensure(s, |_| ())).into_lua_err()?; lua.named_registry_value::("ir")?.push(s); let mod_ = LOADER.load(lua, s); @@ -29,7 +29,7 @@ impl Require { "require", lua.create_async_function(|lua, id: mlua::String| async move { let s = &id.to_str()?; - LOADER.ensure(s).await.into_lua_err()?; + LOADER.ensure(s, |_| ()).await.into_lua_err()?; lua.named_registry_value::("ir")?.push(s); let mod_ = LOADER.load(&lua, s);