fix(tui): avoid blocking on bare separator mentions

Skip bare separator and dot tokens during @-mention completion so Windows and WSL2 workspaces do not trigger an eager filesystem walk on the UI thread.
This commit is contained in:
Hunter Bown
2026-05-23 13:05:48 -05:00
committed by GitHub
parent c09f5bf039
commit d9fb21c0a0
+68 -1
View File
@@ -469,7 +469,18 @@ fn add_local_reference_completions(
}
fn should_try_local_reference_completion(needle: &str) -> bool {
!needle.is_empty() && (needle.starts_with('.') || needle.contains('/') || needle.contains('\\'))
if needle.is_empty() {
return false;
}
// A bare separator or dot isn't an actionable path yet. Without this
// guard, a single `@/` keystroke triggers a `LOCAL_REFERENCE_SCAN_LIMIT`
// (4096-path) walk on the UI thread for #1921 — on WSL2 with a
// `/mnt/c/...` workspace each entry crosses Windows-host I/O and the
// composer appears frozen for seconds to minutes.
if matches!(needle, "/" | "\\" | "." | "..") {
return false;
}
needle.starts_with('.') || needle.contains('/') || needle.contains('\\')
}
fn local_reference_paths(root: &Path, limit: usize) -> Vec<PathBuf> {
@@ -1667,4 +1678,60 @@ mod tests {
"snapshot.pack must not resolve via fuzzy index"
);
}
/// Regression for #1921 — typing `@/` (or `@.`) must NOT trigger the
/// `local_reference_paths` walk, which scans up to
/// `LOCAL_REFERENCE_SCAN_LIMIT` paths on the UI thread. On WSL2 with a
/// `/mnt/c/...` workspace this hangs the composer for seconds to minutes.
#[test]
fn should_try_local_reference_completion_skips_bare_separators_and_dots() {
// The trigger gate must reject bare separators/dots.
assert!(!should_try_local_reference_completion("/"));
assert!(!should_try_local_reference_completion("\\"));
assert!(!should_try_local_reference_completion("."));
assert!(!should_try_local_reference_completion(".."));
// Empty string was already rejected; keep that.
assert!(!should_try_local_reference_completion(""));
// Actionable references must still trigger.
assert!(should_try_local_reference_completion("./foo"));
assert!(should_try_local_reference_completion("../bar"));
assert!(should_try_local_reference_completion(".env"));
assert!(should_try_local_reference_completion("path/"));
assert!(should_try_local_reference_completion("path/to/file"));
assert!(should_try_local_reference_completion("/usr"));
}
/// Regression for #1921 — `completions("/", N)` must return without
/// invoking `local_reference_paths`, even on a workspace large enough
/// to expose the original 4096-path walk. We can't assert "doesn't
/// touch the disk", but we can assert the call completes promptly and
/// stays within the requested limit.
#[test]
fn completions_for_bare_slash_does_not_trigger_local_reference_walk() {
let tmp = TempDir::new().unwrap();
let root = tmp.path();
// Lay out enough files that a runaway walk would be visibly slow,
// but the bounded path returns near-instantly. Depth-1 entries are
// enough; we don't need to stress the filesystem.
for i in 0..40 {
std::fs::write(root.join(format!("file_{i}.txt")), "x").unwrap();
}
let ws = Workspace::with_cwd(root.to_path_buf(), None);
let start = std::time::Instant::now();
let entries = ws.completions("/", 64);
let elapsed = start.elapsed();
// Behavioral assertions:
// 1. The call returns within a generous bound. Real freezes on
// WSL2 were tens of seconds; a 2s budget is comfortable for a
// 40-file tmp dir on any CI host.
assert!(
elapsed < std::time::Duration::from_secs(2),
"completions(\"/\") took too long: {elapsed:?} (likely re-introduced #1921)"
);
// 2. Results stay within the requested cap.
assert!(entries.len() <= 64);
}
}