mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-24 16:21:04 +00:00
feat!: new @sync peek annotation for sync previewers (#2487)
This commit is contained in:
parent
324a439c73
commit
8b3ce1838a
18 changed files with 125 additions and 126 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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" ] }
|
||||
|
|
|
|||
|
|
@ -30,13 +30,13 @@ keymap = [
|
|||
{ on = "<PageDown>", 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 = "<Left>", run = "leave", desc = "Go back to the parent directory" },
|
||||
{ on = "<Left>", run = "leave", desc = "Back to the parent directory" },
|
||||
{ on = "<Right>", run = "enter", desc = "Enter the child directory" },
|
||||
|
||||
{ on = "H", run = "back", desc = "Back to previous directory" },
|
||||
|
|
|
|||
|
|
@ -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" },
|
||||
|
|
|
|||
|
|
@ -10,8 +10,6 @@ pub struct Previewer {
|
|||
pub name: Option<Pattern>,
|
||||
pub mime: Option<Pattern>,
|
||||
pub run: Cmd,
|
||||
#[serde(default)]
|
||||
pub sync: bool,
|
||||
}
|
||||
|
||||
impl Previewer {
|
||||
|
|
|
|||
|
|
@ -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<Cha>, force: bool) {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
--- @sync peek
|
||||
|
||||
local M = {}
|
||||
|
||||
function M:peek(job)
|
||||
|
|
|
|||
|
|
@ -29,16 +29,11 @@ impl<'a> Runtime<'a> {
|
|||
}
|
||||
|
||||
fn args(lua: &Lua) -> mlua::Result<Value> {
|
||||
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),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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<CancellationToken> {
|
||||
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}");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ pub async fn preload(
|
|||
cmd: &'static Cmd,
|
||||
file: yazi_fs::File,
|
||||
) -> mlua::Result<(bool, Option<Error>)> {
|
||||
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)?;
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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<Cow<'static, [u8]>> 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<F, T>(&self, name: &str, f: F) -> Result<T>
|
||||
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<Table> {
|
||||
|
|
|
|||
|
|
@ -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::<RtRefMut>("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::<RtRefMut>("ir")?.push(s);
|
||||
let mod_ = LOADER.load(&lua, s);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue