mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
Add support for an exclude API for Lua plugins allowing the used of exclude patterns externally
This commit is contained in:
parent
d7d9ef12b3
commit
cb11bdbe31
9 changed files with 217 additions and 0 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -4644,6 +4644,7 @@ dependencies = [
|
|||
"anyhow",
|
||||
"crossterm 0.29.0",
|
||||
"futures",
|
||||
"globset",
|
||||
"hashbrown 0.16.0",
|
||||
"libc",
|
||||
"mlua",
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ yazi-widgets = { path = "../yazi-widgets", version = "25.9.15" }
|
|||
anyhow = { workspace = true }
|
||||
crossterm = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
globset = { workspace = true }
|
||||
hashbrown = { workspace = true }
|
||||
mlua = { workspace = true }
|
||||
paste = { workspace = true }
|
||||
|
|
|
|||
180
yazi-actor/src/mgr/exclude_add.rs
Normal file
180
yazi-actor/src/mgr/exclude_add.rs
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use globset::{Glob, GlobSetBuilder};
|
||||
use yazi_config::YAZI;
|
||||
use yazi_core::tab::Folder;
|
||||
use yazi_fs::{FolderStage, IgnoreFilter};
|
||||
use yazi_macro::{act, render, render_and, succ};
|
||||
use yazi_parser::mgr::ExcludeAddOpt;
|
||||
use yazi_shared::{data::Data, url::UrlLike};
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct ExcludeAdd;
|
||||
|
||||
impl Actor for ExcludeAdd {
|
||||
type Options = ExcludeAddOpt;
|
||||
|
||||
const NAME: &str = "exclude_add";
|
||||
|
||||
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
|
||||
if opt.patterns.is_empty() {
|
||||
succ!();
|
||||
}
|
||||
|
||||
// Get the appropriate context string for matching exclude rules
|
||||
let cwd = cx.cwd();
|
||||
let cwd_str = if cwd.is_search() {
|
||||
"search://**".to_string()
|
||||
} else {
|
||||
cwd.as_path().map(|p| p.display().to_string()).unwrap_or_default()
|
||||
};
|
||||
|
||||
// Get existing patterns from config
|
||||
let config_patterns = YAZI.files.excludes_for_context(&cwd_str);
|
||||
|
||||
// Merge plugin patterns with config patterns
|
||||
// Config patterns come last so they can override plugin patterns with negation
|
||||
let mut all_patterns = opt.patterns.clone();
|
||||
all_patterns.extend(config_patterns);
|
||||
|
||||
// Compile glob patterns
|
||||
let mut ignores_builder = GlobSetBuilder::new();
|
||||
let mut whitelists_builder = GlobSetBuilder::new();
|
||||
|
||||
for pattern in &all_patterns {
|
||||
if let Some(negated) = pattern.strip_prefix('!') {
|
||||
// Negation pattern - this is a whitelist
|
||||
if let Ok(glob) = Glob::new(negated) {
|
||||
whitelists_builder.add(glob);
|
||||
}
|
||||
} else {
|
||||
// Regular ignore pattern
|
||||
if let Ok(glob) = Glob::new(pattern) {
|
||||
ignores_builder.add(glob);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let ignores = ignores_builder.build().ok();
|
||||
let whitelists = whitelists_builder.build().ok();
|
||||
|
||||
// Create glob matcher function for compiled patterns
|
||||
let glob_matcher: Option<Arc<dyn Fn(&std::path::Path) -> Option<bool> + Send + Sync>> =
|
||||
if ignores.is_some() || whitelists.is_some() {
|
||||
let context = cwd_str.clone();
|
||||
Some(Arc::new(move |path: &std::path::Path| {
|
||||
// First check config patterns (for user overrides/negation)
|
||||
if let Some(result) = YAZI.files.matches_path(path, &context) {
|
||||
return Some(result);
|
||||
}
|
||||
|
||||
// For absolute paths, try both the full path and relative components
|
||||
let paths_to_check: Vec<&std::path::Path> = if path.is_absolute() {
|
||||
let mut paths = vec![path];
|
||||
if let Some(components) = path.to_str() {
|
||||
for (i, _) in components.match_indices('/').skip(1) {
|
||||
if let Some(subpath) = components.get(i + 1..) {
|
||||
paths.push(std::path::Path::new(subpath));
|
||||
}
|
||||
}
|
||||
}
|
||||
paths
|
||||
} else {
|
||||
vec![path]
|
||||
};
|
||||
|
||||
// Check whitelist first (negation takes precedence)
|
||||
if let Some(ref wl) = whitelists {
|
||||
for p in &paths_to_check {
|
||||
if wl.is_match(p) {
|
||||
return Some(false); // Explicitly NOT ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check ignore patterns
|
||||
if let Some(ref ig) = ignores {
|
||||
for p in &paths_to_check {
|
||||
if ig.is_match(p) {
|
||||
return Some(true); // Should be ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Load ignore filter with merged patterns
|
||||
let ignore_filter = IgnoreFilter::from_patterns(glob_matcher.clone());
|
||||
|
||||
let hovered = cx.hovered().map(|f| f.urn().to_owned());
|
||||
let apply = |f: &mut Folder, filter: Option<IgnoreFilter>| {
|
||||
let changed = f.files.set_ignore_filter(filter);
|
||||
if f.stage == FolderStage::Loading {
|
||||
render!();
|
||||
false
|
||||
} else {
|
||||
render_and!(changed && f.files.catchup_revision())
|
||||
}
|
||||
};
|
||||
|
||||
// Apply to CWD
|
||||
if apply(cx.current_mut(), ignore_filter.clone()) {
|
||||
act!(mgr:hover, cx)?;
|
||||
act!(mgr:update_paged, cx)?;
|
||||
}
|
||||
|
||||
// Apply to hovered folder
|
||||
if let Some(h) = cx.hovered_folder_mut() {
|
||||
let hovered_str = if h.url.is_search() {
|
||||
"search://**".to_string()
|
||||
} else {
|
||||
h.url.as_path().map(|p| p.display().to_string()).unwrap_or_default()
|
||||
};
|
||||
|
||||
let hovered_config_patterns = YAZI.files.excludes_for_context(&hovered_str);
|
||||
let mut hovered_all_patterns = opt.patterns;
|
||||
hovered_all_patterns.extend(hovered_config_patterns);
|
||||
|
||||
let hovered_matcher: Option<Arc<dyn Fn(&std::path::Path) -> Option<bool> + Send + Sync>> =
|
||||
if !hovered_all_patterns.is_empty() {
|
||||
let context = hovered_str.clone();
|
||||
let patterns = hovered_all_patterns.clone();
|
||||
Some(Arc::new(move |path: &std::path::Path| {
|
||||
if let Some(result) = YAZI.files.matches_path(path, &context) {
|
||||
return Some(result);
|
||||
}
|
||||
for pattern in &patterns {
|
||||
if let Some(negated) = pattern.strip_prefix('!') {
|
||||
if path.to_str().map_or(false, |p| p.contains(negated)) {
|
||||
return Some(false);
|
||||
}
|
||||
} else if path.to_str().map_or(false, |p| p.contains(pattern)) {
|
||||
return Some(true);
|
||||
}
|
||||
}
|
||||
None
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let hovered_filter = IgnoreFilter::from_patterns(hovered_matcher);
|
||||
|
||||
if apply(h, hovered_filter) {
|
||||
render!(h.repos(None));
|
||||
act!(mgr:peek, cx, true)?;
|
||||
} else if hovered.as_deref() != cx.hovered().map(|f| f.urn()) {
|
||||
act!(mgr:peek, cx)?;
|
||||
act!(mgr:watch, cx)?;
|
||||
}
|
||||
}
|
||||
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ yazi_macro::mod_flat!(
|
|||
download
|
||||
enter
|
||||
escape
|
||||
exclude_add
|
||||
filter
|
||||
filter_do
|
||||
find
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ pub enum Spark<'a> {
|
|||
EscapeSearch(yazi_parser::VoidOpt),
|
||||
EscapeSelect(yazi_parser::VoidOpt),
|
||||
EscapeVisual(yazi_parser::VoidOpt),
|
||||
ExcludeAdd(yazi_parser::mgr::ExcludeAddOpt),
|
||||
Filter(yazi_parser::mgr::FilterOpt),
|
||||
FilterDo(yazi_parser::mgr::FilterOpt),
|
||||
Find(yazi_parser::mgr::FindOpt),
|
||||
|
|
@ -147,6 +148,7 @@ impl<'a> IntoLua for Spark<'a> {
|
|||
Self::EscapeSearch(b) => b.into_lua(lua),
|
||||
Self::EscapeSelect(b) => b.into_lua(lua),
|
||||
Self::EscapeVisual(b) => b.into_lua(lua),
|
||||
Self::ExcludeAdd(b) => b.into_lua(lua),
|
||||
Self::Filter(b) => b.into_lua(lua),
|
||||
Self::FilterDo(b) => b.into_lua(lua),
|
||||
Self::Find(b) => b.into_lua(lua),
|
||||
|
|
@ -287,6 +289,7 @@ try_from_spark!(mgr::CopyOpt, mgr:copy);
|
|||
try_from_spark!(mgr::CreateOpt, mgr:create);
|
||||
try_from_spark!(mgr::DownloadOpt, mgr:download);
|
||||
try_from_spark!(mgr::EscapeOpt, mgr:escape);
|
||||
try_from_spark!(mgr::ExcludeAddOpt, mgr:exclude_add);
|
||||
try_from_spark!(mgr::FilterOpt, mgr:filter, mgr:filter_do);
|
||||
try_from_spark!(mgr::FindArrowOpt, mgr:find_arrow);
|
||||
try_from_spark!(mgr::FindDoOpt, mgr:find_do);
|
||||
|
|
|
|||
|
|
@ -121,6 +121,9 @@ impl<'a> Executor<'a> {
|
|||
on!(filter);
|
||||
on!(filter_do);
|
||||
|
||||
// Exclude
|
||||
on!(exclude_add);
|
||||
|
||||
// Find
|
||||
on!(find);
|
||||
on!(find_do);
|
||||
|
|
|
|||
23
yazi-parser/src/mgr/exclude_add.rs
Normal file
23
yazi-parser/src/mgr/exclude_add.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
|
||||
use yazi_shared::{SStr, event::CmdCow};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ExcludeAddOpt {
|
||||
pub patterns: Vec<String>,
|
||||
}
|
||||
|
||||
impl TryFrom<CmdCow> for ExcludeAddOpt {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(mut c: CmdCow) -> Result<Self, Self::Error> {
|
||||
Ok(Self { patterns: c.take_seq::<SStr>().into_iter().map(|s| s.to_string()).collect() })
|
||||
}
|
||||
}
|
||||
|
||||
impl FromLua for ExcludeAddOpt {
|
||||
fn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err("unsupported".into_lua_err()) }
|
||||
}
|
||||
|
||||
impl IntoLua for ExcludeAddOpt {
|
||||
fn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err("unsupported".into_lua_err()) }
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ yazi_macro::mod_flat!(
|
|||
create
|
||||
download
|
||||
escape
|
||||
exclude_add
|
||||
filter
|
||||
find
|
||||
find_arrow
|
||||
|
|
|
|||
|
|
@ -72,4 +72,8 @@ impl MgrProxy {
|
|||
{
|
||||
emit!(Call(relay!(mgr:upload).with_seq(urls)));
|
||||
}
|
||||
|
||||
pub fn exclude_add(patterns: Vec<String>) {
|
||||
emit!(Call(relay!(mgr:exclude_add).with_seq(patterns)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue