From 7dcdab9bedcea21e08de69f2409c913086d73000 Mon Sep 17 00:00:00 2001 From: Pierce Boggan Date: Wed, 11 Feb 2026 22:35:19 -0700 Subject: [PATCH] Alias # to @ in chat context references --- .../input/editor/chatInputCompletions.ts | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/widget/input/editor/chatInputCompletions.ts b/src/vs/workbench/contrib/chat/browser/widget/input/editor/chatInputCompletions.ts index fdadd667357..3051a920fe3 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/input/editor/chatInputCompletions.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/input/editor/chatInputCompletions.ts @@ -790,7 +790,7 @@ interface IVariableCompletionsDetails { class BuiltinDynamicCompletions extends Disposable { private static readonly addReferenceCommand = '_addReferenceCmd'; - private static readonly VariableNameDef = new RegExp(`${chatVariableLeader}[\\w:-]*`, 'g'); // MUST be using `g`-flag + private static readonly VariableNameDef = new RegExp(`[${chatVariableLeader}${chatAgentLeader}][\\w:-]*`, 'g'); // MUST be using `g`-flag constructor( @@ -810,7 +810,7 @@ class BuiltinDynamicCompletions extends Disposable { super(); // File/Folder completions in one go and m - const fileWordPattern = new RegExp(`${chatVariableLeader}[^\\s]*`, 'g'); + const fileWordPattern = new RegExp(`[${chatVariableLeader}${chatAgentLeader}][^\\s]*`, 'g'); this.registerVariableCompletions('fileAndFolder', async ({ widget, range }, token) => { if (!widget.supportsFileReferences) { return; @@ -851,6 +851,7 @@ class BuiltinDynamicCompletions extends Disposable { return; } + const typedLeader = range.varWord?.word?.charAt(0) === chatAgentLeader ? chatAgentLeader : chatVariableLeader; const basename = this.labelService.getUriBasenameLabel(currentResource); const text = `${chatVariableLeader}file:${basename}:${currentSelection.startLineNumber}-${currentSelection.endLineNumber}`; const fullRangeText = `:${currentSelection.startLineNumber}:${currentSelection.startColumn}-${currentSelection.endLineNumber}:${currentSelection.endColumn}`; @@ -859,7 +860,7 @@ class BuiltinDynamicCompletions extends Disposable { const result: CompletionList = { suggestions: [] }; result.suggestions.push({ label: { label: `${chatVariableLeader}selection`, description }, - filterText: `${chatVariableLeader}selection`, + filterText: `${typedLeader}selection`, insertText: range.varWord?.endColumn === range.replace.endColumn ? `${text} ` : text, range, kind: CompletionItemKind.Text, @@ -883,7 +884,7 @@ class BuiltinDynamicCompletions extends Disposable { } const result: CompletionList = { suggestions: [] }; - const range2 = computeCompletionRanges(model, position, new RegExp(`${chatVariableLeader}[^\\s]*`, 'g'), true); + const range2 = computeCompletionRanges(model, position, new RegExp(`[${chatVariableLeader}${chatAgentLeader}][^\\s]*`, 'g'), true); if (range2) { this.addSymbolEntries(widget, result, range2, token); } @@ -926,7 +927,7 @@ class BuiltinDynamicCompletions extends Disposable { private registerVariableCompletions(debugName: string, provider: (details: IVariableCompletionsDetails, token: CancellationToken) => ProviderResult, wordPattern: RegExp = BuiltinDynamicCompletions.VariableNameDef) { this._register(this.languageFeaturesService.completionProvider.register({ scheme: Schemas.vscodeChatInput, hasAccessToAllModels: true }, { _debugDisplayName: `chatVarCompletions-${debugName}`, - triggerCharacters: [chatVariableLeader], + triggerCharacters: [chatVariableLeader, chatAgentLeader], provideCompletionItems: async (model: ITextModel, position: Position, context: CompletionContext, token: CancellationToken) => { const widget = this.chatWidgetService.getWidgetByInputUri(model.uri); if (!widget) { @@ -947,6 +948,8 @@ class BuiltinDynamicCompletions extends Disposable { private async addFileAndFolderEntries(widget: IChatWidget, result: CompletionList, info: { insert: Range; replace: Range; varWord: IWordAtPosition | null }, token: CancellationToken) { + const typedLeader = info.varWord?.word?.charAt(0) === chatAgentLeader ? chatAgentLeader : chatVariableLeader; + const makeCompletionItem = (resource: URI, kind: FileKind, description?: string, boostPriority?: boolean): CompletionItem => { const basename = this.labelService.getUriBasenameLabel(resource); const text = `${chatVariableLeader}file:${basename}`; @@ -959,7 +962,7 @@ class BuiltinDynamicCompletions extends Disposable { return { label: { label: basename, description: labelDescription }, - filterText: `${chatVariableLeader}${basename}`, + filterText: `${typedLeader}${basename}`, insertText: info.varWord?.endColumn === info.replace.endColumn ? `${text} ` : text, range: info, kind: kind === FileKind.FILE ? CompletionItemKind.File : CompletionItemKind.Folder, @@ -977,8 +980,8 @@ class BuiltinDynamicCompletions extends Disposable { }; let pattern: string | undefined; - if (info.varWord?.word && info.varWord.word.startsWith(chatVariableLeader)) { - pattern = info.varWord.word.toLowerCase().slice(1); // remove leading # + if (info.varWord?.word && (info.varWord.word.startsWith(chatVariableLeader) || info.varWord.word.startsWith(chatAgentLeader))) { + pattern = info.varWord.word.toLowerCase().slice(1); // remove leading # or @ } const seen = new ResourceSet(); @@ -1041,6 +1044,8 @@ class BuiltinDynamicCompletions extends Disposable { const timeoutMs = 100; const stopwatch = new StopWatch(); + const typedLeader = info.varWord?.word?.charAt(0) === chatAgentLeader ? chatAgentLeader : chatVariableLeader; + const makeSymbolCompletionItem = (symbolItem: { name: string; location: Location; kind: SymbolKind }, pattern: string): CompletionItem => { const text = `${chatVariableLeader}sym:${symbolItem.name}`; const resource = symbolItem.location.uri; @@ -1049,7 +1054,7 @@ class BuiltinDynamicCompletions extends Disposable { return { label: { label: symbolItem.name, description: uriLabel }, - filterText: `${chatVariableLeader}${symbolItem.name}`, + filterText: `${typedLeader}${symbolItem.name}`, insertText: info.varWord?.endColumn === info.replace.endColumn ? `${text} ` : text, range: info, kind: SymbolKinds.toCompletionKind(symbolItem.kind), @@ -1067,8 +1072,8 @@ class BuiltinDynamicCompletions extends Disposable { }; let pattern: string | undefined; - if (info.varWord?.word && info.varWord.word.startsWith(chatVariableLeader)) { - pattern = info.varWord.word.toLowerCase().slice(1); // remove leading # + if (info.varWord?.word && (info.varWord.word.startsWith(chatVariableLeader) || info.varWord.word.startsWith(chatAgentLeader))) { + pattern = info.varWord.word.toLowerCase().slice(1); // remove leading # or @ } const symbolsToAdd: { symbol: DocumentSymbol; uri: URI }[] = []; @@ -1165,7 +1170,7 @@ function isEmptyUpToCompletionWord(model: ITextModel, rangeResult: IChatCompleti class ToolCompletions extends Disposable { - private static readonly VariableNameDef = new RegExp(`(?<=^|\\s)${chatVariableLeader}\\w*`, 'g'); // MUST be using `g`-flag + private static readonly VariableNameDef = new RegExp(`(?<=^|\\s)[${chatVariableLeader}${chatAgentLeader}]\\w*`, 'g'); // MUST be using `g`-flag constructor( @ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService, @@ -1176,7 +1181,7 @@ class ToolCompletions extends Disposable { this._register(this.languageFeaturesService.completionProvider.register({ scheme: Schemas.vscodeChatInput, hasAccessToAllModels: true }, { _debugDisplayName: 'chatVariables', - triggerCharacters: [chatVariableLeader], + triggerCharacters: [chatVariableLeader, chatAgentLeader], provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, _token: CancellationToken) => { const widget = this.chatWidgetService.getWidgetByInputUri(model.uri); if (!widget) { @@ -1206,6 +1211,7 @@ class ToolCompletions extends Disposable { } } + const typedLeader = range.varWord?.word?.charAt(0) === chatAgentLeader ? chatAgentLeader : chatVariableLeader; const suggestions: CompletionItem[] = []; @@ -1241,6 +1247,7 @@ class ToolCompletions extends Disposable { range, detail, documentation, + filterText: `${typedLeader}${name}`, insertText: withLeader + ' ', kind: CompletionItemKind.Tool, });