Alias # to @ in chat context references

This commit is contained in:
Pierce Boggan
2026-02-11 22:35:19 -07:00
parent 438d64aded
commit 7dcdab9bed

View File

@@ -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<CompletionList>, 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,
});