mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
feat: dynamic open/opener Lua API (#3901)
This commit is contained in:
parent
d1a27bca1f
commit
369c47539c
58 changed files with 1431 additions and 702 deletions
|
|
@ -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])
|
||||
- Dynamic open/opener Lua API ([#3901])
|
||||
- Dynamic previewer Lua API ([#3891])
|
||||
- Vim-like `lua` action that runs an inline Lua snippet ([#3813])
|
||||
- Certificate authentication for SFTP VFS provider ([#3716])
|
||||
|
|
@ -1703,3 +1704,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
[#3862]: https://github.com/sxyazi/yazi/pull/3862
|
||||
[#3891]: https://github.com/sxyazi/yazi/pull/3891
|
||||
[#3894]: https://github.com/sxyazi/yazi/pull/3894
|
||||
[#3901]: https://github.com/sxyazi/yazi/pull/3901
|
||||
|
|
|
|||
65
Cargo.lock
generated
65
Cargo.lock
generated
|
|
@ -1310,9 +1310,9 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
|||
|
||||
[[package]]
|
||||
name = "ecdsa"
|
||||
version = "0.17.0-rc.16"
|
||||
version = "0.17.0-rc.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91bbdd377139884fafcad8dc43a760a3e1e681aa26db910257fa6535b70e1829"
|
||||
checksum = "dc4bf51f0534ed6e59a0f2f26272b64ba55c470133f8424c2adfd1c4d59d9988"
|
||||
dependencies = [
|
||||
"der",
|
||||
"digest 0.11.2",
|
||||
|
|
@ -1357,9 +1357,9 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
|||
|
||||
[[package]]
|
||||
name = "elliptic-curve"
|
||||
version = "0.14.0-rc.30"
|
||||
version = "0.14.0-rc.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d7a0bfd012613a7bcfe02cbfccf2b846e9ef9e1bccb641c48d461253cfb034d"
|
||||
checksum = "b148a81cede8f4023248f980cffdf7611c46f2add469c6980e815b7c5b764ba5"
|
||||
dependencies = [
|
||||
"base16ct",
|
||||
"crypto-bigint",
|
||||
|
|
@ -2667,7 +2667,7 @@ dependencies = [
|
|||
"num-integer",
|
||||
"num-iter",
|
||||
"num-traits",
|
||||
"rand 0.8.5",
|
||||
"rand 0.8.6",
|
||||
"serde",
|
||||
"smallvec",
|
||||
]
|
||||
|
|
@ -2860,7 +2860,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "b7d950ca161dc355eaf28f82b11345ed76c6e1f6eb1f4f4479e0323b9e2fbd0e"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"rand 0.8.5",
|
||||
"rand 0.8.6",
|
||||
"serde",
|
||||
]
|
||||
|
||||
|
|
@ -2916,7 +2916,7 @@ dependencies = [
|
|||
"delegate",
|
||||
"futures",
|
||||
"log",
|
||||
"rand 0.8.5",
|
||||
"rand 0.8.6",
|
||||
"sha2 0.10.9",
|
||||
"thiserror 1.0.69",
|
||||
"tokio",
|
||||
|
|
@ -3114,7 +3114,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
"rand 0.8.5",
|
||||
"rand 0.8.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3283,9 +3283,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "primefield"
|
||||
version = "0.14.0-rc.8"
|
||||
version = "0.14.0-rc.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6543f5eec854fbf74ba5ef651fbdc9408919b47c3e1526623687135c16d12e9"
|
||||
checksum = "1b52e6ee42db392378a95622b463c9740631171d1efce43fa445a569c1600cb6"
|
||||
dependencies = [
|
||||
"crypto-bigint",
|
||||
"crypto-common 0.2.1",
|
||||
|
|
@ -3297,9 +3297,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "primeorder"
|
||||
version = "0.14.0-rc.8"
|
||||
version = "0.14.0-rc.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "569d9ad6ef822bb0322c7e7d84e5e286244050bd5246cac4c013535ae91c2c90"
|
||||
checksum = "0556580e42c19833f5d232aca11a7687a503ee41f937b54f5ae1d50fc2a6a36a"
|
||||
dependencies = [
|
||||
"elliptic-curve",
|
||||
]
|
||||
|
|
@ -3356,9 +3356,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pxfm"
|
||||
version = "0.1.28"
|
||||
version = "0.1.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5a041e753da8b807c9255f28de81879c78c876392ff2469cde94799b2896b9d"
|
||||
checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f"
|
||||
|
||||
[[package]]
|
||||
name = "qoi"
|
||||
|
|
@ -3429,9 +3429,9 @@ checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
|
|||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha 0.3.1",
|
||||
|
|
@ -4447,6 +4447,12 @@ version = "2.6.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "symlink"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
|
|
@ -4688,9 +4694,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.52.0"
|
||||
version = "1.52.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a91135f59b1cbf38c91e73cf3386fca9bb77915c45ce2771460c9d92f0f3d776"
|
||||
checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"libc",
|
||||
|
|
@ -4790,11 +4796,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tracing-appender"
|
||||
version = "0.2.4"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf"
|
||||
checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"symlink",
|
||||
"thiserror 2.0.18",
|
||||
"time",
|
||||
"tracing-subscriber",
|
||||
|
|
@ -5086,11 +5093,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
|||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.2+wasi-0.2.9"
|
||||
version = "1.0.3+wasi-0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
|
||||
checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
"wit-bindgen 0.57.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -5099,7 +5106,7 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
"wit-bindgen 0.51.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -5664,6 +5671,12 @@ dependencies = [
|
|||
"wit-bindgen-rust-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.57.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-core"
|
||||
version = "0.51.0"
|
||||
|
|
@ -5767,6 +5780,7 @@ dependencies = [
|
|||
"either",
|
||||
"futures",
|
||||
"hashbrown 0.17.0",
|
||||
"indexmap 2.14.0",
|
||||
"libc",
|
||||
"mlua",
|
||||
"paste",
|
||||
|
|
@ -5909,6 +5923,7 @@ dependencies = [
|
|||
name = "yazi-codegen"
|
||||
version = "26.2.2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
|
@ -6278,6 +6293,7 @@ dependencies = [
|
|||
"paste",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
"serde_with",
|
||||
"strum 0.28.0",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
|
|
@ -6293,6 +6309,7 @@ version = "26.2.2"
|
|||
dependencies = [
|
||||
"arc-swap",
|
||||
"crossterm 0.29.0",
|
||||
"mlua",
|
||||
"ratatui",
|
||||
"serde",
|
||||
"thiserror 2.0.18",
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ serde_with = "3.18.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.0", features = [ "full" ] }
|
||||
tokio = { version = "1.52.1", features = [ "full" ] }
|
||||
tokio-stream = "0.1.18"
|
||||
tokio-util = "0.7.18"
|
||||
toml = { version = "1.1.2" }
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ crossterm = { workspace = true }
|
|||
either = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
hashbrown = { workspace = true }
|
||||
indexmap = { workspace = true }
|
||||
mlua = { workspace = true }
|
||||
paste = { workspace = true }
|
||||
ratatui = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{hash::Hash, io::{Read, Write}, ops::Deref, path::Path};
|
||||
use std::{hash::Hash, io::{Read, Write}, ops::Deref, path::Path, sync::Arc};
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
use crossterm::{execute, style::Print};
|
||||
|
|
@ -154,8 +154,11 @@ impl BulkRename {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn opener() -> Option<&'static OpenerRule> {
|
||||
YAZI.opener.block(YAZI.open.all_dummy(Path::new("bulk-rename.txt"), "text/plain"))
|
||||
fn opener() -> Option<Arc<OpenerRule>> {
|
||||
YAZI
|
||||
.open
|
||||
.match_dummy(Path::new("bulk-rename.txt"), "text/plain")
|
||||
.and_then(|r| YAZI.opener.block(&r))
|
||||
}
|
||||
|
||||
fn replace_url(url: &UrlBuf, take: usize, rep: &StrandBuf) -> Result<UrlBuf> {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use anyhow::Result;
|
||||
use hashbrown::HashMap;
|
||||
use indexmap::IndexSet;
|
||||
use yazi_config::{YAZI, popup::PickCfg};
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::mgr::OpenDoForm;
|
||||
|
|
@ -33,7 +34,8 @@ impl Actor for OpenDo {
|
|||
succ!(Self::match_and_open(cx, opt.cwd, targets));
|
||||
}
|
||||
|
||||
let openers: Vec<_> = YAZI.opener.all(YAZI.open.common(&targets).into_iter()).collect();
|
||||
let openers: IndexSet<_> =
|
||||
YAZI.open.match_common(&targets).flat_map(|r| YAZI.opener.all(r)).collect();
|
||||
if openers.is_empty() {
|
||||
succ!();
|
||||
}
|
||||
|
|
@ -64,7 +66,9 @@ impl OpenDo {
|
|||
fn match_and_open(cx: &Ctx, cwd: UrlBuf, targets: Vec<(yazi_fs::File, &str)>) {
|
||||
let mut openers = HashMap::new();
|
||||
for (file, mime) in targets {
|
||||
if let Some(opener) = YAZI.opener.first(YAZI.open.all(&file, mime)) {
|
||||
if let Some(open) = YAZI.open.matches(&file, mime)
|
||||
&& let Some(opener) = YAZI.opener.first(&open)
|
||||
{
|
||||
openers.entry(opener).or_insert_with(|| vec![UrlCow::default()]).push(file.url.into());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
use std::{ops::Deref, sync::Arc};
|
||||
|
||||
use mlua::{UserData, UserDataFields, Value};
|
||||
use mlua::{ExternalError, FromLua, IntoLua, Lua, Table, UserData, UserDataFields, Value};
|
||||
use yazi_config::YAZI;
|
||||
|
||||
use crate::{Id, cached_field};
|
||||
use crate::{FileRef, Id, Iter, cached_field};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Fetcher {
|
||||
|
|
@ -30,3 +31,45 @@ impl UserData for Fetcher {
|
|||
cached_field!(fields, name, |lua, me| lua.create_string(&*me.name));
|
||||
}
|
||||
}
|
||||
|
||||
// --- Matcher
|
||||
pub struct FetcherMatcher(yazi_config::plugin::FetcherMatcher<'static>);
|
||||
|
||||
impl FetcherMatcher {
|
||||
pub fn new(inner: impl Into<yazi_config::plugin::FetcherMatcher<'static>>) -> Self {
|
||||
Self(inner.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Table> for FetcherMatcher {
|
||||
type Error = mlua::Error;
|
||||
|
||||
fn try_from(value: Table) -> Result<Self, Self::Error> {
|
||||
let id: Id = value.raw_get("id").unwrap_or_default();
|
||||
let file: Option<FileRef> = value.raw_get("file")?;
|
||||
let mime: Option<String> = value.raw_get("mime")?;
|
||||
|
||||
Ok(Self(yazi_config::plugin::FetcherMatcher {
|
||||
fetchers: YAZI.plugin.fetchers.load_full(),
|
||||
id: id.0,
|
||||
file: file.map(|f| f.inner.clone().into()),
|
||||
mime: mime.map(Into::into),
|
||||
..Default::default()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromLua for FetcherMatcher {
|
||||
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
|
||||
match value {
|
||||
Value::Table(t) => t.try_into(),
|
||||
_ => return Err("expected a table of FetcherMatcher".into_lua_err()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoLua for FetcherMatcher {
|
||||
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
|
||||
Iter::new(self.0.into_iter().map(Fetcher::new), None).into_lua(lua)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use mlua::{ExternalError, FromLua, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMethods, Value};
|
||||
use mlua::{IntoLua, MetaMethod, UserData, UserDataMethods};
|
||||
use yazi_config::YAZI;
|
||||
|
||||
use crate::{FileRef, Id, Iter, config::Fetcher};
|
||||
use crate::config::FetcherMatcher;
|
||||
|
||||
pub struct Fetchers;
|
||||
|
||||
|
|
@ -15,45 +15,3 @@ impl UserData for Fetchers {
|
|||
methods.add_meta_method(MetaMethod::Len, |_, _, ()| Ok(YAZI.plugin.fetchers.load().len()));
|
||||
}
|
||||
}
|
||||
|
||||
// --- Matcher
|
||||
pub struct FetcherMatcher(yazi_config::plugin::FetcherMatcher<'static>);
|
||||
|
||||
impl FetcherMatcher {
|
||||
fn new(inner: impl Into<yazi_config::plugin::FetcherMatcher<'static>>) -> Self {
|
||||
Self(inner.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Table> for FetcherMatcher {
|
||||
type Error = mlua::Error;
|
||||
|
||||
fn try_from(value: Table) -> Result<Self, Self::Error> {
|
||||
let id: Id = value.raw_get("id").unwrap_or_default();
|
||||
let file: Option<FileRef> = value.raw_get("file")?;
|
||||
let mime: Option<String> = value.raw_get("mime")?;
|
||||
|
||||
Ok(Self(yazi_config::plugin::FetcherMatcher {
|
||||
fetchers: YAZI.plugin.fetchers.load_full(),
|
||||
id: id.0,
|
||||
file: file.map(|f| f.inner.clone().into()),
|
||||
mime: mime.map(Into::into),
|
||||
..Default::default()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromLua for FetcherMatcher {
|
||||
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
|
||||
match value {
|
||||
Value::Table(t) => t.try_into(),
|
||||
_ => return Err("expected a table of FetcherMatcher".into_lua_err()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoLua for FetcherMatcher {
|
||||
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
|
||||
Iter::new(self.0.into_iter().map(Fetcher::new), None).into_lua(lua)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
yazi_macro::mod_flat!(fetcher fetchers preloader preloaders previewer previewers spotter spotters);
|
||||
yazi_macro::mod_flat!(fetcher fetchers open_rule open_rules opener opener_rule opener_rules preloader preloaders previewer previewers spotter spotters);
|
||||
|
|
|
|||
85
yazi-binding/src/config/open_rule.rs
Normal file
85
yazi-binding/src/config/open_rule.rs
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
use std::{ops::Deref, sync::Arc};
|
||||
|
||||
use mlua::{ExternalError, FromLua, IntoLua, Lua, LuaSerdeExt, Table, UserData, UserDataFields, Value};
|
||||
use yazi_config::YAZI;
|
||||
|
||||
use crate::{FileRef, Id, Iter, cached_field};
|
||||
|
||||
pub struct OpenRule {
|
||||
inner: Arc<yazi_config::open::OpenRule>,
|
||||
|
||||
v_use: Option<Value>,
|
||||
}
|
||||
|
||||
impl Deref for OpenRule {
|
||||
type Target = Arc<yazi_config::open::OpenRule>;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.inner }
|
||||
}
|
||||
|
||||
impl From<OpenRule> for Arc<yazi_config::open::OpenRule> {
|
||||
fn from(value: OpenRule) -> Self { value.inner }
|
||||
}
|
||||
|
||||
impl OpenRule {
|
||||
pub fn new(inner: impl Into<Arc<yazi_config::open::OpenRule>>) -> Self {
|
||||
Self { inner: inner.into(), v_use: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl FromLua for OpenRule {
|
||||
fn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> {
|
||||
Ok(Self::new(lua.from_value::<yazi_config::open::OpenRule>(value)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for OpenRule {
|
||||
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("id", |_, me| Ok(Id(me.id)));
|
||||
cached_field!(fields, use, |lua, me| {
|
||||
lua.create_sequence_from(me.r#use.iter().map(|s| s.as_str()))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// --- Matcher
|
||||
pub struct OpenRuleMatcher(pub(super) yazi_config::open::OpenRuleMatcher<'static>);
|
||||
|
||||
impl OpenRuleMatcher {
|
||||
pub fn new(inner: impl Into<yazi_config::open::OpenRuleMatcher<'static>>) -> Self {
|
||||
Self(inner.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Table> for OpenRuleMatcher {
|
||||
type Error = mlua::Error;
|
||||
|
||||
fn try_from(value: Table) -> Result<Self, Self::Error> {
|
||||
let id: Id = value.raw_get("id").unwrap_or_default();
|
||||
let file: Option<FileRef> = value.raw_get("file")?;
|
||||
let mime: Option<String> = value.raw_get("mime")?;
|
||||
|
||||
Ok(Self(yazi_config::open::OpenRuleMatcher {
|
||||
rules: YAZI.open.load_full(),
|
||||
id: id.0,
|
||||
file: file.map(|f| f.inner.clone().into()),
|
||||
mime: mime.map(Into::into),
|
||||
..Default::default()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromLua for OpenRuleMatcher {
|
||||
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
|
||||
match value {
|
||||
Value::Table(t) => t.try_into(),
|
||||
_ => return Err("expected a table of OpenRuleMatcher".into_lua_err()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoLua for OpenRuleMatcher {
|
||||
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
|
||||
Iter::new(self.0.into_iter().map(OpenRule::new), None).into_lua(lua)
|
||||
}
|
||||
}
|
||||
39
yazi-binding/src/config/open_rules.rs
Normal file
39
yazi-binding/src/config/open_rules.rs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
use mlua::{ExternalError, ExternalResult, IntoLua, MetaMethod, Table, UserData, UserDataMethods};
|
||||
use yazi_config::YAZI;
|
||||
use yazi_shim::mlua::DeserializeOverLua;
|
||||
|
||||
use crate::config::{OpenRule, OpenRuleMatcher};
|
||||
|
||||
pub struct OpenRules;
|
||||
|
||||
impl UserData for OpenRules {
|
||||
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_method("match", |lua, _, matcher: Option<OpenRuleMatcher>| match matcher {
|
||||
Some(matcher) => matcher.into_lua(lua),
|
||||
None => OpenRuleMatcher::new(&*YAZI.open).into_lua(lua),
|
||||
});
|
||||
|
||||
methods.add_method("insert", |_, _, (index, rule): (isize, OpenRule)| {
|
||||
let index = match index {
|
||||
1.. => index - 1,
|
||||
0 => return Err("index must be 1-based or negative".into_lua_err()),
|
||||
_ => index,
|
||||
};
|
||||
|
||||
YAZI.open.insert(index, rule.clone().into()).into_lua_err()?;
|
||||
Ok(rule)
|
||||
});
|
||||
|
||||
methods.add_method("remove", |_, _, matcher: OpenRuleMatcher| {
|
||||
YAZI.open.remove(matcher.0);
|
||||
Ok(())
|
||||
});
|
||||
|
||||
methods.add_method("update", |_, _, (matcher, table): (OpenRuleMatcher, Table)| {
|
||||
YAZI.open.update(matcher.0, |rule| rule.deserialize_over_lua(&table))?;
|
||||
Ok(())
|
||||
});
|
||||
|
||||
methods.add_meta_method(MetaMethod::Len, |_, _, ()| Ok(YAZI.open.load().len()));
|
||||
}
|
||||
}
|
||||
45
yazi-binding/src/config/opener.rs
Normal file
45
yazi-binding/src/config/opener.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
use mlua::{ExternalError, FromLua, IntoLua, IntoLuaMulti, MetaMethod, UserData, UserDataMethods, Value};
|
||||
use yazi_config::{YAZI, opener::OpenerRulesMatcher};
|
||||
|
||||
use crate::config::OpenerRules;
|
||||
|
||||
pub struct Opener;
|
||||
|
||||
impl UserData for Opener {
|
||||
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_meta_method(MetaMethod::Index, |lua, _, key: mlua::String| {
|
||||
let key = key.to_str()?;
|
||||
match YAZI.opener.load().get(&*key) {
|
||||
Some(rules) => OpenerRules::new(rules.clone()).into_lua(lua),
|
||||
None => Ok(Value::Nil),
|
||||
}
|
||||
});
|
||||
|
||||
methods.add_meta_method(MetaMethod::NewIndex, |lua, _, (key, value): (mlua::String, Value)| {
|
||||
let key = key.to_str()?;
|
||||
match value {
|
||||
t @ Value::Table(_) => {
|
||||
YAZI.opener.insert(&key, &*OpenerRules::from_lua(t, lua)?);
|
||||
}
|
||||
Value::Nil => {
|
||||
YAZI.opener.remove(&key);
|
||||
}
|
||||
_ => return Err("expected a table or nil".into_lua_err()),
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
|
||||
methods.add_meta_method(MetaMethod::Pairs, |lua, _, ()| {
|
||||
let mut matcher = OpenerRulesMatcher::from(&YAZI.opener);
|
||||
let iter = lua.create_function_mut(move |lua, ()| {
|
||||
if let Some((name, rules)) = matcher.next() {
|
||||
(name, OpenerRules::new(rules)).into_lua_multi(lua)
|
||||
} else {
|
||||
().into_lua_multi(lua)
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(iter)
|
||||
});
|
||||
}
|
||||
}
|
||||
81
yazi-binding/src/config/opener_rule.rs
Normal file
81
yazi-binding/src/config/opener_rule.rs
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
use std::{ops::Deref, sync::Arc};
|
||||
|
||||
use mlua::{ExternalError, FromLua, IntoLua, Lua, LuaSerdeExt, Table, UserData, UserDataFields, Value};
|
||||
|
||||
use crate::{Id, Iter, cached_field};
|
||||
|
||||
pub struct OpenerRule {
|
||||
inner: Arc<yazi_config::opener::OpenerRule>,
|
||||
|
||||
v_run: Option<Value>,
|
||||
v_desc: Option<Value>,
|
||||
}
|
||||
|
||||
impl Deref for OpenerRule {
|
||||
type Target = Arc<yazi_config::opener::OpenerRule>;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.inner }
|
||||
}
|
||||
|
||||
impl From<OpenerRule> for Arc<yazi_config::opener::OpenerRule> {
|
||||
fn from(value: OpenerRule) -> Self { value.inner }
|
||||
}
|
||||
|
||||
impl OpenerRule {
|
||||
pub fn new(inner: impl Into<Arc<yazi_config::opener::OpenerRule>>) -> Self {
|
||||
Self { inner: inner.into(), v_run: None, v_desc: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl FromLua for OpenerRule {
|
||||
fn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> {
|
||||
let mut inner: yazi_config::opener::OpenerRule = lua.from_value(value)?;
|
||||
inner.fill();
|
||||
|
||||
Ok(Self::new(inner))
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for OpenerRule {
|
||||
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("id", |_, me| Ok(Id(me.id)));
|
||||
cached_field!(fields, run, |lua, me| lua.create_string(&*me.run));
|
||||
fields.add_field_method_get("block", |_, me| Ok(me.block));
|
||||
fields.add_field_method_get("orphan", |_, me| Ok(me.orphan));
|
||||
cached_field!(fields, desc, |lua, me| lua.create_string(&*me.desc));
|
||||
}
|
||||
}
|
||||
|
||||
// --- Matcher
|
||||
pub struct OpenerRuleMatcher(pub(super) yazi_config::opener::OpenerRuleMatcher);
|
||||
|
||||
impl OpenerRuleMatcher {
|
||||
pub fn new(inner: impl Into<yazi_config::opener::OpenerRuleMatcher>) -> Self {
|
||||
Self(inner.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Table> for OpenerRuleMatcher {
|
||||
type Error = mlua::Error;
|
||||
|
||||
fn try_from(t: Table) -> Result<Self, Self::Error> {
|
||||
let id: Id = t.raw_get("id").unwrap_or_default();
|
||||
|
||||
Ok(Self(yazi_config::opener::OpenerRuleMatcher { id: id.0, ..Default::default() }))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromLua for OpenerRuleMatcher {
|
||||
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
|
||||
match value {
|
||||
Value::Table(t) => t.try_into(),
|
||||
_ => Err("expected a table of OpenerRuleMatcher".into_lua_err()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoLua for OpenerRuleMatcher {
|
||||
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
|
||||
Iter::new(self.0.into_iter().map(OpenerRule::new), None).into_lua(lua)
|
||||
}
|
||||
}
|
||||
57
yazi-binding/src/config/opener_rules.rs
Normal file
57
yazi-binding/src/config/opener_rules.rs
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
use std::{ops::Deref, sync::Arc};
|
||||
|
||||
use mlua::{ExternalError, ExternalResult, FromLua, IntoLua, Lua, LuaSerdeExt, MetaMethod, UserData, UserDataMethods, Value};
|
||||
use yazi_shim::toml::DeserializeOverHook;
|
||||
|
||||
use crate::config::{OpenerRule, OpenerRuleMatcher};
|
||||
|
||||
pub struct OpenerRules {
|
||||
inner: Arc<yazi_config::opener::OpenerRules>,
|
||||
}
|
||||
|
||||
impl Deref for OpenerRules {
|
||||
type Target = Arc<yazi_config::opener::OpenerRules>;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.inner }
|
||||
}
|
||||
|
||||
impl OpenerRules {
|
||||
pub fn new(inner: impl Into<Arc<yazi_config::opener::OpenerRules>>) -> Self {
|
||||
Self { inner: inner.into() }
|
||||
}
|
||||
}
|
||||
|
||||
impl FromLua for OpenerRules {
|
||||
fn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> {
|
||||
let inner: yazi_config::opener::OpenerRules = lua.from_value(value)?;
|
||||
|
||||
Ok(Self::new(inner.deserialize_over_hook().into_lua_err()?))
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for OpenerRules {
|
||||
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_method("match", |lua, me, matcher: Option<OpenerRuleMatcher>| match matcher {
|
||||
Some(matcher) => matcher.into_lua(lua),
|
||||
None => OpenerRuleMatcher::new(&*me.inner).into_lua(lua),
|
||||
});
|
||||
|
||||
methods.add_method("insert", |_, me, (index, rule): (isize, OpenerRule)| {
|
||||
let index = match index {
|
||||
1.. => index - 1,
|
||||
0 => return Err("index must be 1-based or negative".into_lua_err()),
|
||||
_ => index,
|
||||
};
|
||||
|
||||
me.inner.insert(index, rule.clone().into()).into_lua_err()?;
|
||||
Ok(rule)
|
||||
});
|
||||
|
||||
methods.add_method("remove", |_, me, matcher: OpenerRuleMatcher| {
|
||||
me.inner.remove(matcher.0);
|
||||
Ok(())
|
||||
});
|
||||
|
||||
methods.add_meta_method(MetaMethod::Len, |_, me, ()| Ok(me.inner.load().len()));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
use std::{ops::Deref, sync::Arc};
|
||||
|
||||
use mlua::{UserData, UserDataFields, Value};
|
||||
use mlua::{ExternalError, FromLua, IntoLua, Lua, Table, UserData, UserDataFields, Value};
|
||||
use yazi_config::YAZI;
|
||||
|
||||
use crate::{Id, cached_field};
|
||||
use crate::{FileRef, Id, Iter, cached_field};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Preloader {
|
||||
|
|
@ -30,3 +31,45 @@ impl UserData for Preloader {
|
|||
cached_field!(fields, name, |lua, me| lua.create_string(&*me.name));
|
||||
}
|
||||
}
|
||||
|
||||
// --- Matcher
|
||||
pub struct PreloaderMatcher(pub(super) yazi_config::plugin::PreloaderMatcher<'static>);
|
||||
|
||||
impl PreloaderMatcher {
|
||||
pub fn new(inner: impl Into<yazi_config::plugin::PreloaderMatcher<'static>>) -> Self {
|
||||
Self(inner.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Table> for PreloaderMatcher {
|
||||
type Error = mlua::Error;
|
||||
|
||||
fn try_from(value: Table) -> Result<Self, Self::Error> {
|
||||
let id: Id = value.raw_get("id").unwrap_or_default();
|
||||
let file: Option<FileRef> = value.raw_get("file")?;
|
||||
let mime: Option<String> = value.raw_get("mime")?;
|
||||
|
||||
Ok(Self(yazi_config::plugin::PreloaderMatcher {
|
||||
preloaders: YAZI.plugin.preloaders.load_full(),
|
||||
id: id.0,
|
||||
file: file.map(|f| f.inner.clone().into()),
|
||||
mime: mime.map(Into::into),
|
||||
..Default::default()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromLua for PreloaderMatcher {
|
||||
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
|
||||
match value {
|
||||
Value::Table(t) => t.try_into(),
|
||||
_ => return Err("expected a table of PreloaderMatcher".into_lua_err()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoLua for PreloaderMatcher {
|
||||
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
|
||||
Iter::new(self.0.into_iter().map(Preloader::new), None).into_lua(lua)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use mlua::{ExternalError, FromLua, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMethods, Value};
|
||||
use mlua::{IntoLua, MetaMethod, UserData, UserDataMethods};
|
||||
use yazi_config::YAZI;
|
||||
|
||||
use crate::{FileRef, Id, Iter, config::Preloader};
|
||||
use crate::config::PreloaderMatcher;
|
||||
|
||||
pub struct Preloaders;
|
||||
|
||||
|
|
@ -15,45 +15,3 @@ impl UserData for Preloaders {
|
|||
methods.add_meta_method(MetaMethod::Len, |_, _, ()| Ok(YAZI.plugin.preloaders.load().len()));
|
||||
}
|
||||
}
|
||||
|
||||
// --- Matcher
|
||||
pub struct PreloaderMatcher(yazi_config::plugin::PreloaderMatcher<'static>);
|
||||
|
||||
impl PreloaderMatcher {
|
||||
fn new(inner: impl Into<yazi_config::plugin::PreloaderMatcher<'static>>) -> Self {
|
||||
Self(inner.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Table> for PreloaderMatcher {
|
||||
type Error = mlua::Error;
|
||||
|
||||
fn try_from(value: Table) -> Result<Self, Self::Error> {
|
||||
let id: Id = value.raw_get("id").unwrap_or_default();
|
||||
let file: Option<FileRef> = value.raw_get("file")?;
|
||||
let mime: Option<String> = value.raw_get("mime")?;
|
||||
|
||||
Ok(Self(yazi_config::plugin::PreloaderMatcher {
|
||||
preloaders: YAZI.plugin.preloaders.load_full(),
|
||||
id: id.0,
|
||||
file: file.map(|f| f.inner.clone().into()),
|
||||
mime: mime.map(Into::into),
|
||||
..Default::default()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromLua for PreloaderMatcher {
|
||||
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
|
||||
match value {
|
||||
Value::Table(t) => t.try_into(),
|
||||
_ => return Err("expected a table of PreloaderMatcher".into_lua_err()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoLua for PreloaderMatcher {
|
||||
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
|
||||
Iter::new(self.0.into_iter().map(Preloader::new), None).into_lua(lua)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
use std::{ops::Deref, sync::Arc};
|
||||
|
||||
use mlua::{AnyUserData, ExternalError, FromLua, Lua, UserData, UserDataFields, Value};
|
||||
use yazi_config::plugin::previewer_id;
|
||||
use mlua::{ExternalError, FromLua, IntoLua, Lua, LuaSerdeExt, Table, UserData, UserDataFields, Value};
|
||||
use yazi_config::YAZI;
|
||||
|
||||
use crate::{Id, Selector, cached_field};
|
||||
|
||||
const EXPECTED: &str = "expected a table or Previewer";
|
||||
use crate::{FileRef, Id, Iter, cached_field};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Previewer {
|
||||
|
|
@ -30,33 +28,9 @@ impl Previewer {
|
|||
}
|
||||
}
|
||||
|
||||
impl TryFrom<mlua::Table> for Previewer {
|
||||
type Error = mlua::Error;
|
||||
|
||||
fn try_from(t: mlua::Table) -> Result<Self, Self::Error> {
|
||||
Ok(Self::new(yazi_config::plugin::Previewer {
|
||||
id: previewer_id(),
|
||||
selector: Selector::try_from(&t)?.into(),
|
||||
run: t.raw_get::<mlua::String>("run")?.to_str()?.parse()?,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<AnyUserData> for Previewer {
|
||||
type Error = mlua::Error;
|
||||
|
||||
fn try_from(value: AnyUserData) -> Result<Self, Self::Error> {
|
||||
Ok(Self::new(value.borrow::<Self>()?.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromLua for Previewer {
|
||||
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
|
||||
match value {
|
||||
Value::Table(tbl) => Self::try_from(tbl),
|
||||
Value::UserData(ud) => Self::try_from(ud),
|
||||
_ => Err(EXPECTED.into_lua_err())?,
|
||||
}
|
||||
fn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> {
|
||||
Ok(Self::new(lua.from_value::<yazi_config::plugin::Previewer>(value)?))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -67,3 +41,45 @@ impl UserData for Previewer {
|
|||
cached_field!(fields, name, |lua, me| lua.create_string(&*me.name));
|
||||
}
|
||||
}
|
||||
|
||||
// --- Matcher
|
||||
pub struct PreviewerMatcher(pub(super) yazi_config::plugin::PreviewerMatcher<'static>);
|
||||
|
||||
impl PreviewerMatcher {
|
||||
pub fn new(inner: impl Into<yazi_config::plugin::PreviewerMatcher<'static>>) -> Self {
|
||||
Self(inner.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Table> for PreviewerMatcher {
|
||||
type Error = mlua::Error;
|
||||
|
||||
fn try_from(value: Table) -> Result<Self, Self::Error> {
|
||||
let id: Id = value.raw_get("id").unwrap_or_default();
|
||||
let file: Option<FileRef> = value.raw_get("file")?;
|
||||
let mime: Option<String> = value.raw_get("mime")?;
|
||||
|
||||
Ok(Self(yazi_config::plugin::PreviewerMatcher {
|
||||
previewers: YAZI.plugin.previewers.load_full(),
|
||||
id: id.0,
|
||||
file: file.map(|f| f.inner.clone().into()),
|
||||
mime: mime.map(Into::into),
|
||||
..Default::default()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromLua for PreviewerMatcher {
|
||||
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
|
||||
match value {
|
||||
Value::Table(t) => t.try_into(),
|
||||
_ => return Err("expected a table of PreviewerMatcher".into_lua_err()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoLua for PreviewerMatcher {
|
||||
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
|
||||
Iter::new(self.0.into_iter().map(Previewer::new), None).into_lua(lua)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use mlua::{ExternalError, ExternalResult, FromLua, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMethods, Value};
|
||||
use mlua::{ExternalError, ExternalResult, IntoLua, MetaMethod, UserData, UserDataMethods};
|
||||
use yazi_config::YAZI;
|
||||
|
||||
use crate::{FileRef, Id, Iter, config::Previewer};
|
||||
use crate::config::{Previewer, PreviewerMatcher};
|
||||
|
||||
pub struct Previewers;
|
||||
|
||||
|
|
@ -31,45 +31,3 @@ impl UserData for Previewers {
|
|||
methods.add_meta_method(MetaMethod::Len, |_, _, ()| Ok(YAZI.plugin.previewers.load().len()));
|
||||
}
|
||||
}
|
||||
|
||||
// --- Matcher
|
||||
pub struct PreviewerMatcher(yazi_config::plugin::PreviewerMatcher<'static>);
|
||||
|
||||
impl PreviewerMatcher {
|
||||
fn new(inner: impl Into<yazi_config::plugin::PreviewerMatcher<'static>>) -> Self {
|
||||
Self(inner.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Table> for PreviewerMatcher {
|
||||
type Error = mlua::Error;
|
||||
|
||||
fn try_from(value: Table) -> Result<Self, Self::Error> {
|
||||
let id: Id = value.raw_get("id").unwrap_or_default();
|
||||
let file: Option<FileRef> = value.raw_get("file")?;
|
||||
let mime: Option<String> = value.raw_get("mime")?;
|
||||
|
||||
Ok(Self(yazi_config::plugin::PreviewerMatcher {
|
||||
previewers: YAZI.plugin.previewers.load_full(),
|
||||
id: id.0,
|
||||
file: file.map(|f| f.inner.clone().into()),
|
||||
mime: mime.map(Into::into),
|
||||
..Default::default()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromLua for PreviewerMatcher {
|
||||
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
|
||||
match value {
|
||||
Value::Table(t) => t.try_into(),
|
||||
_ => return Err("expected a table of PreviewerMatcher".into_lua_err()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoLua for PreviewerMatcher {
|
||||
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
|
||||
Iter::new(self.0.into_iter().map(Previewer::new), None).into_lua(lua)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
use std::{ops::Deref, sync::Arc};
|
||||
|
||||
use mlua::{UserData, UserDataFields, Value};
|
||||
use mlua::{ExternalError, FromLua, IntoLua, Lua, Table, UserData, UserDataFields, Value};
|
||||
use yazi_config::YAZI;
|
||||
|
||||
use crate::{Id, cached_field};
|
||||
use crate::{FileRef, Id, Iter, cached_field};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Spotter {
|
||||
|
|
@ -30,3 +31,45 @@ impl UserData for Spotter {
|
|||
cached_field!(fields, name, |lua, me| lua.create_string(&*me.name));
|
||||
}
|
||||
}
|
||||
|
||||
// --- Matcher
|
||||
pub struct SpotterMatcher(pub(super) yazi_config::plugin::SpotterMatcher<'static>);
|
||||
|
||||
impl SpotterMatcher {
|
||||
pub fn new(inner: impl Into<yazi_config::plugin::SpotterMatcher<'static>>) -> Self {
|
||||
Self(inner.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Table> for SpotterMatcher {
|
||||
type Error = mlua::Error;
|
||||
|
||||
fn try_from(value: Table) -> Result<Self, Self::Error> {
|
||||
let id: Id = value.raw_get("id").unwrap_or_default();
|
||||
let file: Option<FileRef> = value.raw_get("file")?;
|
||||
let mime: Option<String> = value.raw_get("mime")?;
|
||||
|
||||
Ok(Self(yazi_config::plugin::SpotterMatcher {
|
||||
spotters: YAZI.plugin.spotters.load_full(),
|
||||
id: id.0,
|
||||
file: file.map(|f| f.inner.clone().into()),
|
||||
mime: mime.map(Into::into),
|
||||
..Default::default()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromLua for SpotterMatcher {
|
||||
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
|
||||
match value {
|
||||
Value::Table(t) => t.try_into(),
|
||||
_ => return Err("expected a table of SpotterMatcher".into_lua_err()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoLua for SpotterMatcher {
|
||||
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
|
||||
Iter::new(self.0.into_iter().map(Spotter::new), None).into_lua(lua)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use mlua::{ExternalError, FromLua, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMethods, Value};
|
||||
use mlua::{IntoLua, MetaMethod, UserData, UserDataMethods};
|
||||
use yazi_config::YAZI;
|
||||
|
||||
use crate::{FileRef, Id, Iter, config::Spotter};
|
||||
use crate::config::SpotterMatcher;
|
||||
|
||||
pub struct Spotters;
|
||||
|
||||
|
|
@ -15,45 +15,3 @@ impl UserData for Spotters {
|
|||
methods.add_meta_method(MetaMethod::Len, |_, _, ()| Ok(YAZI.plugin.spotters.load().len()));
|
||||
}
|
||||
}
|
||||
|
||||
// --- Matcher
|
||||
pub struct SpotterMatcher(yazi_config::plugin::SpotterMatcher<'static>);
|
||||
|
||||
impl SpotterMatcher {
|
||||
fn new(inner: impl Into<yazi_config::plugin::SpotterMatcher<'static>>) -> Self {
|
||||
Self(inner.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Table> for SpotterMatcher {
|
||||
type Error = mlua::Error;
|
||||
|
||||
fn try_from(value: Table) -> Result<Self, Self::Error> {
|
||||
let id: Id = value.raw_get("id").unwrap_or_default();
|
||||
let file: Option<FileRef> = value.raw_get("file")?;
|
||||
let mime: Option<String> = value.raw_get("mime")?;
|
||||
|
||||
Ok(Self(yazi_config::plugin::SpotterMatcher {
|
||||
spotters: YAZI.plugin.spotters.load_full(),
|
||||
id: id.0,
|
||||
file: file.map(|f| f.inner.clone().into()),
|
||||
mime: mime.map(Into::into),
|
||||
..Default::default()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromLua for SpotterMatcher {
|
||||
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
|
||||
match value {
|
||||
Value::Table(t) => t.try_into(),
|
||||
_ => return Err("expected a table of SpotterMatcher".into_lua_err()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoLua for SpotterMatcher {
|
||||
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
|
||||
Iter::new(self.0.into_iter().map(Spotter::new), None).into_lua(lua)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use mlua::{ExternalError, FromLua, Lua, Table, UserData, Value};
|
||||
use mlua::{FromLua, Lua, LuaSerdeExt, UserData, Value};
|
||||
|
||||
pub struct Selector(yazi_config::Selector);
|
||||
|
||||
|
|
@ -14,30 +14,8 @@ impl From<Selector> for yazi_config::Selector {
|
|||
fn from(value: Selector) -> Self { value.0 }
|
||||
}
|
||||
|
||||
impl TryFrom<&Table> for Selector {
|
||||
type Error = mlua::Error;
|
||||
|
||||
fn try_from(t: &Table) -> Result<Self, Self::Error> {
|
||||
Ok(Self(yazi_config::Selector::new(
|
||||
t.raw_get::<Option<mlua::String>>("url")?.map(|s| s.to_str()?.parse()).transpose()?,
|
||||
t.raw_get::<Option<mlua::String>>("mime")?.map(|s| s.to_str()?.parse()).transpose()?,
|
||||
)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Table> for Selector {
|
||||
type Error = mlua::Error;
|
||||
|
||||
fn try_from(value: Table) -> Result<Self, Self::Error> { Self::try_from(&value) }
|
||||
}
|
||||
|
||||
impl FromLua for Selector {
|
||||
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
|
||||
Ok(match value {
|
||||
Value::Table(t) => t.try_into()?,
|
||||
_ => Err("expected a table of Selector".into_lua_err())?,
|
||||
})
|
||||
}
|
||||
fn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> { Ok(Self(lua.from_value(value)?)) }
|
||||
}
|
||||
|
||||
impl UserData for Selector {}
|
||||
|
|
|
|||
|
|
@ -68,20 +68,20 @@ impl Actions {
|
|||
writeln!(s, " FZF_DEFAULT_COMMAND: {:?}", env::var_os("FZF_DEFAULT_COMMAND"))?;
|
||||
|
||||
writeln!(s, "\nText Opener")?;
|
||||
#[rustfmt::skip]
|
||||
writeln!(
|
||||
s,
|
||||
" default : {:?}",
|
||||
YAZI.opener.first(YAZI.open.all_dummy(Path::new("f75a.txt"), "text/plain"))
|
||||
s, " default : {:?}",
|
||||
YAZI.open.match_dummy(Path::new("f75a.txt"), "text/plain").and_then(|r| YAZI.opener.first(&r))
|
||||
)?;
|
||||
#[rustfmt::skip]
|
||||
writeln!(
|
||||
s,
|
||||
" block-create: {:?}",
|
||||
YAZI.opener.block(YAZI.open.all_dummy(Path::new("bulk-create.txt"), "text/plain"))
|
||||
s, " block-create: {:?}",
|
||||
YAZI.open.match_dummy(Path::new("bulk-create.txt"), "text/plain").and_then(|r| YAZI.opener.block(&r))
|
||||
)?;
|
||||
#[rustfmt::skip]
|
||||
writeln!(
|
||||
s,
|
||||
" block-rename: {:?}",
|
||||
YAZI.opener.block(YAZI.open.all_dummy(Path::new("bulk-rename.txt"), "text/plain"))
|
||||
s, " block-rename: {:?}",
|
||||
YAZI.open.match_dummy(Path::new("bulk-rename.txt"), "text/plain").and_then(|r| YAZI.opener.block(&r))
|
||||
)?;
|
||||
|
||||
writeln!(s, "\nMultiplexers")?;
|
||||
|
|
|
|||
|
|
@ -17,5 +17,6 @@ proc-macro = true
|
|||
|
||||
[dependencies]
|
||||
# External dependencies
|
||||
syn = { version = "2.0.117", features = [ "full" ] }
|
||||
quote = "1.0.45"
|
||||
proc-macro2 = "1"
|
||||
quote = "1.0.45"
|
||||
syn = { version = "2.0.117", features = [ "full" ] }
|
||||
|
|
|
|||
38
yazi-codegen/src/helper.rs
Normal file
38
yazi-codegen/src/helper.rs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
use proc_macro2::Span;
|
||||
use syn::{Attribute, Data, Field, Fields, GenericParam, Ident, Lifetime, LifetimeParam, Meta, punctuated::Punctuated, token::Comma};
|
||||
|
||||
pub(super) fn named_fields(data: Data) -> Punctuated<Field, Comma> {
|
||||
match data {
|
||||
Data::Struct(s) => match s.fields {
|
||||
Fields::Named(f) => f.named,
|
||||
_ => panic!("expected named fields"),
|
||||
},
|
||||
_ => panic!("expected struct"),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn ident_name(ident: &Ident) -> String {
|
||||
let name = ident.to_string();
|
||||
match name.strip_prefix("r#") {
|
||||
Some(s) => s.to_owned(),
|
||||
None => name,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn generics_with_de(generics: &syn::Generics) -> syn::Generics {
|
||||
let mut g = generics.clone();
|
||||
g.params.insert(
|
||||
0,
|
||||
GenericParam::Lifetime(LifetimeParam::new(Lifetime::new("'__de", Span::call_site()))),
|
||||
);
|
||||
g
|
||||
}
|
||||
|
||||
pub(super) fn has_serde_attr(attrs: &[Attribute], name: &str) -> bool {
|
||||
attrs.iter().any(|a| {
|
||||
a.path().is_ident("serde")
|
||||
&& a
|
||||
.parse_args_with(Punctuated::<Meta, Comma>::parse_terminated)
|
||||
.map_or(false, |n| n.iter().any(|m| m.path().is_ident(name)))
|
||||
})
|
||||
}
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
use proc_macro::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{Data, DeriveInput, Fields, parse_macro_input};
|
||||
use quote::quote;
|
||||
mod helper;
|
||||
use syn::{DeriveInput, parse_macro_input};
|
||||
|
||||
use crate::helper::{generics_with_de, has_serde_attr, ident_name, named_fields};
|
||||
|
||||
#[proc_macro_derive(DeserializeOver)]
|
||||
pub fn deserialize_over(input: TokenStream) -> TokenStream {
|
||||
|
|
@ -18,47 +21,48 @@ pub fn deserialize_over1(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 assignments = match data {
|
||||
Data::Struct(struct_) => match struct_.fields {
|
||||
Fields::Named(fields) => {
|
||||
let mut assignments = Vec::with_capacity(fields.named.len());
|
||||
let visitor_generics = generics_with_de(&generics);
|
||||
let (impl_visitor_generics, ..) = visitor_generics.split_for_impl();
|
||||
|
||||
for field in fields.named {
|
||||
let field_ident = &field.ident;
|
||||
let field_name = field_ident.as_ref().unwrap().to_string();
|
||||
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))?, }
|
||||
});
|
||||
|
||||
assignments.push(quote! {
|
||||
if let Some(value) = table.remove(#field_name) {
|
||||
if !matches!(value.get_ref(), toml::de::DeValue::Table(_)) {
|
||||
_ = deserialize_spanned::<toml::Table>(value)?;
|
||||
return Err(serde::de::Error::custom(format!("expected top-level `{}` to be a TOML table", #field_name)));
|
||||
}
|
||||
|
||||
let span = value.span();
|
||||
if let toml::de::DeValue::Table(table) = value.into_inner() {
|
||||
self.#field_ident = self.#field_ident.deserialize_over_with(toml::Spanned::new(span, table))?;
|
||||
}
|
||||
}
|
||||
self.#field_ident = self.#field_ident.deserialize_over_hook()?;
|
||||
});
|
||||
}
|
||||
|
||||
assignments
|
||||
}
|
||||
_ => panic!("DeserializeOver1 only supports structs with named fields"),
|
||||
},
|
||||
_ => panic!("DeserializeOver1 only supports structs"),
|
||||
};
|
||||
let hook_fields = fields.iter().map(|f| {
|
||||
quote! { #f: deserialized.#f.deserialize_over_hook().map_err(Error::custom)? }
|
||||
});
|
||||
|
||||
quote! {
|
||||
impl #impl_generics yazi_shim::toml::DeserializeOverWith for #ident #ty_generics #where_clause {
|
||||
fn deserialize_over_with<'de>(mut self, table: toml::Spanned<toml::de::DeTable<'de>>) -> Result<Self, toml::de::Error> {
|
||||
use yazi_shim::toml::{DeserializeOverHook, DeserializeOverWith, deserialize_spanned};
|
||||
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};
|
||||
|
||||
let mut table = table.into_inner();
|
||||
#(#assignments)*
|
||||
struct V #impl_generics (#ident #ty_generics) #where_clause;
|
||||
|
||||
self.deserialize_over_hook()
|
||||
impl #impl_visitor_generics Visitor<'__de> for V #ty_generics #where_clause {
|
||||
type Value = #ident #ty_generics;
|
||||
|
||||
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::<Cow<str>>()? {
|
||||
match key.as_ref() {
|
||||
#(#match_arms)*
|
||||
_ => { map.next_value::<IgnoredAny>()?; }
|
||||
}
|
||||
}
|
||||
Ok(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
let deserialized = de.deserialize_map(V(self))?;
|
||||
Ok(Self { #(#hook_fields,)* })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -70,47 +74,79 @@ pub fn deserialize_over2(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 assignments = match data {
|
||||
Data::Struct(struct_) => match struct_.fields {
|
||||
Fields::Named(fields) => {
|
||||
let mut assignments = Vec::with_capacity(fields.named.len());
|
||||
let visitor_generics = generics_with_de(&generics);
|
||||
let (impl_visitor_generics, ..) = visitor_generics.split_for_impl();
|
||||
|
||||
for (index, field) in fields.named.into_iter().enumerate() {
|
||||
let field_ident = field.ident;
|
||||
let field_name = field_ident.as_ref().unwrap().to_string();
|
||||
let field_ty = field.ty;
|
||||
let field_attrs = field.attrs;
|
||||
let field_helper = format_ident!("__DeserializeOver2Field{index}");
|
||||
let (mut match_arms, mut post_fields) = (vec![], vec![]);
|
||||
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());
|
||||
|
||||
assignments.push(quote! {
|
||||
#[derive(serde::Deserialize)]
|
||||
struct #field_helper #impl_generics (
|
||||
#(#field_attrs)*
|
||||
#field_ty
|
||||
) #where_clause;
|
||||
if has_serde_attr(&field.attrs, "skip") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(value) = table.remove(#field_name) {
|
||||
self.#field_ident = deserialize_spanned::<#field_helper #ty_generics>(value)?.0;
|
||||
}
|
||||
});
|
||||
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)?;
|
||||
});
|
||||
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()?, });
|
||||
} else {
|
||||
match_arms.push(quote! {
|
||||
#field_name => {
|
||||
#[derive(serde::Deserialize)]
|
||||
struct H #impl_generics(#(#serde_attrs)* #field_ty,) #where_clause;
|
||||
self.0.#field_ident = map.next_value::<H #ty_generics>()?.0;
|
||||
}
|
||||
|
||||
assignments
|
||||
}
|
||||
_ => panic!("DeserializeOver2 only supports structs with named fields"),
|
||||
},
|
||||
_ => panic!("DeserializeOver2 only supports structs"),
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
quote! {
|
||||
impl #impl_generics yazi_shim::toml::DeserializeOverWith for #ident #ty_generics #where_clause {
|
||||
fn deserialize_over_with<'de>(mut self, table: toml::Spanned<toml::de::DeTable<'de>>) -> Result<Self, toml::de::Error> {
|
||||
use yazi_shim::toml::deserialize_spanned;
|
||||
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;
|
||||
|
||||
let mut table = table.into_inner();
|
||||
#(#assignments)*
|
||||
struct V #impl_generics (#ident #ty_generics) #where_clause;
|
||||
|
||||
Ok(self)
|
||||
impl #impl_visitor_generics Visitor<'__de> for V #ty_generics #where_clause {
|
||||
type Value = #ident #ty_generics;
|
||||
|
||||
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> {
|
||||
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>()?; }
|
||||
}
|
||||
}
|
||||
|
||||
#(#post_fields)*
|
||||
Ok(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
de.deserialize_map(V(self))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,10 @@ fn try_init_flavor(light: bool, merge: bool) -> anyhow::Result<()> {
|
|||
let flavor_str = theme::Flavor::from_theme(&theme, &theme_str)?.read(light)?;
|
||||
|
||||
preset = preset.deserialize_over(&flavor_str)?;
|
||||
preset = error_with_input(preset.deserialize_over_with(theme), &theme_str)?;
|
||||
preset = error_with_input(
|
||||
preset.deserialize_over_with(toml::de::Deserializer::from(theme)),
|
||||
&theme_str,
|
||||
)?;
|
||||
}
|
||||
|
||||
THEME.init(preset.reshape(light)?);
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
yazi_macro::mod_flat!(open rule);
|
||||
yazi_macro::mod_flat!(open rule rules);
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
use std::ops::Deref;
|
||||
use std::{ops::Deref, sync::Arc};
|
||||
|
||||
use anyhow::Result;
|
||||
use indexmap::IndexSet;
|
||||
use indexmap::IndexMap;
|
||||
use serde::Deserialize;
|
||||
use yazi_codegen::DeserializeOver2;
|
||||
use yazi_fs::{File, cha::ChaType};
|
||||
use yazi_shared::url::AsUrl;
|
||||
use yazi_shim::toml::DeserializeOverHook;
|
||||
|
||||
use crate::{Selectable, mix, open::OpenRule};
|
||||
use crate::{mix, open::{OpenRule, OpenRules}};
|
||||
|
||||
#[derive(Default, Deserialize, DeserializeOver2)]
|
||||
pub struct Open {
|
||||
rules: Vec<OpenRule>,
|
||||
rules: OpenRules,
|
||||
#[serde(default)]
|
||||
prepend_rules: Vec<OpenRule>,
|
||||
#[serde(default)]
|
||||
|
|
@ -20,23 +20,13 @@ pub struct Open {
|
|||
}
|
||||
|
||||
impl Deref for Open {
|
||||
type Target = Vec<OpenRule>;
|
||||
type Target = OpenRules;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.rules }
|
||||
}
|
||||
|
||||
impl Open {
|
||||
pub fn all<'a>(&'a self, file: &File, mime: &str) -> impl Iterator<Item = &'a str> {
|
||||
self
|
||||
.rules
|
||||
.iter()
|
||||
.find(move |&rule| rule.matches(file, mime))
|
||||
.into_iter()
|
||||
.flat_map(|r| &r.r#use)
|
||||
.map(String::as_str)
|
||||
}
|
||||
|
||||
pub fn all_dummy<'a, U, M>(&'a self, url: U, mime: M) -> impl Iterator<Item = &'a str>
|
||||
pub fn match_dummy<U, M>(&self, url: U, mime: M) -> Option<Arc<OpenRule>>
|
||||
where
|
||||
U: AsUrl,
|
||||
M: AsRef<str>,
|
||||
|
|
@ -47,30 +37,26 @@ impl Open {
|
|||
Some(if mime.starts_with("folder/") { ChaType::Dir } else { ChaType::File }),
|
||||
);
|
||||
|
||||
self
|
||||
.rules
|
||||
.iter()
|
||||
.find(move |&rule| rule.matches(&file, mime))
|
||||
.into_iter()
|
||||
.flat_map(|r| &r.r#use)
|
||||
.map(String::as_str)
|
||||
self.matches(&file, mime)
|
||||
}
|
||||
|
||||
pub fn common<'a>(&'a self, targets: &[(File, &str)]) -> IndexSet<&'a str> {
|
||||
let each: Vec<IndexSet<&str>> = targets
|
||||
.iter()
|
||||
.map(|(file, mime)| self.all(file, mime).collect::<IndexSet<_>>())
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect();
|
||||
pub fn match_common(&self, targets: &[(File, &str)]) -> impl Iterator<Item = Arc<OpenRule>> {
|
||||
let mut seen: IndexMap<Arc<OpenRule>, usize> = IndexMap::new();
|
||||
for (file, mime) in targets {
|
||||
if let Some(rule) = self.matches(file, mime) {
|
||||
*seen.entry(rule).or_default() += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let mut flat: IndexSet<_> = each.iter().flatten().copied().collect();
|
||||
flat.retain(|use_| each.iter().all(|e| e.contains(use_)));
|
||||
flat
|
||||
seen.into_iter().filter(|&(_, count)| count == targets.len()).map(|(rule, _)| rule)
|
||||
}
|
||||
}
|
||||
|
||||
impl DeserializeOverHook for Open {
|
||||
fn deserialize_over_hook(self) -> Result<Self, toml::de::Error> {
|
||||
Ok(Self { rules: mix(self.prepend_rules, self.rules, self.append_rules), ..Default::default() })
|
||||
let rules: Vec<Arc<OpenRule>> =
|
||||
mix(self.prepend_rules, self.rules.unwrap_unchecked(), self.append_rules);
|
||||
|
||||
Ok(Self { rules: rules.into(), ..Default::default() })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,33 @@
|
|||
use std::hash::{Hash, Hasher};
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde_with::{OneOrMany, formats::PreferOne, serde_as};
|
||||
use yazi_codegen::DeserializeOver2;
|
||||
use yazi_shared::Id;
|
||||
|
||||
use crate::{Mixable, Pattern, Selectable, Selector};
|
||||
use crate::{Mixable, Pattern, Selectable, Selector, plugin::open_rule_id};
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Clone, Debug, Deserialize, DeserializeOver2)]
|
||||
pub struct OpenRule {
|
||||
#[serde(skip, default = "open_rule_id")]
|
||||
pub id: Id,
|
||||
#[serde(flatten)]
|
||||
pub selector: Selector,
|
||||
#[serde_as(as = "OneOrMany<_, PreferOne>")]
|
||||
pub r#use: Vec<String>,
|
||||
}
|
||||
|
||||
impl PartialEq for OpenRule {
|
||||
fn eq(&self, other: &Self) -> bool { self.id == other.id }
|
||||
}
|
||||
|
||||
impl Eq for OpenRule {}
|
||||
|
||||
impl Hash for OpenRule {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) { self.id.hash(state); }
|
||||
}
|
||||
|
||||
impl Selectable for OpenRule {
|
||||
fn url_pat(&self) -> Option<&Pattern> { self.selector.url_pat() }
|
||||
|
||||
|
|
|
|||
133
yazi-config/src/open/rules.rs
Normal file
133
yazi-config/src/open/rules.rs
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
use std::{borrow::Cow, ops::Deref, sync::Arc};
|
||||
|
||||
use arc_swap::ArcSwap;
|
||||
use serde::Deserialize;
|
||||
use yazi_fs::File;
|
||||
use yazi_shared::Id;
|
||||
use yazi_shim::{arc_swap::{ArcSwapExt, IntoPointee}, vec::{IndexAtError, VecExt}};
|
||||
|
||||
use super::OpenRule;
|
||||
use crate::{Selectable, mix};
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
pub struct OpenRules(ArcSwap<Vec<Arc<OpenRule>>>);
|
||||
|
||||
impl Deref for OpenRules {
|
||||
type Target = ArcSwap<Vec<Arc<OpenRule>>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl From<Vec<Arc<OpenRule>>> for OpenRules {
|
||||
fn from(inner: Vec<Arc<OpenRule>>) -> Self { Self(inner.into_pointee()) }
|
||||
}
|
||||
|
||||
impl OpenRules {
|
||||
pub fn matches(&self, file: &File, mime: &str) -> Option<Arc<OpenRule>> {
|
||||
self.matcher(Some(file), Some(mime)).next()
|
||||
}
|
||||
|
||||
pub fn matcher<'a, F, M>(&self, file: Option<F>, mime: Option<M>) -> OpenRuleMatcher<'a>
|
||||
where
|
||||
F: Into<Cow<'a, File>>,
|
||||
M: Into<Cow<'a, str>>,
|
||||
{
|
||||
OpenRuleMatcher {
|
||||
rules: self.0.load_full(),
|
||||
file: file.map(Into::into),
|
||||
mime: mime.map(Into::into),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&self, index: isize, rule: Arc<OpenRule>) -> Result<(), IndexAtError> {
|
||||
self.0.try_rcu(|rules| {
|
||||
let i = rules.index_at(index)?;
|
||||
Ok(if i == rules.len() {
|
||||
mix(Vec::<OpenRule>::new(), rules.iter().cloned(), [rule.clone()])
|
||||
} else {
|
||||
let (before, after) = rules.split_at(i);
|
||||
mix(
|
||||
Vec::<OpenRule>::new(),
|
||||
before.iter().cloned().chain([rule.clone()]).chain(after.iter().cloned()),
|
||||
Vec::<OpenRule>::new(),
|
||||
)
|
||||
})
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove(&self, matcher: OpenRuleMatcher) {
|
||||
self.0.rcu(|rules| {
|
||||
let mut next = Vec::clone(rules);
|
||||
next.retain(|rule| !matcher.matches(rule));
|
||||
next
|
||||
});
|
||||
}
|
||||
|
||||
pub fn update<E>(
|
||||
&self,
|
||||
matcher: OpenRuleMatcher,
|
||||
f: impl Fn(OpenRule) -> Result<OpenRule, E>,
|
||||
) -> Result<(), E> {
|
||||
self.0.try_rcu(|rules| {
|
||||
let mut next = Vec::clone(rules);
|
||||
for rule in &mut next {
|
||||
if matcher.matches(rule) {
|
||||
*rule = f(OpenRule::clone(rule))?.into();
|
||||
}
|
||||
}
|
||||
Ok(Arc::new(next))
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn unwrap_unchecked(self) -> Vec<Arc<OpenRule>> {
|
||||
Arc::try_unwrap(self.0.into_inner()).expect("unique open rules arc")
|
||||
}
|
||||
}
|
||||
|
||||
// --- Matcher
|
||||
#[derive(Default)]
|
||||
pub struct OpenRuleMatcher<'a> {
|
||||
pub rules: Arc<Vec<Arc<OpenRule>>>,
|
||||
pub id: Id,
|
||||
pub file: Option<Cow<'a, File>>,
|
||||
pub mime: Option<Cow<'a, str>>,
|
||||
pub all: bool,
|
||||
pub offset: usize,
|
||||
}
|
||||
|
||||
impl From<&OpenRules> for OpenRuleMatcher<'_> {
|
||||
fn from(rules: &OpenRules) -> Self {
|
||||
Self { rules: rules.load_full(), all: true, ..Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
impl OpenRuleMatcher<'_> {
|
||||
pub fn matches(&self, rule: &OpenRule) -> bool {
|
||||
if self.all {
|
||||
true
|
||||
} else if self.id != Id::ZERO {
|
||||
rule.id == self.id
|
||||
} else {
|
||||
rule.match_with(self.file.as_deref(), self.mime.as_deref())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for OpenRuleMatcher<'_> {
|
||||
type Item = Arc<OpenRule>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
while let Some(rule) = self.rules.get(self.offset) {
|
||||
self.offset += 1;
|
||||
if self.matches(rule) {
|
||||
return Some(rule.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
yazi_macro::mod_flat!(opener rule);
|
||||
yazi_macro::mod_flat!(opener rule rules);
|
||||
|
|
|
|||
|
|
@ -1,64 +1,102 @@
|
|||
use std::ops::Deref;
|
||||
use std::{mem, ops::Deref, sync::Arc};
|
||||
|
||||
use arc_swap::ArcSwap;
|
||||
use hashbrown::HashMap;
|
||||
use serde::Deserialize;
|
||||
use toml::{Spanned, de::DeTable};
|
||||
use yazi_shim::toml::{DeserializeOverHook, DeserializeOverWith, deserialize_spanned};
|
||||
use serde::{Deserialize, Deserializer, de::{MapAccess, Visitor}};
|
||||
use yazi_shim::{arc_swap::IntoPointee, toml::{DeserializeOverHook, DeserializeOverWith}};
|
||||
|
||||
use super::OpenerRule;
|
||||
use super::{OpenerRule, OpenerRules};
|
||||
use crate::{open::OpenRule, opener::OpenerRuleMatcher};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Opener(HashMap<String, Vec<OpenerRule>>);
|
||||
pub struct Opener(ArcSwap<HashMap<String, Arc<OpenerRules>>>);
|
||||
|
||||
impl Deref for Opener {
|
||||
type Target = HashMap<String, Vec<OpenerRule>>;
|
||||
type Target = ArcSwap<HashMap<String, Arc<OpenerRules>>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl Opener {
|
||||
pub fn all<'a, I>(&self, uses: I) -> impl Iterator<Item = &OpenerRule>
|
||||
where
|
||||
I: Iterator<Item = &'a str>,
|
||||
{
|
||||
uses.flat_map(|use_| self.get(use_)).flatten()
|
||||
pub fn all(&self, open: Arc<OpenRule>) -> impl Iterator<Item = Arc<OpenerRule>> + use<> {
|
||||
let inner = self.0.load_full();
|
||||
(0..open.r#use.len())
|
||||
.filter_map(move |i| inner.get(&open.r#use[i]).cloned())
|
||||
.flat_map(|rules| OpenerRuleMatcher::from(&*rules))
|
||||
}
|
||||
|
||||
pub fn first<'a, I>(&self, uses: I) -> Option<&OpenerRule>
|
||||
where
|
||||
I: Iterator<Item = &'a str>,
|
||||
{
|
||||
uses.flat_map(|use_| self.get(use_)).flatten().next()
|
||||
pub fn first(&self, open: &OpenRule) -> Option<Arc<OpenerRule>> {
|
||||
let inner = self.0.load();
|
||||
open
|
||||
.r#use
|
||||
.iter()
|
||||
.filter_map(|name| inner.get(name))
|
||||
.find_map(|rules| rules.load().first().cloned())
|
||||
}
|
||||
|
||||
pub fn block<'a, I>(&self, uses: I) -> Option<&OpenerRule>
|
||||
where
|
||||
I: Iterator<Item = &'a str>,
|
||||
{
|
||||
uses.flat_map(|use_| self.get(use_)).flatten().find(|&o| o.block)
|
||||
pub fn block(&self, open: &OpenRule) -> Option<Arc<OpenerRule>> {
|
||||
let inner = self.0.load();
|
||||
open
|
||||
.r#use
|
||||
.iter()
|
||||
.filter_map(|name| inner.get(name))
|
||||
.find_map(|rules| rules.load().iter().find(|r| r.block).cloned())
|
||||
}
|
||||
|
||||
pub fn insert(&self, name: &str, rules: &Arc<OpenerRules>) {
|
||||
self.0.rcu(|inner| {
|
||||
let mut next = HashMap::clone(inner);
|
||||
next.insert(name.to_owned(), rules.clone());
|
||||
next
|
||||
});
|
||||
}
|
||||
|
||||
pub fn remove(&self, name: &str) {
|
||||
self.0.rcu(|inner| {
|
||||
let mut next = HashMap::clone(inner);
|
||||
next.remove(name);
|
||||
next
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn unwrap_unchecked(self) -> HashMap<String, Arc<OpenerRules>> {
|
||||
Arc::try_unwrap(self.0.into_inner()).expect("unique opener arc")
|
||||
}
|
||||
}
|
||||
|
||||
impl DeserializeOverHook for Opener {
|
||||
fn deserialize_over_hook(mut self) -> Result<Self, toml::de::Error> {
|
||||
for rules in self.0.values_mut() {
|
||||
rules.retain(|r| r.r#for.matches());
|
||||
rules.iter_mut().for_each(|r| r.fill());
|
||||
fn deserialize_over_hook(self) -> Result<Self, toml::de::Error> {
|
||||
let mut inner = self.unwrap_unchecked();
|
||||
for mut rules in inner.values_mut() {
|
||||
*rules = Arc::try_unwrap(mem::take(&mut rules))
|
||||
.expect("unique opener value arc")
|
||||
.deserialize_over_hook()?
|
||||
.into();
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
Ok(Self(inner.into_pointee()))
|
||||
}
|
||||
}
|
||||
|
||||
impl DeserializeOverWith for Opener {
|
||||
fn deserialize_over_with<'de>(
|
||||
mut self,
|
||||
table: Spanned<DeTable<'de>>,
|
||||
) -> Result<Self, toml::de::Error> {
|
||||
for (key, value) in table.into_inner() {
|
||||
self.0.insert(key.into_inner().into_owned(), deserialize_spanned(value)?);
|
||||
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()))
|
||||
}
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
de.deserialize_map(V(self.unwrap_unchecked()))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use serde::Deserialize;
|
||||
use yazi_fs::Splatter;
|
||||
use yazi_shared::NonEmptyString;
|
||||
use yazi_shared::{Id, NonEmptyString};
|
||||
|
||||
use crate::Platform;
|
||||
use crate::{Platform, opener::OpenerRules, plugin::opener_rule_id};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub struct OpenerRule {
|
||||
#[serde(skip, default = "opener_rule_id")]
|
||||
pub id: Id,
|
||||
pub run: NonEmptyString,
|
||||
#[serde(default)]
|
||||
pub block: bool,
|
||||
|
|
@ -30,7 +34,7 @@ impl OpenerRule {
|
|||
}
|
||||
}
|
||||
|
||||
pub(super) fn fill(&mut self) {
|
||||
pub fn fill(&mut self) {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
self.spread =
|
||||
|
|
@ -42,3 +46,44 @@ impl OpenerRule {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Matcher
|
||||
#[derive(Default)]
|
||||
pub struct OpenerRuleMatcher {
|
||||
pub rules: Arc<Vec<Arc<OpenerRule>>>,
|
||||
pub id: Id,
|
||||
pub all: bool,
|
||||
pub offset: usize,
|
||||
}
|
||||
|
||||
impl From<&OpenerRules> for OpenerRuleMatcher {
|
||||
fn from(rules: &OpenerRules) -> Self {
|
||||
Self { rules: rules.load_full(), all: true, ..Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
impl OpenerRuleMatcher {
|
||||
pub fn matches(&self, rule: &OpenerRule) -> bool {
|
||||
if self.all {
|
||||
true
|
||||
} else if self.id != Id::ZERO {
|
||||
rule.id == self.id
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for OpenerRuleMatcher {
|
||||
type Item = Arc<OpenerRule>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
while let Some(rule) = self.rules.get(self.offset) {
|
||||
self.offset += 1;
|
||||
if self.matches(rule) {
|
||||
return Some(rule.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
|
|||
86
yazi-config/src/opener/rules.rs
Normal file
86
yazi-config/src/opener/rules.rs
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
use std::{mem, ops::Deref, sync::Arc};
|
||||
|
||||
use arc_swap::ArcSwap;
|
||||
use hashbrown::{HashMap, hash_map};
|
||||
use serde::Deserialize;
|
||||
use yazi_shim::{arc_swap::{ArcSwapExt, IntoPointee}, toml::DeserializeOverHook, vec::{IndexAtError, VecExt}};
|
||||
|
||||
use super::OpenerRule;
|
||||
use crate::opener::{Opener, OpenerRuleMatcher};
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
pub struct OpenerRules(ArcSwap<Vec<Arc<OpenerRule>>>);
|
||||
|
||||
impl Deref for OpenerRules {
|
||||
type Target = ArcSwap<Vec<Arc<OpenerRule>>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl From<Vec<Arc<OpenerRule>>> for OpenerRules {
|
||||
fn from(inner: Vec<Arc<OpenerRule>>) -> Self { Self(inner.into_pointee()) }
|
||||
}
|
||||
|
||||
impl OpenerRules {
|
||||
pub fn insert(&self, index: isize, rule: Arc<OpenerRule>) -> Result<(), IndexAtError> {
|
||||
self.0.try_rcu(|rules| {
|
||||
let (before, after) = rules.split_at(rules.index_at(index)?);
|
||||
Ok(
|
||||
before
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain([rule.clone()])
|
||||
.chain(after.iter().cloned())
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove(&self, matcher: OpenerRuleMatcher) {
|
||||
self.0.rcu(|rules| {
|
||||
let mut next = Vec::clone(rules);
|
||||
next.retain(|rule| !matcher.matches(rule));
|
||||
next
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn unwrap_unchecked(self) -> Vec<Arc<OpenerRule>> {
|
||||
Arc::try_unwrap(self.0.into_inner()).expect("unique opener rules arc")
|
||||
}
|
||||
}
|
||||
|
||||
impl DeserializeOverHook for OpenerRules {
|
||||
fn deserialize_over_hook(self) -> Result<Self, toml::de::Error> {
|
||||
let mut inner = self.unwrap_unchecked();
|
||||
|
||||
inner.retain(|r| r.r#for.matches());
|
||||
inner.iter_mut().for_each(|r| Arc::get_mut(r).expect("unique opener rule arc").fill());
|
||||
|
||||
Ok(Self(inner.into_pointee()))
|
||||
}
|
||||
}
|
||||
|
||||
// --- Matcher
|
||||
pub struct OpenerRulesMatcher {
|
||||
iter: hash_map::Iter<'static, String, Arc<OpenerRules>>,
|
||||
_opener: Arc<HashMap<String, Arc<OpenerRules>>>,
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for OpenerRulesMatcher {
|
||||
type Item = (String, Arc<OpenerRules>);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.iter.next().map(|(name, rules)| (name.clone(), rules.clone()))
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@ use yazi_shared::{scheme::SchemeKind, url::AsUrl};
|
|||
|
||||
use crate::Mixable;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Clone, Deserialize)]
|
||||
#[serde(try_from = "String")]
|
||||
pub struct Pattern {
|
||||
inner: globset::GlobMatcher,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
use std::ops::Deref;
|
||||
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 crate::{Mixable, Pattern, Priority, Selectable, Selector, plugin::fetcher_id};
|
||||
use crate::{Mixable, Pattern, Priority, Selectable, Selector, plugin::{Fetchers, fetcher_id}};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Fetcher {
|
||||
|
|
@ -32,3 +34,50 @@ impl Selectable for Fetcher {
|
|||
}
|
||||
|
||||
impl Mixable for Fetcher {}
|
||||
|
||||
// --- Matcher
|
||||
#[derive(Default)]
|
||||
pub struct FetcherMatcher<'a> {
|
||||
pub fetchers: Arc<Vec<Arc<Fetcher>>>,
|
||||
pub id: Id,
|
||||
pub file: Option<Cow<'a, File>>,
|
||||
pub mime: Option<Cow<'a, str>>,
|
||||
pub all: bool,
|
||||
pub offset: usize,
|
||||
pub seen: HashSet<String>,
|
||||
}
|
||||
|
||||
impl From<&Fetchers> for FetcherMatcher<'_> {
|
||||
fn from(fetchers: &Fetchers) -> Self {
|
||||
Self { fetchers: fetchers.load_full(), all: true, ..Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
impl FetcherMatcher<'_> {
|
||||
pub fn matches(&self, fetcher: &Fetcher) -> bool {
|
||||
if self.all {
|
||||
true
|
||||
} else if self.id != Id::ZERO {
|
||||
fetcher.id == self.id
|
||||
} else {
|
||||
fetcher.match_with(self.file.as_deref(), self.mime.as_deref())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for FetcherMatcher<'_> {
|
||||
type Item = Arc<Fetcher>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
while let Some(fetcher) = self.fetchers.get(self.offset) {
|
||||
self.offset += 1;
|
||||
if !self.matches(fetcher) {
|
||||
continue;
|
||||
}
|
||||
if self.all || self.seen.insert(fetcher.group.clone()) {
|
||||
return Some(fetcher.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,13 @@
|
|||
use std::{borrow::Cow, ops::Deref, sync::Arc};
|
||||
|
||||
use arc_swap::ArcSwap;
|
||||
use hashbrown::HashSet;
|
||||
use serde::Deserialize;
|
||||
use tracing::warn;
|
||||
use yazi_fs::File;
|
||||
use yazi_shared::Id;
|
||||
use yazi_shim::arc_swap::IntoPointee;
|
||||
|
||||
use super::{Fetcher, MAX_FETCHERS};
|
||||
use crate::Selectable;
|
||||
use crate::plugin::FetcherMatcher;
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
pub struct Fetchers(ArcSwap<Vec<Arc<Fetcher>>>);
|
||||
|
|
@ -68,50 +66,3 @@ impl Fetchers {
|
|||
Arc::try_unwrap(self.0.into_inner()).expect("unique fetchers arc")
|
||||
}
|
||||
}
|
||||
|
||||
// --- Matcher
|
||||
#[derive(Default)]
|
||||
pub struct FetcherMatcher<'a> {
|
||||
pub fetchers: Arc<Vec<Arc<Fetcher>>>,
|
||||
pub id: Id,
|
||||
pub file: Option<Cow<'a, File>>,
|
||||
pub mime: Option<Cow<'a, str>>,
|
||||
pub all: bool,
|
||||
pub offset: usize,
|
||||
pub seen: HashSet<String>,
|
||||
}
|
||||
|
||||
impl From<&Fetchers> for FetcherMatcher<'_> {
|
||||
fn from(fetchers: &Fetchers) -> Self {
|
||||
Self { fetchers: fetchers.load_full(), all: true, ..Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
impl FetcherMatcher<'_> {
|
||||
pub fn matches(&self, fetcher: &Fetcher) -> bool {
|
||||
if self.all {
|
||||
true
|
||||
} else if self.id != Id::ZERO {
|
||||
fetcher.id == self.id
|
||||
} else {
|
||||
fetcher.match_with(self.file.as_deref(), self.mime.as_deref())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for FetcherMatcher<'_> {
|
||||
type Item = Arc<Fetcher>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
while let Some(fetcher) = self.fetchers.get(self.offset) {
|
||||
self.offset += 1;
|
||||
if !self.matches(fetcher) {
|
||||
continue;
|
||||
}
|
||||
if self.all || self.seen.insert(fetcher.group.clone()) {
|
||||
return Some(fetcher.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,3 +19,13 @@ pub fn spotter_id() -> Id {
|
|||
static IDS: Ids = Ids::new();
|
||||
IDS.next()
|
||||
}
|
||||
|
||||
pub fn open_rule_id() -> Id {
|
||||
static IDS: Ids = Ids::new();
|
||||
IDS.next()
|
||||
}
|
||||
|
||||
pub fn opener_rule_id() -> Id {
|
||||
static IDS: Ids = Ids::new();
|
||||
IDS.next()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
use std::ops::Deref;
|
||||
use std::{borrow::Cow, ops::Deref, sync::Arc};
|
||||
|
||||
use serde::Deserialize;
|
||||
use yazi_fs::File;
|
||||
use yazi_shared::{Id, event::Action};
|
||||
|
||||
use crate::{Mixable, Pattern, Priority, Selectable, Selector, plugin::preloader_id};
|
||||
use crate::{Mixable, Pattern, Priority, Selectable, Selector, plugin::{Preloaders, preloader_id}};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Preloader {
|
||||
|
|
@ -33,3 +34,52 @@ impl Selectable for Preloader {
|
|||
}
|
||||
|
||||
impl Mixable for Preloader {}
|
||||
|
||||
// --- Matcher
|
||||
#[derive(Default)]
|
||||
pub struct PreloaderMatcher<'a> {
|
||||
pub preloaders: Arc<Vec<Arc<Preloader>>>,
|
||||
pub id: Id,
|
||||
pub file: Option<Cow<'a, File>>,
|
||||
pub mime: Option<Cow<'a, str>>,
|
||||
pub all: bool,
|
||||
pub offset: usize,
|
||||
pub stop: bool,
|
||||
}
|
||||
|
||||
impl From<&Preloaders> for PreloaderMatcher<'_> {
|
||||
fn from(preloaders: &Preloaders) -> Self {
|
||||
Self { preloaders: preloaders.load_full(), all: true, ..Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
impl PreloaderMatcher<'_> {
|
||||
pub fn matches(&self, preloader: &Preloader) -> bool {
|
||||
if self.all {
|
||||
true
|
||||
} else if self.id != Id::ZERO {
|
||||
preloader.id == self.id
|
||||
} else {
|
||||
preloader.match_with(self.file.as_deref(), self.mime.as_deref())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for PreloaderMatcher<'_> {
|
||||
type Item = Arc<Preloader>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.stop && !self.all {
|
||||
return None;
|
||||
}
|
||||
|
||||
while let Some(preloader) = self.preloaders.get(self.offset) {
|
||||
self.offset += 1;
|
||||
if self.matches(preloader) {
|
||||
self.stop = !preloader.next;
|
||||
return Some(preloader.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,11 +3,10 @@ use std::{borrow::Cow, ops::Deref, sync::Arc};
|
|||
use arc_swap::ArcSwap;
|
||||
use serde::Deserialize;
|
||||
use yazi_fs::File;
|
||||
use yazi_shared::Id;
|
||||
use yazi_shim::arc_swap::IntoPointee;
|
||||
|
||||
use super::Preloader;
|
||||
use crate::Selectable;
|
||||
use crate::plugin::PreloaderMatcher;
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
pub struct Preloaders(ArcSwap<Vec<Arc<Preloader>>>);
|
||||
|
|
@ -48,52 +47,3 @@ impl Preloaders {
|
|||
Arc::try_unwrap(self.0.into_inner()).expect("unique preloaders arc")
|
||||
}
|
||||
}
|
||||
|
||||
// --- Matcher
|
||||
#[derive(Default)]
|
||||
pub struct PreloaderMatcher<'a> {
|
||||
pub preloaders: Arc<Vec<Arc<Preloader>>>,
|
||||
pub id: Id,
|
||||
pub file: Option<Cow<'a, File>>,
|
||||
pub mime: Option<Cow<'a, str>>,
|
||||
pub all: bool,
|
||||
pub offset: usize,
|
||||
pub stop: bool,
|
||||
}
|
||||
|
||||
impl From<&Preloaders> for PreloaderMatcher<'_> {
|
||||
fn from(preloaders: &Preloaders) -> Self {
|
||||
Self { preloaders: preloaders.load_full(), all: true, ..Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
impl PreloaderMatcher<'_> {
|
||||
pub fn matches(&self, preloader: &Preloader) -> bool {
|
||||
if self.all {
|
||||
true
|
||||
} else if self.id != Id::ZERO {
|
||||
preloader.id == self.id
|
||||
} else {
|
||||
preloader.match_with(self.file.as_deref(), self.mime.as_deref())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for PreloaderMatcher<'_> {
|
||||
type Item = Arc<Preloader>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.stop && !self.all {
|
||||
return None;
|
||||
}
|
||||
|
||||
while let Some(preloader) = self.preloaders.get(self.offset) {
|
||||
self.offset += 1;
|
||||
if self.matches(preloader) {
|
||||
self.stop = !preloader.next;
|
||||
return Some(preloader.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
use std::ops::Deref;
|
||||
use std::{borrow::Cow, ops::Deref, sync::Arc};
|
||||
|
||||
use serde::Deserialize;
|
||||
use yazi_fs::File;
|
||||
use yazi_shared::{Id, event::Action};
|
||||
|
||||
use crate::{Mixable, Pattern, Selectable, Selector, plugin::previewer_id};
|
||||
use crate::{Mixable, Pattern, Selectable, Selector, plugin::{Previewers, previewer_id}};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Previewer {
|
||||
|
|
@ -32,3 +33,46 @@ impl Mixable for Previewer {
|
|||
|
||||
fn any_dir(&self) -> bool { self.selector.any_dir() }
|
||||
}
|
||||
|
||||
// --- Matcher
|
||||
#[derive(Default)]
|
||||
pub struct PreviewerMatcher<'a> {
|
||||
pub previewers: Arc<Vec<Arc<Previewer>>>,
|
||||
pub id: Id,
|
||||
pub file: Option<Cow<'a, File>>,
|
||||
pub mime: Option<Cow<'a, str>>,
|
||||
pub all: bool,
|
||||
pub offset: usize,
|
||||
}
|
||||
|
||||
impl From<&Previewers> for PreviewerMatcher<'_> {
|
||||
fn from(previewers: &Previewers) -> Self {
|
||||
Self { previewers: previewers.load_full(), all: true, ..Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
impl PreviewerMatcher<'_> {
|
||||
pub fn matches(&self, previewer: &Previewer) -> bool {
|
||||
if self.all {
|
||||
true
|
||||
} else if self.id != Id::ZERO {
|
||||
previewer.id == self.id
|
||||
} else {
|
||||
previewer.match_with(self.file.as_deref(), self.mime.as_deref())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for PreviewerMatcher<'_> {
|
||||
type Item = Arc<Previewer>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
while let Some(previewer) = self.previewers.get(self.offset) {
|
||||
self.offset += 1;
|
||||
if self.matches(previewer) {
|
||||
return Some(previewer.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,11 +3,10 @@ use std::{borrow::Cow, ops::Deref, sync::Arc};
|
|||
use arc_swap::ArcSwap;
|
||||
use serde::Deserialize;
|
||||
use yazi_fs::File;
|
||||
use yazi_shared::Id;
|
||||
use yazi_shim::{arc_swap::IntoPointee, vec::{IndexAtError, VecExt}};
|
||||
use yazi_shim::{arc_swap::{ArcSwapExt, IntoPointee}, vec::{IndexAtError, VecExt}};
|
||||
|
||||
use super::Previewer;
|
||||
use crate::{Selectable, mix};
|
||||
use crate::{mix, plugin::PreviewerMatcher};
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
pub struct Previewers(ArcSwap<Vec<Arc<Previewer>>>);
|
||||
|
|
@ -41,27 +40,21 @@ impl Previewers {
|
|||
}
|
||||
|
||||
pub fn insert(&self, index: isize, previewer: Arc<Previewer>) -> Result<(), IndexAtError> {
|
||||
let mut err = None;
|
||||
|
||||
self.0.rcu(|previewers| match previewers.index_at(index) {
|
||||
Ok(n) if n == previewers.len() => {
|
||||
mix(Vec::<Previewer>::new(), previewers.iter().cloned(), [previewer.clone()])
|
||||
}
|
||||
Ok(n) => {
|
||||
let (before, after) = previewers.split_at(n);
|
||||
mix(
|
||||
self.0.try_rcu(|previewers| {
|
||||
let i = previewers.index_at(index)?;
|
||||
if i == previewers.len() {
|
||||
Ok(mix(Vec::<Previewer>::new(), previewers.iter().cloned(), [previewer.clone()]))
|
||||
} else {
|
||||
let (before, after) = previewers.split_at(i);
|
||||
Ok(mix(
|
||||
Vec::<Previewer>::new(),
|
||||
before.iter().cloned().chain([previewer.clone()]).chain(after.iter().cloned()),
|
||||
Vec::<Previewer>::new(),
|
||||
)
|
||||
))
|
||||
}
|
||||
Err(e) => {
|
||||
err = Some(e);
|
||||
Vec::clone(previewers)
|
||||
}
|
||||
});
|
||||
})?;
|
||||
|
||||
err.map_or(Ok(()), Err)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove(&self, matcher: PreviewerMatcher) {
|
||||
|
|
@ -76,46 +69,3 @@ impl Previewers {
|
|||
Arc::try_unwrap(self.0.into_inner()).expect("unique previewers arc")
|
||||
}
|
||||
}
|
||||
|
||||
// --- Matcher
|
||||
#[derive(Default)]
|
||||
pub struct PreviewerMatcher<'a> {
|
||||
pub previewers: Arc<Vec<Arc<Previewer>>>,
|
||||
pub id: Id,
|
||||
pub file: Option<Cow<'a, File>>,
|
||||
pub mime: Option<Cow<'a, str>>,
|
||||
pub all: bool,
|
||||
pub offset: usize,
|
||||
}
|
||||
|
||||
impl From<&Previewers> for PreviewerMatcher<'_> {
|
||||
fn from(previewers: &Previewers) -> Self {
|
||||
Self { previewers: previewers.load_full(), all: true, ..Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
impl PreviewerMatcher<'_> {
|
||||
pub fn matches(&self, previewer: &Previewer) -> bool {
|
||||
if self.all {
|
||||
true
|
||||
} else if self.id != Id::ZERO {
|
||||
previewer.id == self.id
|
||||
} else {
|
||||
previewer.match_with(self.file.as_deref(), self.mime.as_deref())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for PreviewerMatcher<'_> {
|
||||
type Item = Arc<Previewer>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
while let Some(previewer) = self.previewers.get(self.offset) {
|
||||
self.offset += 1;
|
||||
if self.matches(previewer) {
|
||||
return Some(previewer.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
use std::ops::Deref;
|
||||
use std::{borrow::Cow, ops::Deref, sync::Arc};
|
||||
|
||||
use serde::Deserialize;
|
||||
use yazi_fs::File;
|
||||
use yazi_shared::{Id, event::Action};
|
||||
|
||||
use crate::{Mixable, Pattern, Selectable, Selector, plugin::spotter_id};
|
||||
use crate::{Mixable, Pattern, Selectable, Selector, plugin::{Spotters, spotter_id}};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Spotter {
|
||||
|
|
@ -32,3 +33,46 @@ impl Mixable for Spotter {
|
|||
|
||||
fn any_dir(&self) -> bool { self.selector.any_dir() }
|
||||
}
|
||||
|
||||
// --- Matcher
|
||||
#[derive(Default)]
|
||||
pub struct SpotterMatcher<'a> {
|
||||
pub spotters: Arc<Vec<Arc<Spotter>>>,
|
||||
pub id: Id,
|
||||
pub file: Option<Cow<'a, File>>,
|
||||
pub mime: Option<Cow<'a, str>>,
|
||||
pub all: bool,
|
||||
pub offset: usize,
|
||||
}
|
||||
|
||||
impl From<&Spotters> for SpotterMatcher<'_> {
|
||||
fn from(spotters: &Spotters) -> Self {
|
||||
Self { spotters: spotters.load_full(), all: true, ..Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
impl SpotterMatcher<'_> {
|
||||
pub fn matches(&self, spotter: &Spotter) -> bool {
|
||||
if self.all {
|
||||
true
|
||||
} else if self.id != Id::ZERO {
|
||||
spotter.id == self.id
|
||||
} else {
|
||||
spotter.match_with(self.file.as_deref(), self.mime.as_deref())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for SpotterMatcher<'_> {
|
||||
type Item = Arc<Spotter>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
while let Some(spotter) = self.spotters.get(self.offset) {
|
||||
self.offset += 1;
|
||||
if self.matches(spotter) {
|
||||
return Some(spotter.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use yazi_shared::Id;
|
|||
use yazi_shim::arc_swap::IntoPointee;
|
||||
|
||||
use super::Spotter;
|
||||
use crate::Selectable;
|
||||
use crate::plugin::SpotterMatcher;
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
pub struct Spotters(ArcSwap<Vec<Arc<Spotter>>>);
|
||||
|
|
@ -45,46 +45,3 @@ impl Spotters {
|
|||
Arc::try_unwrap(self.0.into_inner()).expect("unique spotters arc")
|
||||
}
|
||||
}
|
||||
|
||||
// --- Matcher
|
||||
#[derive(Default)]
|
||||
pub struct SpotterMatcher<'a> {
|
||||
pub spotters: Arc<Vec<Arc<Spotter>>>,
|
||||
pub id: Id,
|
||||
pub file: Option<Cow<'a, File>>,
|
||||
pub mime: Option<Cow<'a, str>>,
|
||||
pub all: bool,
|
||||
pub offset: usize,
|
||||
}
|
||||
|
||||
impl From<&Spotters> for SpotterMatcher<'_> {
|
||||
fn from(spotters: &Spotters) -> Self {
|
||||
Self { spotters: spotters.load_full(), all: true, ..Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
impl SpotterMatcher<'_> {
|
||||
pub fn matches(&self, spotter: &Spotter) -> bool {
|
||||
if self.all {
|
||||
true
|
||||
} else if self.id != Id::ZERO {
|
||||
spotter.id == self.id
|
||||
} else {
|
||||
spotter.match_with(self.file.as_deref(), self.mime.as_deref())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for SpotterMatcher<'_> {
|
||||
type Item = Arc<Spotter>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
while let Some(spotter) = self.spotters.get(self.offset) {
|
||||
self.offset += 1;
|
||||
if self.matches(spotter) {
|
||||
return Some(spotter.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@ use serde::{Deserialize, Deserializer, de};
|
|||
|
||||
use crate::{Mixable, Pattern, Selectable};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Selector {
|
||||
url: Option<Pattern>,
|
||||
mime: Option<Pattern>,
|
||||
pub url: Option<Pattern>,
|
||||
pub mime: Option<Pattern>,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Selector {
|
||||
|
|
|
|||
|
|
@ -2,10 +2,9 @@ use std::ops::Deref;
|
|||
|
||||
use anyhow::Result;
|
||||
use hashbrown::HashMap;
|
||||
use serde::{Deserialize, Serialize, de};
|
||||
use toml::{Spanned, de::DeTable};
|
||||
use serde::{Deserialize, Deserializer, Serialize, de, de::{MapAccess, Visitor}};
|
||||
use yazi_codegen::DeserializeOver;
|
||||
use yazi_shim::toml::{DeserializeOverWith, deserialize_spanned};
|
||||
use yazi_shim::toml::DeserializeOverWith;
|
||||
|
||||
use crate::vfs::Service;
|
||||
|
||||
|
|
@ -19,21 +18,31 @@ impl Deref for Services {
|
|||
}
|
||||
|
||||
impl DeserializeOverWith for Services {
|
||||
fn deserialize_over_with<'de>(
|
||||
mut self,
|
||||
table: Spanned<DeTable<'de>>,
|
||||
) -> Result<Self, toml::de::Error> {
|
||||
for (key, value) in table.into_inner() {
|
||||
let key = key.into_inner();
|
||||
if key.is_empty() || key.len() > 20 {
|
||||
Err(de::Error::custom(format!("VFS name `{key}` must be between 1 and 20 characters")))?;
|
||||
} else if !key.bytes().all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'z' | b'-')) {
|
||||
Err(de::Error::custom(format!("VFS name `{key}` must be in kebab-case")))?;
|
||||
} else {
|
||||
self.0.insert(key.into_owned(), deserialize_spanned(value)?);
|
||||
fn deserialize_over_with<'de, D: Deserializer<'de>>(self, de: D) -> Result<Self, D::Error> {
|
||||
struct V(Services);
|
||||
|
||||
impl<'de> Visitor<'de> for V {
|
||||
type Value = Services;
|
||||
|
||||
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
f.write_str("a map of VFS services")
|
||||
}
|
||||
|
||||
fn visit_map<M: MapAccess<'de>>(mut self, mut map: M) -> Result<Self::Value, M::Error> {
|
||||
while let Some(key) = map.next_key::<String>()? {
|
||||
if key.is_empty() || key.len() > 20 {
|
||||
return Err(de::Error::custom(format!(
|
||||
"VFS name `{key}` must be between 1 and 20 characters"
|
||||
)));
|
||||
} else if !key.bytes().all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'z' | b'-')) {
|
||||
return Err(de::Error::custom(format!("VFS name `{key}` must be in kebab-case")));
|
||||
}
|
||||
self.0.0.insert(key, map.next_value()?);
|
||||
}
|
||||
Ok(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
de.deserialize_map(V(self))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ scopeguard = { workspace = true }
|
|||
tokio = { workspace = true }
|
||||
tokio-stream = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tracing-appender = "0.2.4"
|
||||
tracing-appender = "0.2.5"
|
||||
tracing-subscriber = { version = "0.3.23", features = [ "env-filter" ] }
|
||||
|
||||
[target."cfg(unix)".dependencies]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use mlua::{IntoLua, Lua, LuaSerdeExt, Value};
|
||||
use yazi_binding::{Composer, ComposerGet, ComposerSet, SER_OPT, Url, elements::Wrap};
|
||||
use yazi_binding::{Composer, ComposerGet, ComposerSet, SER_OPT, Url, config::{OpenRules, Opener}, elements::Wrap};
|
||||
use yazi_boot::ARGS;
|
||||
use yazi_config::YAZI;
|
||||
|
||||
|
|
@ -9,6 +9,8 @@ pub fn compose() -> Composer<ComposerGet, ComposerSet> {
|
|||
b"args" => args().into_lua(lua)?,
|
||||
b"term" => super::term().into_lua(lua)?,
|
||||
b"mgr" => mgr().into_lua(lua)?,
|
||||
b"open" => open().into_lua(lua)?,
|
||||
b"opener" => Opener.into_lua(lua)?,
|
||||
b"plugin" => super::plugin().into_lua(lua)?,
|
||||
b"preview" => preview().into_lua(lua)?,
|
||||
b"tasks" => tasks().into_lua(lua)?,
|
||||
|
|
@ -22,6 +24,19 @@ pub fn compose() -> Composer<ComposerGet, ComposerSet> {
|
|||
Composer::new(get, set)
|
||||
}
|
||||
|
||||
fn open() -> Composer<ComposerGet, ComposerSet> {
|
||||
fn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {
|
||||
match key {
|
||||
b"rules" => OpenRules.into_lua(lua),
|
||||
_ => Ok(Value::Nil),
|
||||
}
|
||||
}
|
||||
|
||||
fn set(_: &Lua, _: &[u8], value: Value) -> mlua::Result<Value> { Ok(value) }
|
||||
|
||||
Composer::new(get, set)
|
||||
}
|
||||
|
||||
fn args() -> Composer<ComposerGet, ComposerSet> {
|
||||
fn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {
|
||||
match key {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ parking_lot = { workspace = true }
|
|||
paste = { workspace = true }
|
||||
percent-encoding = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_with = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use anyhow::bail;
|
||||
use serde::{Deserialize, Deserializer, de};
|
||||
use serde_with::DeserializeFromStr;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum ConditionOp {
|
||||
|
|
@ -44,7 +44,7 @@ impl ConditionOp {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, DeserializeFromStr)]
|
||||
pub struct Condition {
|
||||
ops: Vec<ConditionOp>,
|
||||
}
|
||||
|
|
@ -62,16 +62,6 @@ impl FromStr for Condition {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Condition {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
FromStr::from_str(&s).map_err(de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
impl Condition {
|
||||
fn build(expr: &str) -> Self {
|
||||
let mut stack: Vec<ConditionOp> = vec![];
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@ use std::{borrow::Cow, fmt::{self, Display}, mem, str::FromStr};
|
|||
|
||||
use anyhow::{Result, anyhow, bail};
|
||||
use hashbrown::HashMap;
|
||||
use serde::{Deserialize, de};
|
||||
use serde_with::DeserializeFromStr;
|
||||
|
||||
use crate::{Layer, SStr, Source, data::{Data, DataAny, DataKey}, event::Replier};
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
#[derive(Clone, Debug, Default, DeserializeFromStr)]
|
||||
pub struct Action {
|
||||
pub name: SStr,
|
||||
pub args: HashMap<DataKey, Data>,
|
||||
|
|
@ -281,12 +281,3 @@ impl FromStr for Action {
|
|||
Ok(me)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Action {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
<_>::from_str(&String::deserialize(deserializer)?).map_err(de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ yazi-macro = { path = "../yazi-macro", version = "26.2.2" }
|
|||
# External dependencies
|
||||
arc-swap = { workspace = true }
|
||||
crossterm = { workspace = true }
|
||||
mlua = { workspace = true }
|
||||
ratatui = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use arc_swap::ArcSwap;
|
||||
use arc_swap::{ArcSwap, ArcSwapAny, Guard, RefCnt, strategy::CaS};
|
||||
|
||||
// --- IntoPointee
|
||||
pub trait IntoPointee: Sized {
|
||||
|
|
@ -9,3 +9,34 @@ impl<T> IntoPointee for T {
|
|||
#[inline]
|
||||
fn into_pointee(self) -> ArcSwap<Self> { ArcSwap::from_pointee(self) }
|
||||
}
|
||||
|
||||
// --- ArcSwapExt
|
||||
pub trait ArcSwapExt<T: RefCnt> {
|
||||
fn try_rcu<R, E, F>(&self, f: F) -> Result<T, E>
|
||||
where
|
||||
F: FnMut(&T) -> Result<R, E>,
|
||||
R: Into<T>;
|
||||
}
|
||||
|
||||
impl<T, S> ArcSwapExt<T> for ArcSwapAny<T, S>
|
||||
where
|
||||
T: RefCnt,
|
||||
S: CaS<T>,
|
||||
{
|
||||
fn try_rcu<R, E, F>(&self, mut f: F) -> Result<T, E>
|
||||
where
|
||||
F: FnMut(&T) -> Result<R, E>,
|
||||
R: Into<T>,
|
||||
{
|
||||
let mut cur = self.load();
|
||||
loop {
|
||||
let new = f(&cur)?.into();
|
||||
let prev = self.compare_and_swap(&*cur, new);
|
||||
if T::as_ptr(&*cur) == T::as_ptr(&*prev) {
|
||||
return Ok(Guard::into_inner(prev));
|
||||
}
|
||||
|
||||
cur = prev;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
yazi_macro::mod_pub!(arc_swap crossterm ratatui strum toml vec);
|
||||
yazi_macro::mod_pub!(arc_swap crossterm mlua ratatui strum toml vec);
|
||||
|
||||
yazi_macro::mod_flat!(twox);
|
||||
|
|
|
|||
1
yazi-shim/src/mlua/mod.rs
Normal file
1
yazi-shim/src/mlua/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
yazi_macro::mod_flat!(traits);
|
||||
12
yazi-shim/src/mlua/traits.rs
Normal file
12
yazi-shim/src/mlua/traits.rs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
use mlua::{ExternalError, Table, Value};
|
||||
|
||||
use crate::toml::DeserializeOverWith;
|
||||
|
||||
pub trait DeserializeOverLua: DeserializeOverWith {
|
||||
fn deserialize_over_lua(self, table: &Table) -> mlua::Result<Self> {
|
||||
let de = mlua::serde::Deserializer::new(Value::Table(table.clone()));
|
||||
self.deserialize_over_with(de).map_err(|e| e.into_lua_err())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: DeserializeOverWith> DeserializeOverLua for T {}
|
||||
|
|
@ -1,20 +1,36 @@
|
|||
use toml::{Spanned, de::{self, DeTable}};
|
||||
use serde::{Deserializer, de::DeserializeSeed};
|
||||
use toml::de;
|
||||
|
||||
pub trait DeserializeOver: DeserializeOverWith + DeserializeOverHook {
|
||||
fn deserialize_over(self, input: &str) -> Result<Self, de::Error> {
|
||||
self.deserialize_over_with(DeTable::parse(input)?).map_err(|mut err| {
|
||||
let table = de::DeTable::parse(input)?;
|
||||
let de = de::Deserializer::from(table);
|
||||
self.deserialize_over_with(de).map_err(|mut err| {
|
||||
err.set_input(Some(input));
|
||||
err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DeserializeOver for T where T: DeserializeOverWith + DeserializeOverHook {}
|
||||
|
||||
// --- DeserializeOverWith
|
||||
pub trait DeserializeOverWith: Sized {
|
||||
fn deserialize_over_with<'de>(self, table: Spanned<DeTable<'de>>) -> Result<Self, de::Error>;
|
||||
fn deserialize_over_with<'de, D: Deserializer<'de>>(self, de: D) -> Result<Self, D::Error>;
|
||||
}
|
||||
|
||||
// --- DeserializeOverHook
|
||||
pub trait DeserializeOverHook: Sized {
|
||||
fn deserialize_over_hook(self) -> Result<Self, de::Error> { Ok(self) }
|
||||
}
|
||||
|
||||
impl<T> DeserializeOver for T where T: DeserializeOverWith + DeserializeOverHook {}
|
||||
// --- DeserializeOverSeed
|
||||
pub struct DeserializeOverSeed<T>(pub T);
|
||||
|
||||
impl<'de, T: DeserializeOverWith> DeserializeSeed<'de> for DeserializeOverSeed<T> {
|
||||
type Value = T;
|
||||
|
||||
fn deserialize<D: Deserializer<'de>>(self, de: D) -> Result<T, D::Error> {
|
||||
self.0.deserialize_over_with(de)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,20 +56,6 @@ impl<'de> Deserialize<'de> for Step {
|
|||
value.parse().map_err(E::custom)
|
||||
}
|
||||
|
||||
fn visit_borrowed_str<E>(self, value: &'_ str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
self.visit_str(value)
|
||||
}
|
||||
|
||||
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
self.visit_str(&value)
|
||||
}
|
||||
|
||||
fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue