mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
Fix plugin mechanic to follow Yazi directory caching.
This commit is contained in:
parent
cb11bdbe31
commit
e32db2a539
5 changed files with 197 additions and 22 deletions
|
|
@ -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!());
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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)?;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue