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])
- Hover cursor over the new file after copying/cutting/linking/hardlinking/extracting ([#3846], [#3854])
- Multi-file spotter ([#3733])
- New `app:theme` action that hot-reload user themes/flavors ([#3906])
- Dynamic open/opener Lua API ([#3901])
- Dynamic previewer Lua API ([#3891])
- 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
[#3894]: https://github.com/sxyazi/yazi/pull/3894
[#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]]
name = "p256"
version = "0.14.0-rc.8"
version = "0.14.0-rc.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44f0a10fe314869359cb2901342b045f4e5a962ef9febc006f03d2a8c848fe4c"
checksum = "8b97e3bf0465157ae90975ff52dbeb1362ba618924878c9f74c25baa27a65f9a"
dependencies = [
"ecdsa",
"elliptic-curve",
@ -2879,9 +2879,9 @@ dependencies = [
[[package]]
name = "p384"
version = "0.14.0-rc.8"
version = "0.14.0-rc.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b079e66810c55ab3d6ba424e056dc4aefcdb8046c8c3f3816142edbdd7af7721"
checksum = "437f30ebcb1e16ff48acead5f08bd69fbcdbc82421687bb48af5c315a0bfab03"
dependencies = [
"ecdsa",
"elliptic-curve",
@ -2893,9 +2893,9 @@ dependencies = [
[[package]]
name = "p521"
version = "0.14.0-rc.8"
version = "0.14.0-rc.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9eecc34c4c6e6596d5271fecf90ac4f16593fa198e77282214d0c22736aa9266"
checksum = "4e9fd792bab86ecf6249561752fb5a413511f999887107dd054bbda5143743d7"
dependencies = [
"base16ct",
"ecdsa",
@ -3795,9 +3795,9 @@ dependencies = [
[[package]]
name = "russh"
version = "0.60.0"
version = "0.60.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b530252dc3ff163b73a7e48c97b925450d2ca53edcb466a46ad0a231e45f998"
checksum = "d937f3f4a79bffd67fc12fd437785effdfc8b94edc89ab90392f9ac9e11cc9fc"
dependencies = [
"aes 0.8.4",
"bitflags 2.11.1",
@ -4898,9 +4898,9 @@ checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
[[package]]
name = "typenum"
version = "1.19.0"
version = "1.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de"
[[package]]
name = "ucd-trie"
@ -5884,6 +5884,7 @@ dependencies = [
"yazi-fs",
"yazi-macro",
"yazi-shared",
"yazi-shim",
"yazi-vfs",
]
@ -6016,6 +6017,7 @@ dependencies = [
"yazi-fs",
"yazi-macro",
"yazi-shared",
"yazi-shim",
]
[[package]]
@ -6030,6 +6032,7 @@ dependencies = [
"tracing",
"yazi-macro",
"yazi-shared",
"yazi-shim",
"yazi-tty",
]
@ -6081,6 +6084,7 @@ dependencies = [
"yazi-runner",
"yazi-scheduler",
"yazi-shared",
"yazi-shim",
"yazi-term",
"yazi-tty",
"yazi-vfs",
@ -6234,6 +6238,7 @@ dependencies = [
"yazi-fs",
"yazi-macro",
"yazi-shared",
"yazi-shim",
]
[[package]]
@ -6301,6 +6306,7 @@ dependencies = [
"uzers",
"windows-sys 0.61.2",
"yazi-macro",
"yazi-shim",
]
[[package]]
@ -6309,6 +6315,7 @@ version = "26.2.2"
dependencies = [
"arc-swap",
"crossterm 0.29.0",
"hashbrown 0.17.0",
"mlua",
"ratatui",
"serde",
@ -6331,7 +6338,6 @@ dependencies = [
"yazi-config",
"yazi-emulator",
"yazi-macro",
"yazi-shared",
"yazi-shim",
"yazi-tty",
]
@ -6346,6 +6352,7 @@ dependencies = [
"windows-sys 0.61.2",
"yazi-macro",
"yazi-shared",
"yazi-shim",
]
[[package]]
@ -6368,6 +6375,7 @@ dependencies = [
"yazi-macro",
"yazi-sftp",
"yazi-shared",
"yazi-shim",
]
[[package]]
@ -6387,6 +6395,7 @@ dependencies = [
"yazi-fs",
"yazi-macro",
"yazi-shared",
"yazi-shim",
"yazi-vfs",
]
@ -6409,6 +6418,7 @@ dependencies = [
"yazi-config",
"yazi-macro",
"yazi-shared",
"yazi-shim",
"yazi-tty",
]

View file

@ -10,7 +10,7 @@ license = "MIT"
authors = [ "sxyazi <sxyazi@gmail.com>" ]
homepage = "https://yazi-rs.github.io"
repository = "https://github.com/sxyazi/yazi"
rust-version = "1.92.0"
rust-version = "1.95.0"
[profile.dev]
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" ] }
ratatui = { version = "0.30.0", features = [ "serde", "unstable-rendered-line-info", "unstable-widget-ref" ] }
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"
serde = { version = "1.0.228", features = [ "derive" ] }
serde_json = "1.0.149"

12
flake.lock generated
View file

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

View file

@ -12,6 +12,7 @@ yazi_macro::mod_flat!(
resize
resume
stop
theme
title
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 yazi_binding::{Range, Style, cached_field};
use yazi_config::{Selectable, THEME};
use yazi_config::THEME;
use yazi_shared::{path::AsPath, url::UrlLike};
use super::Lives;
@ -111,7 +111,7 @@ impl UserData for File {
methods.add_method("style", |lua, me, ()| {
lua.named_registry_value::<AnyUserData>("cx")?.borrow_scoped(|core: &yazi_core::Core| {
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, ()| {

View file

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

View file

@ -38,7 +38,7 @@ impl UserData for Preference {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
// Display
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));
// Sorting

View file

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

View file

@ -7,7 +7,7 @@ use crossterm::{cursor::MoveTo, queue};
use image::DynamicImage;
use ratatui::layout::Rect;
use yazi_emulator::{CLOSE, ESCAPE, Emulator, START};
use yazi_shared::SyncCell;
use yazi_shim::cell::SyncCell;
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 yazi_config::YAZI;
use yazi_emulator::Dimension;
use yazi_shared::{LOG_LEVEL, RoCell, env_exists};
use yazi_shim::strum::IntoStr;
use yazi_shared::{LOG_LEVEL, env_exists};
use yazi_shim::{cell::RoCell, strum::IntoStr};
use crate::Adapter;

View file

@ -3,7 +3,8 @@ yazi_macro::mod_pub!(drivers);
yazi_macro::mod_flat!(adapter adapters icc image info);
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);

View file

@ -1,31 +1,30 @@
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 {
inner: &'static yazi_config::Icon,
v_text: Option<Value>,
v_style: Option<Value>,
inner: yazi_config::Icon,
}
impl Deref for 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 {
fn from(icon: &'static yazi_config::Icon) -> Self {
Self { inner: icon, v_text: None, v_style: None }
}
impl From<yazi_config::Icon> for Icon {
fn from(inner: yazi_config::Icon) -> Self { Self { inner } }
}
impl UserData for Icon {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
cached_field!(fields, text, |lua, me| lua.create_string(&me.text));
cached_field!(fields, style, |_, me| Ok(Style::from(me.style)));
impl IntoLua for Icon {
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
lua
.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 ratatui::style::Modifier;
use yazi_shared::SyncCell;
use yazi_shim::cell::SyncCell;
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-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-vfs = { path = "../yazi-vfs", version = "26.2.2" }
# External dependencies

View file

@ -18,9 +18,14 @@ fn main() -> Result<(), Box<dyn Error>> {
.semver(true)
.build()?,
)?
.add_instructions(&GitclBuilder::default().commit_date(true).sha(true).build()?)?
.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() {
return Ok(());
}

View file

@ -3,7 +3,7 @@ yazi_macro::mod_pub!(actions);
yazi_macro::mod_flat!(args boot);
use clap::Parser;
use yazi_shared::RoCell;
use yazi_shim::cell::RoCell;
pub static ARGS: RoCell<Args> = 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>> {
Emitter::default()
.add_instructions(&BuildBuilder::default().build_date(true).build()?)?
.add_instructions(&GitclBuilder::default().commit_date(true).sha(true).build()?)?
.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() {
return Ok(());
}

View file

@ -1,7 +1,7 @@
use proc_macro::TokenStream;
use quote::quote;
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};
@ -153,6 +153,41 @@ pub fn deserialize_over2(input: TokenStream) -> TokenStream {
.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)]
pub fn from_lua(input: TokenStream) -> TokenStream {
let DeriveInput { ident, generics, .. } = parse_macro_input!(input as DeriveInput);

View file

@ -82,9 +82,6 @@ where
D: Deserializer<'de>,
{
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 {
return Err(de::Error::custom(format!("invalid keymap layer const: {L}")));
@ -92,7 +89,7 @@ where
for action in &mut actions {
action.source = Source::Key;
if action.layer == Default::default() {
if action.layer == Layer::Null {
action.layer = layer;
}
}

View file

@ -21,7 +21,7 @@ pub struct Keymap {
impl Keymap {
pub fn get(&self, layer: Layer) -> &[Chord] {
match layer {
Layer::App => &[],
Layer::Null | Layer::App => &[],
Layer::Mgr => self.mgr.as_erased_slice(),
Layer::Tasks => self.tasks.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 yazi_shared::{RoCell, SyncCell};
use yazi_shim::toml::{DeserializeOver, DeserializeOverWith};
use yazi_shim::{cell::{RoCell, SyncCell}, toml::{DeserializeOver, DeserializeOverWith}};
use yazi_tty::TTY;
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<()> {
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)?;
if merge {
@ -59,8 +63,7 @@ fn try_init_flavor(light: bool, merge: bool) -> anyhow::Result<()> {
)?;
}
THEME.init(preset.reshape(light)?);
Ok(())
Ok(preset.reshape(light)?)
}
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 serde::Deserialize;
use arc_swap::ArcSwap;
use serde::{Deserialize, Deserializer, de};
use yazi_codegen::{DeserializeOver, DeserializeOver2};
use yazi_fs::{SortBy, SortFallback};
use yazi_shared::SyncCell;
use yazi_shim::{arc_swap::IntoPointee, cell::SyncCell};
use super::{MgrRatio, MouseEvents};
use crate::mgr::MgrLinemode;
#[derive(Debug, Deserialize, DeserializeOver, DeserializeOver2)]
pub struct Mgr {
@ -20,9 +20,22 @@ pub struct Mgr {
pub sort_fallback: SyncCell<SortFallback>,
// Display
pub linemode: MgrLinemode,
#[serde(deserialize_with = "deserialize_linemode")]
pub linemode: ArcSwap<String>,
pub show_hidden: SyncCell<bool>,
pub show_symlink: SyncCell<bool>,
pub scrolloff: SyncCell<u8>,
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 hashbrown::HashMap;
use serde::{Deserialize, Deserializer, de::{MapAccess, Visitor}};
use serde::{Deserialize, Deserializer};
use yazi_shim::{arc_swap::IntoPointee, toml::{DeserializeOverHook, DeserializeOverWith}};
use super::{OpenerRule, OpenerRules};
@ -80,23 +80,6 @@ impl DeserializeOverHook for Opener {
impl DeserializeOverWith for Opener {
fn deserialize_over_with<'de, D: Deserializer<'de>>(self, de: D) -> Result<Self, D::Error> {
struct V(HashMap<String, Arc<OpenerRules>>);
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()))
Ok(Self(self.0.deserialize_over_with(de)?))
}
}

View file

@ -3,7 +3,7 @@ use std::{borrow::Cow, ops::Deref, sync::Arc};
use hashbrown::HashSet;
use serde::Deserialize;
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}};
@ -15,14 +15,14 @@ pub struct Fetcher {
pub idx: u8,
#[serde(flatten)]
pub selector: Selector,
pub run: Action,
pub run: Cmd,
#[serde(default)]
pub prio: Priority,
pub group: String,
}
impl Deref for Fetcher {
type Target = Action;
type Target = Cmd;
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 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}};
@ -14,7 +14,7 @@ pub struct Preloader {
pub idx: u8,
#[serde(flatten)]
pub selector: Selector,
pub run: Action,
pub run: Cmd,
#[serde(default)]
pub next: bool,
#[serde(default)]
@ -22,7 +22,7 @@ pub struct Preloader {
}
impl Deref for Preloader {
type Target = Action;
type Target = Cmd;
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 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}};
@ -12,11 +12,11 @@ pub struct Previewer {
pub id: Id,
#[serde(flatten)]
pub selector: Selector,
pub run: Action,
pub run: Cmd,
}
impl Deref for Previewer {
type Target = Action;
type Target = Cmd;
#[inline]
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 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}};
@ -12,11 +12,11 @@ pub struct Spotter {
pub id: Id,
#[serde(flatten)]
pub selector: Selector,
pub run: Action,
pub run: Cmd,
}
impl Deref for Spotter {
type Target = Action;
type Target = Cmd;
#[inline]
fn deref(&self) -> &Self::Target { &self.run }

View file

@ -1,43 +1,17 @@
use std::ops::Deref;
use serde::Deserialize;
use yazi_codegen::{DeserializeOver, DeserializeOver2};
use yazi_codegen::{DeserializeOver, DeserializeOver2, Overlay};
use yazi_fs::File;
use super::Is;
use crate::{Pattern, Selectable, Selector, Style};
use super::FiletypeRules;
use crate::{Selectable, Style};
#[derive(Deserialize, DeserializeOver, DeserializeOver2)]
#[derive(Deserialize, DeserializeOver, DeserializeOver2, Overlay)]
pub struct Filetype {
rules: Vec<FiletypeRule>,
rules: FiletypeRules,
}
impl Deref for Filetype {
type Target = Vec<FiletypeRule>;
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),
}
impl Filetype {
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)
}
}

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 serde::{Deserialize, Serialize};
use toml::{Spanned, de::DeTable};
use yazi_codegen::{DeserializeOver, DeserializeOver2};
use yazi_codegen::{DeserializeOver, DeserializeOver2, Overlay};
use yazi_fs::Xdg;
use yazi_shim::toml::deserialize_spanned;
use crate::error_with_input;
#[derive(Default, Deserialize, DeserializeOver, DeserializeOver2, Serialize)]
#[derive(Default, Deserialize, DeserializeOver, DeserializeOver2, Overlay, Serialize)]
pub struct Flavor {
#[serde(default)]
pub dark: ArcSwap<String>,

View file

@ -1,43 +1,41 @@
use anyhow::Result;
use hashbrown::HashMap;
use serde::{Deserialize, Deserializer};
use yazi_codegen::DeserializeOver2;
use serde::Deserialize;
use yazi_codegen::{DeserializeOver2, Overlay};
use yazi_fs::File;
use yazi_shared::{Condition, url::UrlLike};
use yazi_shared::{NonEmptyString, url::UrlLike};
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 {
globs: Vec<IconPat>,
globs: IconGlobs,
#[serde(default)]
prepend_globs: Vec<IconPat>,
prepend_globs: Vec<IconGlob>,
#[serde(default)]
append_globs: Vec<IconPat>,
append_globs: Vec<IconGlob>,
#[serde(deserialize_with = "deserialize_named_icons")]
dirs: HashMap<String, I>,
#[serde(default, deserialize_with = "deserialize_named_icons")]
prepend_dirs: HashMap<String, I>,
#[serde(default, deserialize_with = "deserialize_named_icons")]
append_dirs: HashMap<String, I>,
dirs: IconNames,
#[serde(default, deserialize_with = "deserialize_icon_names")]
prepend_dirs: HashMap<NonEmptyString, I>,
#[serde(default, deserialize_with = "deserialize_icon_names")]
append_dirs: HashMap<NonEmptyString, I>,
#[serde(deserialize_with = "deserialize_named_icons")]
files: HashMap<String, I>,
#[serde(default, deserialize_with = "deserialize_named_icons")]
prepend_files: HashMap<String, I>,
#[serde(default, deserialize_with = "deserialize_named_icons")]
append_files: HashMap<String, I>,
files: IconNames,
#[serde(default, deserialize_with = "deserialize_icon_names")]
prepend_files: HashMap<NonEmptyString, I>,
#[serde(default, deserialize_with = "deserialize_icon_names")]
append_files: HashMap<NonEmptyString, I>,
#[serde(deserialize_with = "deserialize_named_icons")]
exts: HashMap<String, I>,
#[serde(default, deserialize_with = "deserialize_named_icons")]
prepend_exts: HashMap<String, I>,
#[serde(default, deserialize_with = "deserialize_named_icons")]
append_exts: HashMap<String, I>,
exts: IconNames,
#[serde(default, deserialize_with = "deserialize_icon_names")]
prepend_exts: HashMap<NonEmptyString, I>,
#[serde(default, deserialize_with = "deserialize_icon_names")]
append_exts: HashMap<NonEmptyString, I>,
conds: Vec<IconCond>,
conds: IconConds,
#[serde(default)]
prepend_conds: Vec<IconCond>,
#[serde(default)]
@ -45,109 +43,49 @@ pub struct Icon {
}
impl Icon {
pub fn matches(&self, file: &File, hovered: bool) -> Option<&I> {
if let Some(i) = self.match_by_glob(file) {
pub fn matches(&self, file: &File, hovered: bool) -> Option<I> {
if let Some(i) = self.globs.matches(file) {
return Some(i);
}
if let Some(i) = self.match_by_name(file) {
return Some(i);
let name = file.name().unwrap_or_default();
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 {
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 {
globs: mix(self.prepend_globs, self.globs, self.append_globs),
dirs: self.append_dirs.into_iter().chain(self.dirs).chain(self.prepend_dirs).collect(),
files: self.append_files.into_iter().chain(self.files).chain(self.prepend_files).collect(),
exts: self.append_exts.into_iter().chain(self.exts).chain(self.prepend_exts).collect(),
conds: mix(self.prepend_conds, self.conds, self.append_conds),
globs: mix(self.prepend_globs, self.globs.unwrap_unchecked(), self.append_globs).into(),
dirs: dirs.into(),
files: files.into(),
exts: exts.into(),
conds: mix(self.prepend_conds, self.conds.unwrap_unchecked(), self.append_conds).into(),
..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 arc_swap::ArcSwap;
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_shared::SyncCell;
use yazi_shim::arc_swap::IntoPointee;
use yazi_shim::{arc_swap::IntoPointee, cell::SyncCell};
use super::{Filetype, Flavor, Icon};
use crate::{Style, normalize_path};
#[derive(Deserialize, DeserializeOver, DeserializeOver1)]
#[derive(Deserialize, DeserializeOver, DeserializeOver1, Overlay)]
pub struct Theme {
pub flavor: Flavor,
pub app: App,
@ -35,12 +34,12 @@ pub struct Theme {
pub icon: Icon,
}
#[derive(Deserialize, DeserializeOver, DeserializeOver2)]
#[derive(Deserialize, DeserializeOver, DeserializeOver2, Overlay)]
pub struct App {
pub overall: SyncCell<Style>,
}
#[derive(Deserialize, DeserializeOver, DeserializeOver2)]
#[derive(Deserialize, DeserializeOver, DeserializeOver2, Overlay)]
pub struct Mgr {
pub cwd: SyncCell<Style>,
@ -72,7 +71,7 @@ pub struct Mgr {
pub syntect_theme: ArcSwap<PathBuf>,
}
#[derive(Deserialize, DeserializeOver, DeserializeOver2)]
#[derive(Deserialize, DeserializeOver, DeserializeOver2, Overlay)]
pub struct Tabs {
pub active: SyncCell<Style>,
pub inactive: SyncCell<Style>,
@ -87,7 +86,7 @@ pub struct TabsSep {
pub close: String,
}
#[derive(Deserialize, DeserializeOver, DeserializeOver2)]
#[derive(Deserialize, DeserializeOver, DeserializeOver2, Overlay)]
pub struct Mode {
pub normal_main: SyncCell<Style>,
pub normal_alt: SyncCell<Style>,
@ -99,7 +98,7 @@ pub struct Mode {
pub unset_alt: SyncCell<Style>,
}
#[derive(Deserialize, DeserializeOver, DeserializeOver2)]
#[derive(Deserialize, DeserializeOver, DeserializeOver2, Overlay)]
pub struct Indicator {
pub parent: SyncCell<Style>,
pub current: SyncCell<Style>,
@ -113,7 +112,7 @@ pub struct IndicatorPadding {
pub close: String,
}
#[derive(Deserialize, DeserializeOver, DeserializeOver2)]
#[derive(Deserialize, DeserializeOver, DeserializeOver2, Overlay)]
pub struct Status {
pub overall: SyncCell<Style>,
pub sep_left: ArcSwap<StatusSep>,
@ -138,7 +137,7 @@ pub struct StatusSep {
pub close: String,
}
#[derive(Deserialize, DeserializeOver, DeserializeOver2)]
#[derive(Deserialize, DeserializeOver, DeserializeOver2, Overlay)]
pub struct Which {
#[serde(deserialize_with = "deserialize_which_cols")]
pub cols: SyncCell<u8>,
@ -151,7 +150,7 @@ pub struct Which {
pub separator_style: SyncCell<Style>,
}
#[derive(Deserialize, DeserializeOver, DeserializeOver2)]
#[derive(Deserialize, DeserializeOver, DeserializeOver2, Overlay)]
pub struct Confirm {
pub border: SyncCell<Style>,
pub title: SyncCell<Style>,
@ -160,10 +159,10 @@ pub struct Confirm {
pub btn_yes: 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 border: SyncCell<Style>,
pub title: SyncCell<Style>,
@ -172,7 +171,7 @@ pub struct Spot {
pub tbl_cell: SyncCell<Style>,
}
#[derive(Deserialize, DeserializeOver, DeserializeOver2)]
#[derive(Deserialize, DeserializeOver, DeserializeOver2, Overlay)]
pub struct Notify {
pub title_info: SyncCell<Style>,
pub title_warn: SyncCell<Style>,
@ -183,14 +182,14 @@ pub struct Notify {
pub icon_error: ArcSwap<String>,
}
#[derive(Deserialize, DeserializeOver, DeserializeOver2)]
#[derive(Deserialize, DeserializeOver, DeserializeOver2, Overlay)]
pub struct Pick {
pub border: SyncCell<Style>,
pub active: SyncCell<Style>,
pub inactive: SyncCell<Style>,
}
#[derive(Deserialize, DeserializeOver, DeserializeOver2)]
#[derive(Deserialize, DeserializeOver, DeserializeOver2, Overlay)]
pub struct Input {
pub border: SyncCell<Style>,
pub title: SyncCell<Style>,
@ -198,7 +197,7 @@ pub struct Input {
pub selected: SyncCell<Style>,
}
#[derive(Deserialize, DeserializeOver, DeserializeOver2)]
#[derive(Deserialize, DeserializeOver, DeserializeOver2, Overlay)]
pub struct Cmp {
pub border: SyncCell<Style>,
pub active: SyncCell<Style>,
@ -209,14 +208,14 @@ pub struct Cmp {
pub icon_command: ArcSwap<String>,
}
#[derive(Deserialize, DeserializeOver, DeserializeOver2)]
#[derive(Deserialize, DeserializeOver, DeserializeOver2, Overlay)]
pub struct Tasks {
pub border: SyncCell<Style>,
pub title: SyncCell<Style>,
pub hovered: SyncCell<Style>,
}
#[derive(Deserialize, DeserializeOver, DeserializeOver2)]
#[derive(Deserialize, DeserializeOver, DeserializeOver2, Overlay)]
pub struct Help {
pub on: SyncCell<Style>,
pub run: SyncCell<Style>,

View file

@ -12,6 +12,10 @@ rust-version.workspace = true
[lints]
workspace = true
[features]
default = [ "vendored-lua" ]
vendored-lua = [ "mlua/vendored" ]
[dependencies]
yazi-adapter = { path = "../yazi-adapter", 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 strum::EnumString;
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)]
pub struct PluginOpt {
@ -27,7 +27,7 @@ impl TryFrom<ActionCow> for PluginOpt {
let args = if let Ok(s) = a.second() {
let (words, last) = yazi_shared::shell::unix::split(s, true)?;
Action::parse_args(words, last)?
Cmd::parse_args(words, last)?
} else {
Default::default()
};

View file

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

View file

@ -1,11 +1,13 @@
use std::error::Error;
use std::{env, error::Error};
use vergen_gitcl::{Emitter, GitclBuilder};
fn main() -> Result<(), Box<dyn Error>> {
Emitter::default()
.add_instructions(&GitclBuilder::default().commit_date(true).sha(true).build()?)?
.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");
}
Ok(())
}

View file

@ -7,7 +7,8 @@ use serde::{Deserialize, Serialize};
use tokio::{io::AsyncWriteExt, select, sync::mpsc, task::JoinHandle, time};
use tracing::error;
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}};

View file

@ -5,7 +5,8 @@ use mlua::Function;
use parking_lot::RwLock;
use yazi_boot::BOOT;
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}};

View file

@ -3,7 +3,8 @@ use std::time::Duration;
use tokio::{pin, select, sync::mpsc};
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
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}};

View file

@ -5,7 +5,8 @@ use hashbrown::HashMap;
use parking_lot::RwLock;
use tokio::{io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, select, sync::mpsc::{self, UnboundedReceiver}, task::JoinHandle, time};
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}};

View file

@ -6,7 +6,8 @@ use parking_lot::RwLock;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter};
use yazi_boot::BOOT;
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;

View file

@ -15,6 +15,7 @@ workspace = true
[dependencies]
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-tty = { path = "../yazi-tty", version = "26.2.2" }
# External dependencies

View file

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

View file

@ -3,7 +3,7 @@ use std::borrow::Cow;
use anyhow::Result;
use tracing::error;
use yazi_macro::time;
use yazi_shared::SyncCell;
use yazi_shim::cell::SyncCell;
use yazi_tty::TTY;
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-scheduler = { path = "../yazi-scheduler", 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-tty = { path = "../yazi-tty", 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) {
let chunks =
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()
.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()
.render(chunks[1], buf);
}

View file

@ -16,6 +16,7 @@ impl<'a> Executor<'a> {
pub(super) fn execute(&mut self, action: ActionCow) -> Result<Data> {
match action.layer {
Layer::Null => self.null(action),
Layer::App => self.app(action),
Layer::Mgr => self.mgr(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> {
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!(lua);
on!(deprecate);
on!(theme);
on!(quit);
match &*action.name {

View file

@ -5,7 +5,8 @@ use crossterm::style::{Color, Print, ResetColor, SetForegroundColor};
use tracing_appender::non_blocking::WorkerGuard;
use tracing_subscriber::EnvFilter;
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();

View file

@ -26,7 +26,7 @@ impl<'a> Router<'a> {
use Layer as L;
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 => {
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 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};

View file

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

View file

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

View file

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

View file

@ -31,26 +31,25 @@ function M:spot(job)
end
function M:spot_base(job)
local cha = job.file.cha
local spotter, previewer = nil, nil
local fetchers, preloaders = {}, {}
local cha, pair = job.file.cha, { file = job.file, mime = job.mime }
local spotter, previewer, fetchers, preloaders = nil, nil, {}, {}
for _, v in pairs(rt.plugin.spotters:match(job)) do
for _, v in pairs(rt.plugin.spotters:match(pair)) do
spotter = v
break
end
for _, v in pairs(rt.plugin.previewers:match(job)) do
for _, v in pairs(rt.plugin.previewers:match(pair)) do
previewer = v
break
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
end
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
end
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_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_symlink" => m.show_symlink.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_boot::BOOT;
use yazi_macro::plugin_preset as preset;
use yazi_shared::RoCell;
use yazi_shim::cell::RoCell;
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_config::THEME;
use crate::LUA;
pub fn compose() -> Composer<ComposerGet, ComposerSet> {
fn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {
match key {
@ -30,6 +32,8 @@ pub fn compose() -> Composer<ComposerGet, ComposerSet> {
Composer::new(get, set)
}
pub fn reset() -> mlua::Result<()> { LUA.globals().raw_set("th", compose()) }
fn app() -> Composer<ComposerGet, ComposerSet> {
fn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {
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_no" => Style::from(&t.btn_no).into_lua(lua),
b"btn_labels" => lua
.create_sequence_from([
lua.create_string(&t.btn_labels[0])?,
lua.create_string(&t.btn_labels[1])?,
])?
.into_lua(lua),
b"btn_labels" => {
let labels = t.btn_labels.load();
lua
.create_sequence_from([lua.create_string(&labels[0])?, lua.create_string(&labels[1])?])?
.into_lua(lua)
}
_ => Ok(Value::Nil),
}

View file

@ -3,14 +3,14 @@ use tokio::sync::mpsc;
use yazi_binding::deprecate;
use yazi_dds::Sendable;
use yazi_macro::emit;
use yazi_shared::{Layer, Source, event::Action};
use yazi_shared::{Layer, Source, event::{Action, Cmd}};
use super::Utils;
impl Utils {
pub(super) fn emit(lua: &Lua) -> mlua::Result<Function> {
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)?;
Ok(emit!(Call(action)))
})
@ -20,8 +20,7 @@ impl Utils {
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");
emit!(Call(Action {
name: name.into(),
args: Sendable::table_to_args(lua, args)?,
cmd: Cmd { name: name.into(), args: Sendable::table_to_args(lua, args)? },
layer: Layer::Mgr,
source: Source::Emit,
}));
@ -31,7 +30,7 @@ impl Utils {
pub(super) fn exec(lua: &Lua) -> mlua::Result<Function> {
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)?;
let (tx, mut rx) = mpsc::unbounded_channel();

View file

@ -12,6 +12,10 @@ rust-version.workspace = true
[lints]
workspace = true
[features]
default = [ "vendored-lua" ]
vendored-lua = [ "mlua/vendored" ]
[dependencies]
yazi-binding = { path = "../yazi-binding", 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-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" }
# External dependencies
anyhow = { workspace = true }

View file

@ -2,7 +2,7 @@ yazi_macro::mod_pub!(entry fetcher loader preloader previewer);
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<()>) {
crate::loader::init();

View file

@ -7,7 +7,8 @@ use parking_lot::RwLock;
use yazi_boot::BOOT;
use yazi_fs::provider::local::Local;
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;

View file

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

View file

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

View file

@ -1,6 +1,7 @@
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]
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 hashbrown::HashMap;
use anyhow::{Result, bail};
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)]
pub struct Action {
pub name: SStr,
pub args: HashMap<DataKey, Data>,
pub cmd: Cmd,
pub layer: Layer,
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 {
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
N: Into<SStr>,
{
let cow: SStr = name.into();
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 {
Cow::Borrowed(s) => Cow::Borrowed(&s[i + 1..]),
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
where
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
@ -47,13 +55,13 @@ impl Action {
D: Into<Data>,
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 =
args.into_iter().enumerate().map(|(i, a)| (DataKey::Integer(i as i64), a.into())).collect();
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() }
@ -217,31 +225,6 @@ impl Action {
}
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 {
@ -271,13 +254,11 @@ impl FromStr for Action {
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!("action name cannot be empty");
}
let cmd = Cmd::from_str(s)?;
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)
}
}

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>,
{
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 tokio::sync::mpsc;
use yazi_shim::cell::RoCell;
use super::ActionCow;
use crate::RoCell;
static TX: RoCell<mpsc::UnboundedSender<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>>;

View file

@ -20,6 +20,7 @@ use strum::{Display, EnumString, FromRepr, IntoStaticStr};
#[repr(u8)]
pub enum Layer {
#[default]
Null,
App,
Mgr,
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_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() {
LOCAL_SET.with(tokio::task::LocalSet::new);

View file

@ -1,5 +1,4 @@
use tokio::task::LocalSet;
use crate::RoCell;
use yazi_shim::cell::RoCell;
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};
@ -20,6 +20,16 @@ impl Deref for NonEmptyString {
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 {
#[inline]
fn as_ref(&self) -> &str { &self.0 }

View file

@ -1,5 +1,5 @@
#[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)]
pub fn hostname() -> Option<&'static str> {

View file

@ -1,8 +1,8 @@
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>>,
> = crate::RoCell::new();
> = yazi_shim::cell::RoCell::new();
pub(super) fn init() { SYMBOLS.with(<_>::default); }

View file

@ -12,12 +12,17 @@ rust-version.workspace = true
[lints]
workspace = true
[features]
default = [ "vendored-lua" ]
vendored-lua = [ "mlua/vendored" ]
[dependencies]
yazi-macro = { path = "../yazi-macro", version = "26.2.2" }
# External dependencies
arc-swap = { workspace = true }
crossterm = { workspace = true }
hashbrown = { workspace = true }
mlua = { workspace = true }
ratatui = { 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);

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 crate::arc_swap::IntoPointee;
pub trait DeserializeOver: DeserializeOverWith + DeserializeOverHook {
fn deserialize_over(self, input: &str) -> Result<Self, de::Error> {
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>;
}
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
pub trait DeserializeOverHook: Sized {
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
pub struct DeserializeOverSeed<T>(pub T);

View file

@ -16,7 +16,6 @@ workspace = true
yazi-config = { path = "../yazi-config", version = "26.2.2" }
yazi-emulator = { path = "../yazi-emulator", 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-tty = { path = "../yazi-tty", version = "26.2.2" }

View file

@ -1,4 +1,4 @@
use tokio::sync::Semaphore;
use yazi_shared::RoCell;
use yazi_shim::cell::RoCell;
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 ratatui::{CompletedFrame, Frame, Terminal, backend::CrosstermBackend, buffer::Buffer, layout::Rect};
use yazi_emulator::{Emulator, Mux, TMUX};
use yazi_shared::SyncCell;
use yazi_shim::crossterm::{If, RestoreBackground, RestoreCursor, SetBackground};
use yazi_shim::{cell::SyncCell, crossterm::{If, RestoreBackground, RestoreCursor, SetBackground}};
use yazi_tty::{TTY, TtyWriter};
use crate::{TermOption, TermState};

View file

@ -15,6 +15,7 @@ workspace = true
[dependencies]
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" }
# External dependencies
parking_lot = { workspace = true }

View file

@ -3,6 +3,6 @@ yazi_macro::mod_flat!(handle tty);
#[cfg(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); }

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