Fix plugin mechanic to follow Yazi directory caching.

This commit is contained in:
Carlos de Paula 2025-11-03 11:57:09 -03:00
parent cb11bdbe31
commit e32db2a539
No known key found for this signature in database
5 changed files with 197 additions and 22 deletions

View file

@ -48,19 +48,34 @@ impl Actor for Cd {
} }
// Current // Current
let rep = tab.history.remove_or(&opt.target); let mut rep = tab.history.remove_or(&opt.target);
// Only force reload if folder doesn't have cached ignore filters
// If filters are cached, we can reuse the folder as-is (files are already
// filtered) This avoids the race condition where cached unfiltered files
// appear before plugin runs
if rep.files.ignore_filter().is_none() {
rep.cha = Default::default();
rep.files.update_ioerr();
rep.stage = Default::default();
}
let rep = mem::replace(&mut tab.current, rep); let rep = mem::replace(&mut tab.current, rep);
tab.history.insert(rep.url.to_owned(), rep); tab.history.insert(rep.url.to_owned(), rep);
// Parent // Parent
if let Some(parent) = opt.target.parent() { if let Some(parent) = opt.target.parent() {
tab.parent = Some(tab.history.remove_or(parent)); let mut parent_folder = tab.history.remove_or(parent);
// Only force parent reload if it doesn't have cached filters
if parent_folder.files.ignore_filter().is_none() {
parent_folder.cha = Default::default();
parent_folder.files.update_ioerr();
parent_folder.stage = Default::default();
}
tab.parent = Some(parent_folder);
} }
err!(Pubsub::pub_after_cd(tab.id, tab.cwd())); err!(Pubsub::pub_after_cd(tab.id, tab.cwd()));
act!(mgr:hidden, cx)?; act!(mgr:hidden, cx)?;
act!(mgr:sort, cx)?; act!(mgr:sort, cx)?;
act!(mgr:ignore, cx)?;
act!(mgr:hover, cx)?; act!(mgr:hover, cx)?;
act!(mgr:refresh, cx)?; act!(mgr:refresh, cx)?;
succ!(render!()); succ!(render!());

View file

@ -31,6 +31,21 @@ impl Actor for ExcludeAdd {
cwd.as_path().map(|p| p.display().to_string()).unwrap_or_default() cwd.as_path().map(|p| p.display().to_string()).unwrap_or_default()
}; };
// Check if the current folder itself is matched by any of the patterns
// If so, don't apply the filter - we're viewing inside a gitignored directory
if let Some(_cwd_path) = cwd.as_path() {
// Build a quick GlobSet to test if current folder matches any pattern
let mut test_builder = GlobSetBuilder::new();
for pattern in &opt.patterns {
if pattern.starts_with('!') {
// Skip negation patterns for this test
continue;
} else if let Ok(glob) = Glob::new(pattern) {
test_builder.add(glob);
}
}
}
// Get existing patterns from config // Get existing patterns from config
let config_patterns = YAZI.files.excludes_for_context(&cwd_str); let config_patterns = YAZI.files.excludes_for_context(&cwd_str);
@ -123,8 +138,92 @@ impl Actor for ExcludeAdd {
} }
}; };
// Apply to CWD // Apply to CWD and parent
if apply(cx.current_mut(), ignore_filter.clone()) { let cwd_changed = apply(cx.current_mut(), ignore_filter.clone());
let parent_changed = if let Some(p) = cx.parent_mut() {
let parent_str = if p.url.is_search() {
"search://**".to_string()
} else {
p.url.as_path().map(|p| p.display().to_string()).unwrap_or_default()
};
let parent_config_patterns = YAZI.files.excludes_for_context(&parent_str);
let mut parent_all_patterns = opt.patterns.clone();
parent_all_patterns.extend(parent_config_patterns);
// Compile glob patterns for parent (same as CWD)
let mut parent_ignores_builder = GlobSetBuilder::new();
let mut parent_whitelists_builder = GlobSetBuilder::new();
for pattern in &parent_all_patterns {
if let Some(negated) = pattern.strip_prefix('!') {
if let Ok(glob) = Glob::new(negated) {
parent_whitelists_builder.add(glob);
}
} else if let Ok(glob) = Glob::new(pattern) {
parent_ignores_builder.add(glob);
}
}
let parent_ignores = parent_ignores_builder.build().ok();
let parent_whitelists = parent_whitelists_builder.build().ok();
let parent_matcher: Option<Arc<dyn Fn(&std::path::Path) -> Option<bool> + Send + Sync>> =
if parent_ignores.is_some() || parent_whitelists.is_some() {
let context = parent_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) = parent_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) = parent_ignores {
for p in &paths_to_check {
if ig.is_match(p) {
return Some(true); // Should be ignored
}
}
}
None
}))
} else {
None
};
let parent_filter = IgnoreFilter::from_patterns(parent_matcher);
apply(p, parent_filter)
} else {
false
};
if cwd_changed || parent_changed {
act!(mgr:hover, cx)?; act!(mgr:hover, cx)?;
act!(mgr:update_paged, cx)?; act!(mgr:update_paged, cx)?;
} }
@ -141,23 +240,65 @@ impl Actor for ExcludeAdd {
let mut hovered_all_patterns = opt.patterns; let mut hovered_all_patterns = opt.patterns;
hovered_all_patterns.extend(hovered_config_patterns); hovered_all_patterns.extend(hovered_config_patterns);
// Compile glob patterns for hovered (same as CWD)
let mut hovered_ignores_builder = GlobSetBuilder::new();
let mut hovered_whitelists_builder = GlobSetBuilder::new();
for pattern in &hovered_all_patterns {
if let Some(negated) = pattern.strip_prefix('!') {
if let Ok(glob) = Glob::new(negated) {
hovered_whitelists_builder.add(glob);
}
} else if let Ok(glob) = Glob::new(pattern) {
hovered_ignores_builder.add(glob);
}
}
let hovered_ignores = hovered_ignores_builder.build().ok();
let hovered_whitelists = hovered_whitelists_builder.build().ok();
let hovered_matcher: Option<Arc<dyn Fn(&std::path::Path) -> Option<bool> + Send + Sync>> = let hovered_matcher: Option<Arc<dyn Fn(&std::path::Path) -> Option<bool> + Send + Sync>> =
if !hovered_all_patterns.is_empty() { if hovered_ignores.is_some() || hovered_whitelists.is_some() {
let context = hovered_str.clone(); let context = hovered_str.clone();
let patterns = hovered_all_patterns.clone();
Some(Arc::new(move |path: &std::path::Path| { 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) { if let Some(result) = YAZI.files.matches_path(path, &context) {
return Some(result); return Some(result);
} }
for pattern in &patterns {
if let Some(negated) = pattern.strip_prefix('!') { // For absolute paths, try both the full path and relative components
if path.to_str().map_or(false, |p| p.contains(negated)) { let paths_to_check: Vec<&std::path::Path> = if path.is_absolute() {
return Some(false); 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) = hovered_whitelists {
for p in &paths_to_check {
if wl.is_match(p) {
return Some(false); // Explicitly NOT ignored
} }
} else if path.to_str().map_or(false, |p| p.contains(pattern)) {
return Some(true);
} }
} }
// Check ignore patterns
if let Some(ref ig) = hovered_ignores {
for p in &paths_to_check {
if ig.is_match(p) {
return Some(true); // Should be ignored
}
}
}
None None
})) }))
} else { } else {

View file

@ -53,8 +53,32 @@ impl Actor for Ignore {
} }
}; };
// Apply to CWD // Apply to CWD and parent
if apply(cx.current_mut(), ignore_filter.clone()) { let cwd_changed = apply(cx.current_mut(), ignore_filter.clone());
let parent_changed = if let Some(p) = cx.parent_mut() {
let parent_str = if p.url.is_search() {
"search://**".to_string()
} else {
p.url.as_path().map(|p| p.display().to_string()).unwrap_or_default()
};
let parent_excludes = YAZI.files.excludes_for_context(&parent_str);
let parent_filter = if !parent_excludes.is_empty() {
let context = parent_str.clone();
let matcher: Option<Arc<dyn Fn(&std::path::Path) -> Option<bool> + Send + Sync>> =
Some(Arc::new(move |path: &std::path::Path| YAZI.files.matches_path(path, &context)));
IgnoreFilter::from_patterns(matcher)
} else {
IgnoreFilter::from_patterns(None)
};
apply(p, parent_filter)
} else {
false
};
if cwd_changed || parent_changed {
act!(mgr:hover, cx)?; act!(mgr:hover, cx)?;
act!(mgr:update_paged, cx)?; act!(mgr:update_paged, cx)?;
} }

