Skip to content

Commit 3c64136

Browse files
committed
Fix plugin mechanic to follow Yazi directory caching.
1 parent e49f1ab commit 3c64136

File tree

5 files changed

+197
-22
lines changed

5 files changed

+197
-22
lines changed

yazi-actor/src/mgr/cd.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,19 +48,34 @@ impl Actor for Cd {
4848
}
4949

5050
// Current
51-
let rep = tab.history.remove_or(&opt.target);
51+
let mut rep = tab.history.remove_or(&opt.target);
52+
53+
// Only force reload if folder doesn't have cached ignore filters
54+
// If filters are cached, we can reuse the folder as-is (files are already
55+
// filtered) This avoids the race condition where cached unfiltered files
56+
// appear before plugin runs
57+
if rep.files.ignore_filter().is_none() {
58+
rep.cha = Default::default();
59+
rep.files.update_ioerr();
60+
rep.stage = Default::default();
61+
}
5262
let rep = mem::replace(&mut tab.current, rep);
5363
tab.history.insert(rep.url.to_owned(), rep);
5464

5565
// Parent
5666
if let Some(parent) = opt.target.parent() {
57-
tab.parent = Some(tab.history.remove_or(parent));
67+
let mut parent_folder = tab.history.remove_or(parent);
68+
// Only force parent reload if it doesn't have cached filters
69+
if parent_folder.files.ignore_filter().is_none() {
70+
parent_folder.cha = Default::default();
71+
parent_folder.files.update_ioerr();
72+
parent_folder.stage = Default::default();
73+
}
74+
tab.parent = Some(parent_folder);
5875
}
59-
6076
err!(Pubsub::pub_after_cd(tab.id, tab.cwd()));
6177
act!(mgr:hidden, cx)?;
6278
act!(mgr:sort, cx)?;
63-
act!(mgr:ignore, cx)?;
6479
act!(mgr:hover, cx)?;
6580
act!(mgr:refresh, cx)?;
6681
succ!(render!());

yazi-actor/src/mgr/exclude_add.rs

Lines changed: 151 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,21 @@ impl Actor for ExcludeAdd {
3131
cwd.as_path().map(|p| p.display().to_string()).unwrap_or_default()
3232
};
3333

34+
// Check if the current folder itself is matched by any of the patterns
35+
// If so, don't apply the filter - we're viewing inside a gitignored directory
36+
if let Some(_cwd_path) = cwd.as_path() {
37+
// Build a quick GlobSet to test if current folder matches any pattern
38+
let mut test_builder = GlobSetBuilder::new();
39+
for pattern in &opt.patterns {
40+
if pattern.starts_with('!') {
41+
// Skip negation patterns for this test
42+
continue;
43+
} else if let Ok(glob) = Glob::new(pattern) {
44+
test_builder.add(glob);
45+
}
46+
}
47+
}
48+
3449
// Get existing patterns from config
3550
let config_patterns = YAZI.files.excludes_for_context(&cwd_str);
3651

@@ -123,8 +138,92 @@ impl Actor for ExcludeAdd {
123138
}
124139
};
125140

126-
// Apply to CWD
127-
if apply(cx.current_mut(), ignore_filter.clone()) {
141+
// Apply to CWD and parent
142+
let cwd_changed = apply(cx.current_mut(), ignore_filter.clone());
143+
144+
let parent_changed = if let Some(p) = cx.parent_mut() {
145+
let parent_str = if p.url.is_search() {
146+
"search://**".to_string()
147+
} else {
148+
p.url.as_path().map(|p| p.display().to_string()).unwrap_or_default()
149+
};
150+
151+
let parent_config_patterns = YAZI.files.excludes_for_context(&parent_str);
152+
let mut parent_all_patterns = opt.patterns.clone();
153+
parent_all_patterns.extend(parent_config_patterns);
154+
155+
// Compile glob patterns for parent (same as CWD)
156+
let mut parent_ignores_builder = GlobSetBuilder::new();
157+
let mut parent_whitelists_builder = GlobSetBuilder::new();
158+
159+
for pattern in &parent_all_patterns {
160+
if let Some(negated) = pattern.strip_prefix('!') {
161+
if let Ok(glob) = Glob::new(negated) {
162+
parent_whitelists_builder.add(glob);
163+
}
164+
} else if let Ok(glob) = Glob::new(pattern) {
165+
parent_ignores_builder.add(glob);
166+
}
167+
}
168+
169+
let parent_ignores = parent_ignores_builder.build().ok();
170+
let parent_whitelists = parent_whitelists_builder.build().ok();
171+
172+
let parent_matcher: Option<Arc<dyn Fn(&std::path::Path) -> Option<bool> + Send + Sync>> =
173+
if parent_ignores.is_some() || parent_whitelists.is_some() {
174+
let context = parent_str.clone();
175+
Some(Arc::new(move |path: &std::path::Path| {
176+
// First check config patterns (for user overrides/negation)
177+
if let Some(result) = YAZI.files.matches_path(path, &context) {
178+
return Some(result);
179+
}
180+
181+
// For absolute paths, try both the full path and relative components
182+
let paths_to_check: Vec<&std::path::Path> = if path.is_absolute() {
183+
let mut paths = vec![path];
184+
if let Some(components) = path.to_str() {
185+
for (i, _) in components.match_indices('/').skip(1) {
186+
if let Some(subpath) = components.get(i + 1..) {
187+
paths.push(std::path::Path::new(subpath));
188+
}
189+
}
190+
}
191+
paths
192+
} else {
193+
vec![path]
194+
};
195+
196+
// Check whitelist first (negation takes precedence)
197+
if let Some(ref wl) = parent_whitelists {
198+
for p in &paths_to_check {
199+
if wl.is_match(p) {
200+
return Some(false); // Explicitly NOT ignored
201+
}
202+
}
203+
}
204+
205+
// Check ignore patterns
206+
if let Some(ref ig) = parent_ignores {
207+
for p in &paths_to_check {
208+
if ig.is_match(p) {
209+
return Some(true); // Should be ignored
210+
}
211+
}
212+
}
213+
214+
None
215+
}))
216+
} else {
217+
None
218+
};
219+
220+
let parent_filter = IgnoreFilter::from_patterns(parent_matcher);
221+
apply(p, parent_filter)
222+
} else {
223+
false
224+
};
225+
226+
if cwd_changed || parent_changed {
128227
act!(mgr:hover, cx)?;
129228
act!(mgr:update_paged, cx)?;
130229
}
@@ -141,23 +240,65 @@ impl Actor for ExcludeAdd {
141240
let mut hovered_all_patterns = opt.patterns;
142241
hovered_all_patterns.extend(hovered_config_patterns);
143242

243+
// Compile glob patterns for hovered (same as CWD)
244+
let mut hovered_ignores_builder = GlobSetBuilder::new();
245+
let mut hovered_whitelists_builder = GlobSetBuilder::new();
246+
247+
for pattern in &hovered_all_patterns {
248+
if let Some(negated) = pattern.strip_prefix('!') {
249+
if let Ok(glob) = Glob::new(negated) {
250+
hovered_whitelists_builder.add(glob);
251+
}
252+
} else if let Ok(glob) = Glob::new(pattern) {
253+
hovered_ignores_builder.add(glob);
254+
}
255+
}
256+
257+
let hovered_ignores = hovered_ignores_builder.build().ok();
258+
let hovered_whitelists = hovered_whitelists_builder.build().ok();
259+
144260
let hovered_matcher: Option<Arc<dyn Fn(&std::path::Path) -> Option<bool> + Send + Sync>> =
145-
if !hovered_all_patterns.is_empty() {
261+
if hovered_ignores.is_some() || hovered_whitelists.is_some() {
146262
let context = hovered_str.clone();
147-
let patterns = hovered_all_patterns.clone();
148263
Some(Arc::new(move |path: &std::path::Path| {
264+
// First check config patterns (for user overrides/negation)
149265
if let Some(result) = YAZI.files.matches_path(path, &context) {
150266
return Some(result);
151267
}
152-
for pattern in &patterns {
153-
if let Some(negated) = pattern.strip_prefix('!') {
154-
if path.to_str().map_or(false, |p| p.contains(negated)) {
155-
return Some(false);
268+
269+
// For absolute paths, try both the full path and relative components
270+
let paths_to_check: Vec<&std::path::Path> = if path.is_absolute() {
271+
let mut paths = vec![path];
272+
if let Some(components) = path.to_str() {
273+
for (i, _) in components.match_indices('/').skip(1) {
274+
if let Some(subpath) = components.get(i + 1..) {
275+
paths.push(std::path::Path::new(subpath));
276+
}
277+
}
278+
}
279+
paths
280+
} else {
281+
vec![path]
282+
};
283+
284+
// Check whitelist first (negation takes precedence)
285+
if let Some(ref wl) = hovered_whitelists {
286+
for p in &paths_to_check {
287+
if wl.is_match(p) {
288+
return Some(false); // Explicitly NOT ignored
289+
}
290+
}
291+
}
292+
293+
// Check ignore patterns
294+
if let Some(ref ig) = hovered_ignores {
295+
for p in &paths_to_check {
296+
if ig.is_match(p) {
297+
return Some(true); // Should be ignored
156298
}
157-
} else if path.to_str().map_or(false, |p| p.contains(pattern)) {
158-
return Some(true);
159299
}
160300
}
301+
161302
None
162303
}))
163304
} else {

yazi-actor/src/mgr/ignore.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,32 @@ impl Actor for Ignore {
5353
}
5454
};
5555

56-
// Apply to CWD
57-
if apply(cx.current_mut(), ignore_filter.clone()) {
56+
// Apply to CWD and parent
57+
let cwd_changed = apply(cx.current_mut(), ignore_filter.clone());
58+
59+
let parent_changed = if let Some(p) = cx.parent_mut() {
60+
let parent_str = if p.url.is_search() {
61+
"search://**".to_string()
62+
} else {
63+
p.url.as_path().map(|p| p.display().to_string()).unwrap_or_default()
64+
};
65+
66+
let parent_excludes = YAZI.files.excludes_for_context(&parent_str);
67+
let parent_filter = if !parent_excludes.is_empty() {
68+
let context = parent_str.clone();
69+
let matcher: Option<Arc<dyn Fn(&std::path::Path) -> Option<bool> + Send + Sync>> =
70+
Some(Arc::new(move |path: &std::path::Path| YAZI.files.matches_path(path, &context)));
71+
IgnoreFilter::from_patterns(matcher)
72+
} else {
73+
IgnoreFilter::from_patterns(None)
74+
};
75+
76+
apply(p, parent_filter)
77+
} else {
78+
false
79+
};
80+
81+
if cwd_changed || parent_changed {
5882
act!(mgr:hover, cx)?;
5983
act!(mgr:update_paged, cx)?;
6084
}

yazi-actor/src/mgr/refresh.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,6 @@ impl Actor for Refresh {
2424
execute!(TTY.writer(), SetTitle(s)).ok();
2525
}
2626

27-
// Apply ignore filter before triggering file loads
28-
act!(mgr:ignore, cx)?;
29-
3027
if let Some(p) = cx.parent() {
3128
Self::trigger_dirs(&[cx.current(), p]);
3229
} else {
@@ -53,7 +50,6 @@ impl Refresh {
5350
fn trigger_dirs(folders: &[&Folder]) {
5451
async fn go(cwd: UrlBuf, cha: Cha) {
5552
let Some(cha) = Files::assert_stale(&cwd, cha).await else { return };
56-
5753
match Files::from_dir_bulk(&cwd).await {
5854
Ok(files) => FilesOp::Full(cwd, files, cha).emit(),
5955
Err(e) => FilesOp::issue_error(&cwd, e).await,
@@ -65,7 +61,6 @@ impl Refresh {
6561
.filter(|&f| f.url.is_internal())
6662
.map(|&f| go(f.url.to_owned(), f.cha))
6763
.collect();
68-
6964
if !futs.is_empty() {
7065
tokio::spawn(futures::future::join_all(futs));
7166
}

yazi-config/preset/yazi-default.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ ueberzug_scale = 1
2929
ueberzug_offset = [ 0, 0, 0, 0 ]
3030

3131

32-
[files]
32+
# [files]
3333
# Context-specific exclude patterns
3434
# Patterns are compiled into glob matchers for efficient matching
3535
# Patterns starting with '!' negate (whitelist) previous matches

0 commit comments

Comments
 (0)