From 05ff1fda3cfa97a7e466f36d5b39e9eb8da7e305 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sun, 20 Oct 2024 19:38:14 -0700 Subject: [PATCH] Restore viewport context when no selection (#231804) Was including the full file, after the implicit context widget was added. This sends a Location as the reference value for the current viewport. --- .../attachments/implicitContextAttachment.ts | 2 +- .../browser/contrib/chatImplicitContext.ts | 59 +++++++++++++++---- .../contrib/chat/common/chatModel.ts | 1 + 3 files changed, 49 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/attachments/implicitContextAttachment.ts b/src/vs/workbench/contrib/chat/browser/attachments/implicitContextAttachment.ts index 7ee72a9ce3c..f8fdee41e14 100644 --- a/src/vs/workbench/contrib/chat/browser/attachments/implicitContextAttachment.ts +++ b/src/vs/workbench/contrib/chat/browser/attachments/implicitContextAttachment.ts @@ -41,7 +41,7 @@ export class ImplicitContextAttachmentWidget extends Disposable { this.domNode.classList.toggle('disabled', !this.attachment.enabled); const label = this.resourceLabels.create(this.domNode, { supportIcons: true }); const file = URI.isUri(this.attachment.value) ? this.attachment.value : this.attachment.value!.uri; - const range = URI.isUri(this.attachment.value) ? undefined : this.attachment.value!.range; + const range = URI.isUri(this.attachment.value) || !this.attachment.isSelection ? undefined : this.attachment.value!.range; const fileBasename = basename(file); const fileDirname = dirname(file); diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatImplicitContext.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatImplicitContext.ts index 75fa789596d..9a1810ffa1a 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatImplicitContext.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatImplicitContext.ts @@ -34,6 +34,7 @@ export class ChatImplicitContextContribution extends Disposable implements IWork if (codeEditor) { activeEditorDisposables.add(codeEditor.onDidChangeModel(() => this.updateImplicitContext())); activeEditorDisposables.add(Event.debounce(codeEditor.onDidChangeCursorSelection, () => undefined, 500)(() => this.updateImplicitContext())); + activeEditorDisposables.add(Event.debounce(codeEditor.onDidScrollChange, () => undefined, 500)(() => this.updateImplicitContext())); } this.updateImplicitContext(); @@ -45,21 +46,51 @@ export class ChatImplicitContextContribution extends Disposable implements IWork const codeEditor = this.codeEditorService.getActiveCodeEditor(); const model = codeEditor?.getModel(); const selection = codeEditor?.getSelection(); - const newValue = model ? - (selection && !selection?.isEmpty() ? { uri: model.uri, range: selection } satisfies Location : model.uri) : - undefined; + let newValue: Location | URI | undefined; + let isSelection = false; + if (model) { + if (selection && !selection.isEmpty()) { + newValue = { uri: model.uri, range: selection } satisfies Location; + isSelection = true; + } else { + const visibleRanges = codeEditor?.getVisibleRanges(); + if (visibleRanges && visibleRanges.length > 0) { + // Merge visible ranges. Maybe the reference value could actually be an array of Locations? + // Something like a Location with an array of Ranges? + let range = visibleRanges[0]; + visibleRanges.slice(1).forEach(r => { + range = range.plusRange(r); + }); + newValue = { uri: model.uri, range } satisfies Location; + } else { + newValue = model.uri; + } + } + } const widgets = updateWidget ? [updateWidget] : this.chatWidgetService.getAllWidgets(ChatAgentLocation.Panel); for (const widget of widgets) { if (widget.input.implicitContext) { - widget.input.implicitContext.value = newValue; + widget.input.implicitContext.setValue(newValue, isSelection); } } } } export class ChatImplicitContext extends Disposable implements IChatRequestImplicitVariableEntry { - readonly id = 'vscode.implicit'; + get id() { + if (URI.isUri(this.value)) { + return 'vscode.implicit.file'; + } else if (this.value) { + if (this._isSelection) { + return 'vscode.implicit.selection'; + } else { + return 'vscode.implicit.viewport'; + } + } else { + return 'vscode.implicit'; + } + } get name(): string { if (URI.isUri(this.value)) { @@ -76,14 +107,22 @@ export class ChatImplicitContext extends Disposable implements IChatRequestImpli get modelDescription(): string { if (URI.isUri(this.value)) { return `User's active file`; - } else { + } else if (this._isSelection) { return `User's active selection`; + } else { + return `User's current visible code`; } } + // TODO@roblourens readonly isDynamic = true; readonly isFile = true; + private _isSelection = false; + public get isSelection(): boolean { + return this._isSelection; + } + private _onDidChangeValue = new Emitter(); readonly onDidChangeValue = this._onDidChangeValue.event; @@ -92,11 +131,6 @@ export class ChatImplicitContext extends Disposable implements IChatRequestImpli return this._value; } - set value(value: Location | URI | undefined) { - this._value = value; - this._onDidChangeValue.fire(); - } - private _enabled = true; get enabled() { return this._enabled; @@ -112,8 +146,9 @@ export class ChatImplicitContext extends Disposable implements IChatRequestImpli this._value = value; } - setValue(value: Location | URI) { + setValue(value: Location | URI | undefined, isSelection: boolean) { this._value = value; + this._isSelection = isSelection; this._onDidChangeValue.fire(); } diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 11b4a4d69d6..25bc704f855 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -49,6 +49,7 @@ export interface IBaseChatRequestVariableEntry { export interface IChatRequestImplicitVariableEntry extends Omit { readonly kind: 'implicit'; readonly value: URI | Location | undefined; + readonly isSelection: boolean; enabled: boolean; }