From 49907c09c4decf69fa63dfcf0584721402ff2a78 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Mon, 4 Nov 2024 15:44:13 -0800 Subject: [PATCH] feat: support attaching selection to chat editing view (#233021) * feat: support attaching selection to chat editing view * feat: support attaching file to chat editing view --- .../browser/actions/chatContextActions.ts | 82 +++++++++++++++++-- .../contrib/chat/browser/chatInputPart.ts | 3 +- .../contrib/chat/browser/chatVariables.ts | 8 +- 3 files changed, 82 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts index 2ea46a84352..aacd070548b 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts @@ -45,7 +45,7 @@ import { IChatRequestVariableEntry } from '../../common/chatModel.js'; import { ChatRequestAgentPart } from '../../common/chatParserTypes.js'; import { IChatVariableData, IChatVariablesService } from '../../common/chatVariables.js'; import { ILanguageModelToolsService } from '../../common/languageModelToolsService.js'; -import { IChatWidget, IChatWidgetService, IQuickChatService, showChatView } from '../chat.js'; +import { IChatWidget, IChatWidgetService, IQuickChatService, showChatView, showEditsView } from '../chat.js'; import { imageToHash, isImage } from '../chatPasteProviders.js'; import { isQuickChat } from '../chatWidget.js'; import { convertBufferToScreenshotVariable, ScreenshotVariableId } from '../contrib/screenshot.js'; @@ -53,8 +53,10 @@ import { CHAT_CATEGORY } from './chatActions.js'; export function registerChatContextActions() { registerAction2(AttachContextAction); - registerAction2(AttachFileAction); - registerAction2(AttachSelectionAction); + registerAction2(AttachFileToChatAction); + registerAction2(AttachSelectionToChatAction); + registerAction2(AttachFileToEditingSessionAction); + registerAction2(AttachSelectionToEditingSessionAction); } /** @@ -159,13 +161,13 @@ interface IScreenShotQuickPickItem extends IQuickPickItem { icon?: ThemeIcon; } -class AttachFileAction extends Action2 { +class AttachFileToChatAction extends Action2 { static readonly ID = 'workbench.action.chat.attachFile'; constructor() { super({ - id: AttachFileAction.ID, + id: AttachFileToChatAction.ID, title: localize2('workbench.action.chat.attachFile.label', "Add File to Chat"), category: CHAT_CATEGORY, f1: false, @@ -190,13 +192,13 @@ class AttachFileAction extends Action2 { } } -class AttachSelectionAction extends Action2 { +class AttachSelectionToChatAction extends Action2 { static readonly ID = 'workbench.action.chat.attachSelection'; constructor() { super({ - id: AttachSelectionAction.ID, + id: AttachSelectionToChatAction.ID, title: localize2('workbench.action.chat.attachSelection.label', "Add Selection to Chat"), category: CHAT_CATEGORY, f1: false, @@ -225,6 +227,72 @@ class AttachSelectionAction extends Action2 { } } +class AttachFileToEditingSessionAction extends Action2 { + + static readonly ID = 'workbench.action.edits.attachFile'; + + constructor() { + super({ + id: AttachFileToEditingSessionAction.ID, + title: localize2('workbench.action.edits.attachFile.label', "Add File to {0}", 'Copilot Edits'), + category: CHAT_CATEGORY, + f1: false, + precondition: ContextKeyExpr.and(ChatContextKeys.enabled, ActiveEditorContext.isEqualTo('workbench.editors.files.textFileEditor')), + menu: { + id: MenuId.ChatCommandCenter, + group: 'a_chat', + order: 11, + } + }); + } + + override async run(accessor: ServicesAccessor, ...args: any[]): Promise { + const variablesService = accessor.get(IChatVariablesService); + const textEditorService = accessor.get(IEditorService); + + const activeUri = textEditorService.activeEditor?.resource; + if (textEditorService.activeTextEditorControl?.getEditorType() === EditorType.ICodeEditor && activeUri && [Schemas.file, Schemas.vscodeRemote, Schemas.untitled].includes(activeUri.scheme)) { + (await showEditsView(accessor.get(IViewsService)))?.focusInput(); + variablesService.attachContext('file', activeUri, ChatAgentLocation.EditingSession); + } + } +} + +class AttachSelectionToEditingSessionAction extends Action2 { + + static readonly ID = 'workbench.action.edits.attachSelection'; + + constructor() { + super({ + id: AttachSelectionToEditingSessionAction.ID, + title: localize2('workbench.action.edits.attachSelection.label', "Add Selection to {0}", 'Copilot Edits'), + category: CHAT_CATEGORY, + f1: false, + precondition: ContextKeyExpr.and(ChatContextKeys.enabled, ActiveEditorContext.isEqualTo('workbench.editors.files.textFileEditor')), + menu: { + id: MenuId.ChatCommandCenter, + group: 'a_chat', + order: 12, + } + }); + } + + override async run(accessor: ServicesAccessor, ...args: any[]): Promise { + const variablesService = accessor.get(IChatVariablesService); + const textEditorService = accessor.get(IEditorService); + + const activeEditor = textEditorService.activeTextEditorControl; + const activeUri = textEditorService.activeEditor?.resource; + if (textEditorService.activeTextEditorControl?.getEditorType() === EditorType.ICodeEditor && activeUri && [Schemas.file, Schemas.vscodeRemote, Schemas.untitled].includes(activeUri.scheme)) { + const selection = activeEditor?.getSelection(); + if (selection) { + (await showEditsView(accessor.get(IViewsService)))?.focusInput(); + variablesService.attachContext('file', { uri: activeUri, range: selection }, ChatAgentLocation.EditingSession); + } + } + } +} + export class AttachContextAction extends Action2 { static readonly ID = 'workbench.action.chat.attachContext'; diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index c9a33ded7d9..267e93c75ad 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -720,7 +720,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge dom.clearNode(container); const hoverDelegate = store.add(createInstantHoverDelegate()); const attachments = this.location === ChatAgentLocation.EditingSession - ? [...this.attachmentModel.attachments.entries()].filter(([_, attachment]) => !attachment.isFile) + // Render as attachments anything that isn't a file, but still render specific ranges in a file + ? [...this.attachmentModel.attachments.entries()].filter(([_, attachment]) => !attachment.isFile || attachment.isFile && typeof attachment.value === 'object' && !!attachment.value && 'range' in attachment.value) : [...this.attachmentModel.attachments.entries()]; dom.setVisibility(Boolean(attachments.length) || Boolean(this.implicitContext?.value), this.attachedContextContainer); if (!attachments.length) { diff --git a/src/vs/workbench/contrib/chat/browser/chatVariables.ts b/src/vs/workbench/contrib/chat/browser/chatVariables.ts index a340a3446e1..4ab91fa76d7 100644 --- a/src/vs/workbench/contrib/chat/browser/chatVariables.ts +++ b/src/vs/workbench/contrib/chat/browser/chatVariables.ts @@ -17,7 +17,7 @@ import { IChatModel, IChatRequestVariableData, IChatRequestVariableEntry } from import { ChatRequestDynamicVariablePart, ChatRequestToolPart, ChatRequestVariablePart, IParsedChatRequest } from '../common/chatParserTypes.js'; import { IChatContentReference } from '../common/chatService.js'; import { IChatRequestVariableValue, IChatVariableData, IChatVariableResolver, IChatVariableResolverProgress, IChatVariablesService, IDynamicVariable } from '../common/chatVariables.js'; -import { IChatWidgetService, showChatView } from './chat.js'; +import { IChatWidgetService, showChatView, showEditsView } from './chat.js'; import { ChatDynamicVariableModel } from './contrib/chatDynamicVariables.js'; interface IChatData { @@ -161,11 +161,13 @@ export class ChatVariablesService implements IChatVariablesService { } async attachContext(name: string, value: string | URI | Location, location: ChatAgentLocation) { - if (location !== ChatAgentLocation.Panel) { + if (location !== ChatAgentLocation.Panel && location !== ChatAgentLocation.EditingSession) { return; } - const widget = this.chatWidgetService.lastFocusedWidget ?? await showChatView(this.viewsService); + const widget = location === ChatAgentLocation.EditingSession + ? await showEditsView(this.viewsService) + : (this.chatWidgetService.lastFocusedWidget ?? await showChatView(this.viewsService)); if (!widget || !widget.viewModel) { return; }