mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41: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])
|
- New `--in` for `search` action to set search directory ([#3696])
|
||||||
- Hover cursor over the new file after copying/cutting/linking/hardlinking/extracting ([#3846], [#3854])
|
- Hover cursor over the new file after copying/cutting/linking/hardlinking/extracting ([#3846], [#3854])
|
||||||
- Multi-file spotter ([#3733])
|
- Multi-file spotter ([#3733])
|
||||||
|
- Dynamic open/opener Lua API ([#3901])
|
||||||
- Dynamic previewer Lua API ([#3891])
|
- Dynamic previewer Lua API ([#3891])
|
||||||
- Vim-like `lua` action that runs an inline Lua snippet ([#3813])
|
- Vim-like `lua` action that runs an inline Lua snippet ([#3813])
|
||||||
- Certificate authentication for SFTP VFS provider ([#3716])
|
- 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
|
[#3862]: https://github.com/sxyazi/yazi/pull/3862
|
||||||
[#3891]: https://github.com/sxyazi/yazi/pull/3891
|
[#3891]: https://github.com/sxyazi/yazi/pull/3891
|
||||||
[#3894]: https://github.com/sxyazi/yazi/pull/3894
|
[#3894]: https://github.com/sxyazi/yazi/pull/3894
|
||||||
|
[#3901]: https://github.com/sxyazi/yazi/pull/3901
|
||||||
|
|
|
||||||
65
Cargo.lock
generated
65
Cargo.lock
generated
|
|
@ -1310,9 +1310,9 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ecdsa"
|
name = "ecdsa"
|
||||||
version = "0.17.0-rc.16"
|
version = "0.17.0-rc.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "91bbdd377139884fafcad8dc43a760a3e1e681aa26db910257fa6535b70e1829"
|
checksum = "dc4bf51f0534ed6e59a0f2f26272b64ba55c470133f8424c2adfd1c4d59d9988"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"der",
|
"der",
|
||||||
"digest 0.11.2",
|
"digest 0.11.2",
|
||||||
|
|
@ -1357,9 +1357,9 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "elliptic-curve"
|
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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7d7a0bfd012613a7bcfe02cbfccf2b846e9ef9e1bccb641c48d461253cfb034d"
|
checksum = "b148a81cede8f4023248f980cffdf7611c46f2add469c6980e815b7c5b764ba5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base16ct",
|
"base16ct",
|
||||||
"crypto-bigint",
|
"crypto-bigint",
|
||||||
|
|
@ -2667,7 +2667,7 @@ dependencies = [
|
||||||
"num-integer",
|
"num-integer",
|
||||||
"num-iter",
|
"num-iter",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"rand 0.8.5",
|
"rand 0.8.6",
|
||||||
"serde",
|
"serde",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
|
@ -2860,7 +2860,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b7d950ca161dc355eaf28f82b11345ed76c6e1f6eb1f4f4479e0323b9e2fbd0e"
|
checksum = "b7d950ca161dc355eaf28f82b11345ed76c6e1f6eb1f4f4479e0323b9e2fbd0e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"rand 0.8.5",
|
"rand 0.8.6",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -2916,7 +2916,7 @@ dependencies = [
|
||||||
"delegate",
|
"delegate",
|
||||||
"futures",
|
"futures",
|
||||||
"log",
|
"log",
|
||||||
"rand 0.8.5",
|
"rand 0.8.6",
|
||||||
"sha2 0.10.9",
|
"sha2 0.10.9",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|
@ -3114,7 +3114,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
|
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"phf_shared",
|
"phf_shared",
|
||||||
"rand 0.8.5",
|
"rand 0.8.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -3283,9 +3283,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "primefield"
|
name = "primefield"
|
||||||
version = "0.14.0-rc.8"
|
version = "0.14.0-rc.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c6543f5eec854fbf74ba5ef651fbdc9408919b47c3e1526623687135c16d12e9"
|
checksum = "1b52e6ee42db392378a95622b463c9740631171d1efce43fa445a569c1600cb6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crypto-bigint",
|
"crypto-bigint",
|
||||||
"crypto-common 0.2.1",
|
"crypto-common 0.2.1",
|
||||||
|
|
@ -3297,9 +3297,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "primeorder"
|
name = "primeorder"
|
||||||
version = "0.14.0-rc.8"
|
version = "0.14.0-rc.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "569d9ad6ef822bb0322c7e7d84e5e286244050bd5246cac4c013535ae91c2c90"
|
checksum = "0556580e42c19833f5d232aca11a7687a503ee41f937b54f5ae1d50fc2a6a36a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"elliptic-curve",
|
"elliptic-curve",
|
||||||
]
|
]
|
||||||
|
|
@ -3356,9 +3356,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pxfm"
|
name = "pxfm"
|
||||||
version = "0.1.28"
|
version = "0.1.29"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b5a041e753da8b807c9255f28de81879c78c876392ff2469cde94799b2896b9d"
|
checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "qoi"
|
name = "qoi"
|
||||||
|
|
@ -3429,9 +3429,9 @@ checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.8.5"
|
version = "0.8.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"rand_chacha 0.3.1",
|
"rand_chacha 0.3.1",
|
||||||
|
|
@ -4447,6 +4447,12 @@ version = "2.6.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "symlink"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.109"
|
version = "1.0.109"
|
||||||
|
|
@ -4688,9 +4694,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.52.0"
|
version = "1.52.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a91135f59b1cbf38c91e73cf3386fca9bb77915c45ce2771460c9d92f0f3d776"
|
checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"libc",
|
"libc",
|
||||||
|
|
@ -4790,11 +4796,12 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-appender"
|
name = "tracing-appender"
|
||||||
version = "0.2.4"
|
version = "0.2.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf"
|
checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
|
"symlink",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"time",
|
"time",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
|
@ -5086,11 +5093,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasip2"
|
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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
|
checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"wit-bindgen",
|
"wit-bindgen 0.57.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
|
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"wit-bindgen",
|
"wit-bindgen 0.51.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -5664,6 +5671,12 @@ dependencies = [
|
||||||
"wit-bindgen-rust-macro",
|
"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]]
|
[[package]]
|
||||||
name = "wit-bindgen-core"
|
name = "wit-bindgen-core"
|
||||||
version = "0.51.0"
|
version = "0.51.0"
|
||||||
|
|
@ -5767,6 +5780,7 @@ dependencies = [
|
||||||
"either",
|
"either",
|
||||||
"futures",
|
"futures",
|
||||||
"hashbrown 0.17.0",
|
"hashbrown 0.17.0",
|
||||||
|
"indexmap 2.14.0",
|
||||||
"libc",
|
"libc",
|
||||||
"mlua",
|
"mlua",
|
||||||
"paste",
|
"paste",
|
||||||
|
|
@ -5909,6 +5923,7 @@ dependencies = [
|
||||||
name = "yazi-codegen"
|
name = "yazi-codegen"
|
||||||
version = "26.2.2"
|
version = "26.2.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.117",
|
"syn 2.0.117",
|
||||||
]
|
]
|
||||||
|
|
@ -6278,6 +6293,7 @@ dependencies = [
|
||||||
"paste",
|
"paste",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_with",
|
||||||
"strum 0.28.0",
|
"strum 0.28.0",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|
@ -6293,6 +6309,7 @@ version = "26.2.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arc-swap",
|
"arc-swap",
|
||||||
"crossterm 0.29.0",
|
"crossterm 0.29.0",
|
||||||
|
"mlua",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ serde_with = "3.18.0"
|
||||||
strum = { version = "0.28.0", features = [ "derive" ] }
|
strum = { version = "0.28.0", features = [ "derive" ] }
|
||||||
syntect = { version = "5.3.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
|
syntect = { version = "5.3.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
|
||||||
thiserror = "2.0.18"
|
thiserror = "2.0.18"
|
||||||
tokio = { version = "1.52.0", features = [ "full" ] }
|
tokio = { version = "1.52.1", features = [ "full" ] }
|
||||||
tokio-stream = "0.1.18"
|
tokio-stream = "0.1.18"
|
||||||
tokio-util = "0.7.18"
|
tokio-util = "0.7.18"
|
||||||
toml = { version = "1.1.2" }
|
toml = { version = "1.1.2" }
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ crossterm = { workspace = true }
|
||||||
either = { workspace = true }
|
either = { workspace = true }
|
||||||
futures = { workspace = true }
|
futures = { workspace = true }
|
||||||
hashbrown = { workspace = true }
|
hashbrown = { workspace = true }
|
||||||
|
indexmap = { workspace = true }
|
||||||
mlua = { workspace = true }
|
mlua = { workspace = true }
|
||||||
paste = { workspace = true }
|
paste = { workspace = true }
|
||||||
ratatui = { 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 anyhow::{Result, anyhow};
|
||||||
use crossterm::{execute, style::Print};
|
use crossterm::{execute, style::Print};
|
||||||
|
|
@ -154,8 +154,11 @@ impl BulkRename {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn opener() -> Option<&'static OpenerRule> {
|
fn opener() -> Option<Arc<OpenerRule>> {
|
||||||
YAZI.opener.block(YAZI.open.all_dummy(Path::new("bulk-rename.txt"), "text/plain"))
|
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> {
|
fn replace_url(url: &UrlBuf, take: usize, rep: &StrandBuf) -> Result<UrlBuf> {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
|
use indexmap::IndexSet;
|
||||||
use yazi_config::{YAZI, popup::PickCfg};
|
use yazi_config::{YAZI, popup::PickCfg};
|
||||||
use yazi_macro::succ;
|
use yazi_macro::succ;
|
||||||
use yazi_parser::mgr::OpenDoForm;
|
use yazi_parser::mgr::OpenDoForm;
|
||||||
|
|
@ -33,7 +34,8 @@ impl Actor for OpenDo {
|
||||||
succ!(Self::match_and_open(cx, opt.cwd, targets));
|
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() {
|
if openers.is_empty() {
|
||||||
succ!();
|
succ!();
|
||||||
}
|
}
|
||||||
|
|
@ -64,7 +66,9 @@ impl OpenDo {
|
||||||
fn match_and_open(cx: &Ctx, cwd: UrlBuf, targets: Vec<(yazi_fs::File, &str)>) {
|
fn match_and_open(cx: &Ctx, cwd: UrlBuf, targets: Vec<(yazi_fs::File, &str)>) {
|
||||||
let mut openers = HashMap::new();
|
let mut openers = HashMap::new();
|
||||||
for (file, mime) in targets {
|
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());
|
openers.entry(opener).or_insert_with(|| vec![UrlCow::default()]).push(file.url.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
use std::{ops::Deref, sync::Arc};
|
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)]
|
#[derive(Clone)]
|
||||||
pub struct Fetcher {
|
pub struct Fetcher {
|
||||||
|
|
@ -30,3 +31,45 @@ impl UserData for Fetcher {
|
||||||
cached_field!(fields, name, |lua, me| lua.create_string(&*me.name));
|
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 yazi_config::YAZI;
|
||||||
|
|
||||||
use crate::{FileRef, Id, Iter, config::Fetcher};
|
use crate::config::FetcherMatcher;
|
||||||
|
|
||||||
pub struct Fetchers;
|
pub struct Fetchers;
|
||||||
|
|
||||||
|
|
@ -15,45 +15,3 @@ impl UserData for Fetchers {
|
||||||
methods.add_meta_method(MetaMethod::Len, |_, _, ()| Ok(YAZI.plugin.fetchers.load().len()));
|
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 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)]
|
#[derive(Clone)]
|
||||||
pub struct Preloader {
|
pub struct Preloader {
|
||||||
|
|
@ -30,3 +31,45 @@ impl UserData for Preloader {
|
||||||
cached_field!(fields, name, |lua, me| lua.create_string(&*me.name));
|
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 yazi_config::YAZI;
|
||||||
|
|
||||||
use crate::{FileRef, Id, Iter, config::Preloader};
|
use crate::config::PreloaderMatcher;
|
||||||
|
|
||||||
pub struct Preloaders;
|
pub struct Preloaders;
|
||||||
|
|
||||||
|
|
@ -15,45 +15,3 @@ impl UserData for Preloaders {
|
||||||
methods.add_meta_method(MetaMethod::Len, |_, _, ()| Ok(YAZI.plugin.preloaders.load().len()));
|
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 std::{ops::Deref, sync::Arc};
|
||||||
|
|
||||||
use mlua::{AnyUserData, ExternalError, FromLua, Lua, UserData, UserDataFields, Value};
|
use mlua::{ExternalError, FromLua, IntoLua, Lua, LuaSerdeExt, Table, UserData, UserDataFields, Value};
|
||||||
use yazi_config::plugin::previewer_id;
|
use yazi_config::YAZI;
|
||||||
|
|
||||||
use crate::{Id, Selector, cached_field};
|
use crate::{FileRef, Id, Iter, cached_field};
|
||||||
|
|
||||||
const EXPECTED: &str = "expected a table or Previewer";
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Previewer {
|
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 {
|
impl FromLua for Previewer {
|
||||||
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
|
fn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> {
|
||||||
match value {
|
Ok(Self::new(lua.from_value::<yazi_config::plugin::Previewer>(value)?))
|
||||||
Value::Table(tbl) => Self::try_from(tbl),
|
|
||||||
Value::UserData(ud) => Self::try_from(ud),
|
|
||||||
_ => Err(EXPECTED.into_lua_err())?,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -67,3 +41,45 @@ impl UserData for Previewer {
|
||||||
cached_field!(fields, name, |lua, me| lua.create_string(&*me.name));
|
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 yazi_config::YAZI;
|
||||||
|
|
||||||
use crate::{FileRef, Id, Iter, config::Previewer};
|
use crate::config::{Previewer, PreviewerMatcher};
|
||||||
|
|
||||||
pub struct Previewers;
|
pub struct Previewers;
|
||||||
|
|
||||||
|
|
@ -31,45 +31,3 @@ impl UserData for Previewers {
|
||||||
methods.add_meta_method(MetaMethod::Len, |_, _, ()| Ok(YAZI.plugin.previewers.load().len()));
|
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 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)]
|
#[derive(Clone)]
|
||||||
pub struct Spotter {
|
pub struct Spotter {
|
||||||
|
|
@ -30,3 +31,45 @@ impl UserData for Spotter {
|
||||||
cached_field!(fields, name, |lua, me| lua.create_string(&*me.name));
|
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 yazi_config::YAZI;
|
||||||
|
|
||||||
use crate::{FileRef, Id, Iter, config::Spotter};
|
use crate::config::SpotterMatcher;
|
||||||
|
|
||||||
pub struct Spotters;
|
pub struct Spotters;
|
||||||
|
|
||||||
|
|
@ -15,45 +15,3 @@ impl UserData for Spotters {
|
||||||
methods.add_meta_method(MetaMethod::Len, |_, _, ()| Ok(YAZI.plugin.spotters.load().len()));
|
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 std::ops::Deref;
|
||||||
|
|
||||||
use mlua::{ExternalError, FromLua, Lua, Table, UserData, Value};
|
use mlua::{FromLua, Lua, LuaSerdeExt, UserData, Value};
|
||||||
|
|
||||||
pub struct Selector(yazi_config::Selector);
|
pub struct Selector(yazi_config::Selector);
|
||||||
|
|
||||||
|
|
@ -14,30 +14,8 @@ impl From<Selector> for yazi_config::Selector {
|
||||||
fn from(value: Selector) -> Self { value.0 }
|
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 {
|
impl FromLua for Selector {
|
||||||
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
|
fn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> { Ok(Self(lua.from_value(value)?)) }
|
||||||
Ok(match value {
|
|
||||||
Value::Table(t) => t.try_into()?,
|
|
||||||
_ => Err("expected a table of Selector".into_lua_err())?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserData for Selector {}
|
impl UserData for Selector {}
|
||||||
|
|
|
||||||
|
|
@ -68,20 +68,20 @@ impl Actions {
|
||||||
writeln!(s, " FZF_DEFAULT_COMMAND: {:?}", env::var_os("FZF_DEFAULT_COMMAND"))?;
|
writeln!(s, " FZF_DEFAULT_COMMAND: {:?}", env::var_os("FZF_DEFAULT_COMMAND"))?;
|
||||||
|
|
||||||
writeln!(s, "\nText Opener")?;
|
writeln!(s, "\nText Opener")?;
|
||||||
|
#[rustfmt::skip]
|
||||||
writeln!(
|
writeln!(
|
||||||
s,
|
s, " default : {:?}",
|
||||||
" default : {:?}",
|
YAZI.open.match_dummy(Path::new("f75a.txt"), "text/plain").and_then(|r| YAZI.opener.first(&r))
|
||||||
YAZI.opener.first(YAZI.open.all_dummy(Path::new("f75a.txt"), "text/plain"))
|
|
||||||
)?;
|
)?;
|
||||||
|
#[rustfmt::skip]
|
||||||
writeln!(
|
writeln!(
|
||||||
s,
|
s, " block-create: {:?}",
|
||||||
" block-create: {:?}",
|
YAZI.open.match_dummy(Path::new("bulk-create.txt"), "text/plain").and_then(|r| YAZI.opener.block(&r))
|
||||||
YAZI.opener.block(YAZI.open.all_dummy(Path::new("bulk-create.txt"), "text/plain"))
|
|
||||||
)?;
|
)?;
|
||||||
|
#[rustfmt::skip]
|
||||||
writeln!(
|
writeln!(
|
||||||
s,
|
s, " block-rename: {:?}",
|
||||||
" block-rename: {:?}",
|
YAZI.open.match_dummy(Path::new("bulk-rename.txt"), "text/plain").and_then(|r| YAZI.opener.block(&r))
|
||||||
YAZI.opener.block(YAZI.open.all_dummy(Path::new("bulk-rename.txt"), "text/plain"))
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
writeln!(s, "\nMultiplexers")?;
|
writeln!(s, "\nMultiplexers")?;
|
||||||
|
|
|
||||||
|
|
@ -17,5 +17,6 @@ proc-macro = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# External dependencies
|
# External dependencies
|
||||||
syn = { version = "2.0.117", features = [ "full" ] }
|
proc-macro2 = "1"
|
||||||
quote = "1.0.45"
|
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 proc_macro::TokenStream;
|
||||||
use quote::{format_ident, quote};
|
use quote::quote;
|
||||||
use syn::{Data, DeriveInput, Fields, parse_macro_input};
|
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)]
|
#[proc_macro_derive(DeserializeOver)]
|
||||||
pub fn deserialize_over(input: TokenStream) -> TokenStream {
|
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 DeriveInput { ident, generics, data, .. } = parse_macro_input!(input as DeriveInput);
|
||||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||||
|
|
||||||
let assignments = match data {
|
let visitor_generics = generics_with_de(&generics);
|
||||||
Data::Struct(struct_) => match struct_.fields {
|
let (impl_visitor_generics, ..) = visitor_generics.split_for_impl();
|
||||||
Fields::Named(fields) => {
|
|
||||||
let mut assignments = Vec::with_capacity(fields.named.len());
|
|
||||||
|
|
||||||
for field in fields.named {
|
let fields: Vec<_> = named_fields(data).into_iter().map(|f| f.ident.unwrap()).collect();
|
||||||
let field_ident = &field.ident;
|
let match_arms = fields.iter().map(|f| {
|
||||||
let field_name = field_ident.as_ref().unwrap().to_string();
|
let name = ident_name(f);
|
||||||
|
quote! { #name => self.0.#f = map.next_value_seed(DeserializeOverSeed(self.0.#f))?, }
|
||||||
|
});
|
||||||
|
|
||||||
assignments.push(quote! {
|
let hook_fields = fields.iter().map(|f| {
|
||||||
if let Some(value) = table.remove(#field_name) {
|
quote! { #f: deserialized.#f.deserialize_over_hook().map_err(Error::custom)? }
|
||||||
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"),
|
|
||||||
};
|
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
impl #impl_generics yazi_shim::toml::DeserializeOverWith for #ident #ty_generics #where_clause {
|
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> {
|
fn deserialize_over_with<'__de, __D: serde::Deserializer<'__de>>(self, de: __D) -> Result<Self, __D::Error> {
|
||||||
use yazi_shim::toml::{DeserializeOverHook, DeserializeOverWith, deserialize_spanned};
|
use std::borrow::Cow;
|
||||||
|
use serde::de::{Error, IgnoredAny, MapAccess, Visitor};
|
||||||
|
use yazi_shim::toml::{DeserializeOverHook, DeserializeOverSeed};
|
||||||
|
|
||||||
let mut table = table.into_inner();
|
struct V #impl_generics (#ident #ty_generics) #where_clause;
|
||||||
#(#assignments)*
|
|
||||||
|
|
||||||
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 DeriveInput { ident, generics, data, .. } = parse_macro_input!(input as DeriveInput);
|
||||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||||
|
|
||||||
let assignments = match data {
|
let visitor_generics = generics_with_de(&generics);
|
||||||
Data::Struct(struct_) => match struct_.fields {
|
let (impl_visitor_generics, ..) = visitor_generics.split_for_impl();
|
||||||
Fields::Named(fields) => {
|
|
||||||
let mut assignments = Vec::with_capacity(fields.named.len());
|
|
||||||
|
|
||||||
for (index, field) in fields.named.into_iter().enumerate() {
|
let (mut match_arms, mut post_fields) = (vec![], vec![]);
|
||||||
let field_ident = field.ident;
|
for field in named_fields(data) {
|
||||||
let field_name = field_ident.as_ref().unwrap().to_string();
|
let (field_ident, field_ty) = (field.ident, field.ty);
|
||||||
let field_ty = field.ty;
|
let field_name = ident_name(field_ident.as_ref().unwrap());
|
||||||
let field_attrs = field.attrs;
|
|
||||||
let field_helper = format_ident!("__DeserializeOver2Field{index}");
|
|
||||||
|
|
||||||
assignments.push(quote! {
|
if has_serde_attr(&field.attrs, "skip") {
|
||||||
#[derive(serde::Deserialize)]
|
continue;
|
||||||
struct #field_helper #impl_generics (
|
}
|
||||||
#(#field_attrs)*
|
|
||||||
#field_ty
|
|
||||||
) #where_clause;
|
|
||||||
|
|
||||||
if let Some(value) = table.remove(#field_name) {
|
if field_name == "selector" && has_serde_attr(&field.attrs, "flatten") {
|
||||||
self.#field_ident = deserialize_spanned::<#field_helper #ty_generics>(value)?.0;
|
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! {
|
quote! {
|
||||||
impl #impl_generics yazi_shim::toml::DeserializeOverWith for #ident #ty_generics #where_clause {
|
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> {
|
fn deserialize_over_with<'__de, __D: serde::Deserializer<'__de>>(self, de: __D) -> Result<Self, __D::Error> {
|
||||||
use yazi_shim::toml::deserialize_spanned;
|
use std::borrow::Cow;
|
||||||
|
use serde::de::{Error, IgnoredAny, MapAccess, Visitor};
|
||||||
|
use crate::Selector;
|
||||||
|
|
||||||
let mut table = table.into_inner();
|
struct V #impl_generics (#ident #ty_generics) #where_clause;
|
||||||
#(#assignments)*
|
|
||||||
|
|
||||||
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)?;
|
let flavor_str = theme::Flavor::from_theme(&theme, &theme_str)?.read(light)?;
|
||||||
|
|
||||||
preset = preset.deserialize_over(&flavor_str)?;
|
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)?);
|
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 anyhow::Result;
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexMap;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use yazi_codegen::DeserializeOver2;
|
use yazi_codegen::DeserializeOver2;
|
||||||
use yazi_fs::{File, cha::ChaType};
|
use yazi_fs::{File, cha::ChaType};
|
||||||
use yazi_shared::url::AsUrl;
|
use yazi_shared::url::AsUrl;
|
||||||
use yazi_shim::toml::DeserializeOverHook;
|
use yazi_shim::toml::DeserializeOverHook;
|
||||||
|
|
||||||
use crate::{Selectable, mix, open::OpenRule};
|
use crate::{mix, open::{OpenRule, OpenRules}};
|
||||||
|
|
||||||
#[derive(Default, Deserialize, DeserializeOver2)]
|
#[derive(Default, Deserialize, DeserializeOver2)]
|
||||||
pub struct Open {
|
pub struct Open {
|
||||||
rules: Vec<OpenRule>,
|
rules: OpenRules,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
prepend_rules: Vec<OpenRule>,
|
prepend_rules: Vec<OpenRule>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
|
@ -20,23 +20,13 @@ pub struct Open {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for Open {
|
impl Deref for Open {
|
||||||
type Target = Vec<OpenRule>;
|
type Target = OpenRules;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target { &self.rules }
|
fn deref(&self) -> &Self::Target { &self.rules }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Open {
|
impl Open {
|
||||||
pub fn all<'a>(&'a self, file: &File, mime: &str) -> impl Iterator<Item = &'a str> {
|
pub fn match_dummy<U, M>(&self, url: U, mime: M) -> Option<Arc<OpenRule>>
|
||||||
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>
|
|
||||||
where
|
where
|
||||||
U: AsUrl,
|
U: AsUrl,
|
||||||
M: AsRef<str>,
|
M: AsRef<str>,
|
||||||
|
|
@ -47,30 +37,26 @@ impl Open {
|
||||||
Some(if mime.starts_with("folder/") { ChaType::Dir } else { ChaType::File }),
|
Some(if mime.starts_with("folder/") { ChaType::Dir } else { ChaType::File }),
|
||||||
);
|
);
|
||||||
|
|
||||||
self
|
self.matches(&file, mime)
|
||||||
.rules
|
|
||||||
.iter()
|
|
||||||
.find(move |&rule| rule.matches(&file, mime))
|
|
||||||
.into_iter()
|
|
||||||
.flat_map(|r| &r.r#use)
|
|
||||||
.map(String::as_str)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn common<'a>(&'a self, targets: &[(File, &str)]) -> IndexSet<&'a str> {
|
pub fn match_common(&self, targets: &[(File, &str)]) -> impl Iterator<Item = Arc<OpenRule>> {
|
||||||
let each: Vec<IndexSet<&str>> = targets
|
let mut seen: IndexMap<Arc<OpenRule>, usize> = IndexMap::new();
|
||||||
.iter()
|
for (file, mime) in targets {
|
||||||
.map(|(file, mime)| self.all(file, mime).collect::<IndexSet<_>>())
|
if let Some(rule) = self.matches(file, mime) {
|
||||||
.filter(|s| !s.is_empty())
|
*seen.entry(rule).or_default() += 1;
|
||||||
.collect();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut flat: IndexSet<_> = each.iter().flatten().copied().collect();
|
seen.into_iter().filter(|&(_, count)| count == targets.len()).map(|(rule, _)| rule)
|
||||||
flat.retain(|use_| each.iter().all(|e| e.contains(use_)));
|
|
||||||
flat
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DeserializeOverHook for Open {
|
impl DeserializeOverHook for Open {
|
||||||
fn deserialize_over_hook(self) -> Result<Self, toml::de::Error> {
|
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::Deserialize;
|
||||||
use serde_with::{OneOrMany, formats::PreferOne, serde_as};
|
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]
|
#[serde_as]
|
||||||
#[derive(Deserialize)]
|
#[derive(Clone, Debug, Deserialize, DeserializeOver2)]
|
||||||
pub struct OpenRule {
|
pub struct OpenRule {
|
||||||
|
#[serde(skip, default = "open_rule_id")]
|
||||||
|
pub id: Id,
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub selector: Selector,
|
pub selector: Selector,
|
||||||
#[serde_as(as = "OneOrMany<_, PreferOne>")]
|
#[serde_as(as = "OneOrMany<_, PreferOne>")]
|
||||||
pub r#use: Vec<String>,
|
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 {
|
impl Selectable for OpenRule {
|
||||||
fn url_pat(&self) -> Option<&Pattern> { self.selector.url_pat() }
|
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 hashbrown::HashMap;
|
||||||
use serde::Deserialize;
|
use serde::{Deserialize, Deserializer, de::{MapAccess, Visitor}};
|
||||||
use toml::{Spanned, de::DeTable};
|
use yazi_shim::{arc_swap::IntoPointee, toml::{DeserializeOverHook, DeserializeOverWith}};
|
||||||
use yazi_shim::toml::{DeserializeOverHook, DeserializeOverWith, deserialize_spanned};
|
|
||||||
|
|
||||||
use super::OpenerRule;
|
use super::{OpenerRule, OpenerRules};
|
||||||
|
use crate::{open::OpenRule, opener::OpenerRuleMatcher};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Opener(HashMap<String, Vec<OpenerRule>>);
|
pub struct Opener(ArcSwap<HashMap<String, Arc<OpenerRules>>>);
|
||||||
|
|
||||||
impl Deref for Opener {
|
impl Deref for Opener {
|
||||||
type Target = HashMap<String, Vec<OpenerRule>>;
|
type Target = ArcSwap<HashMap<String, Arc<OpenerRules>>>;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target { &self.0 }
|
fn deref(&self) -> &Self::Target { &self.0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Opener {
|
impl Opener {
|
||||||
pub fn all<'a, I>(&self, uses: I) -> impl Iterator<Item = &OpenerRule>
|
pub fn all(&self, open: Arc<OpenRule>) -> impl Iterator<Item = Arc<OpenerRule>> + use<> {
|
||||||
where
|
let inner = self.0.load_full();
|
||||||
I: Iterator<Item = &'a str>,
|
(0..open.r#use.len())
|
||||||
{
|
.filter_map(move |i| inner.get(&open.r#use[i]).cloned())
|
||||||
uses.flat_map(|use_| self.get(use_)).flatten()
|
.flat_map(|rules| OpenerRuleMatcher::from(&*rules))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn first<'a, I>(&self, uses: I) -> Option<&OpenerRule>
|
pub fn first(&self, open: &OpenRule) -> Option<Arc<OpenerRule>> {
|
||||||
where
|
let inner = self.0.load();
|
||||||
I: Iterator<Item = &'a str>,
|
open
|
||||||
{
|
.r#use
|
||||||
uses.flat_map(|use_| self.get(use_)).flatten().next()
|
.iter()
|
||||||
|
.filter_map(|name| inner.get(name))
|
||||||
|
.find_map(|rules| rules.load().first().cloned())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn block<'a, I>(&self, uses: I) -> Option<&OpenerRule>
|
pub fn block(&self, open: &OpenRule) -> Option<Arc<OpenerRule>> {
|
||||||
where
|
let inner = self.0.load();
|
||||||
I: Iterator<Item = &'a str>,
|
open
|
||||||
{
|
.r#use
|
||||||
uses.flat_map(|use_| self.get(use_)).flatten().find(|&o| o.block)
|
.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 {
|
impl DeserializeOverHook for Opener {
|
||||||
fn deserialize_over_hook(mut self) -> Result<Self, toml::de::Error> {
|
fn deserialize_over_hook(self) -> Result<Self, toml::de::Error> {
|
||||||
for rules in self.0.values_mut() {
|
let mut inner = self.unwrap_unchecked();
|
||||||
rules.retain(|r| r.r#for.matches());
|
for mut rules in inner.values_mut() {
|
||||||
rules.iter_mut().for_each(|r| r.fill());
|
*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 {
|
impl DeserializeOverWith for Opener {
|
||||||
fn deserialize_over_with<'de>(
|
fn deserialize_over_with<'de, D: Deserializer<'de>>(self, de: D) -> Result<Self, D::Error> {
|
||||||
mut self,
|
struct V(HashMap<String, Arc<OpenerRules>>);
|
||||||
table: Spanned<DeTable<'de>>,
|
|
||||||
) -> Result<Self, toml::de::Error> {
|
impl<'de> Visitor<'de> for V {
|
||||||
for (key, value) in table.into_inner() {
|
type Value = Opener;
|
||||||
self.0.insert(key.into_inner().into_owned(), deserialize_spanned(value)?);
|
|
||||||
|
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 serde::Deserialize;
|
||||||
use yazi_fs::Splatter;
|
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)]
|
#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||||
pub struct OpenerRule {
|
pub struct OpenerRule {
|
||||||
|
#[serde(skip, default = "opener_rule_id")]
|
||||||
|
pub id: Id,
|
||||||
pub run: NonEmptyString,
|
pub run: NonEmptyString,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub block: bool,
|
pub block: bool,
|
||||||
|
|
@ -30,7 +34,7 @@ impl OpenerRule {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn fill(&mut self) {
|
pub fn fill(&mut self) {
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
self.spread =
|
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;
|
use crate::Mixable;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Clone, Deserialize)]
|
||||||
#[serde(try_from = "String")]
|
#[serde(try_from = "String")]
|
||||||
pub struct Pattern {
|
pub struct Pattern {
|
||||||
inner: globset::GlobMatcher,
|
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 serde::Deserialize;
|
||||||
|
use yazi_fs::File;
|
||||||
use yazi_shared::{Id, event::Action};
|
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)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Fetcher {
|
pub struct Fetcher {
|
||||||
|
|
@ -32,3 +34,50 @@ impl Selectable for Fetcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mixable 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 std::{borrow::Cow, ops::Deref, sync::Arc};
|
||||||
|
|
||||||
use arc_swap::ArcSwap;
|
use arc_swap::ArcSwap;
|
||||||
use hashbrown::HashSet;
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
use yazi_fs::File;
|
use yazi_fs::File;
|
||||||
use yazi_shared::Id;
|
|
||||||
use yazi_shim::arc_swap::IntoPointee;
|
use yazi_shim::arc_swap::IntoPointee;
|
||||||
|
|
||||||
use super::{Fetcher, MAX_FETCHERS};
|
use super::{Fetcher, MAX_FETCHERS};
|
||||||
use crate::Selectable;
|
use crate::plugin::FetcherMatcher;
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize)]
|
#[derive(Debug, Default, Deserialize)]
|
||||||
pub struct Fetchers(ArcSwap<Vec<Arc<Fetcher>>>);
|
pub struct Fetchers(ArcSwap<Vec<Arc<Fetcher>>>);
|
||||||
|
|
@ -68,50 +66,3 @@ impl Fetchers {
|
||||||
Arc::try_unwrap(self.0.into_inner()).expect("unique fetchers arc")
|
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();
|
static IDS: Ids = Ids::new();
|
||||||
IDS.next()
|
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 serde::Deserialize;
|
||||||
|
use yazi_fs::File;
|
||||||
use yazi_shared::{Id, event::Action};
|
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)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Preloader {
|
pub struct Preloader {
|
||||||
|
|
@ -33,3 +34,52 @@ impl Selectable for Preloader {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mixable 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 arc_swap::ArcSwap;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use yazi_fs::File;
|
use yazi_fs::File;
|
||||||
use yazi_shared::Id;
|
|
||||||
use yazi_shim::arc_swap::IntoPointee;
|
use yazi_shim::arc_swap::IntoPointee;
|
||||||
|
|
||||||
use super::Preloader;
|
use super::Preloader;
|
||||||
use crate::Selectable;
|
use crate::plugin::PreloaderMatcher;
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize)]
|
#[derive(Debug, Default, Deserialize)]
|
||||||
pub struct Preloaders(ArcSwap<Vec<Arc<Preloader>>>);
|
pub struct Preloaders(ArcSwap<Vec<Arc<Preloader>>>);
|
||||||
|
|
@ -48,52 +47,3 @@ impl Preloaders {
|
||||||
Arc::try_unwrap(self.0.into_inner()).expect("unique preloaders arc")
|
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 serde::Deserialize;
|
||||||
|
use yazi_fs::File;
|
||||||
use yazi_shared::{Id, event::Action};
|
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)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Previewer {
|
pub struct Previewer {
|
||||||
|
|
@ -32,3 +33,46 @@ impl Mixable for Previewer {
|
||||||
|
|
||||||
fn any_dir(&self) -> bool { self.selector.any_dir() }
|
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 arc_swap::ArcSwap;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use yazi_fs::File;
|
use yazi_fs::File;
|
||||||
use yazi_shared::Id;
|
use yazi_shim::{arc_swap::{ArcSwapExt, IntoPointee}, vec::{IndexAtError, VecExt}};
|
||||||
use yazi_shim::{arc_swap::IntoPointee, vec::{IndexAtError, VecExt}};
|
|
||||||
|
|
||||||
use super::Previewer;
|
use super::Previewer;
|
||||||
use crate::{Selectable, mix};
|
use crate::{mix, plugin::PreviewerMatcher};
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize)]
|
#[derive(Debug, Default, Deserialize)]
|
||||||
pub struct Previewers(ArcSwap<Vec<Arc<Previewer>>>);
|
pub struct Previewers(ArcSwap<Vec<Arc<Previewer>>>);
|
||||||
|
|
@ -41,27 +40,21 @@ impl Previewers {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(&self, index: isize, previewer: Arc<Previewer>) -> Result<(), IndexAtError> {
|
pub fn insert(&self, index: isize, previewer: Arc<Previewer>) -> Result<(), IndexAtError> {
|
||||||
let mut err = None;
|
self.0.try_rcu(|previewers| {
|
||||||
|
let i = previewers.index_at(index)?;
|
||||||
self.0.rcu(|previewers| match previewers.index_at(index) {
|
if i == previewers.len() {
|
||||||
Ok(n) if n == previewers.len() => {
|
Ok(mix(Vec::<Previewer>::new(), previewers.iter().cloned(), [previewer.clone()]))
|
||||||
mix(Vec::<Previewer>::new(), previewers.iter().cloned(), [previewer.clone()])
|
} else {
|
||||||
}
|
let (before, after) = previewers.split_at(i);
|
||||||
Ok(n) => {
|
Ok(mix(
|
||||||
let (before, after) = previewers.split_at(n);
|
|
||||||
mix(
|
|
||||||
Vec::<Previewer>::new(),
|
Vec::<Previewer>::new(),
|
||||||
before.iter().cloned().chain([previewer.clone()]).chain(after.iter().cloned()),
|
before.iter().cloned().chain([previewer.clone()]).chain(after.iter().cloned()),
|
||||||
Vec::<Previewer>::new(),
|
Vec::<Previewer>::new(),
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
Err(e) => {
|
})?;
|
||||||
err = Some(e);
|
|
||||||
Vec::clone(previewers)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
err.map_or(Ok(()), Err)
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove(&self, matcher: PreviewerMatcher) {
|
pub fn remove(&self, matcher: PreviewerMatcher) {
|
||||||
|
|
@ -76,46 +69,3 @@ impl Previewers {
|
||||||
Arc::try_unwrap(self.0.into_inner()).expect("unique previewers arc")
|
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 serde::Deserialize;
|
||||||
|
use yazi_fs::File;
|
||||||
use yazi_shared::{Id, event::Action};
|
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)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Spotter {
|
pub struct Spotter {
|
||||||
|
|
@ -32,3 +33,46 @@ impl Mixable for Spotter {
|
||||||
|
|
||||||
fn any_dir(&self) -> bool { self.selector.any_dir() }
|
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 yazi_shim::arc_swap::IntoPointee;
|
||||||
|
|
||||||
use super::Spotter;
|
use super::Spotter;
|
||||||
use crate::Selectable;
|
use crate::plugin::SpotterMatcher;
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize)]
|
#[derive(Debug, Default, Deserialize)]
|
||||||
pub struct Spotters(ArcSwap<Vec<Arc<Spotter>>>);
|
pub struct Spotters(ArcSwap<Vec<Arc<Spotter>>>);
|
||||||
|
|
@ -45,46 +45,3 @@ impl Spotters {
|
||||||
Arc::try_unwrap(self.0.into_inner()).expect("unique spotters arc")
|
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};
|
use crate::{Mixable, Pattern, Selectable};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Selector {
|
pub struct Selector {
|
||||||
url: Option<Pattern>,
|
pub url: Option<Pattern>,
|
||||||
mime: Option<Pattern>,
|
pub mime: Option<Pattern>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for Selector {
|
impl<'de> Deserialize<'de> for Selector {
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,9 @@ use std::ops::Deref;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use serde::{Deserialize, Serialize, de};
|
use serde::{Deserialize, Deserializer, Serialize, de, de::{MapAccess, Visitor}};
|
||||||
use toml::{Spanned, de::DeTable};
|
|
||||||
use yazi_codegen::DeserializeOver;
|
use yazi_codegen::DeserializeOver;
|
||||||
use yazi_shim::toml::{DeserializeOverWith, deserialize_spanned};
|
use yazi_shim::toml::DeserializeOverWith;
|
||||||
|
|
||||||
use crate::vfs::Service;
|
use crate::vfs::Service;
|
||||||
|
|
||||||
|
|
@ -19,21 +18,31 @@ impl Deref for Services {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DeserializeOverWith for Services {
|
impl DeserializeOverWith for Services {
|
||||||
fn deserialize_over_with<'de>(
|
fn deserialize_over_with<'de, D: Deserializer<'de>>(self, de: D) -> Result<Self, D::Error> {
|
||||||
mut self,
|
struct V(Services);
|
||||||
table: Spanned<DeTable<'de>>,
|
|
||||||
) -> Result<Self, toml::de::Error> {
|
impl<'de> Visitor<'de> for V {
|
||||||
for (key, value) in table.into_inner() {
|
type Value = Services;
|
||||||
let key = key.into_inner();
|
|
||||||
if key.is_empty() || key.len() > 20 {
|
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
Err(de::Error::custom(format!("VFS name `{key}` must be between 1 and 20 characters")))?;
|
f.write_str("a map of VFS services")
|
||||||
} 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 {
|
fn visit_map<M: MapAccess<'de>>(mut self, mut map: M) -> Result<Self::Value, M::Error> {
|
||||||
self.0.insert(key.into_owned(), deserialize_spanned(value)?);
|
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 = { workspace = true }
|
||||||
tokio-stream = { workspace = true }
|
tokio-stream = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
tracing-appender = "0.2.4"
|
tracing-appender = "0.2.5"
|
||||||
tracing-subscriber = { version = "0.3.23", features = [ "env-filter" ] }
|
tracing-subscriber = { version = "0.3.23", features = [ "env-filter" ] }
|
||||||
|
|
||||||
[target."cfg(unix)".dependencies]
|
[target."cfg(unix)".dependencies]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use mlua::{IntoLua, Lua, LuaSerdeExt, Value};
|
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_boot::ARGS;
|
||||||
use yazi_config::YAZI;
|
use yazi_config::YAZI;
|
||||||
|
|
||||||
|
|
@ -9,6 +9,8 @@ pub fn compose() -> Composer<ComposerGet, ComposerSet> {
|
||||||
b"args" => args().into_lua(lua)?,
|
b"args" => args().into_lua(lua)?,
|
||||||
b"term" => super::term().into_lua(lua)?,
|
b"term" => super::term().into_lua(lua)?,
|
||||||
b"mgr" => mgr().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"plugin" => super::plugin().into_lua(lua)?,
|
||||||
b"preview" => preview().into_lua(lua)?,
|
b"preview" => preview().into_lua(lua)?,
|
||||||
b"tasks" => tasks().into_lua(lua)?,
|
b"tasks" => tasks().into_lua(lua)?,
|
||||||
|
|
@ -22,6 +24,19 @@ pub fn compose() -> Composer<ComposerGet, ComposerSet> {
|
||||||
Composer::new(get, set)
|
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 args() -> Composer<ComposerGet, ComposerSet> {
|
||||||
fn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {
|
fn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {
|
||||||
match key {
|
match key {
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ parking_lot = { workspace = true }
|
||||||
paste = { workspace = true }
|
paste = { workspace = true }
|
||||||
percent-encoding = { workspace = true }
|
percent-encoding = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
|
serde_with = { workspace = true }
|
||||||
strum = { workspace = true }
|
strum = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use serde::{Deserialize, Deserializer, de};
|
use serde_with::DeserializeFromStr;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum ConditionOp {
|
pub enum ConditionOp {
|
||||||
|
|
@ -44,7 +44,7 @@ impl ConditionOp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, DeserializeFromStr)]
|
||||||
pub struct Condition {
|
pub struct Condition {
|
||||||
ops: Vec<ConditionOp>,
|
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 {
|
impl Condition {
|
||||||
fn build(expr: &str) -> Self {
|
fn build(expr: &str) -> Self {
|
||||||
let mut stack: Vec<ConditionOp> = vec![];
|
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 anyhow::{Result, anyhow, bail};
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use serde::{Deserialize, de};
|
use serde_with::DeserializeFromStr;
|
||||||
|
|
||||||
use crate::{Layer, SStr, Source, data::{Data, DataAny, DataKey}, event::Replier};
|
use crate::{Layer, SStr, Source, data::{Data, DataAny, DataKey}, event::Replier};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default, DeserializeFromStr)]
|
||||||
pub struct Action {
|
pub struct Action {
|
||||||
pub name: SStr,
|
pub name: SStr,
|
||||||
pub args: HashMap<DataKey, Data>,
|
pub args: HashMap<DataKey, Data>,
|
||||||
|
|
@ -281,12 +281,3 @@ impl FromStr for Action {
|
||||||
Ok(me)
|
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
|
# External dependencies
|
||||||
arc-swap = { workspace = true }
|
arc-swap = { workspace = true }
|
||||||
crossterm = { workspace = true }
|
crossterm = { workspace = true }
|
||||||
|
mlua = { workspace = true }
|
||||||
ratatui = { workspace = true }
|
ratatui = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use arc_swap::ArcSwap;
|
use arc_swap::{ArcSwap, ArcSwapAny, Guard, RefCnt, strategy::CaS};
|
||||||
|
|
||||||
// --- IntoPointee
|
// --- IntoPointee
|
||||||
pub trait IntoPointee: Sized {
|
pub trait IntoPointee: Sized {
|
||||||
|
|
@ -9,3 +9,34 @@ impl<T> IntoPointee for T {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn into_pointee(self) -> ArcSwap<Self> { ArcSwap::from_pointee(self) }
|
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);
|
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 {
|
pub trait DeserializeOver: DeserializeOverWith + DeserializeOverHook {
|
||||||
fn deserialize_over(self, input: &str) -> Result<Self, de::Error> {
|
fn deserialize_over(self, input: &str) -> Result<Self, de::Error> {
|
||||||
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.set_input(Some(input));
|
||||||
err
|
err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> DeserializeOver for T where T: DeserializeOverWith + DeserializeOverHook {}
|
||||||
|
|
||||||
|
// --- DeserializeOverWith
|
||||||
pub trait DeserializeOverWith: Sized {
|
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 {
|
pub trait DeserializeOverHook: Sized {
|
||||||
fn deserialize_over_hook(self) -> Result<Self, de::Error> { Ok(self) }
|
fn deserialize_over_hook(self) -> Result<Self, de::Error> { Ok(self) }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> 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)
|
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>
|
fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
|
||||||
where
|
where
|
||||||
E: de::Error,
|
E: de::Error,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue