feat(coding-agent,tui): add stacked autocomplete providers closes #2983

This commit is contained in:
Mario Zechner
2026-04-22 15:44:08 +02:00
parent 4e919868f6
commit 8234ebf9ee
16 changed files with 465 additions and 33 deletions

View File

@@ -1,4 +1,4 @@
import type { AutocompleteProvider, AutocompleteSuggestions, CombinedAutocompleteProvider } from "../autocomplete.js";
import type { AutocompleteProvider, AutocompleteSuggestions } from "../autocomplete.js";
import { getKeybindings } from "../keybindings.js";
import { decodePrintableKey, matchesKey } from "../keys.js";
import { KillRing } from "../kill-ring.js";
@@ -1054,17 +1054,16 @@ export class Editor implements Component, Focusable {
if (char === "/" && this.isAtStartOfMessage()) {
this.tryTriggerAutocomplete();
}
// Auto-trigger for "@" file reference (fuzzy search)
else if (char === "@") {
// Auto-trigger for symbol-based completion like @ or # at token boundaries
else if (char === "@" || 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") {
const charBeforeSymbol = textBeforeCursor[textBeforeCursor.length - 2];
if (textBeforeCursor.length === 1 || charBeforeSymbol === " " || charBeforeSymbol === "\t") {
this.tryTriggerAutocomplete();
}
}
// Also auto-trigger when typing letters in a slash command context
// Also auto-trigger when typing letters in a slash command or symbol completion context
else if (/[a-zA-Z0-9.\-_]/.test(char)) {
const currentLine = this.state.lines[this.state.cursorLine] || "";
const textBeforeCursor = currentLine.slice(0, this.state.cursorCol);
@@ -1072,8 +1071,8 @@ export class Editor implements Component, Focusable {
if (this.isInSlashCommandContext(textBeforeCursor)) {
this.tryTriggerAutocomplete();
}
// Check if we're in an @ file reference context
else if (textBeforeCursor.match(/(?:^|[\s])@[^\s]*$/)) {
// Check if we're in a symbol-based completion context like @ or #
else if (textBeforeCursor.match(/(?:^|[\s])[@#][^\s]*$/)) {
this.tryTriggerAutocomplete();
}
}
@@ -1240,8 +1239,8 @@ export class Editor implements Component, Focusable {
if (this.isInSlashCommandContext(textBeforeCursor)) {
this.tryTriggerAutocomplete();
}
// @ file reference context
else if (textBeforeCursor.match(/(?:^|[\s])@[^\s]*$/)) {
// Symbol-based completion context like @ or #
else if (textBeforeCursor.match(/(?:^|[\s])[@#][^\s]*$/)) {
this.tryTriggerAutocomplete();
}
}
@@ -1604,8 +1603,8 @@ export class Editor implements Component, Focusable {
if (this.isInSlashCommandContext(textBeforeCursor)) {
this.tryTriggerAutocomplete();
}
// @ file reference context
else if (textBeforeCursor.match(/(?:^|[\s])@[^\s]*$/)) {
// Symbol-based completion context like @ or #
else if (textBeforeCursor.match(/(?:^|[\s])[@#][^\s]*$/)) {
this.tryTriggerAutocomplete();
}
}
@@ -2108,10 +2107,13 @@ export class Editor implements Component, Focusable {
if (!this.autocompleteProvider) return;
if (options.force) {
const provider = this.autocompleteProvider as CombinedAutocompleteProvider;
const shouldTrigger =
!provider.shouldTriggerFileCompletion ||
provider.shouldTriggerFileCompletion(this.state.lines, this.state.cursorLine, this.state.cursorCol);
!this.autocompleteProvider.shouldTriggerFileCompletion ||
this.autocompleteProvider.shouldTriggerFileCompletion(
this.state.lines,
this.state.cursorLine,
this.state.cursorCol,
);
if (!shouldTrigger) {
return;
}
@@ -2162,8 +2164,8 @@ export class Editor implements Component, Focusable {
const currentLine = this.state.lines[this.state.cursorLine] || "";
const textBeforeCursor = currentLine.slice(0, this.state.cursorCol);
const isAttachmentContext = /(?:^|[ \t])@(?:"[^"]*|[^\s]*)$/.test(textBeforeCursor);
return isAttachmentContext ? ATTACHMENT_AUTOCOMPLETE_DEBOUNCE_MS : 0;
const isSymbolAutocompleteContext = /(?:^|[ \t])(?:@(?:"[^"]*|[^\s]*)|#[^\s]*)$/.test(textBeforeCursor);
return isSymbolAutocompleteContext ? ATTACHMENT_AUTOCOMPLETE_DEBOUNCE_MS : 0;
}
private async runAutocompleteRequest(