Change:
Today which have which triggers quick pick to select a file. This issue is about also allowing to type a file name and have dynamic suggestions, like inserts

Discussion:
import { Position } from '../../../../../editor/common/core/position.js'; import { Range } from '../../../../../editor/common/core/range.js'; import { IWordAtPosition, getWordAtText } from '../../../../../editor/common/core/wordHelper.js'; <del> import { CompletionContext, CompletionItem, CompletionItemKind } from '../../../../../editor/common/languages.js'; </del> <ins> import { CompletionContext, CompletionItem, CompletionItemKind, CompletionList } from '../../../../../editor/common/languages.js'; </ins> import { ITextModel } from '../../../../../editor/common/model.js'; import { ILanguageFeaturesService } from '../../../../../editor/common/services/languageFeatures.js'; import { localize } from '../../../../../nls.js'; import { Action2, registerAction2 } from '../../../../../platform/actions/common/actions.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; <del> import { ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js'; </del> <ins> import { IInstantiationService, ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js'; </ins> import { Registry } from '../../../../../platform/registry/common/platform.js'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from '../../../../common/contributions.js'; import { SubmitAction } from '../actions/chatExecuteActions.js'; import { IChatWidget, IChatWidgetService } from '../chat.js'; import { ChatInputPart } from '../chatInputPart.js'; <del> import { SelectAndInsertFileAction } from './chatDynamicVariables.js'; </del> <ins> import { ChatDynamicVariableModel, SelectAndInsertFileAction } from './chatDynamicVariables.js'; </ins> import { ChatAgentLocation, getFullyQualifiedId, IChatAgentData, IChatAgentNameService, IChatAgentService } from '../../common/chatAgents.js'; import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestTextPart, ChatRequestToolPart, ChatRequestVariablePart, chatAgentLeader, chatSubcommandLeader, chatVariableLeader } from '../../common/chatParserTypes.js'; import { IChatSlashCommandService } from '../../common/chatSlashCommands.js'; <del> import { IChatVariablesService } from '../../common/chatVariables.js'; </del> <ins> import { IChatVariablesService, IDynamicVariable } from '../../common/chatVariables.js'; </ins> import { ILanguageModelToolsService } from '../../common/languageModelToolsService.js'; import { LifecyclePhase } from '../../../../services/lifecycle/common/lifecycle.js'; <ins> import { IHistoryService } from '../../../../services/history/common/history.js'; import { ILabelService } from '../../../../../platform/label/common/label.js'; import { ResourceSet } from '../../../../../base/common/map.js'; import { CommandsRegistry } from '../../../../../platform/commands/common/commands.js'; import { isPatternInWord } from '../../../../../base/common/filters.js'; import { ISearchService } from '../../../../services/search/common/search.js'; import { QueryBuilder } from '../../../../services/search/common/queryBuilder.js'; import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js'; import { URI } from '../../../../../base/common/uri.js'; </ins> class SlashCommandCompletions extends Disposable { constructor(

} registerAction2(AssignSelectedAgentAction); <ins> class ReferenceArgument { constructor( readonly widget: IChatWidget, readonly variable: IDynamicVariable ) { } } </ins> class BuiltinDynamicCompletions extends Disposable { <ins> private static readonly addReferenceCommand = '_addReferenceCmd'; </ins> private static readonly VariableNameDef = new RegExp(`${chatVariableLeader}w*`, 'g'); // MUST be using `g`-flag <ins> private readonly queryBuilder: QueryBuilder; </ins> constructor( <ins> @IHistoryService private readonly historyService: IHistoryService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @ISearchService private readonly searchService: ISearchService, @ILabelService private readonly labelService: ILabelService, </ins> @ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService, @IChatWidgetService private readonly chatWidgetService: IChatWidgetService, <ins> @IInstantiationService private readonly instantiationService: IInstantiationService, </ins> ) { super(); this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, { _debugDisplayName: 'chatDynamicCompletions', triggerCharacters: [chatVariableLeader], <del> provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, _token: CancellationToken) => { </del> <ins> provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, token: CancellationToken) => { </ins> const widget = this.chatWidgetService.getWidgetByInputUri(model.uri); if (!widget || !widget.supportsFileReferences) { return null;

return null; } <ins> const result: CompletionList = { suggestions: [] }; </ins> const afterRange = new Range(position.lineNumber, range.replace.startColumn, position.lineNumber, range.replace.startColumn + '#file:'.length); <del> return { suggestions: [ { label: `${chatVariableLeader}file`, insertText: `${chatVariableLeader}file:`, detail: localize('pickFileLabel', "Pick a file"), range, kind: CompletionItemKind.Text, command: { id: SelectAndInsertFileAction.ID, title: SelectAndInsertFileAction.ID, arguments: [{ widget, range: afterRange }] }, sortText: 'z' } satisfies CompletionItem ] }; </del> <ins> result.suggestions.push({ label: `${chatVariableLeader}file`, insertText: `${chatVariableLeader}file:`, detail: localize('pickFileLabel', "Pick a file"), range, kind: CompletionItemKind.Text, command: { id: SelectAndInsertFileAction.ID, title: SelectAndInsertFileAction.ID, arguments: [{ widget, range: afterRange }] }, sortText: 'z' }); await this.addFileEntries(widget, result, range, token); return result; </ins> } })); <ins> this._register(CommandsRegistry.registerCommand(BuiltinDynamicCompletions.addReferenceCommand, (_services, arg) => this.cmdAddReference(arg))); this.queryBuilder = this.instantiationService.createInstance(QueryBuilder); } private async addFileEntries(widget: IChatWidget, result: CompletionList, info: { insert: Range; replace: Range; varWord: IWordAtPosition | null }, token: CancellationToken) { const makeFileCompletionItem = (resource: URI): CompletionItem => { const basename = this.labelService.getUriBasenameLabel(resource); const insertText = `${chatVariableLeader}file:${basename} `; return { label: { label: `${chatVariableLeader}file:${basename}`, description: this.labelService.getUriLabel(resource) }, insertText, range: info, kind: CompletionItemKind.File, sortText: 'zz', command: { id: BuiltinDynamicCompletions.addReferenceCommand, title: '', arguments: [new ReferenceArgument(widget, { id: 'vscode.file', range: { startLineNumber: info.replace.startLineNumber, startColumn: info.replace.startColumn, endLineNumber: info.replace.endLineNumber, endColumn: info.replace.startColumn + insertText.length }, data: resource })] } }; }; let pattern: string | undefined; if (info.varWord?.word && info.varWord.word.startsWith(chatVariableLeader)) { pattern = info.varWord.word.toLowerCase().slice(1); // remove leading # } const seen = new ResourceSet(); const len = result.suggestions.length; // HISTORY // always take the last N items for (const item of this.historyService.getHistory()) { if (!item.resource || !this.workspaceContextService.getWorkspaceFolder(item.resource)) { // ignore "forgein" editors continue; } if (pattern) { // use pattern if available const basename = this.labelService.getUriBasenameLabel(item.resource).toLowerCase(); if (!isPatternInWord(pattern, 0, pattern.length, basename, 0, basename.length)) { continue; } } seen.add(item.resource); const newLen = result.suggestions.push(makeFileCompletionItem(item.resource)); if (newLen - len >= 5) { break; } } // SEARCH // use file search when having a pattern if (pattern) { const query = this.queryBuilder.file(this.workspaceContextService.getWorkspace().folders, { filePattern: pattern, maxResults: 25 }); const data = await this.searchService.fileSearch(query, token); for (const match of data.results) { if (seen.has(match.resource)) { // already included via history continue; } result.suggestions.push(makeFileCompletionItem(match.resource)); } if (!data.limitHit) { result.incomplete = true; } } } private cmdAddReference(arg: ReferenceArgument) { // invoked via the completion command arg.widget.getContrib<ChatDynamicVariableModel>(ChatDynamicVariableModel.ID)?.addReference(arg.variable); </ins> } }