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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -11,8 +11,10 @@ impl Notify {
let instant = Instant::now();
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 {
#[inline]
pub fn height(&self, width: u16) -> usize {
if width == 0 {
return 0; // In case we can't get the width of the terminal
}
let mut lines = 0;
for line in self.content.lines() {
lines += (line.width() + 1).div_ceil(width as usize)
}
let lines = ratatui::widgets::Paragraph::new(self.content.as_str())
.wrap(ratatui::widgets::Wrap { trim: false })
.line_count(width);
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 tracing::{error, warn};
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 crate::{app::App, lives::Lives};
@ -29,8 +29,9 @@ impl App {
}
tokio::spawn(async move {
if LOADER.ensure(&opt.id).await.is_ok() {
AppProxy::plugin_do(opt);
match LOADER.ensure(&opt.id).await {
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);
};
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 {
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()) }
let plugin = match LOADER.load_with(&LUA, &opt.id, chunk) {
Ok(plugin) => plugin,
Ok(t) => t,
Err(e) => return warn!("{e}"),
};
drop(loader);

View file

@ -240,7 +240,7 @@ mod cursor {
fn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result {
let (shape, shape_blink) = match SHAPE.load(Ordering::Relaxed) {
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;

View file

@ -1,7 +1,10 @@
use std::borrow::Cow;
use yazi_shared::natsort;
pub struct Chunk {
pub bytes: Cow<'static, [u8]>,
pub since: String,
pub sync_entry: bool,
}
@ -9,6 +12,12 @@ impl Chunk {
#[inline]
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) {
for line in self.bytes.split(|&b| b == b'\n') {
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 };
match (rest[..i].trim_ascii(), rest[i..].trim_ascii()) {
(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,
(b, _) if b.strip_prefix(b"@").unwrap_or(b"").is_empty() => break,
_ => continue,
@ -31,7 +44,7 @@ impl Chunk {
impl From<Cow<'static, [u8]>> for Chunk {
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
}

View file

@ -1,6 +1,6 @@
use std::{collections::HashMap, ops::Deref};
use anyhow::{Context, Result};
use anyhow::{Context, Result, bail};
use mlua::{ExternalError, Lua, Table};
use parking_lot::RwLock;
use tokio::fs;
@ -51,8 +51,8 @@ impl Default for Loader {
impl Loader {
pub async fn ensure(&self, name: &str) -> Result<()> {
if self.cache.read().contains_key(name) {
return Ok(());
if let Some(chunk) = self.cache.read().get(name) {
return Self::compatible_or_error(name, chunk);
}
// 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:?}"))?,
};
self.cache.write().insert(name.to_owned(), chunk.into());
Ok(())
let mut cache = self.cache.write();
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> {
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);
}
@ -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> {
let loaded: Table = lua.globals().raw_get::<Table>("package")?.raw_get("loaded")?;
loaded.raw_get(id)
lua.globals().raw_get::<Table>("package")?.raw_get::<Table>("loaded")?.raw_get(id)
}
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")?;
if let Ok(t) = loaded.raw_get::<Table>(id) {
if let Ok(t) = loaded.raw_get(id) {
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())?;
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) => {
use mlua::UserDataMethods;
$methods.add_method("hash", |_, me, ()| Ok(me.hash()));
$methods.add_method("icon", |_, me, ()| {
use yazi_shared::theme::IconCache;
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 {
#[default]
Info,

View file

@ -31,7 +31,7 @@ impl Plugin {
self.prog.send(TaskProg::New(task.id, 0))?;
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(());
}