feat: new @since plugin annotation to specify the minimum supported Yazi version (#2290)

This commit is contained in:
三咲雅 · Misaki Masa 2025-02-05 15:55:56 +08:00 committed by GitHub
parent 4e96341f51
commit b236f34d31
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 81 additions and 46 deletions

28
Cargo.lock generated
View file

@ -313,9 +313,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.9.0" version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9"
[[package]] [[package]]
name = "cassowary" name = "cassowary"
@ -334,9 +334,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.10" version = "1.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" checksum = "e4730490333d58093109dc02c23174c3f4d490998c3fed3cc8e82d57afedb9cf"
dependencies = [ dependencies = [
"jobserver", "jobserver",
"libc", "libc",
@ -373,9 +373,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.27" version = "4.5.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796" checksum = "3e77c3243bd94243c03672cb5154667347c457ca271254724f9f393aee1c05ff"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@ -424,9 +424,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.5.24" version = "4.5.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
@ -2458,9 +2458,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.96" version = "2.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2718,9 +2718,9 @@ dependencies = [
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
version = "0.22.22" version = "0.22.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" checksum = "02a8b472d1a3d7c18e2d61a489aee3453fd9031c33e4f55bd533f4a7adca1bee"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"serde", "serde",
@ -3294,9 +3294,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.6.25" version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad699df48212c6cc6eb4435f35500ac6fd3b9913324f938aea302022ce19d310" checksum = "86e376c75f4f43f44db463cf729e0d3acbf954d13e22c51e26e4c264b4ab545f"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]

View file

@ -18,7 +18,7 @@ ansi-to-tui = "7.0.0"
anyhow = "1.0.95" anyhow = "1.0.95"
base64 = "0.22.1" base64 = "0.22.1"
bitflags = "2.8.0" bitflags = "2.8.0"
clap = { version = "4.5.27", features = [ "derive" ] } clap = { version = "4.5.28", features = [ "derive" ] }
core-foundation-sys = "0.8.7" core-foundation-sys = "0.8.7"
crossterm = { version = "0.28.1", features = [ "event-stream" ] } crossterm = { version = "0.28.1", features = [ "event-stream" ] }
dirs = "6.0.0" dirs = "6.0.0"

View file

@ -1,6 +1,6 @@
use std::process; use std::process;
pub(crate) struct Actions; pub struct Actions;
impl Actions { impl Actions {
pub(crate) fn act(args: &crate::Args) { pub(crate) fn act(args: &crate::Args) {

View file

@ -1,7 +1,7 @@
use super::Actions; use super::Actions;
impl Actions { impl Actions {
pub(super) fn version() -> &'static str { pub fn version() -> &'static str {
concat!( concat!(
env!("CARGO_PKG_VERSION"), env!("CARGO_PKG_VERSION"),
" (", " (",

View file

@ -13,5 +13,5 @@ proc-macro = true
[dependencies] [dependencies]
# External dependencies # External dependencies
syn = { version = "2.0.96", features = [ "full" ] } syn = { version = "2.0.98", features = [ "full" ] }
quote = "1.0.38" quote = "1.0.38"

View file

@ -40,7 +40,7 @@ impl Manager {
_ = time::sleep(Duration::from_millis(50)) => { _ = time::sleep(Duration::from_millis(50)) => {
i += 1; i += 1;
if i > 40 { break } if i > 40 { break }
else if ongoing.lock().len() == 0 { else if ongoing.lock().is_empty() {
emit!(Quit(opt)); emit!(Quit(opt));
return; return;
} }

View file

@ -11,8 +11,10 @@ impl Notify {
let instant = Instant::now(); let instant = Instant::now();
msg.timeout += instant - self.messages.first().map_or(instant, |m| m.instant); msg.timeout += instant - self.messages.first().map_or(instant, |m| m.instant);
self.messages.push(msg);
emit!(Call(Cmd::args("update_notify", &[0]), Layer::App)); if self.messages.iter().all(|m| m != &msg) {
self.messages.push(msg);
emit!(Call(Cmd::args("update_notify", &[0]), Layer::App));
}
} }
} }

View file

@ -39,15 +39,16 @@ impl From<NotifyOpt> for Message {
impl Message { impl Message {
#[inline] #[inline]
pub fn height(&self, width: u16) -> usize { pub fn height(&self, width: u16) -> usize {
if width == 0 { let lines = ratatui::widgets::Paragraph::new(self.content.as_str())
return 0; // In case we can't get the width of the terminal .wrap(ratatui::widgets::Wrap { trim: false })
} .line_count(width);
let mut lines = 0;
for line in self.content.lines() {
lines += (line.width() + 1).div_ceil(width as usize)
}
lines + NOTIFY_BORDER as usize lines + NOTIFY_BORDER as usize
} }
} }
impl PartialEq for Message {
fn eq(&self, other: &Self) -> bool {
self.level == other.level && self.content == other.content && self.title == other.title
}
}

View file

@ -4,7 +4,7 @@ use mlua::ObjectLike;
use scopeguard::defer; use scopeguard::defer;
use tracing::{error, warn}; use tracing::{error, warn};
use yazi_dds::Sendable; use yazi_dds::Sendable;
use yazi_plugin::{LUA, RtRefMut, loader::LOADER}; use yazi_plugin::{LUA, RtRefMut, loader::{LOADER, Loader}};
use yazi_proxy::{AppProxy, options::{PluginMode, PluginOpt}}; use yazi_proxy::{AppProxy, options::{PluginMode, PluginOpt}};
use crate::{app::App, lives::Lives}; use crate::{app::App, lives::Lives};
@ -29,8 +29,9 @@ impl App {
} }
tokio::spawn(async move { tokio::spawn(async move {
if LOADER.ensure(&opt.id).await.is_ok() { match LOADER.ensure(&opt.id).await {
AppProxy::plugin_do(opt); Ok(()) => AppProxy::plugin_do(opt),
Err(e) => AppProxy::notify_error("Plugin load failed", e),
} }
}); });
} }
@ -46,6 +47,10 @@ impl App {
return warn!("plugin `{}` not found", opt.id); return warn!("plugin `{}` not found", opt.id);
}; };
if let Err(e) = Loader::compatible_or_error(&opt.id, chunk) {
return AppProxy::notify_error("Incompatible plugin", e);
}
if opt.mode.auto_then(chunk.sync_entry) != PluginMode::Sync { if opt.mode.auto_then(chunk.sync_entry) != PluginMode::Sync {
return self.cx.tasks.plugin_micro(opt); return self.cx.tasks.plugin_micro(opt);
} }
@ -57,7 +62,7 @@ impl App {
defer! { _ = LUA.named_registry_value::<RtRefMut>("rt").map(|mut r| r.pop()) } defer! { _ = LUA.named_registry_value::<RtRefMut>("rt").map(|mut r| r.pop()) }
let plugin = match LOADER.load_with(&LUA, &opt.id, chunk) { let plugin = match LOADER.load_with(&LUA, &opt.id, chunk) {
Ok(plugin) => plugin, Ok(t) => t,
Err(e) => return warn!("{e}"), Err(e) => return warn!("{e}"),
}; };
drop(loader); drop(loader);

View file

@ -240,7 +240,7 @@ mod cursor {
fn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result { fn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result {
let (shape, shape_blink) = match SHAPE.load(Ordering::Relaxed) { let (shape, shape_blink) = match SHAPE.load(Ordering::Relaxed) {
u8::MAX => (0, false), u8::MAX => (0, false),
n => ((n.max(1) + 1) / 2, n.max(1) & 1 == 1), n => (n.max(1).div_ceil(2), n.max(1) & 1 == 1),
}; };
let blink = BLINK.load(Ordering::Relaxed) ^ shape_blink; let blink = BLINK.load(Ordering::Relaxed) ^ shape_blink;

View file

@ -1,7 +1,10 @@
use std::borrow::Cow; use std::borrow::Cow;
use yazi_shared::natsort;
pub struct Chunk { pub struct Chunk {
pub bytes: Cow<'static, [u8]>, pub bytes: Cow<'static, [u8]>,
pub since: String,
pub sync_entry: bool, pub sync_entry: bool,
} }
@ -9,6 +12,12 @@ impl Chunk {
#[inline] #[inline]
pub fn as_bytes(&self) -> &[u8] { &self.bytes } pub fn as_bytes(&self) -> &[u8] { &self.bytes }
#[inline]
pub fn compatible(&self) -> bool {
let s = yazi_boot::actions::Actions::version();
natsort(s.as_bytes(), self.since.as_bytes(), false) != std::cmp::Ordering::Less
}
fn analyze(&mut self) { fn analyze(&mut self) {
for line in self.bytes.split(|&b| b == b'\n') { for line in self.bytes.split(|&b| b == b'\n') {
if line.trim_ascii().is_empty() { if line.trim_ascii().is_empty() {
@ -21,6 +30,10 @@ impl Chunk {
let Some(i) = rest.iter().position(|&b| b == b' ' || b == b'\t') else { break }; let Some(i) = rest.iter().position(|&b| b == b' ' || b == b'\t') else { break };
match (rest[..i].trim_ascii(), rest[i..].trim_ascii()) { match (rest[..i].trim_ascii(), rest[i..].trim_ascii()) {
(b"@sync", b"entry") => self.sync_entry = true, (b"@sync", b"entry") => self.sync_entry = true,
(b"@since", b"") => continue,
(b"@since", b) => self.since = String::from_utf8_lossy(b).to_string(),
(_, []) => break, (_, []) => break,
(b, _) if b.strip_prefix(b"@").unwrap_or(b"").is_empty() => break, (b, _) if b.strip_prefix(b"@").unwrap_or(b"").is_empty() => break,
_ => continue, _ => continue,
@ -31,7 +44,7 @@ impl Chunk {
impl From<Cow<'static, [u8]>> for Chunk { impl From<Cow<'static, [u8]>> for Chunk {
fn from(b: Cow<'static, [u8]>) -> Self { fn from(b: Cow<'static, [u8]>) -> Self {
let mut chunk = Self { bytes: b, sync_entry: false }; let mut chunk = Self { bytes: b, since: String::new(), sync_entry: false };
chunk.analyze(); chunk.analyze();
chunk chunk
} }

View file

@ -1,6 +1,6 @@
use std::{collections::HashMap, ops::Deref}; use std::{collections::HashMap, ops::Deref};
use anyhow::{Context, Result}; use anyhow::{Context, Result, bail};
use mlua::{ExternalError, Lua, Table}; use mlua::{ExternalError, Lua, Table};
use parking_lot::RwLock; use parking_lot::RwLock;
use tokio::fs; use tokio::fs;
@ -51,8 +51,8 @@ impl Default for Loader {
impl Loader { impl Loader {
pub async fn ensure(&self, name: &str) -> Result<()> { pub async fn ensure(&self, name: &str) -> Result<()> {
if self.cache.read().contains_key(name) { if let Some(chunk) = self.cache.read().get(name) {
return Ok(()); return Self::compatible_or_error(name, chunk);
} }
// TODO: remove this // TODO: remove this
@ -80,13 +80,14 @@ Please run `ya pack -m` to automatically migrate all plugins, or manually rename
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:?}"))?,
}; };
self.cache.write().insert(name.to_owned(), chunk.into()); let mut cache = self.cache.write();
Ok(()) cache.insert(name.to_owned(), chunk.into());
Self::compatible_or_error(name, cache.get(name).unwrap())
} }
pub fn load(&self, lua: &Lua, id: &str) -> mlua::Result<Table> { pub fn load(&self, lua: &Lua, id: &str) -> mlua::Result<Table> {
let loaded: Table = lua.globals().raw_get::<Table>("package")?.raw_get("loaded")?; let loaded: Table = lua.globals().raw_get::<Table>("package")?.raw_get("loaded")?;
if let Ok(t) = loaded.raw_get::<Table>(id) { if let Ok(t) = loaded.raw_get(id) {
return Ok(t); return Ok(t);
} }
@ -101,13 +102,12 @@ Please run `ya pack -m` to automatically migrate all plugins, or manually rename
} }
pub fn try_load(&self, lua: &Lua, id: &str) -> mlua::Result<Table> { pub fn try_load(&self, lua: &Lua, id: &str) -> mlua::Result<Table> {
let loaded: Table = lua.globals().raw_get::<Table>("package")?.raw_get("loaded")?; lua.globals().raw_get::<Table>("package")?.raw_get::<Table>("loaded")?.raw_get(id)
loaded.raw_get(id)
} }
pub fn load_with(&self, lua: &Lua, id: &str, chunk: &Chunk) -> mlua::Result<Table> { pub fn load_with(&self, lua: &Lua, id: &str, chunk: &Chunk) -> mlua::Result<Table> {
let loaded: Table = lua.globals().raw_get::<Table>("package")?.raw_get("loaded")?; let loaded: Table = lua.globals().raw_get::<Table>("package")?.raw_get("loaded")?;
if let Ok(t) = loaded.raw_get::<Table>(id) { if let Ok(t) = loaded.raw_get(id) {
return Ok(t); return Ok(t);
} }
@ -117,4 +117,16 @@ Please run `ya pack -m` to automatically migrate all plugins, or manually rename
loaded.raw_set(id, t.clone())?; loaded.raw_set(id, t.clone())?;
Ok(t) Ok(t)
} }
pub fn compatible_or_error(name: &str, chunk: &Chunk) -> Result<()> {
if chunk.compatible() {
return Ok(());
}
bail!(
"Plugin `{name}` requires at least Yazi {}, but your current version is Yazi {}.",
chunk.since,
yazi_boot::actions::Actions::version()
);
}
} }

View file

@ -102,6 +102,8 @@ macro_rules! impl_file_methods {
($methods:ident) => { ($methods:ident) => {
use mlua::UserDataMethods; use mlua::UserDataMethods;
$methods.add_method("hash", |_, me, ()| Ok(me.hash()));
$methods.add_method("icon", |_, me, ()| { $methods.add_method("icon", |_, me, ()| {
use yazi_shared::theme::IconCache; use yazi_shared::theme::IconCache;
use $crate::bindings::Icon; use $crate::bindings::Icon;

View file

@ -42,7 +42,7 @@ impl TryFrom<mlua::Table> for NotifyOpt {
} }
} }
#[derive(Clone, Copy, Default)] #[derive(Clone, Copy, Default, Eq, PartialEq)]
pub enum NotifyLevel { pub enum NotifyLevel {
#[default] #[default]
Info, Info,

View file

@ -31,7 +31,7 @@ impl Plugin {
self.prog.send(TaskProg::New(task.id, 0))?; self.prog.send(TaskProg::New(task.id, 0))?;
if let Err(e) = isolate::entry(task.opt).await { if let Err(e) = isolate::entry(task.opt).await {
self.fail(task.id, format!("Micro plugin failed:\n{e}"))?; self.fail(task.id, format!("Failed to run the plugin:\n{e}"))?;
return Ok(()); return Ok(());
} }