Path: blob/main/src/vs/workbench/contrib/chat/browser/actions/chatNewActions.ts
4780 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 { Codicon } from '../../../../../base/common/codicons.js';6import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js';7import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js';8import { localize, localize2 } from '../../../../../nls.js';9import { IAccessibilityService } from '../../../../../platform/accessibility/common/accessibility.js';10import { Action2, MenuId, MenuRegistry, registerAction2 } from '../../../../../platform/actions/common/actions.js';11import { CommandsRegistry } from '../../../../../platform/commands/common/commands.js';12import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js';13import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js';14import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js';15import { ActiveEditorContext } from '../../../../common/contextkeys.js';16import { ChatContextKeys } from '../../common/actions/chatContextKeys.js';17import { IChatEditingSession } from '../../common/editing/chatEditingService.js';18import { IChatService } from '../../common/chatService/chatService.js';19import { ChatAgentLocation, ChatModeKind } from '../../common/constants.js';20import { ChatViewId, IChatWidgetService } from '../chat.js';21import { EditingSessionAction, getEditingSessionContext } from '../chatEditing/chatEditingActions.js';22import { ChatEditorInput } from '../widgetHosts/editor/chatEditorInput.js';23import { ACTION_ID_NEW_CHAT, ACTION_ID_NEW_EDIT_SESSION, CHAT_CATEGORY, handleCurrentEditingSession } from './chatActions.js';24import { clearChatEditor } from './chatClear.js';25import { AgentSessionsViewerOrientation } from '../agentSessions/agentSessions.js';2627export interface INewEditSessionActionContext {2829/**30* An initial prompt to write to the chat.31*/32inputValue?: string;3334/**35* Selects opening in agent mode or not. If not set, the current mode is used.36* This is ignored when coming from a chat view title context.37*/38agentMode?: boolean;3940/**41* Whether the inputValue is partial and should wait for further user input.42* If false or not set, the prompt is sent immediately.43*/44isPartialQuery?: boolean;45}4647export function registerNewChatActions() {4849// Add "New Chat" submenu to Chat view menu50MenuRegistry.appendMenuItem(MenuId.ViewTitle, {51submenu: MenuId.ChatNewMenu,52title: localize2('chat.newEdits.label', "New Chat"),53icon: Codicon.plus,54when: ContextKeyExpr.equals('view', ChatViewId),55group: 'navigation',56order: -1,57isSplitButton: true58});5960registerAction2(class NewChatEditorAction extends Action2 {61constructor() {62super({63id: 'workbench.action.chatEditor.newChat',64title: localize2('chat.newChat.label', "New Chat"),65icon: Codicon.plus,66f1: false,67precondition: ChatContextKeys.enabled,68});69}70async run(accessor: ServicesAccessor, ...args: unknown[]) {71await clearChatEditor(accessor);72}73});7475registerAction2(class NewChatAction extends Action2 {76constructor() {77super({78id: ACTION_ID_NEW_CHAT,79title: localize2('chat.newEdits.label', "New Chat"),80category: CHAT_CATEGORY,81icon: Codicon.plus,82precondition: ContextKeyExpr.and(ChatContextKeys.enabled, ChatContextKeys.location.isEqualTo(ChatAgentLocation.Chat)),83f1: true,84menu: [85{86id: MenuId.ChatContext,87group: 'z_clear'88},89{90id: MenuId.ChatNewMenu,91group: '1_open',92order: 1,93},94{95id: MenuId.CompactWindowEditorTitle,96group: 'navigation',97when: ContextKeyExpr.and(ActiveEditorContext.isEqualTo(ChatEditorInput.EditorID), ChatContextKeys.lockedToCodingAgent.negate()),98order: 199}100],101keybinding: {102weight: KeybindingWeight.WorkbenchContrib + 1,103primary: KeyMod.CtrlCmd | KeyCode.KeyN,104secondary: [KeyMod.CtrlCmd | KeyCode.KeyL],105mac: {106primary: KeyMod.CtrlCmd | KeyCode.KeyN,107secondary: [KeyMod.WinCtrl | KeyCode.KeyL]108},109when: ChatContextKeys.inChatSession110}111});112}113114async run(accessor: ServicesAccessor, ...args: unknown[]) {115const accessibilityService = accessor.get(IAccessibilityService);116117const executeCommandContext = args[0] as INewEditSessionActionContext | undefined;118119// Context from toolbar or lastFocusedWidget120const context = getEditingSessionContext(accessor, args);121const { editingSession, chatWidget: widget } = context ?? {};122if (!widget) {123return;124}125126const dialogService = accessor.get(IDialogService);127128const model = widget.viewModel?.model;129if (model && !(await handleCurrentEditingSession(model, undefined, dialogService))) {130return;131}132133await editingSession?.stop();134await widget.clear();135widget.attachmentModel.clear(true);136widget.input.relatedFiles?.clear();137widget.focusInput();138139accessibilityService.alert(localize('newChat', "New chat"));140141if (!executeCommandContext) {142return;143}144145if (typeof executeCommandContext.agentMode === 'boolean') {146widget.input.setChatMode(executeCommandContext.agentMode ? ChatModeKind.Agent : ChatModeKind.Edit);147}148149if (executeCommandContext.inputValue) {150if (executeCommandContext.isPartialQuery) {151widget.setInput(executeCommandContext.inputValue);152} else {153widget.acceptInput(executeCommandContext.inputValue);154}155}156}157});158CommandsRegistry.registerCommandAlias(ACTION_ID_NEW_EDIT_SESSION, ACTION_ID_NEW_CHAT);159160MenuRegistry.appendMenuItem(MenuId.ChatViewSessionTitleNavigationToolbar, {161command: {162id: ACTION_ID_NEW_CHAT,163title: localize2('chat.goBack', "Go Back"),164icon: Codicon.arrowLeft,165},166when: ChatContextKeys.agentSessionsViewerOrientation.notEqualsTo(AgentSessionsViewerOrientation.SideBySide), // when sessions show side by side, no need for a back button167group: 'navigation',168order: 1169});170171registerAction2(class UndoChatEditInteractionAction extends EditingSessionAction {172constructor() {173super({174id: 'workbench.action.chat.undoEdit',175title: localize2('chat.undoEdit.label', "Undo Last Edit"),176category: CHAT_CATEGORY,177icon: Codicon.discard,178precondition: ContextKeyExpr.and(ChatContextKeys.chatEditingCanUndo, ChatContextKeys.enabled),179f1: true,180menu: [{181id: MenuId.ViewTitle,182when: ContextKeyExpr.equals('view', ChatViewId),183group: 'navigation',184order: -3,185isHiddenByDefault: true186}]187});188}189190async runEditingSessionAction(accessor: ServicesAccessor, editingSession: IChatEditingSession) {191await editingSession.undoInteraction();192}193});194195registerAction2(class RedoChatEditInteractionAction extends EditingSessionAction {196constructor() {197super({198id: 'workbench.action.chat.redoEdit',199title: localize2('chat.redoEdit.label', "Redo Last Edit"),200category: CHAT_CATEGORY,201icon: Codicon.redo,202precondition: ContextKeyExpr.and(ChatContextKeys.chatEditingCanRedo, ChatContextKeys.enabled),203f1: true,204menu: [205{206id: MenuId.ViewTitle,207when: ContextKeyExpr.equals('view', ChatViewId),208group: 'navigation',209order: -2,210isHiddenByDefault: true211}212]213});214}215216async runEditingSessionAction(accessor: ServicesAccessor, editingSession: IChatEditingSession) {217const chatService = accessor.get(IChatService);218await editingSession.redoInteraction();219chatService.getSession(editingSession.chatSessionResource)?.setCheckpoint(undefined);220}221});222223registerAction2(class RedoChatCheckpoints extends EditingSessionAction {224constructor() {225super({226id: 'workbench.action.chat.redoEdit2',227title: localize2('chat.redoEdit.label2', "Redo"),228tooltip: localize2('chat.redoEdit.tooltip', "Reapply discarded workspace changes and chat"),229category: CHAT_CATEGORY,230precondition: ContextKeyExpr.and(ChatContextKeys.chatEditingCanRedo, ChatContextKeys.enabled),231f1: true,232menu: [{233id: MenuId.ChatMessageRestoreCheckpoint,234when: ChatContextKeys.lockedToCodingAgent.negate(),235group: 'navigation',236order: -1237}]238});239}240241async runEditingSessionAction(accessor: ServicesAccessor, editingSession: IChatEditingSession) {242const widget = accessor.get(IChatWidgetService);243244while (editingSession.canRedo.get()) {245await editingSession.redoInteraction();246}247248const currentWidget = widget.getWidgetBySessionResource(editingSession.chatSessionResource);249const requestText = currentWidget?.viewModel?.model.checkpoint?.message.text;250251// if the input has the same text that we just restored, clear it.252if (currentWidget?.inputEditor.getValue() === requestText) {253currentWidget?.input.setValue('', false);254}255256currentWidget?.viewModel?.model.setCheckpoint(undefined);257currentWidget?.focusInput();258}259});260}261262263