Path: blob/main/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.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 { CancellationToken } from '../../../../../base/common/cancellation.js';6import { Codicon } from '../../../../../base/common/codicons.js';7import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js';8import { basename } from '../../../../../base/common/resources.js';9import { URI, UriComponents } from '../../../../../base/common/uri.js';10import { isCodeEditor } from '../../../../../editor/browser/editorBrowser.js';11import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js';12import { Position } from '../../../../../editor/common/core/position.js';13import { EditorContextKeys } from '../../../../../editor/common/editorContextKeys.js';14import { isLocation, Location } from '../../../../../editor/common/languages.js';15import { ITextModel } from '../../../../../editor/common/model.js';16import { ILanguageFeaturesService } from '../../../../../editor/common/services/languageFeatures.js';17import { ITextModelService } from '../../../../../editor/common/services/resolverService.js';18import { localize, localize2 } from '../../../../../nls.js';19import { Action2, IAction2Options, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js';20import { CommandsRegistry } from '../../../../../platform/commands/common/commands.js';21import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';22import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js';23import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js';24import { EditorActivation } from '../../../../../platform/editor/common/editor.js';25import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js';26import { IEditorPane } from '../../../../common/editor.js';27import { IEditorService } from '../../../../services/editor/common/editorService.js';28import { isChatViewTitleActionContext } from '../../common/chatActions.js';29import { ChatContextKeys } from '../../common/chatContextKeys.js';30import { applyingChatEditsFailedContextKey, CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME, chatEditingResourceContextKey, chatEditingWidgetFileStateContextKey, decidedChatEditingResourceContextKey, hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, IChatEditingService, IChatEditingSession, ModifiedFileEntryState } from '../../common/chatEditingService.js';31import { IChatService } from '../../common/chatService.js';32import { isRequestVM, isResponseVM } from '../../common/chatViewModel.js';33import { ChatAgentLocation, ChatConfiguration, ChatModeKind } from '../../common/constants.js';34import { CHAT_CATEGORY } from '../actions/chatActions.js';35import { ChatTreeItem, IChatWidget, IChatWidgetService } from '../chat.js';3637export abstract class EditingSessionAction extends Action2 {3839constructor(opts: Readonly<IAction2Options>) {40super({41category: CHAT_CATEGORY,42...opts43});44}4546run(accessor: ServicesAccessor, ...args: any[]) {47const context = getEditingSessionContext(accessor, args);48if (!context || !context.editingSession) {49return;50}5152return this.runEditingSessionAction(accessor, context.editingSession, context.chatWidget, ...args);53}5455abstract runEditingSessionAction(accessor: ServicesAccessor, editingSession: IChatEditingSession, chatWidget: IChatWidget, ...args: any[]): any;56}5758export function getEditingSessionContext(accessor: ServicesAccessor, args: any[]): { editingSession?: IChatEditingSession; chatWidget: IChatWidget } | undefined {59const arg0 = args.at(0);60const context = isChatViewTitleActionContext(arg0) ? arg0 : undefined;6162const chatWidgetService = accessor.get(IChatWidgetService);63const chatEditingService = accessor.get(IChatEditingService);64let chatWidget = context ? chatWidgetService.getWidgetBySessionId(context.sessionId) : undefined;65if (!chatWidget) {66chatWidget = chatWidgetService.lastFocusedWidget ?? chatWidgetService.getWidgetsByLocations(ChatAgentLocation.Panel).find(w => w.supportsChangingModes);67}6869if (!chatWidget?.viewModel) {70return;71}7273const chatSessionId = chatWidget.viewModel.model.sessionId;74const editingSession = chatEditingService.getEditingSession(chatSessionId);7576if (!editingSession) {77return;78}7980return { editingSession, chatWidget };81}828384abstract class WorkingSetAction extends EditingSessionAction {8586runEditingSessionAction(accessor: ServicesAccessor, editingSession: IChatEditingSession, chatWidget: IChatWidget, ...args: any[]) {8788const uris: URI[] = [];89if (URI.isUri(args[0])) {90uris.push(args[0]);91} else if (chatWidget) {92uris.push(...chatWidget.input.selectedElements);93}94if (!uris.length) {95return;96}9798return this.runWorkingSetAction(accessor, editingSession, chatWidget, ...uris);99}100101abstract runWorkingSetAction(accessor: ServicesAccessor, editingSession: IChatEditingSession, chatWidget: IChatWidget | undefined, ...uris: URI[]): any;102}103104registerAction2(class OpenFileInDiffAction extends WorkingSetAction {105constructor() {106super({107id: 'chatEditing.openFileInDiff',108title: localize2('open.fileInDiff', 'Open Changes in Diff Editor'),109icon: Codicon.diffSingle,110menu: [{111id: MenuId.ChatEditingWidgetModifiedFilesToolbar,112when: ContextKeyExpr.equals(chatEditingWidgetFileStateContextKey.key, ModifiedFileEntryState.Modified),113order: 2,114group: 'navigation'115}],116});117}118119async runWorkingSetAction(accessor: ServicesAccessor, currentEditingSession: IChatEditingSession, _chatWidget: IChatWidget, ...uris: URI[]): Promise<void> {120const editorService = accessor.get(IEditorService);121122123for (const uri of uris) {124125let pane: IEditorPane | undefined = editorService.activeEditorPane;126if (!pane) {127pane = await editorService.openEditor({ resource: uri });128}129130if (!pane) {131return;132}133134const editedFile = currentEditingSession.getEntry(uri);135editedFile?.getEditorIntegration(pane).toggleDiff(undefined, true);136}137}138});139140registerAction2(class AcceptAction extends WorkingSetAction {141constructor() {142super({143id: 'chatEditing.acceptFile',144title: localize2('accept.file', 'Keep'),145icon: Codicon.check,146precondition: ChatContextKeys.requestInProgress.negate(),147menu: [{148when: ContextKeyExpr.and(ContextKeyExpr.equals('resourceScheme', CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME), ContextKeyExpr.notIn(chatEditingResourceContextKey.key, decidedChatEditingResourceContextKey.key)),149id: MenuId.MultiDiffEditorFileToolbar,150order: 0,151group: 'navigation',152}, {153id: MenuId.ChatEditingWidgetModifiedFilesToolbar,154when: ContextKeyExpr.equals(chatEditingWidgetFileStateContextKey.key, ModifiedFileEntryState.Modified),155order: 0,156group: 'navigation'157}],158});159}160161async runWorkingSetAction(accessor: ServicesAccessor, currentEditingSession: IChatEditingSession, chatWidget: IChatWidget, ...uris: URI[]): Promise<void> {162await currentEditingSession.accept(...uris);163}164});165166registerAction2(class DiscardAction extends WorkingSetAction {167constructor() {168super({169id: 'chatEditing.discardFile',170title: localize2('discard.file', 'Undo'),171icon: Codicon.discard,172precondition: ChatContextKeys.requestInProgress.negate(),173menu: [{174when: ContextKeyExpr.and(ContextKeyExpr.equals('resourceScheme', CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME), ContextKeyExpr.notIn(chatEditingResourceContextKey.key, decidedChatEditingResourceContextKey.key)),175id: MenuId.MultiDiffEditorFileToolbar,176order: 2,177group: 'navigation',178}, {179id: MenuId.ChatEditingWidgetModifiedFilesToolbar,180when: ContextKeyExpr.equals(chatEditingWidgetFileStateContextKey.key, ModifiedFileEntryState.Modified),181order: 1,182group: 'navigation'183}],184});185}186187async runWorkingSetAction(accessor: ServicesAccessor, currentEditingSession: IChatEditingSession, chatWidget: IChatWidget, ...uris: URI[]): Promise<void> {188await currentEditingSession.reject(...uris);189}190});191192export class ChatEditingAcceptAllAction extends EditingSessionAction {193194constructor() {195super({196id: 'chatEditing.acceptAllFiles',197title: localize('accept', 'Keep'),198icon: Codicon.check,199tooltip: localize('acceptAllEdits', 'Keep All Edits'),200precondition: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), hasUndecidedChatEditingResourceContextKey),201keybinding: {202primary: KeyMod.CtrlCmd | KeyCode.Enter,203when: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), hasUndecidedChatEditingResourceContextKey, ChatContextKeys.inChatInput),204weight: KeybindingWeight.WorkbenchContrib,205},206menu: [207208{209id: MenuId.ChatEditingWidgetToolbar,210group: 'navigation',211order: 0,212when: ContextKeyExpr.and(applyingChatEditsFailedContextKey.negate(), ContextKeyExpr.and(hasUndecidedChatEditingResourceContextKey))213}214]215});216}217218override async runEditingSessionAction(accessor: ServicesAccessor, editingSession: IChatEditingSession, chatWidget: IChatWidget, ...args: any[]) {219await editingSession.accept();220}221}222registerAction2(ChatEditingAcceptAllAction);223224export class ChatEditingDiscardAllAction extends EditingSessionAction {225226constructor() {227super({228id: 'chatEditing.discardAllFiles',229title: localize('discard', 'Undo'),230icon: Codicon.discard,231tooltip: localize('discardAllEdits', 'Undo All Edits'),232precondition: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), hasUndecidedChatEditingResourceContextKey),233menu: [234{235id: MenuId.ChatEditingWidgetToolbar,236group: 'navigation',237order: 1,238when: ContextKeyExpr.and(applyingChatEditsFailedContextKey.negate(), hasUndecidedChatEditingResourceContextKey)239}240],241keybinding: {242when: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), hasUndecidedChatEditingResourceContextKey, ChatContextKeys.inChatInput, ChatContextKeys.inputHasText.negate()),243weight: KeybindingWeight.WorkbenchContrib,244primary: KeyMod.CtrlCmd | KeyCode.Backspace,245},246});247}248249override async runEditingSessionAction(accessor: ServicesAccessor, editingSession: IChatEditingSession, chatWidget: IChatWidget, ...args: any[]) {250await discardAllEditsWithConfirmation(accessor, editingSession);251}252}253registerAction2(ChatEditingDiscardAllAction);254255export async function discardAllEditsWithConfirmation(accessor: ServicesAccessor, currentEditingSession: IChatEditingSession): Promise<boolean> {256257const dialogService = accessor.get(IDialogService);258259// Ask for confirmation if there are any edits260const entries = currentEditingSession.entries.get();261if (entries.length > 0) {262const confirmation = await dialogService.confirm({263title: localize('chat.editing.discardAll.confirmation.title', "Undo all edits?"),264message: entries.length === 1265? localize('chat.editing.discardAll.confirmation.oneFile', "This will undo changes made in {0}. Do you want to proceed?", basename(entries[0].modifiedURI))266: localize('chat.editing.discardAll.confirmation.manyFiles', "This will undo changes made in {0} files. Do you want to proceed?", entries.length),267primaryButton: localize('chat.editing.discardAll.confirmation.primaryButton', "Yes"),268type: 'info'269});270if (!confirmation.confirmed) {271return false;272}273}274275await currentEditingSession.reject();276return true;277}278279export class ChatEditingShowChangesAction extends EditingSessionAction {280static readonly ID = 'chatEditing.viewChanges';281static readonly LABEL = localize('chatEditing.viewChanges', 'View All Edits');282283constructor() {284super({285id: ChatEditingShowChangesAction.ID,286title: { value: ChatEditingShowChangesAction.LABEL, original: ChatEditingShowChangesAction.LABEL },287tooltip: ChatEditingShowChangesAction.LABEL,288f1: true,289icon: Codicon.diffMultiple,290precondition: hasUndecidedChatEditingResourceContextKey,291menu: [292{293id: MenuId.ChatEditingWidgetToolbar,294group: 'navigation',295order: 4,296when: ContextKeyExpr.and(applyingChatEditsFailedContextKey.negate(), ContextKeyExpr.and(hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey))297}298],299});300}301302override async runEditingSessionAction(accessor: ServicesAccessor, editingSession: IChatEditingSession, chatWidget: IChatWidget, ...args: any[]): Promise<void> {303await editingSession.show();304}305}306registerAction2(ChatEditingShowChangesAction);307308async function restoreSnapshotWithConfirmation(accessor: ServicesAccessor, item: ChatTreeItem): Promise<void> {309const configurationService = accessor.get(IConfigurationService);310const dialogService = accessor.get(IDialogService);311const chatWidgetService = accessor.get(IChatWidgetService);312const widget = chatWidgetService.lastFocusedWidget;313const chatService = accessor.get(IChatService);314const chatModel = chatService.getSession(item.sessionId);315if (!chatModel) {316return;317}318319const session = chatModel.editingSession;320if (!session) {321return;322}323324const requestId = isRequestVM(item) ? item.id :325isResponseVM(item) ? item.requestId : undefined;326327if (requestId) {328const chatRequests = chatModel.getRequests();329const itemIndex = chatRequests.findIndex(request => request.id === requestId);330const editsToUndo = chatRequests.length - itemIndex;331332const requestsToRemove = chatRequests.slice(itemIndex);333const requestIdsToRemove = new Set(requestsToRemove.map(request => request.id));334const entriesModifiedInRequestsToRemove = session.entries.get().filter((entry) => requestIdsToRemove.has(entry.lastModifyingRequestId)) ?? [];335const shouldPrompt = entriesModifiedInRequestsToRemove.length > 0 && configurationService.getValue('chat.editing.confirmEditRequestRemoval') === true;336337let message: string;338if (editsToUndo === 1) {339if (entriesModifiedInRequestsToRemove.length === 1) {340message = localize('chat.removeLast.confirmation.message2', "This will remove your last request and undo the edits made to {0}. Do you want to proceed?", basename(entriesModifiedInRequestsToRemove[0].modifiedURI));341} else {342message = localize('chat.removeLast.confirmation.multipleEdits.message', "This will remove your last request and undo edits made to {0} files in your working set. Do you want to proceed?", entriesModifiedInRequestsToRemove.length);343}344} else {345if (entriesModifiedInRequestsToRemove.length === 1) {346message = localize('chat.remove.confirmation.message2', "This will remove all subsequent requests and undo edits made to {0}. Do you want to proceed?", basename(entriesModifiedInRequestsToRemove[0].modifiedURI));347} else {348message = localize('chat.remove.confirmation.multipleEdits.message', "This will remove all subsequent requests and undo edits made to {0} files in your working set. Do you want to proceed?", entriesModifiedInRequestsToRemove.length);349}350}351352const confirmation = shouldPrompt353? await dialogService.confirm({354title: editsToUndo === 1355? localize('chat.removeLast.confirmation.title', "Do you want to undo your last edit?")356: localize('chat.remove.confirmation.title', "Do you want to undo {0} edits?", editsToUndo),357message: message,358primaryButton: localize('chat.remove.confirmation.primaryButton', "Yes"),359checkbox: { label: localize('chat.remove.confirmation.checkbox', "Don't ask again"), checked: false },360type: 'info'361})362: { confirmed: true };363364if (!confirmation.confirmed) {365widget?.viewModel?.model.setCheckpoint(undefined);366return;367}368369if (confirmation.checkboxChecked) {370await configurationService.updateValue('chat.editing.confirmEditRequestRemoval', false);371}372373// Restore the snapshot to what it was before the request(s) that we deleted374const snapshotRequestId = chatRequests[itemIndex].id;375await session.restoreSnapshot(snapshotRequestId, undefined);376}377}378379registerAction2(class RemoveAction extends Action2 {380constructor() {381super({382id: 'workbench.action.chat.undoEdits',383title: localize2('chat.undoEdits.label', "Undo Requests"),384f1: false,385category: CHAT_CATEGORY,386icon: Codicon.discard,387keybinding: {388primary: KeyCode.Delete,389mac: {390primary: KeyMod.CtrlCmd | KeyCode.Backspace,391},392when: ContextKeyExpr.and(ChatContextKeys.inChatSession, EditorContextKeys.textInputFocus.negate()),393weight: KeybindingWeight.WorkbenchContrib,394},395menu: [396{397id: MenuId.ChatMessageTitle,398group: 'navigation',399order: 2,400when: ContextKeyExpr.and(ContextKeyExpr.equals(`config.${ChatConfiguration.EditRequests}`, 'input').negate(), ContextKeyExpr.equals(`config.${ChatConfiguration.CheckpointsEnabled}`, false), ChatContextKeys.lockedToCodingAgent.negate()),401}402]403});404}405406async run(accessor: ServicesAccessor, ...args: any[]) {407let item: ChatTreeItem | undefined = args[0];408const chatWidgetService = accessor.get(IChatWidgetService);409const configurationService = accessor.get(IConfigurationService);410const widget = chatWidgetService.lastFocusedWidget;411if (!isResponseVM(item) && !isRequestVM(item)) {412item = widget?.getFocus();413}414415if (!item) {416return;417}418419await restoreSnapshotWithConfirmation(accessor, item);420421if (isRequestVM(item) && configurationService.getValue('chat.undoRequests.restoreInput')) {422widget?.focusInput();423widget?.input.setValue(item.messageText, false);424}425}426});427428registerAction2(class RestoreCheckpointAction extends Action2 {429constructor() {430super({431id: 'workbench.action.chat.restoreCheckpoint',432title: localize2('chat.restoreCheckpoint.label', "Restore Checkpoint"),433tooltip: localize2('chat.restoreCheckpoint.tooltip', "Restores workspace and chat to this point"),434f1: false,435category: CHAT_CATEGORY,436keybinding: {437primary: KeyCode.Delete,438mac: {439primary: KeyMod.CtrlCmd | KeyCode.Backspace,440},441when: ContextKeyExpr.and(ChatContextKeys.inChatSession, EditorContextKeys.textInputFocus.negate()),442weight: KeybindingWeight.WorkbenchContrib,443},444menu: [445{446id: MenuId.ChatMessageCheckpoint,447group: 'navigation',448order: 2,449when: ContextKeyExpr.and(ChatContextKeys.isRequest, ChatContextKeys.lockedToCodingAgent.negate())450}451]452});453}454455async run(accessor: ServicesAccessor, ...args: any[]) {456let item: ChatTreeItem | undefined = args[0];457const chatWidgetService = accessor.get(IChatWidgetService);458const widget = chatWidgetService.lastFocusedWidget;459if (!isResponseVM(item) && !isRequestVM(item)) {460item = widget?.getFocus();461}462463if (!item) {464return;465}466467if (isRequestVM(item)) {468widget?.focusInput();469widget?.input.setValue(item.messageText, false);470}471472widget?.viewModel?.model.setCheckpoint(item.id);473await restoreSnapshotWithConfirmation(accessor, item);474}475});476477registerAction2(class RestoreLastCheckpoint extends Action2 {478constructor() {479super({480id: 'workbench.action.chat.restoreLastCheckpoint',481title: localize2('chat.restoreLastCheckpoint.label', "Restore to Last Checkpoint"),482f1: false,483category: CHAT_CATEGORY,484icon: Codicon.discard,485menu: [486{487id: MenuId.ChatMessageFooter,488group: 'navigation',489order: 1,490when: ContextKeyExpr.and(ContextKeyExpr.in(ChatContextKeys.itemId.key, ChatContextKeys.lastItemId.key), ContextKeyExpr.equals(`config.${ChatConfiguration.CheckpointsEnabled}`, true), ChatContextKeys.lockedToCodingAgent.negate()),491}492]493});494}495496async run(accessor: ServicesAccessor, ...args: any[]) {497let item: ChatTreeItem | undefined = args[0];498const chatWidgetService = accessor.get(IChatWidgetService);499const chatService = accessor.get(IChatService);500const widget = chatWidgetService.lastFocusedWidget;501if (!isResponseVM(item) && !isRequestVM(item)) {502item = widget?.getFocus();503}504505if (!item) {506return;507}508509const chatModel = chatService.getSession(item.sessionId);510if (!chatModel) {511return;512}513514const session = chatModel.editingSession;515if (!session) {516return;517}518519await restoreSnapshotWithConfirmation(accessor, item);520521if (isResponseVM(item)) {522widget?.viewModel?.model.setCheckpoint(item.requestId);523const request = chatModel.getRequests().find(request => request.id === item.requestId);524if (request) {525widget?.focusInput();526widget?.input.setValue(request.message.text, false);527}528}529}530});531532registerAction2(class EditAction extends Action2 {533constructor() {534super({535id: 'workbench.action.chat.editRequests',536title: localize2('chat.editRequests.label', "Edit Request"),537f1: false,538category: CHAT_CATEGORY,539icon: Codicon.edit,540keybinding: {541primary: KeyCode.Enter,542when: ContextKeyExpr.and(ChatContextKeys.inChatSession, EditorContextKeys.textInputFocus.negate()),543weight: KeybindingWeight.WorkbenchContrib,544},545menu: [546{547id: MenuId.ChatMessageTitle,548group: 'navigation',549order: 2,550when: ContextKeyExpr.and(ContextKeyExpr.or(ContextKeyExpr.equals(`config.${ChatConfiguration.EditRequests}`, 'hover'), ContextKeyExpr.equals(`config.${ChatConfiguration.EditRequests}`, 'input')))551}552]553});554}555556async run(accessor: ServicesAccessor, ...args: any[]) {557let item: ChatTreeItem | undefined = args[0];558const chatWidgetService = accessor.get(IChatWidgetService);559const widget = chatWidgetService.lastFocusedWidget;560if (!isResponseVM(item) && !isRequestVM(item)) {561item = widget?.getFocus();562}563564if (!item) {565return;566}567568if (isRequestVM(item)) {569widget?.startEditing(item.id);570}571}572});573574registerAction2(class OpenWorkingSetHistoryAction extends Action2 {575576static readonly id = 'chat.openFileUpdatedBySnapshot';577constructor() {578super({579id: OpenWorkingSetHistoryAction.id,580title: localize('chat.openFileUpdatedBySnapshot.label', "Open File"),581menu: [{582id: MenuId.ChatEditingCodeBlockContext,583group: 'navigation',584order: 0,585},]586});587}588589override async run(accessor: ServicesAccessor, ...args: any[]): Promise<void> {590const context: { sessionId: string; requestId: string; uri: URI; stopId: string | undefined } | undefined = args[0];591if (!context?.sessionId) {592return;593}594595const editorService = accessor.get(IEditorService);596await editorService.openEditor({ resource: context.uri });597}598});599600registerAction2(class OpenWorkingSetHistoryAction extends Action2 {601602static readonly id = 'chat.openFileSnapshot';603constructor() {604super({605id: OpenWorkingSetHistoryAction.id,606title: localize('chat.openSnapshot.label', "Open File Snapshot"),607menu: [{608id: MenuId.ChatEditingCodeBlockContext,609group: 'navigation',610order: 1,611},]612});613}614615override async run(accessor: ServicesAccessor, ...args: any[]): Promise<void> {616const context: { sessionId: string; requestId: string; uri: URI; stopId: string | undefined } | undefined = args[0];617if (!context?.sessionId) {618return;619}620621const chatService = accessor.get(IChatService);622const chatEditingService = accessor.get(IChatEditingService);623const editorService = accessor.get(IEditorService);624625const chatModel = chatService.getSession(context.sessionId);626if (!chatModel) {627return;628}629630const snapshot = chatEditingService.getEditingSession(chatModel.sessionId)?.getSnapshotUri(context.requestId, context.uri, context.stopId);631if (snapshot) {632const editor = await editorService.openEditor({ resource: snapshot, label: localize('chatEditing.snapshot', '{0} (Snapshot)', basename(context.uri)), options: { transient: true, activation: EditorActivation.ACTIVATE } });633if (isCodeEditor(editor)) {634editor.updateOptions({ readOnly: true });635}636}637}638});639640registerAction2(class ResolveSymbolsContextAction extends EditingSessionAction {641constructor() {642super({643id: 'workbench.action.edits.addFilesFromReferences',644title: localize2('addFilesFromReferences', "Add Files From References"),645f1: false,646category: CHAT_CATEGORY,647menu: {648id: MenuId.ChatInputSymbolAttachmentContext,649group: 'navigation',650order: 1,651when: ContextKeyExpr.and(ChatContextKeys.chatModeKind.isEqualTo(ChatModeKind.Ask), EditorContextKeys.hasReferenceProvider)652}653});654}655656override async runEditingSessionAction(accessor: ServicesAccessor, editingSession: IChatEditingSession, chatWidget: IChatWidget, ...args: any[]): Promise<void> {657if (args.length === 0 || !isLocation(args[0])) {658return;659}660661const textModelService = accessor.get(ITextModelService);662const languageFeaturesService = accessor.get(ILanguageFeaturesService);663const symbol = args[0] as Location;664665const modelReference = await textModelService.createModelReference(symbol.uri);666const textModel = modelReference.object.textEditorModel;667if (!textModel) {668return;669}670671const position = new Position(symbol.range.startLineNumber, symbol.range.startColumn);672673const [references, definitions, implementations] = await Promise.all([674this.getReferences(position, textModel, languageFeaturesService),675this.getDefinitions(position, textModel, languageFeaturesService),676this.getImplementations(position, textModel, languageFeaturesService)677]);678679// Sort the references, definitions and implementations by680// how important it is that they make it into the working set as it has limited size681const attachments = [];682for (const reference of [...definitions, ...implementations, ...references]) {683attachments.push(chatWidget.attachmentModel.asFileVariableEntry(reference.uri));684}685686chatWidget.attachmentModel.addContext(...attachments);687}688689private async getReferences(position: Position, textModel: ITextModel, languageFeaturesService: ILanguageFeaturesService): Promise<Location[]> {690const referenceProviders = languageFeaturesService.referenceProvider.all(textModel);691692const references = await Promise.all(referenceProviders.map(async (referenceProvider) => {693return await referenceProvider.provideReferences(textModel, position, { includeDeclaration: true }, CancellationToken.None) ?? [];694}));695696return references.flat();697}698699private async getDefinitions(position: Position, textModel: ITextModel, languageFeaturesService: ILanguageFeaturesService): Promise<Location[]> {700const definitionProviders = languageFeaturesService.definitionProvider.all(textModel);701702const definitions = await Promise.all(definitionProviders.map(async (definitionProvider) => {703return await definitionProvider.provideDefinition(textModel, position, CancellationToken.None) ?? [];704}));705706return definitions.flat();707}708709private async getImplementations(position: Position, textModel: ITextModel, languageFeaturesService: ILanguageFeaturesService): Promise<Location[]> {710const implementationProviders = languageFeaturesService.implementationProvider.all(textModel);711712const implementations = await Promise.all(implementationProviders.map(async (implementationProvider) => {713return await implementationProvider.provideImplementation(textModel, position, CancellationToken.None) ?? [];714}));715716return implementations.flat();717}718});719720export class ViewPreviousEditsAction extends EditingSessionAction {721static readonly Id = 'chatEditing.viewPreviousEdits';722static readonly Label = localize('chatEditing.viewPreviousEdits', 'View Previous Edits');723724constructor() {725super({726id: ViewPreviousEditsAction.Id,727title: { value: ViewPreviousEditsAction.Label, original: ViewPreviousEditsAction.Label },728tooltip: ViewPreviousEditsAction.Label,729f1: true,730icon: Codicon.diffMultiple,731precondition: ContextKeyExpr.and(ChatContextKeys.enabled, hasUndecidedChatEditingResourceContextKey.negate()),732menu: [733{734id: MenuId.ChatEditingWidgetToolbar,735group: 'navigation',736order: 4,737when: ContextKeyExpr.and(applyingChatEditsFailedContextKey.negate(), ContextKeyExpr.and(hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey.negate()))738}739],740});741}742743override async runEditingSessionAction(accessor: ServicesAccessor, editingSession: IChatEditingSession, chatWidget: IChatWidget, ...args: any[]): Promise<void> {744await editingSession.show(true);745}746}747registerAction2(ViewPreviousEditsAction);748749/**750* Workbench command to explore accepting working set changes from an extension. Executing751* the command will accept the changes for the provided resources across all edit sessions.752*/753CommandsRegistry.registerCommand('_chat.editSessions.accept', async (accessor: ServicesAccessor, resources: UriComponents[]) => {754if (resources.length === 0) {755return;756}757758const uris = resources.map(resource => URI.revive(resource));759const chatEditingService = accessor.get(IChatEditingService);760for (const editingSession of chatEditingService.editingSessionsObs.get()) {761await editingSession.accept(...uris);762}763});764765766