mirror of
https://github.com/sxyazi/yazi.git
synced 2026-05-13 08:16:40 +00:00
feat: custom styles for plugins (#3934)
This commit is contained in:
parent
5ad1e003f2
commit
10fc76db07
25 changed files with 522 additions and 84 deletions
|
|
@ -22,6 +22,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
- New `app:theme` action that hot-reload user themes/flavors ([#3906])
|
||||
- Dynamic open/opener Lua API ([#3901])
|
||||
- Dynamic previewer Lua API ([#3891])
|
||||
- Custom styles for plugins ([#3934])
|
||||
- Vim-like `lua` action that runs an inline Lua snippet ([#3813])
|
||||
- Certificate authentication for SFTP VFS provider ([#3716])
|
||||
- New `hovered` condition specifying different icons for hovered files ([#3728])
|
||||
|
|
@ -1709,3 +1710,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
[#3894]: https://github.com/sxyazi/yazi/pull/3894
|
||||
[#3901]: https://github.com/sxyazi/yazi/pull/3901
|
||||
[#3906]: https://github.com/sxyazi/yazi/pull/3906
|
||||
[#3934]: https://github.com/sxyazi/yazi/pull/3934
|
||||
|
|
|
|||
70
Cargo.lock
generated
70
Cargo.lock
generated
|
|
@ -1044,7 +1044,7 @@ dependencies = [
|
|||
"cfg-if",
|
||||
"cpufeatures 0.2.17",
|
||||
"curve25519-dalek-derive",
|
||||
"digest 0.11.2",
|
||||
"digest 0.11.3",
|
||||
"fiat-crypto",
|
||||
"rustc_version",
|
||||
"subtle",
|
||||
|
|
@ -1262,9 +1262,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.11.2"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c"
|
||||
checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
|
||||
dependencies = [
|
||||
"block-buffer 0.12.0",
|
||||
"const-oid 0.10.2",
|
||||
|
|
@ -1315,7 +1315,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "91bbdd377139884fafcad8dc43a760a3e1e681aa26db910257fa6535b70e1829"
|
||||
dependencies = [
|
||||
"der",
|
||||
"digest 0.11.2",
|
||||
"digest 0.11.3",
|
||||
"elliptic-curve",
|
||||
"rfc6979",
|
||||
"signature",
|
||||
|
|
@ -1364,7 +1364,7 @@ dependencies = [
|
|||
"base16ct",
|
||||
"crypto-bigint",
|
||||
"crypto-common 0.2.1",
|
||||
"digest 0.11.2",
|
||||
"digest 0.11.3",
|
||||
"hkdf",
|
||||
"hybrid-array",
|
||||
"once_cell",
|
||||
|
|
@ -1896,7 +1896,7 @@ version = "0.13.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f"
|
||||
dependencies = [
|
||||
"digest 0.11.2",
|
||||
"digest 0.11.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2209,11 +2209,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "kqueue-sys"
|
||||
version = "1.0.4"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b"
|
||||
checksum = "a7b65860415f949f23fa882e669f2dbd4a0f0eeb1acdd56790b30494afd7da2f"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"bitflags 2.11.1",
|
||||
"libc",
|
||||
]
|
||||
|
||||
|
|
@ -3002,7 +3002,7 @@ version = "0.13.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629"
|
||||
dependencies = [
|
||||
"digest 0.11.2",
|
||||
"digest 0.11.3",
|
||||
"hmac 0.13.0",
|
||||
]
|
||||
|
||||
|
|
@ -3323,18 +3323,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "profiling"
|
||||
version = "1.0.17"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773"
|
||||
checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5"
|
||||
dependencies = [
|
||||
"profiling-procmacros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "profiling-procmacros"
|
||||
version = "1.0.17"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b"
|
||||
checksum = "4488a4a36b9a4ba6b9334a32a39971f77c1436ec82c38707bce707699cc3bbcb"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
|
|
@ -3379,9 +3379,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
|||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.39.2"
|
||||
version = "0.39.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d"
|
||||
checksum = "721da970c312655cde9b4ffe0547f20a8494866a4af5ff51f18b7c633d0c870b"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
|
@ -3769,7 +3769,7 @@ dependencies = [
|
|||
"const-oid 0.10.2",
|
||||
"crypto-bigint",
|
||||
"crypto-primes",
|
||||
"digest 0.11.2",
|
||||
"digest 0.11.3",
|
||||
"pkcs1",
|
||||
"pkcs8",
|
||||
"rand_core 0.10.1",
|
||||
|
|
@ -4118,9 +4118,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "3.18.0"
|
||||
version = "3.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f"
|
||||
checksum = "f05839ce67618e14a09b286535c0d9c94e85ef25469b0e13cb4f844e5593eb19"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"chrono",
|
||||
|
|
@ -4137,9 +4137,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_with_macros"
|
||||
version = "3.18.0"
|
||||
version = "3.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65"
|
||||
checksum = "cf2ebbe86054f9b45bc3881e865683ccfaccce97b9b4cb53f3039d67f355a334"
|
||||
dependencies = [
|
||||
"darling 0.23.0",
|
||||
"proc-macro2",
|
||||
|
|
@ -4149,9 +4149,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serdect"
|
||||
version = "0.4.2"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9af4a3e75ebd5599b30d4de5768e00b5095d518a79fefc3ecbaf77e665d1ec06"
|
||||
checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e"
|
||||
dependencies = [
|
||||
"base16ct",
|
||||
"serde",
|
||||
|
|
@ -4176,7 +4176,7 @@ checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214"
|
|||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.3.0",
|
||||
"digest 0.11.2",
|
||||
"digest 0.11.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -4198,7 +4198,7 @@ checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
|
|||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.3.0",
|
||||
"digest 0.11.2",
|
||||
"digest 0.11.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -4207,7 +4207,7 @@ version = "0.11.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be176f1a57ce4e3d31c1a166222d9768de5954f811601fb7ca06fc8203905ce1"
|
||||
dependencies = [
|
||||
"digest 0.11.2",
|
||||
"digest 0.11.3",
|
||||
"keccak",
|
||||
]
|
||||
|
||||
|
|
@ -4281,11 +4281,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "signature"
|
||||
version = "3.0.0-rc.10"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f1880df446116126965eeec169136b2e0251dba37c6223bcc819569550edea3"
|
||||
checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5"
|
||||
dependencies = [
|
||||
"digest 0.11.2",
|
||||
"digest 0.11.3",
|
||||
"rand_core 0.10.1",
|
||||
]
|
||||
|
||||
|
|
@ -4312,9 +4312,9 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
|||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "1.0.2"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
|
||||
checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
|
|
@ -4696,9 +4696,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.52.1"
|
||||
version = "1.52.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6"
|
||||
checksum = "110a78583f19d5cdb2c5ccf321d1290344e71313c6c37d43520d386027d18386"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"libc",
|
||||
|
|
@ -4861,9 +4861,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "trash"
|
||||
version = "5.2.5"
|
||||
version = "5.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9b93a14fcf658568eb11b3ac4cb406822e916e2c55cdebc421beeb0bd7c94d8"
|
||||
checksum = "7602e0c7d66ec2d92a8c917219fbc7894039efa2063b9064260110828a356f46"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"libc",
|
||||
|
|
|
|||
|
|
@ -67,11 +67,11 @@ russh = { version = "0.60.2", default-features = false, features =
|
|||
scopeguard = "1.2.0"
|
||||
serde = { version = "1.0.228", features = [ "derive" ] }
|
||||
serde_json = "1.0.149"
|
||||
serde_with = "3.18.0"
|
||||
serde_with = "3.19.0"
|
||||
strum = { version = "0.28.0", features = [ "derive" ] }
|
||||
syntect = { version = "5.3.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
|
||||
thiserror = "2.0.18"
|
||||
tokio = { version = "1.52.1", features = [ "full" ] }
|
||||
tokio = { version = "1.52.2", features = [ "full" ] }
|
||||
tokio-stream = "0.1.18"
|
||||
tokio-util = "0.7.18"
|
||||
toml = { version = "1.1.2" }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
mod macros;
|
||||
|
||||
yazi_macro::mod_pub!(config elements process);
|
||||
yazi_macro::mod_pub!(config elements process theme);
|
||||
|
||||
yazi_macro::mod_flat!(access calculator cha chan chord_cow composer error fd file handle icon id image input iter layer mouse path permit range runtime scheme selector stage style url utils);
|
||||
|
|
|
|||
18
yazi-binding/src/theme/custom_field.rs
Normal file
18
yazi-binding/src/theme/custom_field.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
use mlua::IntoLua;
|
||||
|
||||
use crate::Style;
|
||||
|
||||
pub struct CustomField(yazi_config::theme::CustomField);
|
||||
|
||||
impl CustomField {
|
||||
pub fn new(inner: impl Into<yazi_config::theme::CustomField>) -> Self { Self(inner.into()) }
|
||||
}
|
||||
|
||||
impl IntoLua for CustomField {
|
||||
fn into_lua(self, lua: &mlua::Lua) -> mlua::Result<mlua::Value> {
|
||||
match self.0 {
|
||||
yazi_config::theme::CustomField::Style(style) => Style::from(style).into_lua(lua),
|
||||
yazi_config::theme::CustomField::String(s) => s.into_lua(lua),
|
||||
}
|
||||
}
|
||||
}
|
||||
28
yazi-binding/src/theme/custom_section.rs
Normal file
28
yazi-binding/src/theme/custom_section.rs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
use mlua::{IntoLua, MetaMethod, UserData, UserDataMethods, Value};
|
||||
use yazi_shared::SnakeCasedString;
|
||||
|
||||
use crate::theme::CustomField;
|
||||
|
||||
pub struct CustomSection {
|
||||
inner: Arc<HashMap<SnakeCasedString, yazi_config::theme::CustomField>>,
|
||||
}
|
||||
|
||||
impl CustomSection {
|
||||
pub fn new(inner: Arc<HashMap<SnakeCasedString, yazi_config::theme::CustomField>>) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for CustomSection {
|
||||
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_meta_method(MetaMethod::Index, |lua, me, key: mlua::String| {
|
||||
match me.inner.get(&*key.to_str()?) {
|
||||
Some(value) => CustomField::new(value).into_lua(lua),
|
||||
None => Ok(Value::Nil),
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
1
yazi-binding/src/theme/mod.rs
Normal file
1
yazi-binding/src/theme/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
yazi_macro::mod_flat!(custom_field custom_section);
|
||||
|
|
@ -165,7 +165,7 @@ impl Actions {
|
|||
fn file1_output() -> String {
|
||||
use std::io::Write;
|
||||
|
||||
let p = Xdg::temp_dir().join(format!(".debug-{}.tmp", timestamp_us()));
|
||||
let p = env::temp_dir().join(format!(".yazi-debug-{}.tmp", timestamp_us()));
|
||||
std::fs::File::create_new(&p).map(|mut f| f.write_all(b"Hello, World!")).ok();
|
||||
|
||||
let program = env::var_os("YAZI_FILE_ONE").unwrap_or("file".into());
|
||||
|
|
|
|||
|
|
@ -24,22 +24,38 @@ pub fn deserialize_over1(input: TokenStream) -> TokenStream {
|
|||
let visitor_generics = generics_with_de(&generics);
|
||||
let (impl_visitor_generics, ..) = visitor_generics.split_for_impl();
|
||||
|
||||
let fields: Vec<_> = named_fields(data).into_iter().map(|f| f.ident.unwrap()).collect();
|
||||
let match_arms = fields.iter().map(|f| {
|
||||
let name = ident_name(f);
|
||||
quote! { #name => self.0.#f = map.next_value_seed(DeserializeOverSeed(self.0.#f))?, }
|
||||
let (flatten_fields, normal_fields): (Vec<_>, Vec<_>) =
|
||||
named_fields(data).into_iter().partition(|f| has_serde_attr(&f.attrs, "flatten"));
|
||||
|
||||
let field_hooks: Vec<_> = flatten_fields
|
||||
.iter()
|
||||
.chain(&normal_fields)
|
||||
.map(|f| {
|
||||
let ident = f.ident.as_ref().unwrap();
|
||||
quote! { #ident: deserialized.#ident.deserialize_over_hook().map_err(Error::custom)? }
|
||||
})
|
||||
.collect();
|
||||
|
||||
let normal_arms = normal_fields.into_iter().map(|f| {
|
||||
let ident = f.ident.unwrap();
|
||||
let name = ident_name(&ident);
|
||||
quote! { #name => self.0.#ident = map.next_value_seed(DeserializeOverSeed(self.0.#ident))? }
|
||||
});
|
||||
|
||||
let hook_fields = fields.iter().map(|f| {
|
||||
quote! { #f: deserialized.#f.deserialize_over_hook().map_err(Error::custom)? }
|
||||
});
|
||||
let flatten_arm = match flatten_fields.into_iter().next() {
|
||||
Some(f) => {
|
||||
let ident = f.ident.unwrap();
|
||||
quote! { _ => self.0.#ident = self.0.#ident.deserialize_over_with(single_map_entry(key, &mut map))? }
|
||||
}
|
||||
None => quote! { _ => _ = map.next_value::<IgnoredAny>()? },
|
||||
};
|
||||
|
||||
quote! {
|
||||
impl #impl_generics yazi_shim::toml::DeserializeOverWith for #ident #ty_generics #where_clause {
|
||||
fn deserialize_over_with<'__de, __D: serde::Deserializer<'__de>>(self, de: __D) -> Result<Self, __D::Error> {
|
||||
use std::borrow::Cow;
|
||||
use serde::de::{Error, IgnoredAny, MapAccess, Visitor};
|
||||
use yazi_shim::toml::{DeserializeOverHook, DeserializeOverSeed};
|
||||
use yazi_shared::KebabCasedString;
|
||||
use yazi_shim::{serde::single_map_entry, toml::{DeserializeOverHook, DeserializeOverSeed, DeserializeOverWith}};
|
||||
|
||||
struct V #impl_generics (#ident #ty_generics) #where_clause;
|
||||
|
||||
|
|
@ -51,10 +67,10 @@ pub fn deserialize_over1(input: TokenStream) -> TokenStream {
|
|||
}
|
||||
|
||||
fn visit_map<__M: MapAccess<'__de>>(mut self, mut map: __M) -> Result<Self::Value, __M::Error> {
|
||||
while let Some(key) = map.next_key::<Cow<str>>()? {
|
||||
while let Some(key) = map.next_key::<KebabCasedString>()? {
|
||||
match key.as_ref() {
|
||||
#(#match_arms)*
|
||||
_ => { map.next_value::<IgnoredAny>()?; }
|
||||
#(#normal_arms,)*
|
||||
#flatten_arm
|
||||
}
|
||||
}
|
||||
Ok(self.0)
|
||||
|
|
@ -62,7 +78,7 @@ pub fn deserialize_over1(input: TokenStream) -> TokenStream {
|
|||
}
|
||||
|
||||
let deserialized = de.deserialize_map(V(self))?;
|
||||
Ok(Self { #(#hook_fields,)* })
|
||||
Ok(Self { #(#field_hooks,)* })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -77,7 +93,8 @@ pub fn deserialize_over2(input: TokenStream) -> TokenStream {
|
|||
let visitor_generics = generics_with_de(&generics);
|
||||
let (impl_visitor_generics, ..) = visitor_generics.split_for_impl();
|
||||
|
||||
let (mut match_arms, mut post_fields) = (vec![], vec![]);
|
||||
let mut normal_arms = vec![];
|
||||
let mut flatten_arm = quote! { _ => _ = map.next_value::<IgnoredAny>()? };
|
||||
for field in named_fields(data) {
|
||||
let (field_ident, field_ty) = (field.ident, field.ty);
|
||||
let field_name = ident_name(field_ident.as_ref().unwrap());
|
||||
|
|
@ -86,25 +103,16 @@ pub fn deserialize_over2(input: TokenStream) -> TokenStream {
|
|||
continue;
|
||||
}
|
||||
|
||||
if field_name == "selector" && has_serde_attr(&field.attrs, "flatten") {
|
||||
match_arms.push(quote! {
|
||||
"url" => selector_url = Some(map.next_value()?),
|
||||
"mime" => selector_mime = Some(map.next_value()?),
|
||||
});
|
||||
post_fields.push(quote! {
|
||||
self.0.#field_ident = Selector::new(
|
||||
selector_url.or(self.0.#field_ident.url),
|
||||
selector_mime.or(self.0.#field_ident.mime)
|
||||
).map_err(Error::custom)?;
|
||||
});
|
||||
if has_serde_attr(&field.attrs, "flatten") {
|
||||
flatten_arm = quote! { _ => self.0.#field_ident = self.0.#field_ident.deserialize_over_with(single_map_entry(key, &mut map))? };
|
||||
continue;
|
||||
}
|
||||
|
||||
let serde_attrs: Vec<_> = field.attrs.iter().filter(|a| a.path().is_ident("serde")).collect();
|
||||
if serde_attrs.is_empty() {
|
||||
match_arms.push(quote! { #field_name => self.0.#field_ident = map.next_value()?, });
|
||||
normal_arms.push(quote! { #field_name => self.0.#field_ident = map.next_value()? });
|
||||
} else {
|
||||
match_arms.push(quote! {
|
||||
normal_arms.push(quote! {
|
||||
#field_name => {
|
||||
#[derive(serde::Deserialize)]
|
||||
struct H #impl_generics(#(#serde_attrs)* #field_ty,) #where_clause;
|
||||
|
|
@ -117,9 +125,9 @@ pub fn deserialize_over2(input: TokenStream) -> TokenStream {
|
|||
quote! {
|
||||
impl #impl_generics yazi_shim::toml::DeserializeOverWith for #ident #ty_generics #where_clause {
|
||||
fn deserialize_over_with<'__de, __D: serde::Deserializer<'__de>>(self, de: __D) -> Result<Self, __D::Error> {
|
||||
use std::borrow::Cow;
|
||||
use serde::de::{Error, IgnoredAny, MapAccess, Visitor};
|
||||
use crate::Selector;
|
||||
use std::borrow::Cow;
|
||||
use yazi_shim::{serde::single_map_entry, toml::DeserializeOverWith};
|
||||
|
||||
struct V #impl_generics (#ident #ty_generics) #where_clause;
|
||||
|
||||
|
|
@ -131,17 +139,13 @@ pub fn deserialize_over2(input: TokenStream) -> TokenStream {
|
|||
}
|
||||
|
||||
fn visit_map<__M: MapAccess<'__de>>(mut self, mut map: __M) -> Result<Self::Value, __M::Error> {
|
||||
let mut selector_url: Option<crate::Pattern> = None;
|
||||
let mut selector_mime: Option<crate::Pattern> = None;
|
||||
|
||||
while let Some(key) = map.next_key::<Cow<str>>()? {
|
||||
match key.as_ref() {
|
||||
#(#match_arms)*
|
||||
_ => { map.next_value::<IgnoredAny>()?; }
|
||||
#(#normal_arms,)*
|
||||
#flatten_arm
|
||||
}
|
||||
}
|
||||
|
||||
#(#post_fields)*
|
||||
Ok(self.0)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,9 +71,15 @@ pub struct OpenerRulesMatcher {
|
|||
impl From<&Opener> for OpenerRulesMatcher {
|
||||
fn from(opener: &Opener) -> Self {
|
||||
let opener = opener.load_full();
|
||||
let iter: hash_map::Iter<String, Arc<OpenerRules>> = opener.iter();
|
||||
|
||||
Self { iter: unsafe { mem::transmute(iter) }, _opener: opener }
|
||||
let iter = unsafe {
|
||||
mem::transmute::<
|
||||
hash_map::Iter<'_, String, Arc<OpenerRules>>,
|
||||
hash_map::Iter<'static, String, Arc<OpenerRules>>,
|
||||
>(opener.iter())
|
||||
};
|
||||
|
||||
Self { iter, _opener: opener }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use anyhow::{Result, ensure};
|
||||
use serde::{Deserialize, Deserializer, de};
|
||||
use yazi_shim::toml::DeserializeOverWith;
|
||||
|
||||
use crate::{Mixable, Pattern, Selectable};
|
||||
|
||||
|
|
@ -22,6 +23,16 @@ impl<'de> Deserialize<'de> for Selector {
|
|||
}
|
||||
}
|
||||
|
||||
impl DeserializeOverWith for Selector {
|
||||
fn deserialize_over_with<'de, D: Deserializer<'de>>(
|
||||
self,
|
||||
deserializer: D,
|
||||
) -> Result<Self, D::Error> {
|
||||
let new = Selector::deserialize(deserializer)?;
|
||||
Self::new(new.url.or(self.url), new.mime.or(self.mime)).map_err(de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
impl Selector {
|
||||
pub fn new(url: Option<Pattern>, mime: Option<Pattern>) -> Result<Self> {
|
||||
ensure!(url.is_some() || mime.is_some(), "at least one of `url` or `mime` must be specified");
|
||||
|
|
|
|||
85
yazi-config/src/theme/custom.rs
Normal file
85
yazi-config/src/theme/custom.rs
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
use std::{fmt, mem, ops::Deref, sync::Arc};
|
||||
|
||||
use arc_swap::ArcSwap;
|
||||
use hashbrown::{HashMap, hash_map};
|
||||
use serde::{Deserialize, Deserializer, de::{MapAccess, Visitor}};
|
||||
use yazi_codegen::{DeserializeOver, Overlay};
|
||||
use yazi_shared::{KebabCasedString, SnakeCasedString};
|
||||
use yazi_shim::{arc_swap::IntoPointee, toml::DeserializeOverWith};
|
||||
|
||||
use crate::theme::CustomSection;
|
||||
|
||||
#[derive(Debug, Default, DeserializeOver, Overlay)]
|
||||
pub struct Custom(ArcSwap<HashMap<SnakeCasedString, CustomSection>>);
|
||||
|
||||
impl Deref for Custom {
|
||||
type Target = ArcSwap<HashMap<SnakeCasedString, CustomSection>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl From<HashMap<SnakeCasedString, CustomSection>> for Custom {
|
||||
fn from(value: HashMap<SnakeCasedString, CustomSection>) -> Self { Self(value.into_pointee()) }
|
||||
}
|
||||
|
||||
impl Custom {
|
||||
pub(super) fn unwrap_unchecked(self) -> HashMap<SnakeCasedString, CustomSection> {
|
||||
Arc::try_unwrap(self.0.into_inner()).expect("unique custom arc")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Custom {
|
||||
fn deserialize<D: Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
|
||||
struct V;
|
||||
|
||||
impl<'de> Visitor<'de> for V {
|
||||
type Value = HashMap<SnakeCasedString, CustomSection>;
|
||||
|
||||
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("a map") }
|
||||
|
||||
fn visit_map<M: MapAccess<'de>>(self, mut map: M) -> Result<Self::Value, M::Error> {
|
||||
let mut sections = HashMap::with_capacity(map.size_hint().unwrap_or(0));
|
||||
while let Some(key) = map.next_key::<KebabCasedString>()? {
|
||||
let section = map.next_value::<CustomSection>()?;
|
||||
if !section.load().is_empty() {
|
||||
sections.insert(key.into_snake_cased(), section);
|
||||
}
|
||||
}
|
||||
Ok(sections)
|
||||
}
|
||||
}
|
||||
|
||||
Ok(de.deserialize_map(V)?.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl DeserializeOverWith for Custom {
|
||||
fn deserialize_over_with<'de, D: Deserializer<'de>>(self, de: D) -> Result<Self, D::Error> {
|
||||
struct V(Custom);
|
||||
|
||||
impl<'de> Visitor<'de> for V {
|
||||
type Value = Custom;
|
||||
|
||||
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("a map") }
|
||||
|
||||
fn visit_map<M: MapAccess<'de>>(self, mut map: M) -> Result<Custom, M::Error> {
|
||||
let mut sections = self.0.unwrap_unchecked();
|
||||
while let Some(key) = map.next_key::<KebabCasedString>()? {
|
||||
let (key, new) = (key.into_snake_cased(), map.next_value::<CustomSection>()?);
|
||||
match sections.entry(key) {
|
||||
hash_map::Entry::Occupied(mut oe) => {
|
||||
let mut old = mem::take(oe.get_mut()).unwrap_unchecked();
|
||||
old.extend(new.unwrap_unchecked());
|
||||
oe.insert(old.into());
|
||||
}
|
||||
hash_map::Entry::Vacant(_) if new.load().is_empty() => {}
|
||||
hash_map::Entry::Vacant(ve) => _ = ve.insert(new),
|
||||
}
|
||||
}
|
||||
Ok(sections.into())
|
||||
}
|
||||
}
|
||||
|
||||
de.deserialize_map(V(self))
|
||||
}
|
||||
}
|
||||
12
yazi-config/src/theme/custom_field.rs
Normal file
12
yazi-config/src/theme/custom_field.rs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
use serde::Deserialize;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum CustomField {
|
||||
Style(crate::Style),
|
||||
String(String),
|
||||
}
|
||||
|
||||
impl From<&CustomField> for CustomField {
|
||||
fn from(value: &CustomField) -> Self { value.clone() }
|
||||
}
|
||||
90
yazi-config/src/theme/custom_section.rs
Normal file
90
yazi-config/src/theme/custom_section.rs
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
use std::{ops::Deref, sync::Arc};
|
||||
|
||||
use arc_swap::ArcSwap;
|
||||
use hashbrown::HashMap;
|
||||
use serde::{Deserialize, Deserializer, de::{self, MapAccess, SeqAccess, Visitor}};
|
||||
use yazi_codegen::{DeserializeOver, Overlay};
|
||||
use yazi_shim::arc_swap::IntoPointee;
|
||||
use yazi_shared::SnakeCasedString;
|
||||
|
||||
use crate::theme::CustomField;
|
||||
|
||||
#[derive(Debug, Default, DeserializeOver, Overlay)]
|
||||
pub struct CustomSection(ArcSwap<HashMap<SnakeCasedString, CustomField>>);
|
||||
|
||||
impl Deref for CustomSection {
|
||||
type Target = ArcSwap<HashMap<SnakeCasedString, CustomField>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl From<HashMap<SnakeCasedString, CustomField>> for CustomSection {
|
||||
fn from(value: HashMap<SnakeCasedString, CustomField>) -> Self { Self(value.into_pointee()) }
|
||||
}
|
||||
|
||||
impl CustomSection {
|
||||
pub(super) fn unwrap_unchecked(self) -> HashMap<SnakeCasedString, CustomField> {
|
||||
Arc::try_unwrap(self.0.into_inner()).expect("unique custom section arc")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for CustomSection {
|
||||
fn deserialize<D: Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
|
||||
struct V;
|
||||
|
||||
impl<'de> Visitor<'de> for V {
|
||||
type Value = CustomSection;
|
||||
|
||||
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
f.write_str("a style section table or a skippable scalar")
|
||||
}
|
||||
|
||||
fn visit_map<M: MapAccess<'de>>(self, mut map: M) -> Result<Self::Value, M::Error> {
|
||||
let mut fields = HashMap::with_capacity(map.size_hint().unwrap_or(0));
|
||||
while let Some(k) = map.next_key::<SnakeCasedString>()? {
|
||||
fields.insert(k, map.next_value::<CustomField>()?);
|
||||
}
|
||||
Ok(CustomSection(fields.into_pointee()))
|
||||
}
|
||||
|
||||
fn visit_bool<E: de::Error>(self, _: bool) -> Result<Self::Value, E> {
|
||||
Ok(CustomSection::default())
|
||||
}
|
||||
|
||||
fn visit_i64<E: de::Error>(self, _: i64) -> Result<Self::Value, E> {
|
||||
Ok(CustomSection::default())
|
||||
}
|
||||
|
||||
fn visit_u64<E: de::Error>(self, _: u64) -> Result<Self::Value, E> {
|
||||
Ok(CustomSection::default())
|
||||
}
|
||||
|
||||
fn visit_f64<E: de::Error>(self, _: f64) -> Result<Self::Value, E> {
|
||||
Ok(CustomSection::default())
|
||||
}
|
||||
|
||||
fn visit_str<E: de::Error>(self, _: &str) -> Result<Self::Value, E> {
|
||||
Ok(CustomSection::default())
|
||||
}
|
||||
|
||||
fn visit_bytes<E: de::Error>(self, _: &[u8]) -> Result<Self::Value, E> {
|
||||
Ok(CustomSection::default())
|
||||
}
|
||||
|
||||
fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> { Ok(CustomSection::default()) }
|
||||
|
||||
fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> { Ok(CustomSection::default()) }
|
||||
|
||||
fn visit_some<D2: Deserializer<'de>>(self, de: D2) -> Result<Self::Value, D2::Error> {
|
||||
CustomSection::deserialize(de)
|
||||
}
|
||||
|
||||
fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
|
||||
while seq.next_element::<de::IgnoredAny>()?.is_some() {}
|
||||
Ok(CustomSection::default())
|
||||
}
|
||||
}
|
||||
|
||||
de.deserialize_any(V)
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
yazi_macro::mod_flat!(filetype filetype_rule filetype_rules flavor icon icon_cond icon_conds icon_glob icon_globs icon_names is theme);
|
||||
yazi_macro::mod_flat!(custom custom_field custom_section filetype filetype_rule filetype_rules flavor icon icon_cond icon_conds icon_glob icon_globs icon_names is theme);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use yazi_codegen::{DeserializeOver, DeserializeOver1, DeserializeOver2, Overlay}
|
|||
use yazi_fs::{Xdg, ok_or_not_found};
|
||||
use yazi_shim::{arc_swap::IntoPointee, cell::SyncCell};
|
||||
|
||||
use super::{Filetype, Flavor, Icon};
|
||||
use super::{Custom, Filetype, Flavor, Icon};
|
||||
use crate::{Style, normalize_path};
|
||||
|
||||
#[derive(Deserialize, DeserializeOver, DeserializeOver1, Overlay)]
|
||||
|
|
@ -32,6 +32,10 @@ pub struct Theme {
|
|||
// File-specific styles
|
||||
pub filetype: Filetype,
|
||||
pub icon: Icon,
|
||||
|
||||
// User-defined custom sections
|
||||
#[serde(flatten, default)]
|
||||
pub custom: Custom,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, DeserializeOver, DeserializeOver2, Overlay)]
|
||||
|
|
|
|||
|
|
@ -49,4 +49,4 @@ core-foundation-sys = { workspace = true }
|
|||
objc2 = { workspace = true }
|
||||
|
||||
[target.'cfg(not(target_os = "android"))'.dependencies]
|
||||
trash = "5.2.5"
|
||||
trash = "5.2.6"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use mlua::{IntoLua, Lua, Value};
|
||||
use yazi_binding::{Composer, ComposerGet, ComposerSet, Style, Url};
|
||||
use yazi_binding::{Composer, ComposerGet, ComposerSet, Style, Url, theme::CustomSection};
|
||||
use yazi_config::THEME;
|
||||
|
||||
use crate::LUA;
|
||||
|
|
@ -22,7 +22,7 @@ pub fn compose() -> Composer<ComposerGet, ComposerSet> {
|
|||
b"cmp" => cmp(),
|
||||
b"tasks" => tasks(),
|
||||
b"help" => help(),
|
||||
_ => return Ok(Value::Nil),
|
||||
_ => return custom(lua, key),
|
||||
}
|
||||
.into_lua(lua)
|
||||
}
|
||||
|
|
@ -375,3 +375,10 @@ fn help() -> Composer<ComposerGet, ComposerSet> {
|
|||
|
||||
Composer::new(get, set)
|
||||
}
|
||||
|
||||
fn custom(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {
|
||||
match THEME.custom.load().get(str::from_utf8(key)?) {
|
||||
Some(section) => CustomSection::new(section.load_full()).into_lua(lua),
|
||||
None => Ok(Value::Nil),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ pub trait BytesExt {
|
|||
|
||||
fn rsplit_seq_once(&self, sep: &[u8]) -> Option<(&[u8], &[u8])>;
|
||||
|
||||
fn snake_cased(&self) -> bool;
|
||||
|
||||
fn split_seq_once(&self, sep: &[u8]) -> Option<(&[u8], &[u8])>;
|
||||
}
|
||||
|
||||
|
|
@ -47,6 +49,10 @@ impl BytesExt for [u8] {
|
|||
None
|
||||
}
|
||||
|
||||
fn snake_cased(&self) -> bool {
|
||||
self.iter().all(|&b| matches!(b, b'0'..=b'9' | b'a'..=b'z' | b'_'))
|
||||
}
|
||||
|
||||
fn split_seq_once(&self, sep: &[u8]) -> Option<(&[u8], &[u8])> {
|
||||
let idx = memchr::memmem::find(self, sep)?;
|
||||
let (a, b) = self.split_at(idx);
|
||||
|
|
|
|||
71
yazi-shared/src/kebab_cased_string.rs
Normal file
71
yazi-shared/src/kebab_cased_string.rs
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
use std::{borrow::{Borrow, Cow}, ffi::OsStr, fmt::{Display, Formatter}, ops::Deref};
|
||||
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
|
||||
use crate::{BytesExt, SnakeCasedString};
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct KebabCasedString(String);
|
||||
|
||||
impl KebabCasedString {
|
||||
pub fn new(s: String) -> Option<Self> { s.as_bytes().kebab_cased().then_some(Self(s)) }
|
||||
|
||||
pub fn into_snake_cased(self) -> SnakeCasedString {
|
||||
let mut b = self.0.into_bytes();
|
||||
b.iter_mut().for_each(|c| {
|
||||
if *c == b'-' {
|
||||
*c = b'_'
|
||||
}
|
||||
});
|
||||
SnakeCasedString(unsafe { String::from_utf8_unchecked(b) })
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for KebabCasedString {
|
||||
type Target = str;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl Borrow<str> for KebabCasedString {
|
||||
#[inline]
|
||||
fn borrow(&self) -> &str { &self.0 }
|
||||
}
|
||||
|
||||
impl Borrow<String> for KebabCasedString {
|
||||
#[inline]
|
||||
fn borrow(&self) -> &String { &self.0 }
|
||||
}
|
||||
|
||||
impl AsRef<str> for KebabCasedString {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &str { &self.0 }
|
||||
}
|
||||
|
||||
impl AsRef<OsStr> for KebabCasedString {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &OsStr { self.0.as_ref() }
|
||||
}
|
||||
|
||||
impl Display for KebabCasedString {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { Display::fmt(&self.0, f) }
|
||||
}
|
||||
|
||||
impl From<KebabCasedString> for String {
|
||||
#[inline]
|
||||
fn from(value: KebabCasedString) -> Self { value.0 }
|
||||
}
|
||||
|
||||
impl From<KebabCasedString> for Cow<'_, str> {
|
||||
#[inline]
|
||||
fn from(value: KebabCasedString) -> Self { Cow::Owned(value.0) }
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for KebabCasedString {
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
let value = String::deserialize(deserializer)?;
|
||||
Self::new(value).ok_or_else(|| serde::de::Error::custom("must be a kebab-cased string"))
|
||||
}
|
||||
}
|
||||
|
|
@ -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 source terminal tests throttle time utf8);
|
||||
yazi_macro::mod_flat!(alias bytes chars completion_token condition debounce env id kebab_cased_string last_value layer localset natsort non_empty_string os predictor snake_cased_string source terminal tests throttle time utf8);
|
||||
|
||||
pub fn init() {
|
||||
LOCAL_SET.with(tokio::task::LocalSet::new);
|
||||
|
|
|
|||
|
|
@ -57,6 +57,6 @@ impl From<NonEmptyString> for OsString {
|
|||
impl<'de> Deserialize<'de> for NonEmptyString {
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
let value = String::deserialize(deserializer)?;
|
||||
Self::new(value).ok_or_else(|| serde::de::Error::custom("string cannot be empty"))
|
||||
Self::new(value).ok_or_else(|| serde::de::Error::custom("must be a non-empty string"))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
56
yazi-shared/src/snake_cased_string.rs
Normal file
56
yazi-shared/src/snake_cased_string.rs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
use std::{borrow::Borrow, ffi::OsStr, fmt::{Display, Formatter}, ops::Deref};
|
||||
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
|
||||
use crate::BytesExt;
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct SnakeCasedString(pub(super) String);
|
||||
|
||||
impl SnakeCasedString {
|
||||
pub fn new(s: String) -> Option<Self> { s.as_bytes().snake_cased().then_some(Self(s)) }
|
||||
}
|
||||
|
||||
impl Deref for SnakeCasedString {
|
||||
type Target = str;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl Borrow<str> for SnakeCasedString {
|
||||
#[inline]
|
||||
fn borrow(&self) -> &str { &self.0 }
|
||||
}
|
||||
|
||||
impl Borrow<String> for SnakeCasedString {
|
||||
#[inline]
|
||||
fn borrow(&self) -> &String { &self.0 }
|
||||
}
|
||||
|
||||
impl AsRef<str> for SnakeCasedString {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &str { &self.0 }
|
||||
}
|
||||
|
||||
impl AsRef<OsStr> for SnakeCasedString {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &OsStr { self.0.as_ref() }
|
||||
}
|
||||
|
||||
impl Display for SnakeCasedString {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { Display::fmt(&self.0, f) }
|
||||
}
|
||||
|
||||
impl From<SnakeCasedString> for String {
|
||||
#[inline]
|
||||
fn from(value: SnakeCasedString) -> Self { value.0 }
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for SnakeCasedString {
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
let value = String::deserialize(deserializer)?;
|
||||
Self::new(value).ok_or_else(|| serde::de::Error::custom("must be a snake-cased string"))
|
||||
}
|
||||
}
|
||||
37
yazi-shim/src/serde/map.rs
Normal file
37
yazi-shim/src/serde/map.rs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use serde::{Deserializer, de::{DeserializeSeed, IntoDeserializer, MapAccess, value::MapAccessDeserializer}};
|
||||
|
||||
struct SingleMapEntryAccess<'k, 'a, M> {
|
||||
key: Option<Cow<'k, str>>,
|
||||
map: &'a mut M,
|
||||
}
|
||||
|
||||
impl<'k, 'a, 'de, M: MapAccess<'de>> MapAccess<'de> for SingleMapEntryAccess<'k, 'a, M> {
|
||||
type Error = M::Error;
|
||||
|
||||
fn next_key_seed<K: DeserializeSeed<'de>>(
|
||||
&mut self,
|
||||
seed: K,
|
||||
) -> Result<Option<K::Value>, Self::Error> {
|
||||
match self.key.take() {
|
||||
Some(k) => seed.deserialize(k.into_deserializer()).map(Some),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn next_value_seed<V: DeserializeSeed<'de>>(&mut self, seed: V) -> Result<V::Value, Self::Error> {
|
||||
self.map.next_value_seed(seed)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn single_map_entry<'k, 'a, 'de, K, M>(
|
||||
key: K,
|
||||
map: &'a mut M,
|
||||
) -> impl Deserializer<'de, Error = M::Error> + use<'k, 'a, 'de, K, M>
|
||||
where
|
||||
K: Into<Cow<'k, str>>,
|
||||
M: MapAccess<'de>,
|
||||
{
|
||||
MapAccessDeserializer::new(SingleMapEntryAccess { key: Some(key.into()), map })
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
yazi_macro::mod_flat!(traits);
|
||||
yazi_macro::mod_flat!(map traits);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue