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
This commit is contained in:
Joyce Er 2024-11-04 15:44:13 -08:00 committed by GitHub
parent 3c62ba9766
commit 49907c09c4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 82 additions and 11 deletions

View File

@ -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<void> {
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<void> {
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';

View File

@ -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) {

View File

@ -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;
}