fix(tui): prevent freeze when using @ file finder (#1832)
The @ file autocomplete triggered a synchronous native fuzzyFind call that walks the entire directory tree. On large repos this blocked the Node.js event loop and froze the TUI. Three changes fix this: 1. Skip the fuzzy search when the query is empty (bare "@" with nothing typed yet) — there is no point walking the full tree with no query. 2. Debounce the initial "@" keystroke instead of firing the search synchronously, so rapid typing cancels pending walks and the search only runs once the user pauses. 3. Deduplicate consecutive lookups using lastAutocompleteLookupPrefix (previously declared but unused) to avoid redundant synchronous searches when the prefix hasn't changed. Fixes #1824 Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2f0c078cc2
commit
f29d54b7e0
3 changed files with 41 additions and 2 deletions
|
|
@ -119,6 +119,21 @@ describe("CombinedAutocompleteProvider — @ file prefix extraction", () => {
|
|||
const result = provider.getSuggestions(["check @nonexistent_xyz"], 0, 22);
|
||||
assert.ok(result === null || result.items.length >= 0);
|
||||
});
|
||||
|
||||
it("returns null for bare @ with no query to avoid full tree walk (#1824)", () => {
|
||||
const provider = makeProvider([], process.cwd());
|
||||
// A bare "@" produces an empty rawPrefix after stripping the "@".
|
||||
// This must return null to avoid a synchronous full filesystem walk
|
||||
// via the native fuzzyFind addon, which freezes the TUI on large repos.
|
||||
const result = provider.getSuggestions(["@"], 0, 1);
|
||||
assert.equal(result, null, "bare @ should not trigger fuzzy file search");
|
||||
});
|
||||
|
||||
it("returns null for @ after space with no query (#1824)", () => {
|
||||
const provider = makeProvider([], process.cwd());
|
||||
const result = provider.getSuggestions(["look at @"], 0, 9);
|
||||
assert.equal(result, null, "@ after space with no query should not trigger fuzzy file search");
|
||||
});
|
||||
});
|
||||
|
||||
describe("CombinedAutocompleteProvider — applyCompletion", () => {
|
||||
|
|
|
|||
|
|
@ -573,9 +573,18 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
|||
private getFuzzyFileSuggestions(query: string, options: { isQuotedPrefix: boolean }): AutocompleteItem[] {
|
||||
try {
|
||||
const scopedQuery = this.resolveScopedFuzzyQuery(query);
|
||||
const searchPath = scopedQuery?.baseDir ?? this.basePath;
|
||||
const searchQuery = scopedQuery?.query ?? query;
|
||||
|
||||
// Skip the expensive filesystem walk when the query is empty.
|
||||
// An empty query (bare "@" with nothing typed yet) would walk the
|
||||
// entire directory tree via the native fuzzyFind call, blocking
|
||||
// the event loop and freezing the TUI on large repos.
|
||||
if (searchQuery.length === 0 && !scopedQuery) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const searchPath = scopedQuery?.baseDir ?? this.basePath;
|
||||
|
||||
const result = fuzzyFind({
|
||||
query: searchQuery,
|
||||
path: searchPath,
|
||||
|
|
|
|||
|
|
@ -967,13 +967,19 @@ export class Editor implements Component, Focusable {
|
|||
this.tryTriggerAutocomplete();
|
||||
}
|
||||
// Auto-trigger for "@" file reference (fuzzy search)
|
||||
// Debounced: the bare "@" triggers a fuzzyFind call that does a
|
||||
// synchronous filesystem walk via the native addon. Firing it
|
||||
// immediately on the keystroke blocks the event loop and freezes
|
||||
// the TUI on large repos. Debouncing lets subsequent keystrokes
|
||||
// cancel the pending search so the walk only runs once the user
|
||||
// pauses typing.
|
||||
else if (char === "@") {
|
||||
const currentLine = this.state.lines[this.state.cursorLine] || "";
|
||||
const textBeforeCursor = currentLine.slice(0, this.state.cursorCol);
|
||||
// Only trigger if @ is after whitespace or at start of line
|
||||
const charBeforeAt = textBeforeCursor[textBeforeCursor.length - 2];
|
||||
if (textBeforeCursor.length === 1 || charBeforeAt === " " || charBeforeAt === "\t") {
|
||||
this.tryTriggerAutocomplete();
|
||||
this.debouncedTriggerAutocomplete();
|
||||
}
|
||||
}
|
||||
// Also auto-trigger when typing letters in a slash command context
|
||||
|
|
@ -2116,6 +2122,15 @@ https://github.com/EsotericSoftware/spine-runtimes/actions/runs/19536643416/job/
|
|||
private applyAutocompleteSuggestions(): void {
|
||||
if (!this.autocompleteProvider) return;
|
||||
|
||||
// Deduplicate: skip the (potentially expensive synchronous) lookup
|
||||
// when the prefix hasn't changed since the last call.
|
||||
const currentLine = this.state.lines[this.state.cursorLine] || "";
|
||||
const textBeforeCursor = currentLine.slice(0, this.state.cursorCol);
|
||||
if (this.lastAutocompleteLookupPrefix !== null && this.lastAutocompleteLookupPrefix === textBeforeCursor) {
|
||||
return;
|
||||
}
|
||||
this.lastAutocompleteLookupPrefix = textBeforeCursor;
|
||||
|
||||
const suggestions = this.autocompleteProvider.getSuggestions(
|
||||
this.state.lines,
|
||||
this.state.cursorLine,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue