feat: new app:theme action that hot-reload user themes/flavors (#3906)

This commit is contained in:
sxyazi 2026-04-21 23:34:56 +08:00
parent 369c47539c
commit 4a2e5addcd
No known key found for this signature in database
107 changed files with 799 additions and 405 deletions

View file

@ -18,6 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
- New `--in` for `search` action to set search directory ([#3696]) - New `--in` for `search` action to set search directory ([#3696])
- Hover cursor over the new file after copying/cutting/linking/hardlinking/extracting ([#3846], [#3854]) - Hover cursor over the new file after copying/cutting/linking/hardlinking/extracting ([#3846], [#3854])
- Multi-file spotter ([#3733]) - Multi-file spotter ([#3733])
- New `app:theme` action that hot-reload user themes/flavors ([#3906])
- Dynamic open/opener Lua API ([#3901]) - Dynamic open/opener Lua API ([#3901])
- Dynamic previewer Lua API ([#3891]) - Dynamic previewer Lua API ([#3891])
- Vim-like `lua` action that runs an inline Lua snippet ([#3813]) - Vim-like `lua` action that runs an inline Lua snippet ([#3813])
@ -1705,3 +1706,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
[#3891]: https://github.com/sxyazi/yazi/pull/3891 [#3891]: https://github.com/sxyazi/yazi/pull/3891
[#3894]: https://github.com/sxyazi/yazi/pull/3894 [#3894]: https://github.com/sxyazi/yazi/pull/3894
[#3901]: https://github.com/sxyazi/yazi/pull/3901 [#3901]: https://github.com/sxyazi/yazi/pull/3901
[#3906]: https://github.com/sxyazi/yazi/pull/3906

32
Cargo.lock generated
View file

@ -2866,9 +2866,9 @@ dependencies = [
[[package]] [[package]]
name = "p256" name = "p256"
version = "0.14.0-rc.8" version = "0.14.0-rc.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44f0a10fe314869359cb2901342b045f4e5a962ef9febc006f03d2a8c848fe4c" checksum = "8b97e3bf0465157ae90975ff52dbeb1362ba618924878c9f74c25baa27a65f9a"
dependencies = [ dependencies = [
"ecdsa", "ecdsa",
"elliptic-curve", "elliptic-curve",
@ -2879,9 +2879,9 @@ dependencies = [
[[package]] [[package]]
name = "p384" name = "p384"
version = "0.14.0-rc.8" version = "0.14.0-rc.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b079e66810c55ab3d6ba424e056dc4aefcdb8046c8c3f3816142edbdd7af7721" checksum = "437f30ebcb1e16ff48acead5f08bd69fbcdbc82421687bb48af5c315a0bfab03"
dependencies = [ dependencies = [
"ecdsa", "ecdsa",
"elliptic-curve", "elliptic-curve",
@ -2893,9 +2893,9 @@ dependencies = [
[[package]] [[package]]
name = "p521" name = "p521"
version = "0.14.0-rc.8" version = "0.14.0-rc.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9eecc34c4c6e6596d5271fecf90ac4f16593fa198e77282214d0c22736aa9266" checksum = "4e9fd792bab86ecf6249561752fb5a413511f999887107dd054bbda5143743d7"
dependencies = [ dependencies = [
"base16ct", "base16ct",
"ecdsa", "ecdsa",
@ -3795,9 +3795,9 @@ dependencies = [
[[package]] [[package]]
name = "russh" name = "russh"
version = "0.60.0" version = "0.60.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b530252dc3ff163b73a7e48c97b925450d2ca53edcb466a46ad0a231e45f998" checksum = "d937f3f4a79bffd67fc12fd437785effdfc8b94edc89ab90392f9ac9e11cc9fc"
dependencies = [ dependencies = [
"aes 0.8.4", "aes 0.8.4",
"bitflags 2.11.1", "bitflags 2.11.1",
@ -4898,9 +4898,9 @@ checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
[[package]] [[package]]
name = "typenum" name = "typenum"
version = "1.19.0" version = "1.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de"
[[package]] [[package]]
name = "ucd-trie" name = "ucd-trie"
@ -5884,6 +5884,7 @@ dependencies = [
"yazi-fs", "yazi-fs",
"yazi-macro", "yazi-macro",
"yazi-shared", "yazi-shared",
"yazi-shim",
"yazi-vfs", "yazi-vfs",
] ]
@ -6016,6 +6017,7 @@ dependencies = [
"yazi-fs", "yazi-fs",
"yazi-macro", "yazi-macro",
"yazi-shared", "yazi-shared",
"yazi-shim",
] ]
[[package]] [[package]]
@ -6030,6 +6032,7 @@ dependencies = [
"tracing", "tracing",
"yazi-macro", "yazi-macro",
"yazi-shared", "yazi-shared",
"yazi-shim",
"yazi-tty", "yazi-tty",
] ]
@ -6081,6 +6084,7 @@ dependencies = [
"yazi-runner", "yazi-runner",
"yazi-scheduler", "yazi-scheduler",
"yazi-shared", "yazi-shared",
"yazi-shim",
"yazi-term", "yazi-term",
"yazi-tty", "yazi-tty",
"yazi-vfs", "yazi-vfs",
@ -6234,6 +6238,7 @@ dependencies = [
"yazi-fs", "yazi-fs",
"yazi-macro", "yazi-macro",
"yazi-shared", "yazi-shared",
"yazi-shim",
] ]
[[package]] [[package]]
@ -6301,6 +6306,7 @@ dependencies = [
"uzers", "uzers",
"windows-sys 0.61.2", "windows-sys 0.61.2",
"yazi-macro", "yazi-macro",
"yazi-shim",
] ]
[[package]] [[package]]
@ -6309,6 +6315,7 @@ version = "26.2.2"
dependencies = [ dependencies = [
"arc-swap", "arc-swap",
"crossterm 0.29.0", "crossterm 0.29.0",
"hashbrown 0.17.0",
"mlua", "mlua",
"ratatui", "ratatui",
"serde", "serde",
@ -6331,7 +6338,6 @@ dependencies = [
"yazi-config", "yazi-config",
"yazi-emulator", "yazi-emulator",
"yazi-macro", "yazi-macro",
"yazi-shared",
"yazi-shim", "yazi-shim",
"yazi-tty", "yazi-tty",
] ]
@ -6346,6 +6352,7 @@ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.61.2",
"yazi-macro", "yazi-macro",
"yazi-shared", "yazi-shared",
"yazi-shim",
] ]
[[package]] [[package]]
@ -6368,6 +6375,7 @@ dependencies = [
"yazi-macro", "yazi-macro",
"yazi-sftp", "yazi-sftp",
"yazi-shared", "yazi-shared",
"yazi-shim",
] ]
[[package]] [[package]]
@ -6387,6 +6395,7 @@ dependencies = [
"yazi-fs", "yazi-fs",
"yazi-macro", "yazi-macro",
"yazi-shared", "yazi-shared",
"yazi-shim",
"yazi-vfs", "yazi-vfs",
] ]
@ -6409,6 +6418,7 @@ dependencies = [
"yazi-config", "yazi-config",
"yazi-macro", "yazi-macro",
"yazi-shared", "yazi-shared",
"yazi-shim",
"yazi-tty", "yazi-tty",
] ]

View file

@ -10,7 +10,7 @@ license = "MIT"
authors = [ "sxyazi <sxyazi@gmail.com>" ] authors = [ "sxyazi <sxyazi@gmail.com>" ]
homepage = "https://yazi-rs.github.io" homepage = "https://yazi-rs.github.io"
repository = "https://github.com/sxyazi/yazi" repository = "https://github.com/sxyazi/yazi"
rust-version = "1.92.0" rust-version = "1.95.0"
[profile.dev] [profile.dev]
debug = "line-tables-only" debug = "line-tables-only"
@ -63,7 +63,7 @@ percent-encoding = "2.3.2"
rand = { version = "0.10.1", default-features = false, features = [ "std", "sys_rng" ] } rand = { version = "0.10.1", default-features = false, features = [ "std", "sys_rng" ] }
ratatui = { version = "0.30.0", features = [ "serde", "unstable-rendered-line-info", "unstable-widget-ref" ] } ratatui = { version = "0.30.0", features = [ "serde", "unstable-rendered-line-info", "unstable-widget-ref" ] }
regex = "1.12.3" regex = "1.12.3"
russh = { version = "0.60.0", default-features = false, features = [ "ring", "rsa" ] } russh = { version = "0.60.1", default-features = false, features = [ "ring", "rsa" ] }
scopeguard = "1.2.0" scopeguard = "1.2.0"
serde = { version = "1.0.228", features = [ "derive" ] } serde = { version = "1.0.228", features = [ "derive" ] }
serde_json = "1.0.149" serde_json = "1.0.149"

12
flake.lock generated
View file

@ -20,11 +20,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1772419343, "lastModified": 1776329215,
"narHash": "sha256-QU3Cd5DJH7dHyMnGEFfPcZDaCAsJQ6tUD+JuUsYqnKU=", "narHash": "sha256-a8BYi3mzoJ/AcJP8UldOx8emoPRLeWqALZWu4ZvjPXw=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "93178f6a00c22fcdee1c6f5f9ab92f2072072ea9", "rev": "b86751bc4085f48661017fa226dee99fab6c651b",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -48,11 +48,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1772420823, "lastModified": 1776741231,
"narHash": "sha256-q3oVwz1Rx41D1D+F6vg41kpOkk3Zi3KwnkHEZp7DCGs=", "narHash": "sha256-k9G98qzn+7npROUaks8VqCFm7cFtEG8ulQLBBo5lItg=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "458eea8d905c609e9d889423e6b8a1c7bc2f792c", "rev": "02061303f7c4c964f7b4584dabd9e985b4cd442b",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

@ -12,6 +12,7 @@ yazi_macro::mod_flat!(
resize resize
resume resume
stop stop
theme
title title
update_progress update_progress
); );

View file

@ -0,0 +1,25 @@
use anyhow::Result;
use yazi_actor::Ctx;
use yazi_config::{THEME, build_flavor};
use yazi_emulator::EMULATOR;
use yazi_macro::{render, succ};
use yazi_parser::VoidForm;
use yazi_shared::data::Data;
use yazi_shim::serde::Overlay;
use crate::Actor;
pub struct Theme;
impl Actor for Theme {
type Form = VoidForm;
const NAME: &str = "theme";
fn act(_cx: &mut Ctx, _: Self::Form) -> Result<Data> {
THEME.overlay(build_flavor(EMULATOR.light, true)?);
yazi_plugin::theme::reset()?;
succ!(render!());
}
}

View file

@ -2,7 +2,7 @@ use std::{ops::Deref, ptr};
use mlua::{AnyUserData, IntoLua, UserData, UserDataFields, UserDataMethods, Value}; use mlua::{AnyUserData, IntoLua, UserData, UserDataFields, UserDataMethods, Value};
use yazi_binding::{Range, Style, cached_field}; use yazi_binding::{Range, Style, cached_field};
use yazi_config::{Selectable, THEME}; use yazi_config::THEME;
use yazi_shared::{path::AsPath, url::UrlLike}; use yazi_shared::{path::AsPath, url::UrlLike};
use super::Lives; use super::Lives;
@ -111,7 +111,7 @@ impl UserData for File {
methods.add_method("style", |lua, me, ()| { methods.add_method("style", |lua, me, ()| {
lua.named_registry_value::<AnyUserData>("cx")?.borrow_scoped(|core: &yazi_core::Core| { lua.named_registry_value::<AnyUserData>("cx")?.borrow_scoped(|core: &yazi_core::Core| {
let mime = core.mgr.mimetype.get(&me.url).unwrap_or_default(); let mime = core.mgr.mimetype.get(&me.url).unwrap_or_default();
THEME.filetype.iter().find(|&x| x.matches(me, mime)).map(|x| Style::from(x.style)) THEME.filetype.match_style(me, mime).map(Style::from)
}) })
}); });
methods.add_method("is_yanked", |lua, me, ()| { methods.add_method("is_yanked", |lua, me, ()| {

View file

@ -5,7 +5,7 @@ use mlua::{AnyUserData, UserData};
use scopeguard::defer; use scopeguard::defer;
use tracing::error; use tracing::error;
use yazi_plugin::LUA; use yazi_plugin::LUA;
use yazi_shared::RoCell; use yazi_shim::cell::RoCell;
use super::{Core, PtrCell}; use super::{Core, PtrCell};

View file

@ -38,7 +38,7 @@ impl UserData for Preference {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) { fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
// Display // Display
cached_field!(fields, name, |lua, me| lua.create_string(&me.name)); cached_field!(fields, name, |lua, me| lua.create_string(&me.name));
cached_field!(fields, linemode, |lua, me| lua.create_string(&me.linemode)); cached_field!(fields, linemode, |lua, me| lua.create_string(&*me.linemode));
fields.add_field_method_get("show_hidden", |_, me| Ok(me.show_hidden)); fields.add_field_method_get("show_hidden", |_, me| Ok(me.show_hidden));
// Sorting // Sorting

View file

@ -15,8 +15,8 @@ impl Actor for Linemode {
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> { fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
let tab = cx.tab_mut(); let tab = cx.tab_mut();
if form.new != tab.pref.linemode { if form.new != *tab.pref.linemode {
tab.pref.linemode = form.new.into_owned(); tab.pref.linemode = form.new.into();
render!(); render!();
} }

View file

@ -7,7 +7,7 @@ use crossterm::{cursor::MoveTo, queue};
use image::DynamicImage; use image::DynamicImage;
use ratatui::layout::Rect; use ratatui::layout::Rect;
use yazi_emulator::{CLOSE, ESCAPE, Emulator, START}; use yazi_emulator::{CLOSE, ESCAPE, Emulator, START};
use yazi_shared::SyncCell; use yazi_shim::cell::SyncCell;
use crate::{adapter::Adapter, image::Image}; use crate::{adapter::Adapter, image::Image};

View file

@ -7,8 +7,8 @@ use tokio::{io::AsyncWriteExt, process::{Child, Command}, sync::mpsc::{self, Unb
use tracing::{debug, warn}; use tracing::{debug, warn};
use yazi_config::YAZI; use yazi_config::YAZI;
use yazi_emulator::Dimension; use yazi_emulator::Dimension;
use yazi_shared::{LOG_LEVEL, RoCell, env_exists}; use yazi_shared::{LOG_LEVEL, env_exists};
use yazi_shim::strum::IntoStr; use yazi_shim::{cell::RoCell, strum::IntoStr};
use crate::Adapter; use crate::Adapter;

View file

@ -3,7 +3,8 @@ yazi_macro::mod_pub!(drivers);
yazi_macro::mod_flat!(adapter adapters icc image info); yazi_macro::mod_flat!(adapter adapters icc image info);
use yazi_emulator::{Brand, CLOSE, EMULATOR, ESCAPE, Emulator, Mux, START, TMUX}; use yazi_emulator::{Brand, CLOSE, EMULATOR, ESCAPE, Emulator, Mux, START, TMUX};
use yazi_shared::{SyncCell, in_wsl}; use yazi_shared::in_wsl;
use yazi_shim::cell::SyncCell;
pub static ADAPTOR: SyncCell<Adapter> = SyncCell::new(Adapter::Chafa); pub static ADAPTOR: SyncCell<Adapter> = SyncCell::new(Adapter::Chafa);

View file

@ -1,31 +1,30 @@
use std::ops::Deref; use std::ops::Deref;
use mlua::{UserData, UserDataFields, Value}; use mlua::{IntoLua, Lua, Value};
use crate::{Style, cached_field}; use crate::Style;
pub struct Icon { pub struct Icon {
inner: &'static yazi_config::Icon, inner: yazi_config::Icon,
v_text: Option<Value>,
v_style: Option<Value>,
} }
impl Deref for Icon { impl Deref for Icon {
type Target = yazi_config::Icon; type Target = yazi_config::Icon;
fn deref(&self) -> &Self::Target { self.inner } fn deref(&self) -> &Self::Target { &self.inner }
} }
impl From<&'static yazi_config::Icon> for Icon { impl From<yazi_config::Icon> for Icon {
fn from(icon: &'static yazi_config::Icon) -> Self { fn from(inner: yazi_config::Icon) -> Self { Self { inner } }
Self { inner: icon, v_text: None, v_style: None }
}
} }
impl UserData for Icon { impl IntoLua for Icon {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) { fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
cached_field!(fields, text, |lua, me| lua.create_string(&me.text)); lua
cached_field!(fields, style, |_, me| Ok(Style::from(me.style))); .create_table_from([
("text", self.inner.text.into_lua(lua)?),
("style", Style::from(self.inner.style).into_lua(lua)?),
])?
.into_lua(lua)
} }
} }

View file

@ -2,7 +2,7 @@ use std::ops::Deref;
use mlua::{ExternalError, FromLua, IntoLua, Lua, LuaSerdeExt, MetaMethod, Table, UserData, UserDataMethods, Value}; use mlua::{ExternalError, FromLua, IntoLua, Lua, LuaSerdeExt, MetaMethod, Table, UserData, UserDataMethods, Value};
use ratatui::style::Modifier; use ratatui::style::Modifier;
use yazi_shared::SyncCell; use yazi_shim::cell::SyncCell;
use crate::{SER_OPT, elements::Color}; use crate::{SER_OPT, elements::Color};

View file

@ -19,6 +19,7 @@ yazi-emulator = { path = "../yazi-emulator", version = "26.2.2" }
yazi-fs = { path = "../yazi-fs", version = "26.2.2" } yazi-fs = { path = "../yazi-fs", version = "26.2.2" }
yazi-macro = { path = "../yazi-macro", version = "26.2.2" } yazi-macro = { path = "../yazi-macro", version = "26.2.2" }
yazi-shared = { path = "../yazi-shared", version = "26.2.2" } yazi-shared = { path = "../yazi-shared", version = "26.2.2" }
yazi-shim = { path = "../yazi-shim", version = "26.2.2" }
yazi-vfs = { path = "../yazi-vfs", version = "26.2.2" } yazi-vfs = { path = "../yazi-vfs", version = "26.2.2" }
# External dependencies # External dependencies

View file

@ -18,9 +18,14 @@ fn main() -> Result<(), Box<dyn Error>> {
.semver(true) .semver(true)
.build()?, .build()?,
)? )?
.add_instructions(&GitclBuilder::default().commit_date(true).sha(true).build()?)?
.emit()?; .emit()?;
if env::var_os("YAZI_NO_GITCL").is_none() {
Emitter::default().add_instructions(&GitclBuilder::default().sha(true).build()?)?.emit()?;
} else {
println!("cargo:rustc-env=VERGEN_GIT_SHA=no-gitcl");
}
if env::var_os("YAZI_GEN_COMPLETIONS").is_none() { if env::var_os("YAZI_GEN_COMPLETIONS").is_none() {
return Ok(()); return Ok(());
} }

View file

@ -3,7 +3,7 @@ yazi_macro::mod_pub!(actions);
yazi_macro::mod_flat!(args boot); yazi_macro::mod_flat!(args boot);
use clap::Parser; use clap::Parser;
use yazi_shared::RoCell; use yazi_shim::cell::RoCell;
pub static ARGS: RoCell<Args> = RoCell::new(); pub static ARGS: RoCell<Args> = RoCell::new();
pub static BOOT: RoCell<Boot> = RoCell::new(); pub static BOOT: RoCell<Boot> = RoCell::new();

View file

@ -24,9 +24,14 @@ fn main() -> Result<(), Box<dyn Error>> {
fn generate() -> Result<(), Box<dyn Error>> { fn generate() -> Result<(), Box<dyn Error>> {
Emitter::default() Emitter::default()
.add_instructions(&BuildBuilder::default().build_date(true).build()?)? .add_instructions(&BuildBuilder::default().build_date(true).build()?)?
.add_instructions(&GitclBuilder::default().commit_date(true).sha(true).build()?)?
.emit()?; .emit()?;
if env::var_os("YAZI_NO_GITCL").is_none() {
Emitter::default().add_instructions(&GitclBuilder::default().sha(true).build()?)?.emit()?;
} else {
println!("cargo:rustc-env=VERGEN_GIT_SHA=no-gitcl");
}
if env::var_os("YAZI_GEN_COMPLETIONS").is_none() { if env::var_os("YAZI_GEN_COMPLETIONS").is_none() {
return Ok(()); return Ok(());
} }

View file

@ -1,7 +1,7 @@
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::quote; use quote::quote;
mod helper; mod helper;
use syn::{DeriveInput, parse_macro_input}; use syn::{Data, DeriveInput, Fields, parse_macro_input};
use crate::helper::{generics_with_de, has_serde_attr, ident_name, named_fields}; use crate::helper::{generics_with_de, has_serde_attr, ident_name, named_fields};
@ -153,6 +153,41 @@ pub fn deserialize_over2(input: TokenStream) -> TokenStream {
.into() .into()
} }
#[proc_macro_derive(Overlay)]
pub fn overlay(input: TokenStream) -> TokenStream {
let DeriveInput { ident, generics, data, .. } = parse_macro_input!(input as DeriveInput);
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let stmts: Vec<_> = match data {
Data::Struct(s) => match s.fields {
Fields::Named(fields) => fields
.named
.into_iter()
.map(|f| {
let field_ident = f.ident;
quote! { self.#field_ident.overlay(new.#field_ident); }
})
.collect(),
Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
vec![quote! { self.0.overlay(new.0); }]
}
_ => panic!("expected named fields or a single-field tuple struct"),
},
_ => panic!("expected struct"),
};
quote! {
impl #impl_generics yazi_shim::serde::Overlay for #ident #ty_generics #where_clause {
fn overlay(&self, new: Self) {
use yazi_shim::serde::Overlay;
#(#stmts)*
}
}
}
.into()
}
#[proc_macro_derive(FromLuaOwned)] #[proc_macro_derive(FromLuaOwned)]
pub fn from_lua(input: TokenStream) -> TokenStream { pub fn from_lua(input: TokenStream) -> TokenStream {
let DeriveInput { ident, generics, .. } = parse_macro_input!(input as DeriveInput); let DeriveInput { ident, generics, .. } = parse_macro_input!(input as DeriveInput);

View file

@ -82,9 +82,6 @@ where
D: Deserializer<'de>, D: Deserializer<'de>,
{ {
let mut actions: Vec<Action> = OneOrMany::<DisplayFromStr>::deserialize_as(deserializer)?; let mut actions: Vec<Action> = OneOrMany::<DisplayFromStr>::deserialize_as(deserializer)?;
if actions.is_empty() {
return Err(de::Error::custom("'run' cannot be empty"));
}
let Some(layer) = Layer::from_repr(L) else { let Some(layer) = Layer::from_repr(L) else {
return Err(de::Error::custom(format!("invalid keymap layer const: {L}"))); return Err(de::Error::custom(format!("invalid keymap layer const: {L}")));
@ -92,7 +89,7 @@ where
for action in &mut actions { for action in &mut actions {
action.source = Source::Key; action.source = Source::Key;
if action.layer == Default::default() { if action.layer == Layer::Null {
action.layer = layer; action.layer = layer;
} }
} }

View file

@ -21,7 +21,7 @@ pub struct Keymap {
impl Keymap { impl Keymap {
pub fn get(&self, layer: Layer) -> &[Chord] { pub fn get(&self, layer: Layer) -> &[Chord] {
match layer { match layer {
Layer::App => &[], Layer::Null | Layer::App => &[],
Layer::Mgr => self.mgr.as_erased_slice(), Layer::Mgr => self.mgr.as_erased_slice(),
Layer::Tasks => self.tasks.as_erased_slice(), Layer::Tasks => self.tasks.as_erased_slice(),
Layer::Spot => self.spot.as_erased_slice(), Layer::Spot => self.spot.as_erased_slice(),

View file

@ -4,8 +4,7 @@ yazi_macro::mod_flat!(icon layout mixing pattern platform preset priority select
use std::io::{Read, Write}; use std::io::{Read, Write};
use yazi_shared::{RoCell, SyncCell}; use yazi_shim::{cell::{RoCell, SyncCell}, toml::{DeserializeOver, DeserializeOverWith}};
use yazi_shim::toml::{DeserializeOver, DeserializeOverWith};
use yazi_tty::TTY; use yazi_tty::TTY;
pub static YAZI: RoCell<yazi::Yazi> = RoCell::new(); pub static YAZI: RoCell<yazi::Yazi> = RoCell::new();
@ -44,6 +43,11 @@ pub fn init_flavor(light: bool) -> anyhow::Result<()> {
} }
fn try_init_flavor(light: bool, merge: bool) -> anyhow::Result<()> { fn try_init_flavor(light: bool, merge: bool) -> anyhow::Result<()> {
THEME.init(build_flavor(light, merge)?);
Ok(())
}
pub fn build_flavor(light: bool, merge: bool) -> anyhow::Result<theme::Theme> {
let mut preset = Preset::theme(light)?; let mut preset = Preset::theme(light)?;
if merge { if merge {
@ -59,8 +63,7 @@ fn try_init_flavor(light: bool, merge: bool) -> anyhow::Result<()> {
)?; )?;
} }
THEME.init(preset.reshape(light)?); Ok(preset.reshape(light)?)
Ok(())
} }
fn wait_for_key(e: anyhow::Error) -> anyhow::Result<()> { fn wait_for_key(e: anyhow::Error) -> anyhow::Result<()> {

View file

@ -1,23 +0,0 @@
use std::ops::Deref;
use serde::{Deserialize, Deserializer, de};
#[derive(Debug)]
pub struct MgrLinemode(String);
impl Deref for MgrLinemode {
type Target = String;
fn deref(&self) -> &Self::Target { &self.0 }
}
impl<'de> Deserialize<'de> for MgrLinemode {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
if s.is_empty() || s.len() > 20 {
return Err(de::Error::custom("linemode must be between 1 and 20 characters."));
}
Ok(Self(s))
}
}

View file

@ -1,11 +1,11 @@
use anyhow::Result; use anyhow::Result;
use serde::Deserialize; use arc_swap::ArcSwap;
use serde::{Deserialize, Deserializer, de};
use yazi_codegen::{DeserializeOver, DeserializeOver2}; use yazi_codegen::{DeserializeOver, DeserializeOver2};
use yazi_fs::{SortBy, SortFallback}; use yazi_fs::{SortBy, SortFallback};
use yazi_shared::SyncCell; use yazi_shim::{arc_swap::IntoPointee, cell::SyncCell};
use super::{MgrRatio, MouseEvents}; use super::{MgrRatio, MouseEvents};
use crate::mgr::MgrLinemode;
#[derive(Debug, Deserialize, DeserializeOver, DeserializeOver2)] #[derive(Debug, Deserialize, DeserializeOver, DeserializeOver2)]
pub struct Mgr { pub struct Mgr {
@ -20,9 +20,22 @@ pub struct Mgr {
pub sort_fallback: SyncCell<SortFallback>, pub sort_fallback: SyncCell<SortFallback>,
// Display // Display
pub linemode: MgrLinemode, #[serde(deserialize_with = "deserialize_linemode")]
pub linemode: ArcSwap<String>,
pub show_hidden: SyncCell<bool>, pub show_hidden: SyncCell<bool>,
pub show_symlink: SyncCell<bool>, pub show_symlink: SyncCell<bool>,
pub scrolloff: SyncCell<u8>, pub scrolloff: SyncCell<u8>,
pub mouse_events: SyncCell<MouseEvents>, pub mouse_events: SyncCell<MouseEvents>,
} }
fn deserialize_linemode<'de, D>(deserializer: D) -> Result<ArcSwap<String>, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
if s.is_empty() || s.len() > 20 {
return Err(de::Error::custom("linemode must be between 1 and 20 characters."));
}
Ok(s.into_pointee())
}

View file

@ -1 +1 @@
yazi_macro::mod_flat!(linemode mgr mouse ratio); yazi_macro::mod_flat!(mgr mouse ratio);

View file

@ -2,7 +2,7 @@ use std::{mem, ops::Deref, sync::Arc};
use arc_swap::ArcSwap; use arc_swap::ArcSwap;
use hashbrown::HashMap; use hashbrown::HashMap;
use serde::{Deserialize, Deserializer, de::{MapAccess, Visitor}}; use serde::{Deserialize, Deserializer};
use yazi_shim::{arc_swap::IntoPointee, toml::{DeserializeOverHook, DeserializeOverWith}}; use yazi_shim::{arc_swap::IntoPointee, toml::{DeserializeOverHook, DeserializeOverWith}};
use super::{OpenerRule, OpenerRules}; use super::{OpenerRule, OpenerRules};
@ -80,23 +80,6 @@ impl DeserializeOverHook for Opener {
impl DeserializeOverWith for Opener { impl DeserializeOverWith for Opener {
fn deserialize_over_with<'de, D: Deserializer<'de>>(self, de: D) -> Result<Self, D::Error> { fn deserialize_over_with<'de, D: Deserializer<'de>>(self, de: D) -> Result<Self, D::Error> {
struct V(HashMap<String, Arc<OpenerRules>>); Ok(Self(self.0.deserialize_over_with(de)?))
impl<'de> Visitor<'de> for V {
type Value = Opener;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.write_str("a map of opener rules")
}
fn visit_map<M: MapAccess<'de>>(mut self, mut map: M) -> Result<Self::Value, M::Error> {
while let Some(key) = map.next_key()? {
self.0.insert(key, map.next_value()?);
}
Ok(Opener(self.0.into_pointee()))
}
}
de.deserialize_map(V(self.unwrap_unchecked()))
} }
} }

View file

@ -3,7 +3,7 @@ use std::{borrow::Cow, ops::Deref, sync::Arc};
use hashbrown::HashSet; use hashbrown::HashSet;
use serde::Deserialize; use serde::Deserialize;
use yazi_fs::File; use yazi_fs::File;
use yazi_shared::{Id, event::Action}; use yazi_shared::{Id, event::Cmd};
use crate::{Mixable, Pattern, Priority, Selectable, Selector, plugin::{Fetchers, fetcher_id}}; use crate::{Mixable, Pattern, Priority, Selectable, Selector, plugin::{Fetchers, fetcher_id}};
@ -15,14 +15,14 @@ pub struct Fetcher {
pub idx: u8, pub idx: u8,
#[serde(flatten)] #[serde(flatten)]
pub selector: Selector, pub selector: Selector,
pub run: Action, pub run: Cmd,
#[serde(default)] #[serde(default)]
pub prio: Priority, pub prio: Priority,
pub group: String, pub group: String,
} }
impl Deref for Fetcher { impl Deref for Fetcher {
type Target = Action; type Target = Cmd;
fn deref(&self) -> &Self::Target { &self.run } fn deref(&self) -> &Self::Target { &self.run }
} }

View file

@ -2,7 +2,7 @@ use std::{borrow::Cow, ops::Deref, sync::Arc};
use serde::Deserialize; use serde::Deserialize;
use yazi_fs::File; use yazi_fs::File;
use yazi_shared::{Id, event::Action}; use yazi_shared::{Id, event::Cmd};
use crate::{Mixable, Pattern, Priority, Selectable, Selector, plugin::{Preloaders, preloader_id}}; use crate::{Mixable, Pattern, Priority, Selectable, Selector, plugin::{Preloaders, preloader_id}};
@ -14,7 +14,7 @@ pub struct Preloader {
pub idx: u8, pub idx: u8,
#[serde(flatten)] #[serde(flatten)]
pub selector: Selector, pub selector: Selector,
pub run: Action, pub run: Cmd,
#[serde(default)] #[serde(default)]
pub next: bool, pub next: bool,
#[serde(default)] #[serde(default)]
@ -22,7 +22,7 @@ pub struct Preloader {
} }
impl Deref for Preloader { impl Deref for Preloader {
type Target = Action; type Target = Cmd;
fn deref(&self) -> &Self::Target { &self.run } fn deref(&self) -> &Self::Target { &self.run }
} }

View file

@ -2,7 +2,7 @@ use std::{borrow::Cow, ops::Deref, sync::Arc};
use serde::Deserialize; use serde::Deserialize;
use yazi_fs::File; use yazi_fs::File;
use yazi_shared::{Id, event::Action}; use yazi_shared::{Id, event::Cmd};
use crate::{Mixable, Pattern, Selectable, Selector, plugin::{Previewers, previewer_id}}; use crate::{Mixable, Pattern, Selectable, Selector, plugin::{Previewers, previewer_id}};
@ -12,11 +12,11 @@ pub struct Previewer {
pub id: Id, pub id: Id,
#[serde(flatten)] #[serde(flatten)]
pub selector: Selector, pub selector: Selector,
pub run: Action, pub run: Cmd,
} }
impl Deref for Previewer { impl Deref for Previewer {
type Target = Action; type Target = Cmd;
#[inline] #[inline]
fn deref(&self) -> &Self::Target { &self.run } fn deref(&self) -> &Self::Target { &self.run }

View file

@ -2,7 +2,7 @@ use std::{borrow::Cow, ops::Deref, sync::Arc};
use serde::Deserialize; use serde::Deserialize;
use yazi_fs::File; use yazi_fs::File;
use yazi_shared::{Id, event::Action}; use yazi_shared::{Id, event::Cmd};
use crate::{Mixable, Pattern, Selectable, Selector, plugin::{Spotters, spotter_id}}; use crate::{Mixable, Pattern, Selectable, Selector, plugin::{Spotters, spotter_id}};
@ -12,11 +12,11 @@ pub struct Spotter {
pub id: Id, pub id: Id,
#[serde(flatten)] #[serde(flatten)]
pub selector: Selector, pub selector: Selector,
pub run: Action, pub run: Cmd,
} }
impl Deref for Spotter { impl Deref for Spotter {
type Target = Action; type Target = Cmd;
#[inline] #[inline]
fn deref(&self) -> &Self::Target { &self.run } fn deref(&self) -> &Self::Target { &self.run }

View file

@ -1,43 +1,17 @@
use std::ops::Deref;
use serde::Deserialize; use serde::Deserialize;
use yazi_codegen::{DeserializeOver, DeserializeOver2}; use yazi_codegen::{DeserializeOver, DeserializeOver2, Overlay};
use yazi_fs::File; use yazi_fs::File;
use super::Is; use super::FiletypeRules;
use crate::{Pattern, Selectable, Selector, Style}; use crate::{Selectable, Style};
#[derive(Deserialize, DeserializeOver, DeserializeOver2)] #[derive(Deserialize, DeserializeOver, DeserializeOver2, Overlay)]
pub struct Filetype { pub struct Filetype {
rules: Vec<FiletypeRule>, rules: FiletypeRules,
} }
impl Deref for Filetype { impl Filetype {
type Target = Vec<FiletypeRule>; pub fn match_style(&self, file: &File, mime: &str) -> Option<Style> {
self.rules.load().iter().find(|rule| rule.matches(file, mime)).map(|rule| rule.style)
fn deref(&self) -> &Self::Target { &self.rules }
}
#[derive(Deserialize)]
pub struct FiletypeRule {
#[serde(default)]
is: Is,
#[serde(flatten)]
selector: Selector,
#[serde(flatten)]
pub style: Style,
}
impl Selectable for FiletypeRule {
fn url_pat(&self) -> Option<&Pattern> { self.selector.url_pat() }
fn mime_pat(&self) -> Option<&Pattern> { self.selector.mime_pat() }
fn match_with(&self, file: Option<&File>, mime: Option<&str>) -> bool {
match (self.is.enabled(), file) {
(Some(is), Some(f)) if !is.check(&f.cha) => false,
(Some(_), None) => false,
_ => self.selector.match_with(file, mime),
}
} }
} }

View file

@ -0,0 +1,27 @@
use serde::Deserialize;
use crate::{Pattern, Selectable};
#[derive(Deserialize)]
pub struct FiletypeRule {
#[serde(default)]
is: super::Is,
#[serde(flatten)]
selector: crate::Selector,
#[serde(flatten)]
pub style: crate::Style,
}
impl Selectable for FiletypeRule {
fn url_pat(&self) -> Option<&Pattern> { self.selector.url_pat() }
fn mime_pat(&self) -> Option<&Pattern> { self.selector.mime_pat() }
fn match_with(&self, file: Option<&yazi_fs::File>, mime: Option<&str>) -> bool {
match (self.is.enabled(), file) {
(Some(is), Some(f)) if !is.check(&f.cha) => false,
(Some(_), None) => false,
_ => self.selector.match_with(file, mime),
}
}
}

View file

@ -0,0 +1,21 @@
use std::ops::Deref;
use arc_swap::ArcSwap;
use serde::Deserialize;
use yazi_codegen::Overlay;
use yazi_shim::arc_swap::IntoPointee;
use crate::theme::FiletypeRule;
#[derive(Default, Deserialize, Overlay)]
pub struct FiletypeRules(ArcSwap<Vec<FiletypeRule>>);
impl Deref for FiletypeRules {
type Target = ArcSwap<Vec<FiletypeRule>>;
fn deref(&self) -> &Self::Target { &self.0 }
}
impl From<Vec<FiletypeRule>> for FiletypeRules {
fn from(inner: Vec<FiletypeRule>) -> Self { Self(inner.into_pointee()) }
}

View file

@ -4,13 +4,13 @@ use anyhow::{Context, Result};
use arc_swap::ArcSwap; use arc_swap::ArcSwap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use toml::{Spanned, de::DeTable}; use toml::{Spanned, de::DeTable};
use yazi_codegen::{DeserializeOver, DeserializeOver2}; use yazi_codegen::{DeserializeOver, DeserializeOver2, Overlay};
use yazi_fs::Xdg; use yazi_fs::Xdg;
use yazi_shim::toml::deserialize_spanned; use yazi_shim::toml::deserialize_spanned;
use crate::error_with_input; use crate::error_with_input;
#[derive(Default, Deserialize, DeserializeOver, DeserializeOver2, Serialize)] #[derive(Default, Deserialize, DeserializeOver, DeserializeOver2, Overlay, Serialize)]
pub struct Flavor { pub struct Flavor {
#[serde(default)] #[serde(default)]
pub dark: ArcSwap<String>, pub dark: ArcSwap<String>,

View file

@ -1,43 +1,41 @@
use anyhow::Result; use anyhow::Result;
use hashbrown::HashMap; use hashbrown::HashMap;
use serde::{Deserialize, Deserializer}; use serde::Deserialize;
use yazi_codegen::DeserializeOver2; use yazi_codegen::{DeserializeOver2, Overlay};
use yazi_fs::File; use yazi_fs::File;
use yazi_shared::{Condition, url::UrlLike}; use yazi_shared::{NonEmptyString, url::UrlLike};
use yazi_shim::toml::DeserializeOverHook; use yazi_shim::toml::DeserializeOverHook;
use crate::{Icon as I, Mixable, Pattern, mix}; use super::{IconCond, IconConds, IconGlob, IconGlobs, IconNames, deserialize_icon_names};
use crate::{Icon as I, mix};
#[derive(Default, Deserialize, DeserializeOver2)] #[derive(Default, Deserialize, DeserializeOver2, Overlay)]
pub struct Icon { pub struct Icon {
globs: Vec<IconPat>, globs: IconGlobs,
#[serde(default)] #[serde(default)]
prepend_globs: Vec<IconPat>, prepend_globs: Vec<IconGlob>,
#[serde(default)] #[serde(default)]
append_globs: Vec<IconPat>, append_globs: Vec<IconGlob>,
#[serde(deserialize_with = "deserialize_named_icons")] dirs: IconNames,
dirs: HashMap<String, I>, #[serde(default, deserialize_with = "deserialize_icon_names")]
#[serde(default, deserialize_with = "deserialize_named_icons")] prepend_dirs: HashMap<NonEmptyString, I>,
prepend_dirs: HashMap<String, I>, #[serde(default, deserialize_with = "deserialize_icon_names")]
#[serde(default, deserialize_with = "deserialize_named_icons")] append_dirs: HashMap<NonEmptyString, I>,
append_dirs: HashMap<String, I>,
#[serde(deserialize_with = "deserialize_named_icons")] files: IconNames,
files: HashMap<String, I>, #[serde(default, deserialize_with = "deserialize_icon_names")]
#[serde(default, deserialize_with = "deserialize_named_icons")] prepend_files: HashMap<NonEmptyString, I>,
prepend_files: HashMap<String, I>, #[serde(default, deserialize_with = "deserialize_icon_names")]
#[serde(default, deserialize_with = "deserialize_named_icons")] append_files: HashMap<NonEmptyString, I>,
append_files: HashMap<String, I>,
#[serde(deserialize_with = "deserialize_named_icons")] exts: IconNames,
exts: HashMap<String, I>, #[serde(default, deserialize_with = "deserialize_icon_names")]
#[serde(default, deserialize_with = "deserialize_named_icons")] prepend_exts: HashMap<NonEmptyString, I>,
prepend_exts: HashMap<String, I>, #[serde(default, deserialize_with = "deserialize_icon_names")]
#[serde(default, deserialize_with = "deserialize_named_icons")] append_exts: HashMap<NonEmptyString, I>,
append_exts: HashMap<String, I>,
conds: Vec<IconCond>, conds: IconConds,
#[serde(default)] #[serde(default)]
prepend_conds: Vec<IconCond>, prepend_conds: Vec<IconCond>,
#[serde(default)] #[serde(default)]
@ -45,109 +43,49 @@ pub struct Icon {
} }
impl Icon { impl Icon {
pub fn matches(&self, file: &File, hovered: bool) -> Option<&I> { pub fn matches(&self, file: &File, hovered: bool) -> Option<I> {
if let Some(i) = self.match_by_glob(file) { if let Some(i) = self.globs.matches(file) {
return Some(i); return Some(i);
} }
if let Some(i) = self.match_by_name(file) { let name = file.name().unwrap_or_default();
return Some(i); match file.is_dir() {
true if let Some(i) = self.dirs.matches(name) => Some(i),
false if let Some(i) = self.files.matches(name) => Some(i),
false if let Some(i) = self.exts.matches(file.url.ext().unwrap_or_default()) => Some(i),
_ => self.conds.matches(file, hovered),
} }
let f = |s: &str| match s {
"dir" => file.is_dir(),
"hidden" => file.is_hidden(),
"link" => file.is_link(),
"orphan" => file.is_orphan(),
"dummy" => file.is_dummy(),
"block" => file.is_block(),
"char" => file.is_char(),
"fifo" => file.is_fifo(),
"sock" => file.is_sock(),
"exec" => file.is_exec(),
"sticky" => file.is_sticky(),
"hovered" => hovered,
_ => false,
};
self.conds.iter().find(|&c| c.r#if.eval(f) == Some(true)).map(|c| &c.icon)
}
fn match_by_glob(&self, file: &File) -> Option<&I> {
self.globs.iter().find(|&g| g.url.match_url(&file.url, file.is_dir())).map(|g| &g.icon)
}
fn match_by_name(&self, file: &File) -> Option<&I> {
let name = file.name()?.to_str().ok()?;
if file.is_dir() {
self.dirs.get(name).or_else(|| self.dirs.get(&name.to_ascii_lowercase()))
} else {
self
.files
.get(name)
.or_else(|| self.files.get(&name.to_ascii_lowercase()))
.or_else(|| self.match_by_ext(file))
}
}
fn match_by_ext(&self, file: &File) -> Option<&I> {
let ext = file.url.ext()?.to_str().ok()?;
self.exts.get(ext).or_else(|| self.exts.get(&ext.to_ascii_lowercase()))
} }
} }
impl DeserializeOverHook for Icon { impl DeserializeOverHook for Icon {
fn deserialize_over_hook(self) -> Result<Self, toml::de::Error> { fn deserialize_over_hook(self) -> Result<Self, toml::de::Error> {
let dirs: HashMap<NonEmptyString, I> = self
.append_dirs
.into_iter()
.chain(self.dirs.unwrap_unchecked())
.chain(self.prepend_dirs)
.collect();
let files: HashMap<NonEmptyString, I> = self
.append_files
.into_iter()
.chain(self.files.unwrap_unchecked())
.chain(self.prepend_files)
.collect();
let exts: HashMap<NonEmptyString, I> = self
.append_exts
.into_iter()
.chain(self.exts.unwrap_unchecked())
.chain(self.prepend_exts)
.collect();
Ok(Self { Ok(Self {
globs: mix(self.prepend_globs, self.globs, self.append_globs), globs: mix(self.prepend_globs, self.globs.unwrap_unchecked(), self.append_globs).into(),
dirs: self.append_dirs.into_iter().chain(self.dirs).chain(self.prepend_dirs).collect(), dirs: dirs.into(),
files: self.append_files.into_iter().chain(self.files).chain(self.prepend_files).collect(), files: files.into(),
exts: self.append_exts.into_iter().chain(self.exts).chain(self.prepend_exts).collect(), exts: exts.into(),
conds: mix(self.prepend_conds, self.conds, self.append_conds), conds: mix(self.prepend_conds, self.conds.unwrap_unchecked(), self.append_conds).into(),
..Default::default() ..Default::default()
}) })
} }
} }
// --- IconPat
#[derive(Deserialize)]
pub struct IconPat {
pub url: Pattern,
#[serde(flatten)]
pub icon: I,
}
impl Mixable for IconPat {
fn any_file(&self) -> bool { self.url.any_file() }
fn any_dir(&self) -> bool { self.url.any_dir() }
}
// --- IconNamed
#[derive(Deserialize)]
struct IconNamed {
name: String,
#[serde(flatten)]
icon: I,
}
fn deserialize_named_icons<'de, D>(deserializer: D) -> Result<HashMap<String, I>, D::Error>
where
D: Deserializer<'de>,
{
Ok(
Vec::<IconNamed>::deserialize(deserializer)?
.into_iter()
.map(|entry| (entry.name, entry.icon))
.collect(),
)
}
// --- IconCond
#[derive(Deserialize)]
pub struct IconCond {
pub r#if: Condition,
#[serde(flatten)]
pub icon: I,
}
impl Mixable for IconCond {}

View file

@ -0,0 +1,36 @@
use serde::Deserialize;
use yazi_fs::File;
use yazi_shared::Condition;
use crate::{Icon, Mixable};
#[derive(Debug, Deserialize)]
pub struct IconCond {
pub r#if: Condition,
#[serde(flatten)]
pub icon: Icon,
}
impl IconCond {
pub fn matches(&self, file: &File, hovered: bool) -> bool {
let f = |s: &str| match s {
"dir" => file.is_dir(),
"hidden" => file.is_hidden(),
"link" => file.is_link(),
"orphan" => file.is_orphan(),
"dummy" => file.is_dummy(),
"block" => file.is_block(),
"char" => file.is_char(),
"fifo" => file.is_fifo(),
"sock" => file.is_sock(),
"exec" => file.is_exec(),
"sticky" => file.is_sticky(),
"hovered" => hovered,
_ => false,
};
self.r#if.eval(f) == Some(true)
}
}
impl Mixable for IconCond {}

View file

@ -0,0 +1,32 @@
use std::{ops::Deref, sync::Arc};
use arc_swap::ArcSwap;
use serde::Deserialize;
use yazi_codegen::Overlay;
use yazi_fs::File;
use yazi_shim::arc_swap::IntoPointee;
use crate::{Icon, theme::IconCond};
#[derive(Default, Deserialize, Overlay)]
pub struct IconConds(ArcSwap<Vec<IconCond>>);
impl Deref for IconConds {
type Target = ArcSwap<Vec<IconCond>>;
fn deref(&self) -> &Self::Target { &self.0 }
}
impl From<Vec<IconCond>> for IconConds {
fn from(inner: Vec<IconCond>) -> Self { Self(inner.into_pointee()) }
}
impl IconConds {
pub fn matches(&self, file: &File, hovered: bool) -> Option<Icon> {
self.0.load().iter().find(|&c| c.matches(file, hovered)).map(|c| c.icon.clone())
}
pub(super) fn unwrap_unchecked(self) -> Vec<IconCond> {
Arc::try_unwrap(self.0.into_inner()).expect("unique icon conds arc")
}
}

View file

@ -0,0 +1,22 @@
use serde::Deserialize;
use crate::{Icon, Mixable, Pattern, Selectable};
#[derive(Debug, Deserialize)]
pub struct IconGlob {
pub url: Pattern,
#[serde(flatten)]
pub icon: Icon,
}
impl Selectable for IconGlob {
fn url_pat(&self) -> Option<&Pattern> { Some(&self.url) }
fn mime_pat(&self) -> Option<&Pattern> { None }
}
impl Mixable for IconGlob {
fn any_file(&self) -> bool { self.url.any_file() }
fn any_dir(&self) -> bool { self.url.any_dir() }
}

View file

@ -0,0 +1,32 @@
use std::{ops::Deref, sync::Arc};
use arc_swap::ArcSwap;
use serde::Deserialize;
use yazi_codegen::Overlay;
use yazi_fs::File;
use yazi_shim::arc_swap::IntoPointee;
use crate::{Icon, Selectable, theme::IconGlob};
#[derive(Default, Deserialize, Overlay)]
pub struct IconGlobs(ArcSwap<Vec<IconGlob>>);
impl Deref for IconGlobs {
type Target = ArcSwap<Vec<IconGlob>>;
fn deref(&self) -> &Self::Target { &self.0 }
}
impl From<Vec<IconGlob>> for IconGlobs {
fn from(inner: Vec<IconGlob>) -> Self { Self(inner.into_pointee()) }
}
impl IconGlobs {
pub fn matches(&self, file: &File) -> Option<Icon> {
self.0.load().iter().find(|&g| g.match_with(Some(file), None)).map(|g| g.icon.clone())
}
pub(super) fn unwrap_unchecked(self) -> Vec<IconGlob> {
Arc::try_unwrap(self.0.into_inner()).expect("unique icon globs arc")
}
}

View file

@ -0,0 +1,69 @@
use std::{ops::Deref, sync::Arc};
use arc_swap::ArcSwap;
use hashbrown::HashMap;
use serde::{Deserialize, Deserializer};
use yazi_codegen::Overlay;
use yazi_shared::{NonEmptyString, strand::AsStrand};
use yazi_shim::arc_swap::IntoPointee;
use crate::Icon;
#[derive(Default, Overlay)]
pub struct IconNames(ArcSwap<HashMap<NonEmptyString, Icon>>);
impl Deref for IconNames {
type Target = ArcSwap<HashMap<NonEmptyString, Icon>>;
fn deref(&self) -> &Self::Target { &self.0 }
}
impl From<HashMap<NonEmptyString, Icon>> for IconNames {
fn from(inner: HashMap<NonEmptyString, Icon>) -> Self { Self(inner.into_pointee()) }
}
impl IconNames {
pub fn matches<S>(&self, name: S) -> Option<Icon>
where
S: AsStrand,
{
let name = name.as_strand().to_str().ok()?;
if name.is_empty() {
return None;
}
let inner = self.0.load();
inner.get(name).or_else(|| inner.get(&name.to_ascii_lowercase())).cloned()
}
pub(super) fn unwrap_unchecked(self) -> HashMap<NonEmptyString, Icon> {
Arc::try_unwrap(self.0.into_inner()).expect("unique icon names arc")
}
}
impl<'de> Deserialize<'de> for IconNames {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
deserialize_icon_names(deserializer).map(Self::from)
}
}
pub(super) fn deserialize_icon_names<'de, D>(
deserializer: D,
) -> Result<HashMap<NonEmptyString, Icon>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Helper {
name: NonEmptyString,
#[serde(flatten)]
icon: Icon,
}
Ok(
Vec::<Helper>::deserialize(deserializer)?
.into_iter()
.map(|entry| (entry.name, entry.icon))
.collect(),
)
}

View file

@ -1 +1 @@
yazi_macro::mod_flat!(filetype flavor icon is theme); yazi_macro::mod_flat!(filetype filetype_rule filetype_rules flavor icon icon_cond icon_conds icon_glob icon_globs icon_names is theme);

View file

@ -3,15 +3,14 @@ use std::path::PathBuf;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use arc_swap::ArcSwap; use arc_swap::ArcSwap;
use serde::{Deserialize, Deserializer, de}; use serde::{Deserialize, Deserializer, de};
use yazi_codegen::{DeserializeOver, DeserializeOver1, DeserializeOver2}; use yazi_codegen::{DeserializeOver, DeserializeOver1, DeserializeOver2, Overlay};
use yazi_fs::{Xdg, ok_or_not_found}; use yazi_fs::{Xdg, ok_or_not_found};
use yazi_shared::SyncCell; use yazi_shim::{arc_swap::IntoPointee, cell::SyncCell};
use yazi_shim::arc_swap::IntoPointee;
use super::{Filetype, Flavor, Icon}; use super::{Filetype, Flavor, Icon};
use crate::{Style, normalize_path}; use crate::{Style, normalize_path};
#[derive(Deserialize, DeserializeOver, DeserializeOver1)] #[derive(Deserialize, DeserializeOver, DeserializeOver1, Overlay)]
pub struct Theme { pub struct Theme {
pub flavor: Flavor, pub flavor: Flavor,
pub app: App, pub app: App,
@ -35,12 +34,12 @@ pub struct Theme {
pub icon: Icon, pub icon: Icon,
} }
#[derive(Deserialize, DeserializeOver, DeserializeOver2)] #[derive(Deserialize, DeserializeOver, DeserializeOver2, Overlay)]
pub struct App { pub struct App {
pub overall: SyncCell<Style>, pub overall: SyncCell<Style>,
} }
#[derive(Deserialize, DeserializeOver, DeserializeOver2)] #[derive(Deserialize, DeserializeOver, DeserializeOver2, Overlay)]
pub struct Mgr { pub struct Mgr {
pub cwd: SyncCell<Style>, pub cwd: SyncCell<Style>,
@ -72,7 +71,7 @@ pub struct Mgr {
pub syntect_theme: ArcSwap<PathBuf>, pub syntect_theme: ArcSwap<PathBuf>,
} }
#[derive(Deserialize, DeserializeOver, DeserializeOver2)] #[derive(Deserialize, DeserializeOver, DeserializeOver2, Overlay)]
pub struct Tabs { pub struct Tabs {
pub active: SyncCell<Style>, pub active: SyncCell<Style>,
pub inactive: SyncCell<Style>, pub inactive: SyncCell<Style>,
@ -87,7 +86,7 @@ pub struct TabsSep {
pub close: String, pub close: String,
} }
#[derive(Deserialize, DeserializeOver, DeserializeOver2)] #[derive(Deserialize, DeserializeOver, DeserializeOver2, Overlay)]
pub struct Mode { pub struct Mode {
pub normal_main: SyncCell<Style>, pub normal_main: SyncCell<Style>,
pub normal_alt: SyncCell<Style>, pub normal_alt: SyncCell<Style>,
@ -99,7 +98,7 @@ pub struct Mode {
pub unset_alt: SyncCell<Style>, pub unset_alt: SyncCell<Style>,
} }
#[derive(Deserialize, DeserializeOver, DeserializeOver2)] #[derive(Deserialize, DeserializeOver, DeserializeOver2, Overlay)]
pub struct Indicator { pub struct Indicator {
pub parent: SyncCell<Style>, pub parent: SyncCell<Style>,
pub current: SyncCell<Style>, pub current: SyncCell<Style>,
@ -113,7 +112,7 @@ pub struct IndicatorPadding {
pub close: String, pub close: String,
} }
#[derive(Deserialize, DeserializeOver, DeserializeOver2)] #[derive(Deserialize, DeserializeOver, DeserializeOver2, Overlay)]
pub struct Status { pub struct Status {
pub overall: SyncCell<Style>, pub overall: SyncCell<Style>,
pub sep_left: ArcSwap<StatusSep>, pub sep_left: ArcSwap<StatusSep>,
@ -138,7 +137,7 @@ pub struct StatusSep {
pub close: String, pub close: String,
} }
#[derive(Deserialize, DeserializeOver, DeserializeOver2)] #[derive(Deserialize, DeserializeOver, DeserializeOver2, Overlay)]
pub struct Which { pub struct Which {
#[serde(deserialize_with = "deserialize_which_cols")] #[serde(deserialize_with = "deserialize_which_cols")]
pub cols: SyncCell<u8>, pub cols: SyncCell<u8>,
@ -151,7 +150,7 @@ pub struct Which {
pub separator_style: SyncCell<Style>, pub separator_style: SyncCell<Style>,
} }
#[derive(Deserialize, DeserializeOver, DeserializeOver2)] #[derive(Deserialize, DeserializeOver, DeserializeOver2, Overlay)]
pub struct Confirm { pub struct Confirm {
pub border: SyncCell<Style>, pub border: SyncCell<Style>,
pub title: SyncCell<Style>, pub title: SyncCell<Style>,
@ -160,10 +159,10 @@ pub struct Confirm {
pub btn_yes: SyncCell<Style>, pub btn_yes: SyncCell<Style>,
pub btn_no: SyncCell<Style>, pub btn_no: SyncCell<Style>,
pub btn_labels: [String; 2], pub btn_labels: ArcSwap<[String; 2]>,
} }
#[derive(Deserialize, DeserializeOver, DeserializeOver2)] #[derive(Deserialize, DeserializeOver, DeserializeOver2, Overlay)]
pub struct Spot { pub struct Spot {
pub border: SyncCell<Style>, pub border: SyncCell<Style>,
pub title: SyncCell<Style>, pub title: SyncCell<Style>,
@ -172,7 +171,7 @@ pub struct Spot {
pub tbl_cell: SyncCell<Style>, pub tbl_cell: SyncCell<Style>,
} }
#[derive(Deserialize, DeserializeOver, DeserializeOver2)] #[derive(Deserialize, DeserializeOver, DeserializeOver2, Overlay)]
pub struct Notify { pub struct Notify {
pub title_info: SyncCell<Style>, pub title_info: SyncCell<Style>,
pub title_warn: SyncCell<Style>, pub title_warn: SyncCell<Style>,
@ -183,14 +182,14 @@ pub struct Notify {
pub icon_error: ArcSwap<String>, pub icon_error: ArcSwap<String>,
} }
#[derive(Deserialize, DeserializeOver, DeserializeOver2)] #[derive(Deserialize, DeserializeOver, DeserializeOver2, Overlay)]
pub struct Pick { pub struct Pick {
pub border: SyncCell<Style>, pub border: SyncCell<Style>,
pub active: SyncCell<Style>, pub active: SyncCell<Style>,
pub inactive: SyncCell<Style>, pub inactive: SyncCell<Style>,
} }
#[derive(Deserialize, DeserializeOver, DeserializeOver2)] #[derive(Deserialize, DeserializeOver, DeserializeOver2, Overlay)]
pub struct Input { pub struct Input {
pub border: SyncCell<Style>, pub border: SyncCell<Style>,
pub title: SyncCell<Style>, pub title: SyncCell<Style>,
@ -198,7 +197,7 @@ pub struct Input {
pub selected: SyncCell<Style>, pub selected: SyncCell<Style>,
} }
#[derive(Deserialize, DeserializeOver, DeserializeOver2)] #[derive(Deserialize, DeserializeOver, DeserializeOver2, Overlay)]
pub struct Cmp { pub struct Cmp {
pub border: SyncCell<Style>, pub border: SyncCell<Style>,
pub active: SyncCell<Style>, pub active: SyncCell<Style>,
@ -209,14 +208,14 @@ pub struct Cmp {
pub icon_command: ArcSwap<String>, pub icon_command: ArcSwap<String>,
} }
#[derive(Deserialize, DeserializeOver, DeserializeOver2)] #[derive(Deserialize, DeserializeOver, DeserializeOver2, Overlay)]
pub struct Tasks { pub struct Tasks {
pub border: SyncCell<Style>, pub border: SyncCell<Style>,
pub title: SyncCell<Style>, pub title: SyncCell<Style>,
pub hovered: SyncCell<Style>, pub hovered: SyncCell<Style>,
} }
#[derive(Deserialize, DeserializeOver, DeserializeOver2)] #[derive(Deserialize, DeserializeOver, DeserializeOver2, Overlay)]
pub struct Help { pub struct Help {
pub on: SyncCell<Style>, pub on: SyncCell<Style>,
pub run: SyncCell<Style>, pub run: SyncCell<Style>,

View file

@ -12,6 +12,10 @@ rust-version.workspace = true
[lints] [lints]
workspace = true workspace = true
[features]
default = [ "vendored-lua" ]
vendored-lua = [ "mlua/vendored" ]
[dependencies] [dependencies]
yazi-adapter = { path = "../yazi-adapter", version = "26.2.2" } yazi-adapter = { path = "../yazi-adapter", version = "26.2.2" }
yazi-binding = { path = "../yazi-binding", version = "26.2.2" } yazi-binding = { path = "../yazi-binding", version = "26.2.2" }

View file

@ -7,7 +7,7 @@ use mlua::{Lua, Table};
use serde::Deserialize; use serde::Deserialize;
use strum::EnumString; use strum::EnumString;
use yazi_scheduler::plugin::PluginInEntry; use yazi_scheduler::plugin::PluginInEntry;
use yazi_shared::{SStr, data::{Data, DataKey}, event::{Action, ActionCow}}; use yazi_shared::{SStr, data::{Data, DataKey}, event::{ActionCow, Cmd}};
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct PluginOpt { pub struct PluginOpt {
@ -27,7 +27,7 @@ impl TryFrom<ActionCow> for PluginOpt {
let args = if let Ok(s) = a.second() { let args = if let Ok(s) = a.second() {
let (words, last) = yazi_shared::shell::unix::split(s, true)?; let (words, last) = yazi_shared::shell::unix::split(s, true)?;
Action::parse_args(words, last)? Cmd::parse_args(words, last)?
} else { } else {
Default::default() Default::default()
}; };

View file

@ -1,3 +1,5 @@
use std::sync::Arc;
use yazi_config::YAZI; use yazi_config::YAZI;
use yazi_fs::{FilesSorter, SortBy, SortFallback}; use yazi_fs::{FilesSorter, SortBy, SortFallback};
@ -5,7 +7,7 @@ use yazi_fs::{FilesSorter, SortBy, SortFallback};
pub struct Preference { pub struct Preference {
// Display // Display
pub name: String, pub name: String,
pub linemode: String, pub linemode: Arc<String>,
pub show_hidden: bool, pub show_hidden: bool,
// Sorting // Sorting
@ -22,7 +24,7 @@ impl Default for Preference {
Self { Self {
// Display // Display
name: String::new(), name: String::new(),
linemode: YAZI.mgr.linemode.clone(), linemode: YAZI.mgr.linemode.load_full(),
show_hidden: YAZI.mgr.show_hidden.get(), show_hidden: YAZI.mgr.show_hidden.get(),
// Sorting // Sorting

View file

@ -22,6 +22,7 @@ yazi-boot = { path = "../yazi-boot", version = "26.2.2" }
yazi-fs = { path = "../yazi-fs", version = "26.2.2" } yazi-fs = { path = "../yazi-fs", version = "26.2.2" }
yazi-macro = { path = "../yazi-macro", version = "26.2.2" } yazi-macro = { path = "../yazi-macro", version = "26.2.2" }
yazi-shared = { path = "../yazi-shared", version = "26.2.2" } yazi-shared = { path = "../yazi-shared", version = "26.2.2" }
yazi-shim = { path = "../yazi-shim", version = "26.2.2" }
# External dependencies # External dependencies
anyhow = { workspace = true } anyhow = { workspace = true }

View file

@ -1,11 +1,13 @@
use std::error::Error; use std::{env, error::Error};
use vergen_gitcl::{Emitter, GitclBuilder}; use vergen_gitcl::{Emitter, GitclBuilder};
fn main() -> Result<(), Box<dyn Error>> { fn main() -> Result<(), Box<dyn Error>> {
Emitter::default() if env::var_os("YAZI_NO_GITCL").is_none() {
.add_instructions(&GitclBuilder::default().commit_date(true).sha(true).build()?)? Emitter::default().add_instructions(&GitclBuilder::default().sha(true).build()?)?.emit()?;
.emit()?; } else {
println!("cargo:rustc-env=VERGEN_GIT_SHA=no-gitcl");
}
Ok(()) Ok(())
} }

View file

@ -7,7 +7,8 @@ use serde::{Deserialize, Serialize};
use tokio::{io::AsyncWriteExt, select, sync::mpsc, task::JoinHandle, time}; use tokio::{io::AsyncWriteExt, select, sync::mpsc, task::JoinHandle, time};
use tracing::error; use tracing::error;
use yazi_macro::try_format; use yazi_macro::try_format;
use yazi_shared::{Id, RoCell}; use yazi_shared::Id;
use yazi_shim::cell::RoCell;
use crate::{ClientReader, ClientWriter, Payload, Pubsub, Server, Stream, ember::{Ember, EmberHey}}; use crate::{ClientReader, ClientWriter, Payload, Pubsub, Server, Stream, ember::{Ember, EmberHey}};

View file

@ -5,7 +5,8 @@ use mlua::Function;
use parking_lot::RwLock; use parking_lot::RwLock;
use yazi_boot::BOOT; use yazi_boot::BOOT;
use yazi_fs::FolderStage; use yazi_fs::FolderStage;
use yazi_shared::{Id, RoCell, url::{Url, UrlBuf, UrlBufCov}}; use yazi_shared::{Id, url::{Url, UrlBuf, UrlBufCov}};
use yazi_shim::cell::RoCell;
use crate::{Client, ID, PEERS, ember::{Ember, EmberBulk, EmberDuplicateItem, EmberHi, EmberMoveItem}}; use crate::{Client, ID, PEERS, ember::{Ember, EmberBulk, EmberDuplicateItem, EmberHi, EmberMoveItem}};

View file

@ -3,7 +3,8 @@ use std::time::Duration;
use tokio::{pin, select, sync::mpsc}; use tokio::{pin, select, sync::mpsc};
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream}; use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
use yazi_macro::err; use yazi_macro::err;
use yazi_shared::{RoCell, url::UrlBuf}; use yazi_shared::url::UrlBuf;
use yazi_shim::cell::RoCell;
use crate::{Pubsub, ember::{EmberDuplicateItem, EmberMoveItem}}; use crate::{Pubsub, ember::{EmberDuplicateItem, EmberMoveItem}};

View file

@ -5,7 +5,8 @@ use hashbrown::HashMap;
use parking_lot::RwLock; use parking_lot::RwLock;
use tokio::{io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, select, sync::mpsc::{self, UnboundedReceiver}, task::JoinHandle, time}; use tokio::{io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, select, sync::mpsc::{self, UnboundedReceiver}, task::JoinHandle, time};
use yazi_macro::try_format; use yazi_macro::try_format;
use yazi_shared::{Id, RoCell}; use yazi_shared::Id;
use yazi_shim::cell::RoCell;
use crate::{Client, ClientWriter, Payload, Peer, STATE, Stream, ember::{Ember, EmberBye, EmberHey}}; use crate::{Client, ClientWriter, Payload, Peer, STATE, Stream, ember::{Ember, EmberBye, EmberHey}};

View file

@ -6,7 +6,8 @@ use parking_lot::RwLock;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter}; use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter};
use yazi_boot::BOOT; use yazi_boot::BOOT;
use yazi_fs::provider::{FileBuilder, Provider, local::{Gate, Local}}; use yazi_fs::provider::{FileBuilder, Provider, local::{Gate, Local}};
use yazi_shared::{RoCell, timestamp_us}; use yazi_shared::timestamp_us;
use yazi_shim::cell::RoCell;
use crate::CLIENTS; use crate::CLIENTS;

View file

@ -15,6 +15,7 @@ workspace = true
[dependencies] [dependencies]
yazi-macro = { path = "../yazi-macro", version = "26.2.2" } yazi-macro = { path = "../yazi-macro", version = "26.2.2" }
yazi-shared = { path = "../yazi-shared", version = "26.2.2" } yazi-shared = { path = "../yazi-shared", version = "26.2.2" }
yazi-shim = { path = "../yazi-shim", version = "26.2.2" }
yazi-tty = { path = "../yazi-tty", version = "26.2.2" } yazi-tty = { path = "../yazi-tty", version = "26.2.2" }
# External dependencies # External dependencies

View file

@ -6,7 +6,7 @@ use either::Either;
use scopeguard::defer; use scopeguard::defer;
use tokio::time::sleep; use tokio::time::sleep;
use tracing::{debug, error, warn}; use tracing::{debug, error, warn};
use yazi_shared::RoCell; use yazi_shim::cell::RoCell;
use yazi_tty::{Handle, TTY}; use yazi_tty::{Handle, TTY};
use crate::{Brand, Dimension, Mux, TMUX, Unknown}; use crate::{Brand, Dimension, Mux, TMUX, Unknown};

View file

@ -3,7 +3,7 @@ use std::borrow::Cow;
use anyhow::Result; use anyhow::Result;
use tracing::error; use tracing::error;
use yazi_macro::time; use yazi_macro::time;
use yazi_shared::SyncCell; use yazi_shim::cell::SyncCell;
use yazi_tty::TTY; use yazi_tty::TTY;
use crate::Emulator; use crate::Emulator;

View file

@ -43,6 +43,7 @@ yazi-proxy = { path = "../yazi-proxy", version = "26.2.2" }
yazi-runner = { path = "../yazi-runner", version = "26.2.2" } yazi-runner = { path = "../yazi-runner", version = "26.2.2" }
yazi-scheduler = { path = "../yazi-scheduler", version = "26.2.2" } yazi-scheduler = { path = "../yazi-scheduler", version = "26.2.2" }
yazi-shared = { path = "../yazi-shared", version = "26.2.2" } yazi-shared = { path = "../yazi-shared", version = "26.2.2" }
yazi-shim = { path = "../yazi-shim", version = "26.2.2" }
yazi-term = { path = "../yazi-term", version = "26.2.2" } yazi-term = { path = "../yazi-term", version = "26.2.2" }
yazi-tty = { path = "../yazi-tty", version = "26.2.2" } yazi-tty = { path = "../yazi-tty", version = "26.2.2" }
yazi-vfs = { path = "../yazi-vfs", version = "26.2.2" } yazi-vfs = { path = "../yazi-vfs", version = "26.2.2" }

View file

@ -7,11 +7,12 @@ impl Widget for Buttons {
fn render(self, area: Rect, buf: &mut Buffer) { fn render(self, area: Rect, buf: &mut Buffer) {
let chunks = let chunks =
ratatui::layout::Layout::horizontal([Constraint::Fill(1), Constraint::Fill(1)]).split(area); ratatui::layout::Layout::horizontal([Constraint::Fill(1), Constraint::Fill(1)]).split(area);
let labels = THEME.confirm.btn_labels.load();
Paragraph::new(Span::raw(&THEME.confirm.btn_labels[0]).style(THEME.confirm.btn_yes.get())) Paragraph::new(Span::raw(&labels[0]).style(THEME.confirm.btn_yes.get()))
.centered() .centered()
.render(chunks[0], buf); .render(chunks[0], buf);
Paragraph::new(Span::raw(&THEME.confirm.btn_labels[1]).style(THEME.confirm.btn_no.get())) Paragraph::new(Span::raw(&labels[1]).style(THEME.confirm.btn_no.get()))
.centered() .centered()
.render(chunks[1], buf); .render(chunks[1], buf);
} }

View file

@ -16,6 +16,7 @@ impl<'a> Executor<'a> {
pub(super) fn execute(&mut self, action: ActionCow) -> Result<Data> { pub(super) fn execute(&mut self, action: ActionCow) -> Result<Data> {
match action.layer { match action.layer {
Layer::Null => self.null(action),
Layer::App => self.app(action), Layer::App => self.app(action),
Layer::Mgr => self.mgr(action), Layer::Mgr => self.mgr(action),
Layer::Tasks => self.tasks(action), Layer::Tasks => self.tasks(action),
@ -30,6 +31,8 @@ impl<'a> Executor<'a> {
} }
} }
fn null(&mut self, _action: ActionCow) -> Result<Data> { succ!() }
fn app(&mut self, mut action: ActionCow) -> Result<Data> { fn app(&mut self, mut action: ActionCow) -> Result<Data> {
let cx = &mut Ctx::new(&action, &mut self.app.core, &mut self.app.term)?; let cx = &mut Ctx::new(&action, &mut self.app.core, &mut self.app.term)?;
@ -47,6 +50,7 @@ impl<'a> Executor<'a> {
on!(update_progress); on!(update_progress);
on!(lua); on!(lua);
on!(deprecate); on!(deprecate);
on!(theme);
on!(quit); on!(quit);
match &*action.name { match &*action.name {

View file

@ -5,7 +5,8 @@ use crossterm::style::{Color, Print, ResetColor, SetForegroundColor};
use tracing_appender::non_blocking::WorkerGuard; use tracing_appender::non_blocking::WorkerGuard;
use tracing_subscriber::EnvFilter; use tracing_subscriber::EnvFilter;
use yazi_fs::Xdg; use yazi_fs::Xdg;
use yazi_shared::{LOG_LEVEL, RoCell}; use yazi_shared::LOG_LEVEL;
use yazi_shim::cell::RoCell;
static _GUARD: RoCell<WorkerGuard> = RoCell::new(); static _GUARD: RoCell<WorkerGuard> = RoCell::new();

View file

@ -26,7 +26,7 @@ impl<'a> Router<'a> {
use Layer as L; use Layer as L;
Ok(match layer { Ok(match layer {
L::App | L::Notify => unreachable!(), L::Null | L::App | L::Notify => unreachable!(),
L::Mgr | L::Tasks | L::Spot | L::Pick | L::Input | L::Confirm | L::Help => { L::Mgr | L::Tasks | L::Spot | L::Pick | L::Input | L::Confirm | L::Help => {
self.matches(layer, key) self.matches(layer, key)
} }

View file

@ -1,7 +1,8 @@
use std::{borrow::Cow, env::{current_dir, set_current_dir}, ops::Deref, path::{Path, PathBuf}, sync::{Arc, atomic::{AtomicBool, Ordering}}}; use std::{borrow::Cow, env::{current_dir, set_current_dir}, ops::Deref, path::{Path, PathBuf}, sync::{Arc, atomic::{AtomicBool, Ordering}}};
use arc_swap::ArcSwap; use arc_swap::ArcSwap;
use yazi_shared::{RoCell, url::{AsUrl, Url, UrlBuf, UrlLike}}; use yazi_shared::url::{AsUrl, Url, UrlBuf, UrlLike};
use yazi_shim::cell::RoCell;
use crate::{FsUrl, Xdg}; use crate::{FsUrl, Xdg};

View file

@ -1,7 +1,7 @@
use std::ops::Deref; use std::ops::Deref;
use parking_lot::RwLock; use parking_lot::RwLock;
use yazi_shared::RoCell; use yazi_shim::cell::RoCell;
use super::Partition; use super::Partition;
use crate::cha::Cha; use crate::cha::Cha;

View file

@ -1,12 +1,12 @@
use anyhow::bail; use anyhow::bail;
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value}; use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
use serde::Deserialize; use serde::Deserialize;
use yazi_shared::{SStr, event::ActionCow}; use yazi_shared::event::ActionCow;
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct LinemodeForm { pub struct LinemodeForm {
#[serde(alias = "0")] #[serde(alias = "0")]
pub new: SStr, pub new: String,
} }
impl TryFrom<ActionCow> for LinemodeForm { impl TryFrom<ActionCow> for LinemodeForm {

View file

@ -21,6 +21,7 @@ pub enum Spark<'a> {
AppResize(crate::app::ReflowForm), AppResize(crate::app::ReflowForm),
AppResume(crate::app::ResumeForm), AppResume(crate::app::ResumeForm),
AppStop(crate::app::StopForm), AppStop(crate::app::StopForm),
AppTheme(crate::VoidForm),
AppTitle(crate::app::TitleForm), AppTitle(crate::app::TitleForm),
AppUpdateProgress(crate::app::UpdateProgressForm), AppUpdateProgress(crate::app::UpdateProgressForm),
@ -205,6 +206,7 @@ impl<'a> IntoLua for Spark<'a> {
Self::AppResize(b) => b.into_lua(lua), Self::AppResize(b) => b.into_lua(lua),
Self::AppResume(b) => b.into_lua(lua), Self::AppResume(b) => b.into_lua(lua),
Self::AppStop(b) => b.into_lua(lua), Self::AppStop(b) => b.into_lua(lua),
Self::AppTheme(b) => b.into_lua(lua),
Self::AppTitle(b) => b.into_lua(lua), Self::AppTitle(b) => b.into_lua(lua),
Self::AppUpdateProgress(b) => b.into_lua(lua), Self::AppUpdateProgress(b) => b.into_lua(lua),
@ -346,6 +348,7 @@ try_from_spark!(
crate::VoidForm, crate::VoidForm,
app:bootstrap, app:bootstrap,
app:focus, app:focus,
app:theme,
mgr:back, mgr:back,
mgr:bulk_rename, mgr:bulk_rename,
mgr:enter, mgr:enter,

View file

@ -31,26 +31,25 @@ function M:spot(job)
end end
function M:spot_base(job) function M:spot_base(job)
local cha = job.file.cha local cha, pair = job.file.cha, { file = job.file, mime = job.mime }
local spotter, previewer = nil, nil local spotter, previewer, fetchers, preloaders = nil, nil, {}, {}
local fetchers, preloaders = {}, {}
for _, v in pairs(rt.plugin.spotters:match(job)) do for _, v in pairs(rt.plugin.spotters:match(pair)) do
spotter = v spotter = v
break break
end end
for _, v in pairs(rt.plugin.previewers:match(job)) do for _, v in pairs(rt.plugin.previewers:match(pair)) do
previewer = v previewer = v
break break
end end
for _, v in pairs(rt.plugin.fetchers:match(job)) do for _, v in pairs(rt.plugin.fetchers:match(pair)) do
fetchers[#fetchers + 1] = v.name fetchers[#fetchers + 1] = v.name
end end
fetchers = #fetchers ~= 0 and fetchers or { "-" } fetchers = #fetchers ~= 0 and fetchers or { "-" }
for _, v in pairs(rt.plugin.preloaders:match(job)) do for _, v in pairs(rt.plugin.preloaders:match(pair)) do
preloaders[#preloaders + 1] = v.name preloaders[#preloaders + 1] = v.name
end end
preloaders = #preloaders ~= 0 and preloaders or { "-" } preloaders = #preloaders ~= 0 and preloaders or { "-" }

View file

@ -65,7 +65,7 @@ fn mgr() -> Composer<ComposerGet, ComposerSet> {
b"sort_translit" => m.sort_translit.get().into_lua(lua)?, b"sort_translit" => m.sort_translit.get().into_lua(lua)?,
b"sort_fallback" => lua.to_value_with(&m.sort_fallback, SER_OPT)?, b"sort_fallback" => lua.to_value_with(&m.sort_fallback, SER_OPT)?,
b"linemode" => lua.create_string(&*m.linemode)?.into_lua(lua)?, b"linemode" => lua.create_string(&**m.linemode.load())?.into_lua(lua)?,
b"show_hidden" => m.show_hidden.get().into_lua(lua)?, b"show_hidden" => m.show_hidden.get().into_lua(lua)?,
b"show_symlink" => m.show_symlink.get().into_lua(lua)?, b"show_symlink" => m.show_symlink.get().into_lua(lua)?,
b"scrolloff" => m.scrolloff.get().into_lua(lua)?, b"scrolloff" => m.scrolloff.get().into_lua(lua)?,

View file

@ -4,7 +4,7 @@ use mlua::Lua;
use yazi_binding::{Runtime, runtime_scope}; use yazi_binding::{Runtime, runtime_scope};
use yazi_boot::BOOT; use yazi_boot::BOOT;
use yazi_macro::plugin_preset as preset; use yazi_macro::plugin_preset as preset;
use yazi_shared::RoCell; use yazi_shim::cell::RoCell;
pub static LUA: RoCell<Lua> = RoCell::new(); pub static LUA: RoCell<Lua> = RoCell::new();

View file

@ -2,6 +2,8 @@ use mlua::{IntoLua, Lua, Value};
use yazi_binding::{Composer, ComposerGet, ComposerSet, Style, Url}; use yazi_binding::{Composer, ComposerGet, ComposerSet, Style, Url};
use yazi_config::THEME; use yazi_config::THEME;
use crate::LUA;
pub fn compose() -> Composer<ComposerGet, ComposerSet> { pub fn compose() -> Composer<ComposerGet, ComposerSet> {
fn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> { fn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {
match key { match key {
@ -30,6 +32,8 @@ pub fn compose() -> Composer<ComposerGet, ComposerSet> {
Composer::new(get, set) Composer::new(get, set)
} }
pub fn reset() -> mlua::Result<()> { LUA.globals().raw_set("th", compose()) }
fn app() -> Composer<ComposerGet, ComposerSet> { fn app() -> Composer<ComposerGet, ComposerSet> {
fn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> { fn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {
let a = &THEME.app; let a = &THEME.app;
@ -223,12 +227,12 @@ fn confirm() -> Composer<ComposerGet, ComposerSet> {
b"btn_yes" => Style::from(&t.btn_yes).into_lua(lua), b"btn_yes" => Style::from(&t.btn_yes).into_lua(lua),
b"btn_no" => Style::from(&t.btn_no).into_lua(lua), b"btn_no" => Style::from(&t.btn_no).into_lua(lua),
b"btn_labels" => lua b"btn_labels" => {
.create_sequence_from([ let labels = t.btn_labels.load();
lua.create_string(&t.btn_labels[0])?, lua
lua.create_string(&t.btn_labels[1])?, .create_sequence_from([lua.create_string(&labels[0])?, lua.create_string(&labels[1])?])?
])? .into_lua(lua)
.into_lua(lua), }
_ => Ok(Value::Nil), _ => Ok(Value::Nil),
} }

View file

@ -3,14 +3,14 @@ use tokio::sync::mpsc;
use yazi_binding::deprecate; use yazi_binding::deprecate;
use yazi_dds::Sendable; use yazi_dds::Sendable;
use yazi_macro::emit; use yazi_macro::emit;
use yazi_shared::{Layer, Source, event::Action}; use yazi_shared::{Layer, Source, event::{Action, Cmd}};
use super::Utils; use super::Utils;
impl Utils { impl Utils {
pub(super) fn emit(lua: &Lua) -> mlua::Result<Function> { pub(super) fn emit(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(|lua, (name, args): (String, Table)| { lua.create_function(|lua, (name, args): (String, Table)| {
let mut action = Action::new(name, Source::Emit, Some(Layer::Mgr))?; let mut action = Action::new(name, Source::Emit, Layer::Mgr)?;
action.args = Sendable::table_to_args(lua, args)?; action.args = Sendable::table_to_args(lua, args)?;
Ok(emit!(Call(action))) Ok(emit!(Call(action)))
}) })
@ -20,8 +20,7 @@ impl Utils {
lua.create_function(|lua, (name, args): (String, Table)| { lua.create_function(|lua, (name, args): (String, Table)| {
deprecate!(lua, "ya.mgr_emit() has been deprecated since v25.5.28 and will soon-to-be removed in a future release. \n\nUse ya.emit() in your {} instead, see #2653 for details: https://github.com/sxyazi/yazi/pull/2653"); deprecate!(lua, "ya.mgr_emit() has been deprecated since v25.5.28 and will soon-to-be removed in a future release. \n\nUse ya.emit() in your {} instead, see #2653 for details: https://github.com/sxyazi/yazi/pull/2653");
emit!(Call(Action { emit!(Call(Action {
name: name.into(), cmd: Cmd { name: name.into(), args: Sendable::table_to_args(lua, args)? },
args: Sendable::table_to_args(lua, args)?,
layer: Layer::Mgr, layer: Layer::Mgr,
source: Source::Emit, source: Source::Emit,
})); }));
@ -31,7 +30,7 @@ impl Utils {
pub(super) fn exec(lua: &Lua) -> mlua::Result<Function> { pub(super) fn exec(lua: &Lua) -> mlua::Result<Function> {
lua.create_async_function(|lua, (name, args): (String, Table)| async move { lua.create_async_function(|lua, (name, args): (String, Table)| async move {
let mut action = Action::new(name, Source::Emit, Some(Layer::Mgr))?; let mut action = Action::new(name, Source::Emit, Layer::Mgr)?;
action.args = Sendable::table_to_args(&lua, args)?; action.args = Sendable::table_to_args(&lua, args)?;
let (tx, mut rx) = mpsc::unbounded_channel(); let (tx, mut rx) = mpsc::unbounded_channel();

View file

@ -12,6 +12,10 @@ rust-version.workspace = true
[lints] [lints]
workspace = true workspace = true
[features]
default = [ "vendored-lua" ]
vendored-lua = [ "mlua/vendored" ]
[dependencies] [dependencies]
yazi-binding = { path = "../yazi-binding", version = "26.2.2" } yazi-binding = { path = "../yazi-binding", version = "26.2.2" }
yazi-boot = { path = "../yazi-boot", version = "26.2.2" } yazi-boot = { path = "../yazi-boot", version = "26.2.2" }
@ -20,6 +24,7 @@ yazi-dds = { path = "../yazi-dds", version = "26.2.2" }
yazi-fs = { path = "../yazi-fs", version = "26.2.2" } yazi-fs = { path = "../yazi-fs", version = "26.2.2" }
yazi-macro = { path = "../yazi-macro", version = "26.2.2" } yazi-macro = { path = "../yazi-macro", version = "26.2.2" }
yazi-shared = { path = "../yazi-shared", version = "26.2.2" } yazi-shared = { path = "../yazi-shared", version = "26.2.2" }
yazi-shim = { path = "../yazi-shim", version = "26.2.2" }
# External dependencies # External dependencies
anyhow = { workspace = true } anyhow = { workspace = true }

View file

@ -2,7 +2,7 @@ yazi_macro::mod_pub!(entry fetcher loader preloader previewer);
yazi_macro::mod_flat!(runner spot); yazi_macro::mod_flat!(runner spot);
pub static RUNNER: yazi_shared::RoCell<Runner> = yazi_shared::RoCell::new(); pub static RUNNER: yazi_shim::cell::RoCell<Runner> = yazi_shim::cell::RoCell::new();
pub fn init(setter: fn(&mlua::Lua) -> mlua::Result<()>) { pub fn init(setter: fn(&mlua::Lua) -> mlua::Result<()>) {
crate::loader::init(); crate::loader::init();

View file

@ -7,7 +7,8 @@ use parking_lot::RwLock;
use yazi_boot::BOOT; use yazi_boot::BOOT;
use yazi_fs::provider::local::Local; use yazi_fs::provider::local::Local;
use yazi_macro::plugin_preset as preset; use yazi_macro::plugin_preset as preset;
use yazi_shared::{BytesExt, LOG_LEVEL, RoCell}; use yazi_shared::{BytesExt, LOG_LEVEL};
use yazi_shim::cell::RoCell;
use super::Chunk; use super::Chunk;

View file

@ -12,6 +12,10 @@ rust-version.workspace = true
[lints] [lints]
workspace = true workspace = true
[features]
default = [ "vendored-lua" ]
vendored-lua = [ "mlua/vendored" ]
[dependencies] [dependencies]
yazi-binding = { path = "../yazi-binding", version = "26.2.2" } yazi-binding = { path = "../yazi-binding", version = "26.2.2" }
yazi-config = { path = "../yazi-config", version = "26.2.2" } yazi-config = { path = "../yazi-config", version = "26.2.2" }

View file

@ -14,6 +14,7 @@ workspace = true
[dependencies] [dependencies]
yazi-macro = { path = "../yazi-macro", version = "26.2.2" } yazi-macro = { path = "../yazi-macro", version = "26.2.2" }
yazi-shim = { path = "../yazi-shim", version = "26.2.2" }
# External dependencies # External dependencies
anyhow = { workspace = true } anyhow = { workspace = true }

View file

@ -1,6 +1,7 @@
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
pub static LOG_LEVEL: crate::SyncCell<LogLevel> = crate::SyncCell::new(LogLevel::None); pub static LOG_LEVEL: yazi_shim::cell::SyncCell<LogLevel> =
yazi_shim::cell::SyncCell::new(LogLevel::None);
#[inline] #[inline]
pub fn env_exists(name: &str) -> bool { std::env::var_os(name).is_some_and(|s| !s.is_empty()) } pub fn env_exists(name: &str) -> bool { std::env::var_os(name).is_some_and(|s| !s.is_empty()) }

View file

@ -1,27 +1,35 @@
use std::{borrow::Cow, fmt::{self, Display}, mem, str::FromStr}; use std::{borrow::Cow, fmt::{self, Display}, ops::{Deref, DerefMut}, str::FromStr};
use anyhow::{Result, anyhow, bail}; use anyhow::{Result, bail};
use hashbrown::HashMap;
use serde_with::DeserializeFromStr; use serde_with::DeserializeFromStr;
use crate::{Layer, SStr, Source, data::{Data, DataAny, DataKey}, event::Replier}; use crate::{Layer, SStr, Source, data::{Data, DataAny, DataKey}, event::{Cmd, Replier}};
#[derive(Clone, Debug, Default, DeserializeFromStr)] #[derive(Clone, Debug, Default, DeserializeFromStr)]
pub struct Action { pub struct Action {
pub name: SStr, pub cmd: Cmd,
pub args: HashMap<DataKey, Data>,
pub layer: Layer, pub layer: Layer,
pub source: Source, pub source: Source,
} }
impl Deref for Action {
type Target = Cmd;
fn deref(&self) -> &Self::Target { &self.cmd }
}
impl DerefMut for Action {
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.cmd }
}
impl Action { impl Action {
pub fn new<N>(name: N, source: Source, default: Option<Layer>) -> Result<Self> pub fn new<N>(name: N, source: Source, layer: Layer) -> Result<Self>
where where
N: Into<SStr>, N: Into<SStr>,
{ {
let cow: SStr = name.into(); let cow: SStr = name.into();
let (layer, name) = match cow.find(':') { let (layer, name) = match cow.find(':') {
None => (default.ok_or_else(|| anyhow!("Cannot infer layer from action name: {cow}"))?, cow), None => (layer, cow),
Some(i) => (cow[..i].parse()?, match cow { Some(i) => (cow[..i].parse()?, match cow {
Cow::Borrowed(s) => Cow::Borrowed(&s[i + 1..]), Cow::Borrowed(s) => Cow::Borrowed(&s[i + 1..]),
Cow::Owned(mut s) => { Cow::Owned(mut s) => {
@ -31,14 +39,14 @@ impl Action {
}), }),
}; };
Ok(Self { name, args: Default::default(), layer, source }) Ok(Self { cmd: Cmd { name, args: Default::default() }, layer, source })
} }
pub fn new_relay<N>(name: N) -> Self pub fn new_relay<N>(name: N) -> Self
where where
N: Into<SStr>, N: Into<SStr>,
{ {
Self::new(name, Source::Relay, None).unwrap_or(Self::null()) Self::new(name, Source::Relay, Layer::Null).unwrap_or(Self::null())
} }
pub fn new_relay_args<N, D, I>(name: N, args: I) -> Self pub fn new_relay_args<N, D, I>(name: N, args: I) -> Self
@ -47,13 +55,13 @@ impl Action {
D: Into<Data>, D: Into<Data>,
I: IntoIterator<Item = D>, I: IntoIterator<Item = D>,
{ {
let mut action = Self::new(name, Source::Relay, None).unwrap_or(Self::null()); let mut action = Self::new(name, Source::Relay, Layer::Null).unwrap_or(Self::null());
action.args = action.args =
args.into_iter().enumerate().map(|(i, a)| (DataKey::Integer(i as i64), a.into())).collect(); args.into_iter().enumerate().map(|(i, a)| (DataKey::Integer(i as i64), a.into())).collect();
action action
} }
fn null() -> Self { Self { name: Cow::Borrowed("null"), ..Default::default() } } fn null() -> Self { Self { cmd: Cmd::null(), layer: Layer::Null, source: Source::Unknown } }
pub fn len(&self) -> usize { self.args.len() } pub fn len(&self) -> usize { self.args.len() }
@ -217,31 +225,6 @@ impl Action {
} }
pub fn take_replier(&mut self) -> Option<Replier> { self.take_any("replier") } pub fn take_replier(&mut self) -> Option<Replier> { self.take_any("replier") }
// Parse
pub fn parse_args<I>(words: I, last: Option<String>) -> Result<HashMap<DataKey, Data>>
where
I: IntoIterator<Item = String>,
{
let mut i = 0i64;
words
.into_iter()
.map(|s| (s, true))
.chain(last.into_iter().map(|s| (s, false)))
.map(|(word, normal)| {
let Some(arg) = word.strip_prefix("--").filter(|&s| normal && !s.is_empty()) else {
i += 1;
return Ok((DataKey::Integer(i - 1), word.into()));
};
let mut parts = arg.splitn(2, '=');
let key = parts.next().expect("at least one part");
let val = parts.next().map_or(Data::Boolean(true), Data::from);
Ok((key.to_owned().into(), val))
})
.collect()
}
} }
impl Display for Action { impl Display for Action {
@ -271,13 +254,11 @@ impl FromStr for Action {
type Err = anyhow::Error; type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
let (mut words, last) = crate::shell::unix::split(s, true)?; let cmd = Cmd::from_str(s)?;
if words.is_empty() || words[0].is_empty() {
bail!("action name cannot be empty"); let mut me = Self::new(cmd.name, Source::Unknown, Layer::Null)?;
} me.args = cmd.args;
let mut me = Self::new(mem::take(&mut words[0]), Default::default(), Some(Default::default()))?;
me.args = Self::parse_args(words.into_iter().skip(1), last)?;
Ok(me) Ok(me)
} }
} }

View file

@ -0,0 +1,57 @@
use std::{mem, str::FromStr};
use anyhow::{Result, bail};
use hashbrown::HashMap;
use serde_with::DeserializeFromStr;
use crate::{SStr, data::{Data, DataKey}};
#[derive(Clone, Debug, Default, DeserializeFromStr)]
pub struct Cmd {
pub name: SStr,
pub args: HashMap<DataKey, Data>,
}
impl FromStr for Cmd {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (mut words, last) = crate::shell::unix::split(s, true)?;
if words.is_empty() || words[0].is_empty() {
bail!("command name cannot be empty");
}
Ok(Self {
name: mem::take(&mut words[0]).into(),
args: Self::parse_args(words.into_iter().skip(1), last)?,
})
}
}
impl Cmd {
pub fn null() -> Self { Self { name: "null".into(), ..Default::default() } }
pub fn parse_args<I>(words: I, last: Option<String>) -> Result<HashMap<DataKey, Data>>
where
I: IntoIterator<Item = String>,
{
let mut i = 0i64;
words
.into_iter()
.map(|s| (s, true))
.chain(last.into_iter().map(|s| (s, false)))
.map(|(word, normal)| {
let Some(arg) = word.strip_prefix("--").filter(|&s| normal && !s.is_empty()) else {
i += 1;
return Ok((DataKey::Integer(i - 1), word.into()));
};
let mut parts = arg.splitn(2, '=');
let key = parts.next().expect("at least one part");
let val = parts.next().map_or(Data::Boolean(true), Data::from);
Ok((key.to_owned().into(), val))
})
.collect()
}
}

View file

@ -22,7 +22,7 @@ impl<'de> serde::Deserializer<'de> for Action {
V: serde::de::Visitor<'de>, V: serde::de::Visitor<'de>,
{ {
visitor.visit_map(MapDeserializer::new( visitor.visit_map(MapDeserializer::new(
self.args.into_iter().map(|(key, value)| (KeyDeserializer::Owned(key), value)), self.cmd.args.into_iter().map(|(key, value)| (KeyDeserializer::Owned(key), value)),
)) ))
} }

View file

@ -1,8 +1,8 @@
use crossterm::event::{KeyEvent, MouseEvent}; use crossterm::event::{KeyEvent, MouseEvent};
use tokio::sync::mpsc; use tokio::sync::mpsc;
use yazi_shim::cell::RoCell;
use super::ActionCow; use super::ActionCow;
use crate::RoCell;
static TX: RoCell<mpsc::UnboundedSender<Event>> = RoCell::new(); static TX: RoCell<mpsc::UnboundedSender<Event>> = RoCell::new();
static RX: RoCell<mpsc::UnboundedReceiver<Event>> = RoCell::new(); static RX: RoCell<mpsc::UnboundedReceiver<Event>> = RoCell::new();

View file

@ -1,4 +1,4 @@
yazi_macro::mod_flat!(action cow de de_owned event); yazi_macro::mod_flat!(action cmd cow de de_owned event);
pub type Replier = tokio::sync::mpsc::UnboundedSender<anyhow::Result<crate::data::Data>>; pub type Replier = tokio::sync::mpsc::UnboundedSender<anyhow::Result<crate::data::Data>>;

View file

@ -20,6 +20,7 @@ use strum::{Display, EnumString, FromRepr, IntoStaticStr};
#[repr(u8)] #[repr(u8)]
pub enum Layer { pub enum Layer {
#[default] #[default]
Null,
App, App,
Mgr, Mgr,
Tasks, Tasks,

View file

@ -1,6 +1,6 @@
yazi_macro::mod_pub!(data event loc path pool scheme shell strand translit url wtf8); yazi_macro::mod_pub!(data event loc path pool scheme shell strand translit url wtf8);
yazi_macro::mod_flat!(alias bytes chars completion_token condition debounce env id last_value layer localset natsort non_empty_string os predictor ro_cell source sync_cell terminal tests throttle time utf8); yazi_macro::mod_flat!(alias bytes chars completion_token condition debounce env id last_value layer localset natsort non_empty_string os predictor source terminal tests throttle time utf8);
pub fn init() { pub fn init() {
LOCAL_SET.with(tokio::task::LocalSet::new); LOCAL_SET.with(tokio::task::LocalSet::new);

View file

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

View file

@ -1,4 +1,4 @@
use std::{ffi::{OsStr, OsString}, fmt::{Display, Formatter}, ops::Deref}; use std::{borrow::Borrow, ffi::{OsStr, OsString}, fmt::{Display, Formatter}, ops::Deref};
use serde::{Deserialize, Deserializer, Serialize}; use serde::{Deserialize, Deserializer, Serialize};
@ -20,6 +20,16 @@ impl Deref for NonEmptyString {
fn deref(&self) -> &Self::Target { &self.0 } fn deref(&self) -> &Self::Target { &self.0 }
} }
impl Borrow<str> for NonEmptyString {
#[inline]
fn borrow(&self) -> &str { &self.0 }
}
impl Borrow<String> for NonEmptyString {
#[inline]
fn borrow(&self) -> &String { &self.0 }
}
impl AsRef<str> for NonEmptyString { impl AsRef<str> for NonEmptyString {
#[inline] #[inline]
fn as_ref(&self) -> &str { &self.0 } fn as_ref(&self) -> &str { &self.0 }

View file

@ -1,5 +1,5 @@
#[cfg(unix)] #[cfg(unix)]
pub static USERS_CACHE: crate::RoCell<uzers::UsersCache> = crate::RoCell::new(); pub static USERS_CACHE: yazi_shim::cell::RoCell<uzers::UsersCache> = yazi_shim::cell::RoCell::new();
#[cfg(unix)] #[cfg(unix)]
pub fn hostname() -> Option<&'static str> { pub fn hostname() -> Option<&'static str> {

View file

@ -1,8 +1,8 @@
yazi_macro::mod_flat!(cow pool ptr symbol traits); yazi_macro::mod_flat!(cow pool ptr symbol traits);
static SYMBOLS: crate::RoCell< static SYMBOLS: yazi_shim::cell::RoCell<
parking_lot::Mutex<hashbrown::HashMap<SymbolPtr, u64, foldhash::fast::FixedState>>, parking_lot::Mutex<hashbrown::HashMap<SymbolPtr, u64, foldhash::fast::FixedState>>,
> = crate::RoCell::new(); > = yazi_shim::cell::RoCell::new();
pub(super) fn init() { SYMBOLS.with(<_>::default); } pub(super) fn init() { SYMBOLS.with(<_>::default); }

View file

@ -12,12 +12,17 @@ rust-version.workspace = true
[lints] [lints]
workspace = true workspace = true
[features]
default = [ "vendored-lua" ]
vendored-lua = [ "mlua/vendored" ]
[dependencies] [dependencies]
yazi-macro = { path = "../yazi-macro", version = "26.2.2" } yazi-macro = { path = "../yazi-macro", version = "26.2.2" }
# External dependencies # External dependencies
arc-swap = { workspace = true } arc-swap = { workspace = true }
crossterm = { workspace = true } crossterm = { workspace = true }
hashbrown = { workspace = true }
mlua = { workspace = true } mlua = { workspace = true }
ratatui = { workspace = true } ratatui = { workspace = true }
serde = { workspace = true } serde = { workspace = true }

View file

@ -0,0 +1 @@
yazi_macro::mod_flat!(ro_cell sync_cell);

View file

@ -1,3 +1,3 @@
yazi_macro::mod_pub!(arc_swap crossterm mlua ratatui strum toml vec); yazi_macro::mod_pub!(arc_swap cell crossterm mlua ratatui serde strum toml vec);
yazi_macro::mod_flat!(twox); yazi_macro::mod_flat!(twox);

View file

@ -0,0 +1 @@
yazi_macro::mod_flat!(traits);

View file

@ -0,0 +1,27 @@
use arc_swap::ArcSwap;
use hashbrown::HashMap;
use crate::cell::SyncCell;
pub trait Overlay<Rhs = Self> {
fn overlay(&self, new: Rhs);
}
impl<T> Overlay for ArcSwap<T> {
fn overlay(&self, new: Self) { self.store(new.into_inner()); }
}
impl<T> Overlay for SyncCell<T>
where
T: Copy,
{
fn overlay(&self, new: Self) { self.set(new.get()); }
}
impl<T> Overlay for Vec<T> {
fn overlay(&self, _: Self) {}
}
impl<K, V, S> Overlay for HashMap<K, V, S> {
fn overlay(&self, _: Self) {}
}

View file

@ -1,6 +1,12 @@
use serde::{Deserializer, de::DeserializeSeed}; use std::{hash::{BuildHasher, Hash}, sync::Arc};
use arc_swap::ArcSwap;
use hashbrown::HashMap;
use serde::{Deserialize, Deserializer, de::{DeserializeSeed, MapAccess, Visitor}};
use toml::de; use toml::de;
use crate::arc_swap::IntoPointee;
pub trait DeserializeOver: DeserializeOverWith + DeserializeOverHook { pub trait DeserializeOver: DeserializeOverWith + DeserializeOverHook {
fn deserialize_over(self, input: &str) -> Result<Self, de::Error> { fn deserialize_over(self, input: &str) -> Result<Self, de::Error> {
let table = de::DeTable::parse(input)?; let table = de::DeTable::parse(input)?;
@ -19,11 +25,61 @@ pub trait DeserializeOverWith: Sized {
fn deserialize_over_with<'de, D: Deserializer<'de>>(self, de: D) -> Result<Self, D::Error>; fn deserialize_over_with<'de, D: Deserializer<'de>>(self, de: D) -> Result<Self, D::Error>;
} }
impl<K, V, S> DeserializeOverWith for HashMap<K, V, S>
where
K: Eq + Hash,
S: BuildHasher,
for<'de> K: Deserialize<'de>,
for<'de> V: Deserialize<'de>,
{
fn deserialize_over_with<'de, D: Deserializer<'de>>(self, de: D) -> Result<Self, D::Error> {
struct HashMapVisitor<K, V, S>(HashMap<K, V, S>);
impl<'de, K, V, S> Visitor<'de> for HashMapVisitor<K, V, S>
where
K: Eq + Hash + Deserialize<'de>,
V: Deserialize<'de>,
S: BuildHasher,
{
type Value = HashMap<K, V, S>;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { f.write_str("a map") }
fn visit_map<M: MapAccess<'de>>(mut self, mut map: M) -> Result<Self::Value, M::Error> {
while let Some(key) = map.next_key()? {
self.0.insert(key, map.next_value()?);
}
Ok(self.0)
}
}
de.deserialize_map(HashMapVisitor(self))
}
}
impl<T: DeserializeOverWith> DeserializeOverWith for ArcSwap<T> {
fn deserialize_over_with<'de, D: Deserializer<'de>>(self, de: D) -> Result<Self, D::Error> {
Arc::try_unwrap(self.into_inner())
.unwrap_or_else(|_| panic!("ArcSwap must have single owner during deserialization"))
.deserialize_over_with(de)
.map(IntoPointee::into_pointee)
}
}
// --- DeserializeOverHook // --- DeserializeOverHook
pub trait DeserializeOverHook: Sized { pub trait DeserializeOverHook: Sized {
fn deserialize_over_hook(self) -> Result<Self, de::Error> { Ok(self) } fn deserialize_over_hook(self) -> Result<Self, de::Error> { Ok(self) }
} }
impl<T: DeserializeOverHook> DeserializeOverHook for ArcSwap<T> {
fn deserialize_over_hook(self) -> Result<Self, toml::de::Error> {
Arc::try_unwrap(self.into_inner())
.unwrap_or_else(|_| panic!("ArcSwap must have single owner during hook"))
.deserialize_over_hook()
.map(IntoPointee::into_pointee)
}
}
// --- DeserializeOverSeed // --- DeserializeOverSeed
pub struct DeserializeOverSeed<T>(pub T); pub struct DeserializeOverSeed<T>(pub T);

View file

@ -16,7 +16,6 @@ workspace = true
yazi-config = { path = "../yazi-config", version = "26.2.2" } yazi-config = { path = "../yazi-config", version = "26.2.2" }
yazi-emulator = { path = "../yazi-emulator", version = "26.2.2" } yazi-emulator = { path = "../yazi-emulator", version = "26.2.2" }
yazi-macro = { path = "../yazi-macro", version = "26.2.2" } yazi-macro = { path = "../yazi-macro", version = "26.2.2" }
yazi-shared = { path = "../yazi-shared", version = "26.2.2" }
yazi-shim = { path = "../yazi-shim", version = "26.2.2" } yazi-shim = { path = "../yazi-shim", version = "26.2.2" }
yazi-tty = { path = "../yazi-tty", version = "26.2.2" } yazi-tty = { path = "../yazi-tty", version = "26.2.2" }

View file

@ -1,4 +1,4 @@
use tokio::sync::Semaphore; use tokio::sync::Semaphore;
use yazi_shared::RoCell; use yazi_shim::cell::RoCell;
pub static YIELD_TO_SUBPROCESS: RoCell<Semaphore> = RoCell::new(); pub static YIELD_TO_SUBPROCESS: RoCell<Semaphore> = RoCell::new();

View file

@ -4,8 +4,7 @@ use anyhow::Result;
use crossterm::{event::{DisableBracketedPaste, DisableFocusChange, DisableMouseCapture, EnableBracketedPaste, EnableFocusChange, EnableMouseCapture, KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags}, execute, queue, style::Print, terminal::{EnterAlternateScreen, LeaveAlternateScreen, SetTitle, disable_raw_mode, enable_raw_mode}}; use crossterm::{event::{DisableBracketedPaste, DisableFocusChange, DisableMouseCapture, EnableBracketedPaste, EnableFocusChange, EnableMouseCapture, KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags}, execute, queue, style::Print, terminal::{EnterAlternateScreen, LeaveAlternateScreen, SetTitle, disable_raw_mode, enable_raw_mode}};
use ratatui::{CompletedFrame, Frame, Terminal, backend::CrosstermBackend, buffer::Buffer, layout::Rect}; use ratatui::{CompletedFrame, Frame, Terminal, backend::CrosstermBackend, buffer::Buffer, layout::Rect};
use yazi_emulator::{Emulator, Mux, TMUX}; use yazi_emulator::{Emulator, Mux, TMUX};
use yazi_shared::SyncCell; use yazi_shim::{cell::SyncCell, crossterm::{If, RestoreBackground, RestoreCursor, SetBackground}};
use yazi_shim::crossterm::{If, RestoreBackground, RestoreCursor, SetBackground};
use yazi_tty::{TTY, TtyWriter}; use yazi_tty::{TTY, TtyWriter};
use crate::{TermOption, TermState}; use crate::{TermOption, TermState};

View file

@ -15,6 +15,7 @@ workspace = true
[dependencies] [dependencies]
yazi-macro = { path = "../yazi-macro", version = "26.2.2" } yazi-macro = { path = "../yazi-macro", version = "26.2.2" }
yazi-shared = { path = "../yazi-shared", version = "26.2.2" } yazi-shared = { path = "../yazi-shared", version = "26.2.2" }
yazi-shim = { path = "../yazi-shim", version = "26.2.2" }
# External dependencies # External dependencies
parking_lot = { workspace = true } parking_lot = { workspace = true }

View file

@ -3,6 +3,6 @@ yazi_macro::mod_flat!(handle tty);
#[cfg(windows)] #[cfg(windows)]
yazi_macro::mod_flat!(windows); yazi_macro::mod_flat!(windows);
pub static TTY: yazi_shared::RoCell<Tty> = yazi_shared::RoCell::new(); pub static TTY: yazi_shim::cell::RoCell<Tty> = yazi_shim::cell::RoCell::new();
pub fn init() { TTY.with(<_>::default); } pub fn init() { TTY.with(<_>::default); }

Some files were not shown because too many files have changed in this diff Show more