Path: blob/main/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { AsyncIterableObject } from '../../../../../base/common/async.js';6import { CancellationToken } from '../../../../../base/common/cancellation.js';7import { Codicon } from '../../../../../base/common/codicons.js';8import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js';9import { Disposable, markAsSingleton } from '../../../../../base/common/lifecycle.js';10import { ICodeEditor } from '../../../../../editor/browser/editorBrowser.js';11import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js';12import { ICodeEditorService } from '../../../../../editor/browser/services/codeEditorService.js';13import { EditorContextKeys } from '../../../../../editor/common/editorContextKeys.js';14import { CopyAction } from '../../../../../editor/contrib/clipboard/browser/clipboard.js';15import { localize, localize2 } from '../../../../../nls.js';16import { IActionViewItemService } from '../../../../../platform/actions/browser/actionViewItemService.js';17import { MenuEntryActionViewItem } from '../../../../../platform/actions/browser/menuEntryActionViewItem.js';18import { Action2, MenuId, MenuItemAction, registerAction2 } from '../../../../../platform/actions/common/actions.js';19import { IClipboardService } from '../../../../../platform/clipboard/common/clipboardService.js';20import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js';21import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';22import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js';23import { ILabelService } from '../../../../../platform/label/common/label.js';24import { TerminalLocation } from '../../../../../platform/terminal/common/terminal.js';25import { IWorkbenchContribution } from '../../../../common/contributions.js';26import { IUntitledTextResourceEditorInput } from '../../../../common/editor.js';27import { IEditorService } from '../../../../services/editor/common/editorService.js';28import { accessibleViewInCodeBlock } from '../../../accessibility/browser/accessibilityConfiguration.js';29import { IAiEditTelemetryService } from '../../../editTelemetry/browser/telemetry/aiEditTelemetry/aiEditTelemetryService.js';30import { EditDeltaInfo } from '../../../../../editor/common/textModelEditSource.js';31import { reviewEdits } from '../../../inlineChat/browser/inlineChatController.js';32import { ITerminalEditorService, ITerminalGroupService, ITerminalService } from '../../../terminal/browser/terminal.js';33import { ChatContextKeys } from '../../common/chatContextKeys.js';34import { ChatCopyKind, IChatService } from '../../common/chatService.js';35import { IChatRequestViewModel, IChatResponseViewModel, isRequestVM, isResponseVM } from '../../common/chatViewModel.js';36import { ChatAgentLocation } from '../../common/constants.js';37import { IChatCodeBlockContextProviderService, IChatWidgetService } from '../chat.js';38import { DefaultChatTextEditor, ICodeBlockActionContext, ICodeCompareBlockActionContext } from '../codeBlockPart.js';39import { CHAT_CATEGORY } from './chatActions.js';40import { ApplyCodeBlockOperation, InsertCodeBlockOperation } from './codeBlockOperations.js';4142const shellLangIds = [43'fish',44'ps1',45'pwsh',46'powershell',47'sh',48'shellscript',49'zsh'50];5152export interface IChatCodeBlockActionContext extends ICodeBlockActionContext {53element: IChatResponseViewModel;54}5556export function isCodeBlockActionContext(thing: unknown): thing is ICodeBlockActionContext {57return typeof thing === 'object' && thing !== null && 'code' in thing && 'element' in thing;58}5960export function isCodeCompareBlockActionContext(thing: unknown): thing is ICodeCompareBlockActionContext {61return typeof thing === 'object' && thing !== null && 'element' in thing;62}6364function isResponseFiltered(context: ICodeBlockActionContext) {65return isResponseVM(context.element) && context.element.errorDetails?.responseIsFiltered;66}6768abstract class ChatCodeBlockAction extends Action2 {69run(accessor: ServicesAccessor, ...args: any[]) {70let context = args[0];71if (!isCodeBlockActionContext(context)) {72const codeEditorService = accessor.get(ICodeEditorService);73const editor = codeEditorService.getFocusedCodeEditor() || codeEditorService.getActiveCodeEditor();74if (!editor) {75return;76}7778context = getContextFromEditor(editor, accessor);79if (!isCodeBlockActionContext(context)) {80return;81}82}8384return this.runWithContext(accessor, context);85}8687abstract runWithContext(accessor: ServicesAccessor, context: ICodeBlockActionContext): any;88}8990const APPLY_IN_EDITOR_ID = 'workbench.action.chat.applyInEditor';9192export class CodeBlockActionRendering extends Disposable implements IWorkbenchContribution {9394static readonly ID = 'chat.codeBlockActionRendering';9596constructor(97@IActionViewItemService actionViewItemService: IActionViewItemService,98@IInstantiationService instantiationService: IInstantiationService,99@ILabelService labelService: ILabelService,100) {101super();102103const disposable = actionViewItemService.register(MenuId.ChatCodeBlock, APPLY_IN_EDITOR_ID, (action, options) => {104if (!(action instanceof MenuItemAction)) {105return undefined;106}107return instantiationService.createInstance(class extends MenuEntryActionViewItem {108protected override getTooltip(): string {109const context = this._context;110if (isCodeBlockActionContext(context) && context.codemapperUri) {111const label = labelService.getUriLabel(context.codemapperUri, { relative: true });112return localize('interactive.applyInEditorWithURL.label', "Apply to {0}", label);113}114return super.getTooltip();115}116override setActionContext(newContext: unknown): void {117super.setActionContext(newContext);118this.updateTooltip();119}120}, action, undefined);121});122123// Reduces flicker a bit on reload/restart124markAsSingleton(disposable);125}126}127128export function registerChatCodeBlockActions() {129registerAction2(class CopyCodeBlockAction extends Action2 {130constructor() {131super({132id: 'workbench.action.chat.copyCodeBlock',133title: localize2('interactive.copyCodeBlock.label', "Copy"),134f1: false,135category: CHAT_CATEGORY,136icon: Codicon.copy,137menu: {138id: MenuId.ChatCodeBlock,139group: 'navigation',140order: 30141}142});143}144145run(accessor: ServicesAccessor, ...args: any[]) {146const context = args[0];147if (!isCodeBlockActionContext(context) || isResponseFiltered(context)) {148return;149}150151const clipboardService = accessor.get(IClipboardService);152const aiEditTelemetryService = accessor.get(IAiEditTelemetryService);153clipboardService.writeText(context.code);154155if (isResponseVM(context.element)) {156const chatService = accessor.get(IChatService);157const requestId = context.element.requestId;158const request = context.element.session.getItems().find(item => item.id === requestId && isRequestVM(item)) as IChatRequestViewModel | undefined;159chatService.notifyUserAction({160agentId: context.element.agent?.id,161command: context.element.slashCommand?.name,162sessionId: context.element.sessionId,163requestId: context.element.requestId,164result: context.element.result,165action: {166kind: 'copy',167codeBlockIndex: context.codeBlockIndex,168copyKind: ChatCopyKind.Toolbar,169copiedCharacters: context.code.length,170totalCharacters: context.code.length,171copiedText: context.code,172copiedLines: context.code.split('\n').length,173languageId: context.languageId,174totalLines: context.code.split('\n').length,175modelId: request?.modelId ?? ''176}177});178179const codeBlockInfo = context.element.model.codeBlockInfos?.at(context.codeBlockIndex);180aiEditTelemetryService.handleCodeAccepted({181acceptanceMethod: 'copyButton',182suggestionId: codeBlockInfo?.suggestionId,183editDeltaInfo: EditDeltaInfo.fromText(context.code),184feature: 'sideBarChat',185languageId: context.languageId,186modeId: context.element.model.request?.modeInfo?.modeId,187modelId: request?.modelId,188presentation: 'codeBlock',189applyCodeBlockSuggestionId: undefined,190});191}192}193});194195CopyAction?.addImplementation(50000, 'chat-codeblock', (accessor) => {196// get active code editor197const editor = accessor.get(ICodeEditorService).getFocusedCodeEditor();198if (!editor) {199return false;200}201202const editorModel = editor.getModel();203if (!editorModel) {204return false;205}206207const context = getContextFromEditor(editor, accessor);208if (!context) {209return false;210}211212const noSelection = editor.getSelections()?.length === 1 && editor.getSelection()?.isEmpty();213const copiedText = noSelection ?214editorModel.getValue() :215editor.getSelections()?.reduce((acc, selection) => acc + editorModel.getValueInRange(selection), '') ?? '';216const totalCharacters = editorModel.getValueLength();217218// Report copy to extensions219const chatService = accessor.get(IChatService);220const aiEditTelemetryService = accessor.get(IAiEditTelemetryService);221const element = context.element as IChatResponseViewModel | undefined;222if (isResponseVM(element)) {223const requestId = element.requestId;224const request = element.session.getItems().find(item => item.id === requestId && isRequestVM(item)) as IChatRequestViewModel | undefined;225chatService.notifyUserAction({226agentId: element.agent?.id,227command: element.slashCommand?.name,228sessionId: element.sessionId,229requestId: element.requestId,230result: element.result,231action: {232kind: 'copy',233codeBlockIndex: context.codeBlockIndex,234copyKind: ChatCopyKind.Action,235copiedText,236copiedCharacters: copiedText.length,237totalCharacters,238languageId: context.languageId,239totalLines: context.code.split('\n').length,240copiedLines: copiedText.split('\n').length,241modelId: request?.modelId ?? ''242}243});244245const codeBlockInfo = element.model.codeBlockInfos?.at(context.codeBlockIndex);246aiEditTelemetryService.handleCodeAccepted({247acceptanceMethod: 'copyManual',248suggestionId: codeBlockInfo?.suggestionId,249editDeltaInfo: EditDeltaInfo.fromText(copiedText),250feature: 'sideBarChat',251languageId: context.languageId,252modeId: element.model.request?.modeInfo?.modeId,253modelId: request?.modelId,254presentation: 'codeBlock',255applyCodeBlockSuggestionId: undefined,256});257}258259// Copy full cell if no selection, otherwise fall back on normal editor implementation260if (noSelection) {261accessor.get(IClipboardService).writeText(context.code);262return true;263}264265return false;266});267268registerAction2(class SmartApplyInEditorAction extends ChatCodeBlockAction {269270private operation: ApplyCodeBlockOperation | undefined;271272constructor() {273super({274id: APPLY_IN_EDITOR_ID,275title: localize2('interactive.applyInEditor.label', "Apply in Editor"),276precondition: ChatContextKeys.enabled,277f1: true,278category: CHAT_CATEGORY,279icon: Codicon.gitPullRequestGoToChanges,280281menu: [282{283id: MenuId.ChatCodeBlock,284group: 'navigation',285when: ContextKeyExpr.and(286...shellLangIds.map(e => ContextKeyExpr.notEquals(EditorContextKeys.languageId.key, e))287),288order: 10289},290{291id: MenuId.ChatCodeBlock,292when: ContextKeyExpr.or(293...shellLangIds.map(e => ContextKeyExpr.equals(EditorContextKeys.languageId.key, e))294)295},296],297keybinding: {298when: ContextKeyExpr.or(ContextKeyExpr.and(ChatContextKeys.inChatSession, ChatContextKeys.inChatInput.negate()), accessibleViewInCodeBlock),299primary: KeyMod.CtrlCmd | KeyCode.Enter,300mac: { primary: KeyMod.WinCtrl | KeyCode.Enter },301weight: KeybindingWeight.ExternalExtension + 1302},303});304}305306override runWithContext(accessor: ServicesAccessor, context: ICodeBlockActionContext) {307if (!this.operation) {308this.operation = accessor.get(IInstantiationService).createInstance(ApplyCodeBlockOperation);309}310return this.operation.run(context);311}312});313314registerAction2(class InsertAtCursorAction extends ChatCodeBlockAction {315constructor() {316super({317id: 'workbench.action.chat.insertCodeBlock',318title: localize2('interactive.insertCodeBlock.label', "Insert At Cursor"),319precondition: ChatContextKeys.enabled,320f1: true,321category: CHAT_CATEGORY,322icon: Codicon.insert,323menu: [{324id: MenuId.ChatCodeBlock,325group: 'navigation',326when: ContextKeyExpr.and(ChatContextKeys.inChatSession, ChatContextKeys.location.notEqualsTo(ChatAgentLocation.Terminal)),327order: 20328}, {329id: MenuId.ChatCodeBlock,330group: 'navigation',331when: ContextKeyExpr.and(ChatContextKeys.inChatSession, ChatContextKeys.location.isEqualTo(ChatAgentLocation.Terminal)),332isHiddenByDefault: true,333order: 20334}],335keybinding: {336when: ContextKeyExpr.or(ContextKeyExpr.and(ChatContextKeys.inChatSession, ChatContextKeys.inChatInput.negate()), accessibleViewInCodeBlock),337primary: KeyMod.CtrlCmd | KeyCode.Enter,338mac: { primary: KeyMod.WinCtrl | KeyCode.Enter },339weight: KeybindingWeight.ExternalExtension + 1340},341});342}343344override runWithContext(accessor: ServicesAccessor, context: ICodeBlockActionContext) {345const operation = accessor.get(IInstantiationService).createInstance(InsertCodeBlockOperation);346return operation.run(context);347}348});349350registerAction2(class InsertIntoNewFileAction extends ChatCodeBlockAction {351constructor() {352super({353id: 'workbench.action.chat.insertIntoNewFile',354title: localize2('interactive.insertIntoNewFile.label', "Insert into New File"),355precondition: ChatContextKeys.enabled,356f1: true,357category: CHAT_CATEGORY,358icon: Codicon.newFile,359menu: {360id: MenuId.ChatCodeBlock,361group: 'navigation',362isHiddenByDefault: true,363order: 40,364}365});366}367368override async runWithContext(accessor: ServicesAccessor, context: ICodeBlockActionContext) {369if (isResponseFiltered(context)) {370// When run from command palette371return;372}373374const editorService = accessor.get(IEditorService);375const chatService = accessor.get(IChatService);376const aiEditTelemetryService = accessor.get(IAiEditTelemetryService);377378editorService.openEditor({ contents: context.code, languageId: context.languageId, resource: undefined } satisfies IUntitledTextResourceEditorInput);379380if (isResponseVM(context.element)) {381const requestId = context.element.requestId;382const request = context.element.session.getItems().find(item => item.id === requestId && isRequestVM(item)) as IChatRequestViewModel | undefined;383chatService.notifyUserAction({384agentId: context.element.agent?.id,385command: context.element.slashCommand?.name,386sessionId: context.element.sessionId,387requestId: context.element.requestId,388result: context.element.result,389action: {390kind: 'insert',391codeBlockIndex: context.codeBlockIndex,392totalCharacters: context.code.length,393newFile: true,394totalLines: context.code.split('\n').length,395languageId: context.languageId,396modelId: request?.modelId ?? ''397}398});399400const codeBlockInfo = context.element.model.codeBlockInfos?.at(context.codeBlockIndex);401402aiEditTelemetryService.handleCodeAccepted({403acceptanceMethod: 'insertInNewFile',404suggestionId: codeBlockInfo?.suggestionId,405editDeltaInfo: EditDeltaInfo.fromText(context.code),406feature: 'sideBarChat',407languageId: context.languageId,408modeId: context.element.model.request?.modeInfo?.modeId,409modelId: request?.modelId,410presentation: 'codeBlock',411applyCodeBlockSuggestionId: undefined,412});413}414}415});416417registerAction2(class RunInTerminalAction extends ChatCodeBlockAction {418constructor() {419super({420id: 'workbench.action.chat.runInTerminal',421title: localize2('interactive.runInTerminal.label', "Insert into Terminal"),422precondition: ChatContextKeys.enabled,423f1: true,424category: CHAT_CATEGORY,425icon: Codicon.terminal,426menu: [{427id: MenuId.ChatCodeBlock,428group: 'navigation',429when: ContextKeyExpr.and(430ChatContextKeys.inChatSession,431ContextKeyExpr.or(...shellLangIds.map(e => ContextKeyExpr.equals(EditorContextKeys.languageId.key, e)))432),433},434{435id: MenuId.ChatCodeBlock,436group: 'navigation',437isHiddenByDefault: true,438when: ContextKeyExpr.and(439ChatContextKeys.inChatSession,440...shellLangIds.map(e => ContextKeyExpr.notEquals(EditorContextKeys.languageId.key, e))441)442}],443keybinding: [{444primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Enter,445mac: {446primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.Enter447},448weight: KeybindingWeight.EditorContrib,449when: ContextKeyExpr.or(ChatContextKeys.inChatSession, accessibleViewInCodeBlock),450}]451});452}453454override async runWithContext(accessor: ServicesAccessor, context: ICodeBlockActionContext) {455if (isResponseFiltered(context)) {456// When run from command palette457return;458}459460const chatService = accessor.get(IChatService);461const terminalService = accessor.get(ITerminalService);462const editorService = accessor.get(IEditorService);463const terminalEditorService = accessor.get(ITerminalEditorService);464const terminalGroupService = accessor.get(ITerminalGroupService);465466let terminal = await terminalService.getActiveOrCreateInstance();467468// isFeatureTerminal = debug terminal or task terminal469const unusableTerminal = terminal.xterm?.isStdinDisabled || terminal.shellLaunchConfig.isFeatureTerminal;470terminal = unusableTerminal ? await terminalService.createTerminal() : terminal;471472terminalService.setActiveInstance(terminal);473await terminal.focusWhenReady(true);474if (terminal.target === TerminalLocation.Editor) {475const existingEditors = editorService.findEditors(terminal.resource);476terminalEditorService.openEditor(terminal, { viewColumn: existingEditors?.[0].groupId });477} else {478terminalGroupService.showPanel(true);479}480481terminal.runCommand(context.code, false);482483if (isResponseVM(context.element)) {484chatService.notifyUserAction({485agentId: context.element.agent?.id,486command: context.element.slashCommand?.name,487sessionId: context.element.sessionId,488requestId: context.element.requestId,489result: context.element.result,490action: {491kind: 'runInTerminal',492codeBlockIndex: context.codeBlockIndex,493languageId: context.languageId,494}495});496}497}498});499500function navigateCodeBlocks(accessor: ServicesAccessor, reverse?: boolean): void {501const codeEditorService = accessor.get(ICodeEditorService);502const chatWidgetService = accessor.get(IChatWidgetService);503const widget = chatWidgetService.lastFocusedWidget;504if (!widget) {505return;506}507508const editor = codeEditorService.getFocusedCodeEditor();509const editorUri = editor?.getModel()?.uri;510const curCodeBlockInfo = editorUri ? widget.getCodeBlockInfoForEditor(editorUri) : undefined;511const focused = !widget.inputEditor.hasWidgetFocus() && widget.getFocus();512const focusedResponse = isResponseVM(focused) ? focused : undefined;513514const elementId = curCodeBlockInfo?.elementId;515const element = elementId ? widget.viewModel?.getItems().find(item => item.id === elementId) : undefined;516const currentResponse = element ??517(focusedResponse ?? widget.viewModel?.getItems().reverse().find((item): item is IChatResponseViewModel => isResponseVM(item)));518if (!currentResponse || !isResponseVM(currentResponse)) {519return;520}521522widget.reveal(currentResponse);523const responseCodeblocks = widget.getCodeBlockInfosForResponse(currentResponse);524const focusIdx = curCodeBlockInfo ?525(curCodeBlockInfo.codeBlockIndex + (reverse ? -1 : 1) + responseCodeblocks.length) % responseCodeblocks.length :526reverse ? responseCodeblocks.length - 1 : 0;527528responseCodeblocks[focusIdx]?.focus();529}530531registerAction2(class NextCodeBlockAction extends Action2 {532constructor() {533super({534id: 'workbench.action.chat.nextCodeBlock',535title: localize2('interactive.nextCodeBlock.label', "Next Code Block"),536keybinding: {537primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageDown,538mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageDown, },539weight: KeybindingWeight.WorkbenchContrib,540when: ChatContextKeys.inChatSession,541},542precondition: ChatContextKeys.enabled,543f1: true,544category: CHAT_CATEGORY,545});546}547548run(accessor: ServicesAccessor, ...args: any[]) {549navigateCodeBlocks(accessor);550}551});552553registerAction2(class PreviousCodeBlockAction extends Action2 {554constructor() {555super({556id: 'workbench.action.chat.previousCodeBlock',557title: localize2('interactive.previousCodeBlock.label', "Previous Code Block"),558keybinding: {559primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageUp,560mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageUp, },561weight: KeybindingWeight.WorkbenchContrib,562when: ChatContextKeys.inChatSession,563},564precondition: ChatContextKeys.enabled,565f1: true,566category: CHAT_CATEGORY,567});568}569570run(accessor: ServicesAccessor, ...args: any[]) {571navigateCodeBlocks(accessor, true);572}573});574}575576function getContextFromEditor(editor: ICodeEditor, accessor: ServicesAccessor): ICodeBlockActionContext | undefined {577const chatWidgetService = accessor.get(IChatWidgetService);578const chatCodeBlockContextProviderService = accessor.get(IChatCodeBlockContextProviderService);579const model = editor.getModel();580if (!model) {581return;582}583584const widget = chatWidgetService.lastFocusedWidget;585const codeBlockInfo = widget?.getCodeBlockInfoForEditor(model.uri);586if (!codeBlockInfo) {587for (const provider of chatCodeBlockContextProviderService.providers) {588const context = provider.getCodeBlockContext(editor);589if (context) {590return context;591}592}593return;594}595596const element = widget?.viewModel?.getItems().find(item => item.id === codeBlockInfo.elementId);597return {598element,599codeBlockIndex: codeBlockInfo.codeBlockIndex,600code: editor.getValue(),601languageId: editor.getModel()!.getLanguageId(),602codemapperUri: codeBlockInfo.codemapperUri,603chatSessionId: codeBlockInfo.chatSessionId,604};605}606607export function registerChatCodeCompareBlockActions() {608609abstract class ChatCompareCodeBlockAction extends Action2 {610run(accessor: ServicesAccessor, ...args: any[]) {611const context = args[0];612if (!isCodeCompareBlockActionContext(context)) {613return;614// TODO@jrieken derive context615}616617return this.runWithContext(accessor, context);618}619620abstract runWithContext(accessor: ServicesAccessor, context: ICodeCompareBlockActionContext): any;621}622623registerAction2(class ApplyEditsCompareBlockAction extends ChatCompareCodeBlockAction {624constructor() {625super({626id: 'workbench.action.chat.applyCompareEdits',627title: localize2('interactive.compare.apply', "Apply Edits"),628f1: false,629category: CHAT_CATEGORY,630icon: Codicon.gitPullRequestGoToChanges,631precondition: ContextKeyExpr.and(EditorContextKeys.hasChanges, ChatContextKeys.editApplied.negate()),632menu: {633id: MenuId.ChatCompareBlock,634group: 'navigation',635order: 1,636}637});638}639640async runWithContext(accessor: ServicesAccessor, context: ICodeCompareBlockActionContext): Promise<any> {641642const instaService = accessor.get(IInstantiationService);643const editorService = accessor.get(ICodeEditorService);644645const item = context.edit;646const response = context.element;647648if (item.state?.applied) {649// already applied650return false;651}652653if (!response.response.value.includes(item)) {654// bogous item655return false;656}657658const firstEdit = item.edits[0]?.[0];659if (!firstEdit) {660return false;661}662const textEdits = AsyncIterableObject.fromArray(item.edits);663664const editorToApply = await editorService.openCodeEditor({ resource: item.uri }, null);665if (editorToApply) {666editorToApply.revealLineInCenterIfOutsideViewport(firstEdit.range.startLineNumber);667instaService.invokeFunction(reviewEdits, editorToApply, textEdits, CancellationToken.None, undefined);668response.setEditApplied(item, 1);669return true;670}671return false;672}673});674675registerAction2(class DiscardEditsCompareBlockAction extends ChatCompareCodeBlockAction {676constructor() {677super({678id: 'workbench.action.chat.discardCompareEdits',679title: localize2('interactive.compare.discard', "Discard Edits"),680f1: false,681category: CHAT_CATEGORY,682icon: Codicon.trash,683precondition: ContextKeyExpr.and(EditorContextKeys.hasChanges, ChatContextKeys.editApplied.negate()),684menu: {685id: MenuId.ChatCompareBlock,686group: 'navigation',687order: 2,688}689});690}691692async runWithContext(accessor: ServicesAccessor, context: ICodeCompareBlockActionContext): Promise<any> {693const instaService = accessor.get(IInstantiationService);694const editor = instaService.createInstance(DefaultChatTextEditor);695editor.discard(context.element, context.edit);696}697});698}699700701