Skip to content

Commit

Permalink
Add builtin #selection variable
Browse files Browse the repository at this point in the history
Acts as a helper to add the current selection as a reference
  • Loading branch information
roblourens committed Feb 4, 2025
1 parent 30dbfbb commit cef6512
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ class OpenChatGlobalAction extends Action2 {
}
}
if (opts?.variableIds && opts.variableIds.length > 0) {
const actualVariables = chatVariablesService.getVariables(ChatAgentLocation.Panel);
const actualVariables = chatVariablesService.getVariables();
for (const actualVariable of actualVariables) {
if (opts.variableIds.includes(actualVariable.id)) {
chatWidget.attachmentModel.addContext({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -677,7 +677,7 @@ export class AttachContextAction extends Action2 {
const slowSupported = usedAgent ? usedAgent.agent.metadata.supportsSlowVariables : true;
const quickPickItems: IAttachmentQuickPickItem[] = [];
if (!context || !context.showFilesOnly) {
for (const variable of chatVariablesService.getVariables(widget.location)) {
for (const variable of chatVariablesService.getVariables()) {
if (variable.fullName && (!variable.isSlow || slowSupported)) {
quickPickItems.push({
kind: 'variable',
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/contrib/chat/browser/chat.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable {
}

const variables = [
...chatVariablesService.getVariables(ChatAgentLocation.Panel),
...chatVariablesService.getVariables(),
{ name: 'file', description: nls.localize('file', "Choose a file in the workspace") }
];
const variableText = variables
Expand Down
7 changes: 2 additions & 5 deletions src/vs/workbench/contrib/chat/browser/chatVariables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,9 @@ export class ChatVariablesService implements IChatVariablesService {
return this._resolver.get(name.toLowerCase())?.data;
}

getVariables(location: ChatAgentLocation): Iterable<Readonly<IChatVariableData>> {
getVariables(): Iterable<Readonly<IChatVariableData>> {
const all = Iterable.map(this._resolver.values(), data => data.data);
return Iterable.filter(all, data => {
// TODO@jrieken this is improper and should be know from the variable registeration data
return location !== ChatAgentLocation.Editor || !new Set(['selection', 'editor']).has(data.name);
});
return all;
}

getDynamicVariables(sessionId: string): ReadonlyArray<IDynamicVariable> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Disposable } from '../../../../../base/common/lifecycle.js';
import { ResourceSet } from '../../../../../base/common/map.js';
import { URI } from '../../../../../base/common/uri.js';
import { generateUuid } from '../../../../../base/common/uuid.js';
import { isCodeEditor } from '../../../../../editor/browser/editorBrowser.js';
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';
Expand All @@ -27,6 +28,7 @@ import { ILabelService } from '../../../../../platform/label/common/label.js';
import { Registry } from '../../../../../platform/registry/common/platform.js';
import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from '../../../../common/contributions.js';
import { IEditorService } from '../../../../services/editor/common/editorService.js';
import { IHistoryService } from '../../../../services/history/common/history.js';
import { LifecyclePhase } from '../../../../services/lifecycle/common/lifecycle.js';
import { QueryBuilder } from '../../../../services/search/common/queryBuilder.js';
Expand Down Expand Up @@ -452,6 +454,7 @@ class BuiltinDynamicCompletions extends Disposable {
@IChatEditingService private readonly _chatEditingService: IChatEditingService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IOutlineModelService private readonly outlineService: IOutlineModelService,
@IEditorService private readonly editorService: IEditorService,
) {
super();

Expand Down Expand Up @@ -490,6 +493,62 @@ class BuiltinDynamicCompletions extends Disposable {
}
}));

// Selection completion
this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, {
_debugDisplayName: 'chatDynamicSelectionCompletions',
triggerCharacters: [chatVariableLeader],
provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, token: CancellationToken) => {
const widget = this.chatWidgetService.getWidgetByInputUri(model.uri);
if (!widget || !widget.supportsFileReferences) {
return null;
}

if (widget.location === ChatAgentLocation.Editor) {
return;
}

const result: CompletionList = { suggestions: [] };
const range = computeCompletionRanges(model, position, BuiltinDynamicCompletions.VariableNameDef, true);
if (range) {
const active = this.editorService.activeTextEditorControl;
if (!isCodeEditor(active)) {
return result;
}

const currentResource = active.getModel()?.uri;
const currentSelection = active.getSelection();
if (!currentSelection || !currentResource || currentSelection.isEmpty()) {
return result;
}

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}`;
const description = this.labelService.getUriLabel(currentResource, { relative: true }) + fullRangeText;

result.suggestions.push({
label: { label: `${chatVariableLeader}selection`, description },
filterText: `${chatVariableLeader}selection`,
insertText: range.varWord?.endColumn === range.replace.endColumn ? `${text} ` : text,
range,
kind: CompletionItemKind.Text,
sortText: 'z',
command: {
id: BuiltinDynamicCompletions.addReferenceCommand, title: '', arguments: [new ReferenceArgument(widget, {
id: 'vscode.file',
prefix: 'file',
isFile: true,
range: { startLineNumber: range.replace.startLineNumber, startColumn: range.replace.startColumn, endLineNumber: range.replace.endLineNumber, endColumn: range.replace.startColumn + text.length },
data: { range: currentSelection, uri: currentResource } satisfies Location
})]
}
});
}

return result;
}
}));

// Symbol completions
this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, {
_debugDisplayName: 'chatDynamicSymbolCompletions',
Expand Down Expand Up @@ -797,7 +856,7 @@ class VariableCompletions extends Disposable {

const usedVariables = widget.parsedInput.parts.filter((p): p is ChatRequestVariablePart => p instanceof ChatRequestVariablePart);
const usedVariableNames = new Set(usedVariables.map(v => v.variableName));
const variableItems = Array.from(this.chatVariablesService.getVariables(widget.location))
const variableItems = Array.from(this.chatVariablesService.getVariables())
// This doesn't look at dynamic variables like `file`, where multiple makes sense.
.filter(v => !usedVariableNames.has(v.name))
.filter(v => !v.isSlow || slowSupported)
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/contrib/chat/common/chatVariables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export interface IChatVariablesService {
registerVariable(data: IChatVariableData, resolver: IChatVariableResolver): IDisposable;
hasVariable(name: string): boolean;
getVariable(name: string): IChatVariableData | undefined;
getVariables(location: ChatAgentLocation): Iterable<Readonly<IChatVariableData>>;
getVariables(): Iterable<Readonly<IChatVariableData>>;
getDynamicVariables(sessionId: string): ReadonlyArray<IDynamicVariable>; // should be its own service?
attachContext(name: string, value: string | URI | Location | unknown, location: ChatAgentLocation): void;

Expand Down

0 comments on commit cef6512

Please sign in to comment.