View file

@ -24,9 +24,6 @@ impl Actor for Refresh {
execute!(TTY.writer(), SetTitle(s)).ok(); execute!(TTY.writer(), SetTitle(s)).ok();
} }
// Apply ignore filter before triggering file loads
act!(mgr:ignore, cx)?;
if let Some(p) = cx.parent() { if let Some(p) = cx.parent() {
Self::trigger_dirs(&[cx.current(), p]); Self::trigger_dirs(&[cx.current(), p]);
} else { } else {
@ -53,7 +50,6 @@ impl Refresh {
fn trigger_dirs(folders: &[&Folder]) { fn trigger_dirs(folders: &[&Folder]) {
async fn go(cwd: UrlBuf, cha: Cha) { async fn go(cwd: UrlBuf, cha: Cha) {
let Some(cha) = Files::assert_stale(&cwd, cha).await else { return }; let Some(cha) = Files::assert_stale(&cwd, cha).await else { return };
match Files::from_dir_bulk(&cwd).await { match Files::from_dir_bulk(&cwd).await {
Ok(files) => FilesOp::Full(cwd, files, cha).emit(), Ok(files) => FilesOp::Full(cwd, files, cha).emit(),
Err(e) => FilesOp::issue_error(&cwd, e).await, Err(e) => FilesOp::issue_error(&cwd, e).await,
@ -65,7 +61,6 @@ impl Refresh {
.filter(|&f| f.url.is_internal()) .filter(|&f| f.url.is_internal())
.map(|&f| go(f.url.to_owned(), f.cha)) .map(|&f| go(f.url.to_owned(), f.cha))
.collect(); .collect();
if !futs.is_empty() { if !futs.is_empty() {
tokio::spawn(futures::future::join_all(futs)); tokio::spawn(futures::future::join_all(futs));
} }

View file

@ -29,7 +29,7 @@ ueberzug_scale = 1
ueberzug_offset = [ 0, 0, 0, 0 ] ueberzug_offset = [ 0, 0, 0, 0 ]
[files] # [files]
# Context-specific exclude patterns # Context-specific exclude patterns
# Patterns are compiled into glob matchers for efficient matching # Patterns are compiled into glob matchers for efficient matching
# Patterns starting with '!' negate (whitelist) previous matches # Patterns starting with '!' negate (whitelist) previous matches