From 79c28fc19f8a0a12212d63dbd2ff605e07bae76e Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sat, 22 Mar 2025 23:43:36 -0700 Subject: [PATCH] Go back to original edits mode in unified view with all models (#244354) By default, adding setting chat.edits2.enabled to control it. Disallow switching from edits mode to others, unless using edits2. --- .../chat/browser/actions/chatActions.ts | 6 +- .../browser/actions/chatExecuteActions.ts | 80 +++++++++++-------- .../contrib/chat/browser/chatInputPart.ts | 9 ++- .../contrib/chat/common/constants.ts | 1 + 4 files changed, 57 insertions(+), 39 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index c7ab0c27b05..3041fae400d 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -805,19 +805,19 @@ export async function handleCurrentEditingSession(currentEditingSession: IChatEd phrase = phrase ?? defaultPhrase; const { result } = await dialogService.prompt({ title: localize('chat.startEditing.confirmation.title', "Start new chat?"), - message: phrase + ' ' + localize('chat.startEditing.confirmation.pending.message.2', "Do you want to accept pending edits to {0} files?", undecidedEdits.length), + message: phrase + ' ' + localize('chat.startEditing.confirmation.pending.message.2', "Do you want to keep pending edits to {0} files?", undecidedEdits.length), type: 'info', cancelButton: true, buttons: [ { - label: localize('chat.startEditing.confirmation.acceptEdits', "Accept & Continue"), + label: localize('chat.startEditing.confirmation.acceptEdits', "Keep & Continue"), run: async () => { await currentEditingSession.accept(); return true; } }, { - label: localize('chat.startEditing.confirmation.discardEdits', "Discard & Continue"), + label: localize('chat.startEditing.confirmation.discardEdits', "Undo & Continue"), run: async () => { await currentEditingSession.reject(); return true; diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index c0931610c8f..81bf66493ed 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -10,6 +10,7 @@ import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions import { localize, localize2 } from '../../../../../nls.js'; import { Action2, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js'; import { ICommandService } from '../../../../../platform/commands/common/commands.js'; +import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js'; import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; @@ -19,12 +20,12 @@ import { ChatContextKeyExprs, ChatContextKeys } from '../../common/chatContextKe import { IChatEditingService, IChatEditingSession, WorkingSetEntryState } from '../../common/chatEditingService.js'; import { chatVariableLeader } from '../../common/chatParserTypes.js'; import { IChatService } from '../../common/chatService.js'; -import { ChatAgentLocation, ChatMode } from '../../common/constants.js'; +import { ChatAgentLocation, ChatConfiguration, ChatMode } from '../../common/constants.js'; import { ILanguageModelToolsService } from '../../common/languageModelToolsService.js'; import { EditsViewId, IChatWidget, IChatWidgetService } from '../chat.js'; -import { discardAllEditsWithConfirmation, getEditingSessionContext } from '../chatEditing/chatEditingActions.js'; +import { getEditingSessionContext } from '../chatEditing/chatEditingActions.js'; import { ChatViewPane } from '../chatViewPane.js'; -import { CHAT_CATEGORY } from './chatActions.js'; +import { CHAT_CATEGORY, handleCurrentEditingSession } from './chatActions.js'; import { ACTION_ID_NEW_CHAT, ChatDoneActionId } from './chatClearActions.js'; export interface IVoiceChatExecuteActionContext { @@ -146,6 +147,7 @@ class ToggleChatModeAction extends Action2 { async run(accessor: ServicesAccessor, ...args: any[]) { const chatService = accessor.get(IChatService); const commandService = accessor.get(ICommandService); + const configurationService = accessor.get(IConfigurationService); const dialogService = accessor.get(IDialogService); const context = getEditingSessionContext(accessor, args); @@ -154,52 +156,62 @@ class ToggleChatModeAction extends Action2 { } const arg = args.at(0) as IToggleChatModeArgs | undefined; - if (arg?.mode === context.chatWidget.input.currentMode) { + const chatSession = context.chatWidget.viewModel?.model; + const requestCount = chatSession?.getRequests().length ?? 0; + const switchToMode = arg?.mode ?? this.getNextMode(context.chatWidget, requestCount, configurationService); + const needToClearEdits = (!chatService.unifiedViewEnabled || (!configurationService.getValue(ChatConfiguration.Edits2Enabled) && (context.chatWidget.input.currentMode === ChatMode.Edit || switchToMode === ChatMode.Edit))) && requestCount > 0; + + if (switchToMode === context.chatWidget.input.currentMode) { return; } - if (!chatService.unifiedViewEnabled) { - // TODO will not require discarding the session when we are able to switch modes mid-session - const entries = context.editingSession?.entries.get(); - if (context.editingSession && entries && entries.length > 0 && entries.some(entry => entry.state.get() === WorkingSetEntryState.Modified)) { - if (!await discardAllEditsWithConfirmation(accessor, context.editingSession)) { - // User cancelled + if (needToClearEdits) { + // If not in unified view, or not using edits2 and switching into or out of edit mode, ask to discard the session + const phrase = localize('switchMode.confirmPhrase', "Switching chat modes will end your current edit session."); + if (!context.editingSession) { + return; + } + + const currentEdits = context.editingSession.entries.get(); + const undecidedEdits = currentEdits.filter((edit) => edit.state.get() === WorkingSetEntryState.Modified); + if (undecidedEdits.length > 0) { + if (!await handleCurrentEditingSession(context.editingSession, phrase, dialogService)) { return; } } else { - const chatSession = context.chatWidget.viewModel?.model; - if (chatSession?.getRequests().length) { - const confirmation = await dialogService.confirm({ - title: localize('agent.newSession', "Start new session?"), - message: localize('agent.newSessionMessage', "Changing the chat mode will start a new session. Would you like to continue?"), - primaryButton: localize('agent.newSession.confirm', "Yes"), - type: 'info' - }); - if (!confirmation.confirmed) { - return; - } + const confirmation = await dialogService.confirm({ + title: localize('agent.newSession', "Start new session?"), + message: localize('agent.newSessionMessage', "Changing the chat mode will end your current edit session. Would you like to continue?"), + primaryButton: localize('agent.newSession.confirm', "Yes"), + type: 'info' + }); + if (!confirmation.confirmed) { + return; } } } - if (arg?.mode) { - context.chatWidget.input.setChatMode(arg.mode); - } else { - const modes = [ChatMode.Agent, ChatMode.Edit]; - if (context.chatWidget.location === ChatAgentLocation.Panel) { - modes.push(ChatMode.Ask); - } + context.chatWidget.input.setChatMode(switchToMode); - const modeIndex = modes.indexOf(context.chatWidget.input.currentMode); - const newMode = modes[(modeIndex + 1) % modes.length]; - context.chatWidget.input.setChatMode(newMode); - } - - if (!chatService.unifiedViewEnabled && context.chatWidget.viewModel?.model.getRequests().length) { + if (needToClearEdits) { const clearAction = chatService.unifiedViewEnabled ? ACTION_ID_NEW_CHAT : ChatDoneActionId; await commandService.executeCommand(clearAction); } } + + private getNextMode(chatWidget: IChatWidget, requestCount: number, configurationService: IConfigurationService): ChatMode { + const modes = [ChatMode.Agent]; + if (configurationService.getValue(ChatConfiguration.Edits2Enabled) || requestCount === 0) { + modes.push(ChatMode.Edit); + } + if (chatWidget.location === ChatAgentLocation.Panel) { + modes.push(ChatMode.Ask); + } + + const modeIndex = modes.indexOf(chatWidget.input.currentMode); + const newMode = modes[(modeIndex + 1) % modes.length]; + return newMode; + } } export const ToggleRequestPausedActionId = 'workbench.action.chat.toggleRequestPaused'; diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 4cf63a6cc1e..42ba0173b6c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -76,7 +76,7 @@ import { IChatFollowup, IChatService } from '../common/chatService.js'; import { IChatVariablesService } from '../common/chatVariables.js'; import { IChatResponseViewModel } from '../common/chatViewModel.js'; import { ChatInputHistoryMaxEntries, IChatHistoryEntry, IChatInputState, IChatWidgetHistoryService } from '../common/chatWidgetHistoryService.js'; -import { ChatAgentLocation, ChatMode } from '../common/constants.js'; +import { ChatAgentLocation, ChatConfiguration, ChatMode } from '../common/constants.js'; import { ILanguageModelChatMetadataAndIdentifier, ILanguageModelsService } from '../common/languageModels.js'; import { CancelAction, ChatEditingSessionSubmitAction, ChatSubmitAction, ChatSwitchToNextModelActionId, IChatExecuteActionContext, IToggleChatModeArgs, ToggleAgentModeActionId } from './actions/chatExecuteActions.js'; import { AttachToolsAction } from './actions/chatToolActions.js'; @@ -437,6 +437,11 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge this._register(this._onDidChangeCurrentChatMode.event(() => { this.checkModelSupported(); })); + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(ChatConfiguration.Edits2Enabled)) { + this.checkModelSupported(); + } + })); } public switchToNextModel(): void { @@ -470,7 +475,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge private modelSupportedForDefaultAgent(model: ILanguageModelChatMetadataAndIdentifier): boolean { // Probably this logic could live in configuration on the agent, or somewhere else, if it gets more complex - if (this.currentMode === ChatMode.Agent || (this.currentMode === ChatMode.Edit && this.chatService.unifiedViewEnabled)) { + if (this.currentMode === ChatMode.Agent || (this.currentMode === ChatMode.Edit && this.configurationService.getValue(ChatConfiguration.Edits2Enabled))) { if (this.configurationService.getValue('chat.agent.allModels')) { return true; } diff --git a/src/vs/workbench/contrib/chat/common/constants.ts b/src/vs/workbench/contrib/chat/common/constants.ts index 4cebe20bc0a..57c6fb73d1d 100644 --- a/src/vs/workbench/contrib/chat/common/constants.ts +++ b/src/vs/workbench/contrib/chat/common/constants.ts @@ -7,6 +7,7 @@ export enum ChatConfiguration { UnifiedChatView = 'chat.unifiedChatView', UseFileStorage = 'chat.useFileStorage', AgentEnabled = 'chat.agent.enabled', + Edits2Enabled = 'chat.edits2.enabled', } export enum ChatMode {