diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index c7eb41e1b30..9a33d55ae7b 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -121,10 +121,10 @@ class VoiceChatSessionControllerFactory { if (activeCodeEditor) { const inlineChat = InlineChatController.get(activeCodeEditor); if (inlineChat) { - if (!inlineChat.joinCurrentRun()) { + if (!inlineChat.isActive) { inlineChat.run(); } - return VoiceChatSessionControllerFactory.doCreateForChatWidget('inline', inlineChat.chatWidget); + return VoiceChatSessionControllerFactory.doCreateForChatWidget('inline', inlineChat.widget.chatWidget); } } break; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts index b5da56628a4..c26b4b49211 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts @@ -5,7 +5,7 @@ import { EditorContributionInstantiation, registerEditorContribution } from '../../../../editor/browser/editorExtensions.js'; import { IMenuItem, MenuRegistry, registerAction2 } from '../../../../platform/actions/common/actions.js'; -import { InlineChatController } from './inlineChatController.js'; +import { InlineChatController, InlineChatController1, InlineChatController2 } from './inlineChatController.js'; import * as InlineChatActions from './inlineChatActions.js'; import { CTX_INLINE_CHAT_EDITING, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, INLINE_CHAT_ID, MENU_INLINE_CHAT_WIDGET_STATUS } from '../common/inlineChat.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; @@ -23,17 +23,18 @@ import { ChatContextKeys } from '../../chat/common/chatContextKeys.js'; import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js'; import { InlineChatAccessibilityHelp } from './inlineChatAccessibilityHelp.js'; import { InlineChatExpandLineAction, InlineChatHintsController, HideInlineChatHintAction, ShowInlineChatHintAction } from './inlineChatCurrentLine.js'; -import { InlineChatController2, StartSessionAction2, StopSessionAction2 } from './inlineChatController2.js'; registerEditorContribution(InlineChatController2.ID, InlineChatController2, EditorContributionInstantiation.Eager); // EAGER because of notebook dispose/create of editors -registerAction2(StartSessionAction2); -registerAction2(StopSessionAction2); +registerEditorContribution(INLINE_CHAT_ID, InlineChatController1, EditorContributionInstantiation.Eager); // EAGER because of notebook dispose/create of editors +registerEditorContribution(InlineChatController.ID, InlineChatController, EditorContributionInstantiation.Eager); // EAGER because of notebook dispose/create of editors + +registerAction2(InlineChatActions.StopSessionAction2); +registerAction2(InlineChatActions.RevealWidget); // --- browser registerSingleton(IInlineChatSessionService, InlineChatSessionServiceImpl, InstantiationType.Delayed); -registerEditorContribution(INLINE_CHAT_ID, InlineChatController, EditorContributionInstantiation.Eager); // EAGER because of notebook dispose/create of editors registerAction2(InlineChatExpandLineAction); registerAction2(ShowInlineChatHintAction); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index ba75016bb02..7f01bf0d3f5 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -10,8 +10,8 @@ import { EditorAction2 } from '../../../../editor/browser/editorExtensions.js'; import { EmbeddedDiffEditorWidget } from '../../../../editor/browser/widget/diffEditor/embeddedDiffEditorWidget.js'; import { EmbeddedCodeEditorWidget } from '../../../../editor/browser/widget/codeEditor/embeddedCodeEditorWidget.js'; import { EditorContextKeys } from '../../../../editor/common/editorContextKeys.js'; -import { InlineChatController, InlineChatRunOptions } from './inlineChatController.js'; -import { ACTION_ACCEPT_CHANGES, CTX_INLINE_CHAT_HAS_AGENT, CTX_INLINE_CHAT_HAS_STASHED_SESSION, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_VISIBLE, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, MENU_INLINE_CHAT_WIDGET_STATUS, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, CTX_INLINE_CHAT_RESPONSE_TYPE, InlineChatResponseType, ACTION_REGENERATE_RESPONSE, ACTION_VIEW_IN_CHAT, ACTION_TOGGLE_DIFF, CTX_INLINE_CHAT_CHANGE_HAS_DIFF, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, MENU_INLINE_CHAT_ZONE, ACTION_DISCARD_CHANGES, CTX_INLINE_CHAT_POSSIBLE, ACTION_START, CTX_INLINE_CHAT_HAS_AGENT2 } from '../common/inlineChat.js'; +import { InlineChatController, InlineChatController1, InlineChatController2, InlineChatRunOptions } from './inlineChatController.js'; +import { ACTION_ACCEPT_CHANGES, CTX_INLINE_CHAT_HAS_AGENT, CTX_INLINE_CHAT_HAS_STASHED_SESSION, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_VISIBLE, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, MENU_INLINE_CHAT_WIDGET_STATUS, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, CTX_INLINE_CHAT_RESPONSE_TYPE, InlineChatResponseType, ACTION_REGENERATE_RESPONSE, ACTION_VIEW_IN_CHAT, ACTION_TOGGLE_DIFF, CTX_INLINE_CHAT_CHANGE_HAS_DIFF, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, MENU_INLINE_CHAT_ZONE, ACTION_DISCARD_CHANGES, CTX_INLINE_CHAT_POSSIBLE, ACTION_START, CTX_INLINE_CHAT_HAS_AGENT2, CTX_HAS_SESSION } from '../common/inlineChat.js'; import { localize, localize2 } from '../../../../nls.js'; import { Action2, IAction2Options, MenuId } from '../../../../platform/actions/common/actions.js'; import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; @@ -20,7 +20,7 @@ import { KeybindingWeight } from '../../../../platform/keybinding/common/keybind import { IEditorService } from '../../../services/editor/common/editorService.js'; import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js'; import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from '../../../../platform/accessibility/common/accessibility.js'; -import { CommandsRegistry, ICommandService } from '../../../../platform/commands/common/commands.js'; +import { CommandsRegistry } from '../../../../platform/commands/common/commands.js'; import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js'; import { IPreferencesService } from '../../../services/preferences/common/preferences.js'; import { ILogService } from '../../../../platform/log/common/log.js'; @@ -28,6 +28,8 @@ import { IChatService } from '../../chat/common/chatService.js'; import { ChatContextKeys } from '../../chat/common/chatContextKeys.js'; import { HunkInformation } from './inlineChatSession.js'; import { IChatWidgetService } from '../../chat/browser/chat.js'; +import { IInlineChatSessionService } from './inlineChatSessionService.js'; +import { ctxIsGlobalEditingSession } from '../../chat/browser/chatEditing/chatEditingEditorController.js'; CommandsRegistry.registerCommandAlias('interactiveEditor.start', 'inlineChat.start'); CommandsRegistry.registerCommandAlias('interactive.acceptChanges', ACTION_ACCEPT_CHANGES); @@ -51,10 +53,10 @@ export class StartSessionAction extends Action2 { super({ id: ACTION_START, title: localize2('run', 'Editor Inline Chat'), - category: AbstractInlineChatAction.category, + category: AbstractInline1ChatAction.category, f1: true, precondition: ContextKeyExpr.and( - CTX_INLINE_CHAT_HAS_AGENT, + ContextKeyExpr.or(CTX_INLINE_CHAT_HAS_AGENT, CTX_INLINE_CHAT_HAS_AGENT2), CTX_INLINE_CHAT_POSSIBLE, EditorContextKeys.writable, EditorContextKeys.editorSimpleInput.negate() @@ -74,8 +76,6 @@ export class StartSessionAction extends Action2 { } override run(accessor: ServicesAccessor, ...args: any[]): any { - const commandService = accessor.get(ICommandService); - const contextKeyService = accessor.get(IContextKeyService); const codeEditorService = accessor.get(ICodeEditorService); const editor = codeEditorService.getActiveCodeEditor(); if (!editor || editor.isSimpleWidget) { @@ -83,9 +83,6 @@ export class StartSessionAction extends Action2 { return; } - if (contextKeyService.contextMatchesRules(CTX_INLINE_CHAT_HAS_AGENT2)) { - return commandService.executeCommand('inlineChat2.start', ...args); - } // precondition does hold return editor.invokeWithinContext((editorAccessor) => { @@ -120,12 +117,40 @@ export class StartSessionAction extends Action2 { } } +export class FocusInlineChat extends EditorAction2 { + + constructor() { + super({ + id: 'inlineChat.focus', + title: localize2('focus', "Focus Input"), + f1: true, + category: AbstractInline1ChatAction.category, + precondition: ContextKeyExpr.and(EditorContextKeys.editorTextFocus, CTX_INLINE_CHAT_VISIBLE, CTX_INLINE_CHAT_FOCUSED.negate(), CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), + keybinding: [{ + weight: KeybindingWeight.EditorCore + 10, // win against core_command + when: ContextKeyExpr.and(CTX_INLINE_CHAT_OUTER_CURSOR_POSITION.isEqualTo('above'), EditorContextKeys.isEmbeddedDiffEditor.negate()), + primary: KeyMod.CtrlCmd | KeyCode.DownArrow, + }, { + weight: KeybindingWeight.EditorCore + 10, // win against core_command + when: ContextKeyExpr.and(CTX_INLINE_CHAT_OUTER_CURSOR_POSITION.isEqualTo('below'), EditorContextKeys.isEmbeddedDiffEditor.negate()), + primary: KeyMod.CtrlCmd | KeyCode.UpArrow, + }] + }); + } + + override runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) { + InlineChatController.get(editor)?.focus(); + } +} + +//#region --- VERSION 1 + export class UnstashSessionAction extends EditorAction2 { constructor() { super({ id: 'inlineChat.unstash', title: localize2('unstash', "Resume Last Dismissed Inline Chat"), - category: AbstractInlineChatAction.category, + category: AbstractInline1ChatAction.category, precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_HAS_STASHED_SESSION, EditorContextKeys.writable), keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -135,7 +160,7 @@ export class UnstashSessionAction extends EditorAction2 { } override async runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) { - const ctrl = InlineChatController.get(editor); + const ctrl = InlineChatController1.get(editor); if (ctrl) { const session = ctrl.unstashLastSession(); if (session) { @@ -147,14 +172,14 @@ export class UnstashSessionAction extends EditorAction2 { } } -export abstract class AbstractInlineChatAction extends EditorAction2 { +export abstract class AbstractInline1ChatAction extends EditorAction2 { static readonly category = localize2('cat', "Inline Chat"); constructor(desc: IAction2Options) { super({ ...desc, - category: AbstractInlineChatAction.category, + category: AbstractInline1ChatAction.category, precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_HAS_AGENT, desc.precondition) }); } @@ -163,7 +188,7 @@ export abstract class AbstractInlineChatAction extends EditorAction2 { const editorService = accessor.get(IEditorService); const logService = accessor.get(ILogService); - let ctrl = InlineChatController.get(editor); + let ctrl = InlineChatController1.get(editor); if (!ctrl) { const { activeTextEditorControl } = editorService; if (isCodeEditor(activeTextEditorControl)) { @@ -171,7 +196,7 @@ export abstract class AbstractInlineChatAction extends EditorAction2 { } else if (isDiffEditor(activeTextEditorControl)) { editor = activeTextEditorControl.getModifiedEditor(); } - ctrl = InlineChatController.get(editor); + ctrl = InlineChatController1.get(editor); } if (!ctrl) { @@ -195,10 +220,10 @@ export abstract class AbstractInlineChatAction extends EditorAction2 { this.runInlineChatCommand(accessor, ctrl, editor, ..._args); } - abstract runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController, editor: ICodeEditor, ...args: any[]): void; + abstract runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController1, editor: ICodeEditor, ...args: any[]): void; } -export class ArrowOutUpAction extends AbstractInlineChatAction { +export class ArrowOutUpAction extends AbstractInline1ChatAction { constructor() { super({ id: 'inlineChat.arrowOutUp', @@ -211,12 +236,12 @@ export class ArrowOutUpAction extends AbstractInlineChatAction { }); } - runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController, _editor: ICodeEditor, ..._args: any[]): void { + runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, ..._args: any[]): void { ctrl.arrowOut(true); } } -export class ArrowOutDownAction extends AbstractInlineChatAction { +export class ArrowOutDownAction extends AbstractInline1ChatAction { constructor() { super({ id: 'inlineChat.arrowOutDown', @@ -229,39 +254,12 @@ export class ArrowOutDownAction extends AbstractInlineChatAction { }); } - runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController, _editor: ICodeEditor, ..._args: any[]): void { + runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, ..._args: any[]): void { ctrl.arrowOut(false); } } -export class FocusInlineChat extends EditorAction2 { - - constructor() { - super({ - id: 'inlineChat.focus', - title: localize2('focus', "Focus Input"), - f1: true, - category: AbstractInlineChatAction.category, - precondition: ContextKeyExpr.and(EditorContextKeys.editorTextFocus, CTX_INLINE_CHAT_VISIBLE, CTX_INLINE_CHAT_FOCUSED.negate(), CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), - keybinding: [{ - weight: KeybindingWeight.EditorCore + 10, // win against core_command - when: ContextKeyExpr.and(CTX_INLINE_CHAT_OUTER_CURSOR_POSITION.isEqualTo('above'), EditorContextKeys.isEmbeddedDiffEditor.negate()), - primary: KeyMod.CtrlCmd | KeyCode.DownArrow, - }, { - weight: KeybindingWeight.EditorCore + 10, // win against core_command - when: ContextKeyExpr.and(CTX_INLINE_CHAT_OUTER_CURSOR_POSITION.isEqualTo('below'), EditorContextKeys.isEmbeddedDiffEditor.negate()), - primary: KeyMod.CtrlCmd | KeyCode.UpArrow, - }] - }); - } - - override runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) { - InlineChatController.get(editor)?.focus(); - } -} - - -export class AcceptChanges extends AbstractInlineChatAction { +export class AcceptChanges extends AbstractInline1ChatAction { constructor() { super({ @@ -292,12 +290,12 @@ export class AcceptChanges extends AbstractInlineChatAction { }); } - override async runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController, _editor: ICodeEditor, hunk?: HunkInformation | any): Promise { + override async runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, hunk?: HunkInformation | any): Promise { ctrl.acceptHunk(hunk); } } -export class DiscardHunkAction extends AbstractInlineChatAction { +export class DiscardHunkAction extends AbstractInline1ChatAction { constructor() { super({ @@ -318,12 +316,12 @@ export class DiscardHunkAction extends AbstractInlineChatAction { }); } - async runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController, _editor: ICodeEditor, hunk?: HunkInformation | any): Promise { + async runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, hunk?: HunkInformation | any): Promise { return ctrl.discardHunk(hunk); } } -export class RerunAction extends AbstractInlineChatAction { +export class RerunAction extends AbstractInline1ChatAction { constructor() { super({ id: ACTION_REGENERATE_RESPONSE, @@ -349,7 +347,7 @@ export class RerunAction extends AbstractInlineChatAction { }); } - override async runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController, _editor: ICodeEditor, ..._args: any[]): Promise { + override async runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, ..._args: any[]): Promise { const chatService = accessor.get(IChatService); const chatWidgetService = accessor.get(IChatWidgetService); const model = ctrl.chatWidget.viewModel?.model; @@ -370,7 +368,7 @@ export class RerunAction extends AbstractInlineChatAction { } } -export class CloseAction extends AbstractInlineChatAction { +export class CloseAction extends AbstractInline1ChatAction { constructor() { super({ @@ -394,12 +392,12 @@ export class CloseAction extends AbstractInlineChatAction { }); } - async runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController, _editor: ICodeEditor, ..._args: any[]): Promise { + async runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, ..._args: any[]): Promise { ctrl.cancelSession(); } } -export class ConfigureInlineChatAction extends AbstractInlineChatAction { +export class ConfigureInlineChatAction extends AbstractInline1ChatAction { constructor() { super({ id: 'inlineChat.configure', @@ -415,12 +413,12 @@ export class ConfigureInlineChatAction extends AbstractInlineChatAction { }); } - async runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController, _editor: ICodeEditor, ..._args: any[]): Promise { + async runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, ..._args: any[]): Promise { accessor.get(IPreferencesService).openSettings({ query: 'inlineChat' }); } } -export class MoveToNextHunk extends AbstractInlineChatAction { +export class MoveToNextHunk extends AbstractInline1ChatAction { constructor() { super({ @@ -435,12 +433,12 @@ export class MoveToNextHunk extends AbstractInlineChatAction { }); } - override runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController, editor: ICodeEditor, ...args: any[]): void { + override runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController1, editor: ICodeEditor, ...args: any[]): void { ctrl.moveHunk(true); } } -export class MoveToPreviousHunk extends AbstractInlineChatAction { +export class MoveToPreviousHunk extends AbstractInline1ChatAction { constructor() { super({ @@ -455,12 +453,12 @@ export class MoveToPreviousHunk extends AbstractInlineChatAction { }); } - override runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController, editor: ICodeEditor, ...args: any[]): void { + override runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController1, editor: ICodeEditor, ...args: any[]): void { ctrl.moveHunk(false); } } -export class ViewInChatAction extends AbstractInlineChatAction { +export class ViewInChatAction extends AbstractInline1ChatAction { constructor() { super({ id: ACTION_VIEW_IN_CHAT, @@ -489,12 +487,12 @@ export class ViewInChatAction extends AbstractInlineChatAction { } }); } - override runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController, _editor: ICodeEditor, ..._args: any[]) { + override runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, ..._args: any[]) { return ctrl.viewInChat(); } } -export class ToggleDiffForChange extends AbstractInlineChatAction { +export class ToggleDiffForChange extends AbstractInline1ChatAction { constructor() { super({ @@ -518,7 +516,119 @@ export class ToggleDiffForChange extends AbstractInlineChatAction { }); } - override runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController, _editor: ICodeEditor, hunkInfo: HunkInformation | any): void { + override runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, hunkInfo: HunkInformation | any): void { ctrl.toggleDiff(hunkInfo); } } + +//#endregion + + +//#region --- VERSION 2 +abstract class AbstractInline2ChatAction extends EditorAction2 { + + static readonly category = localize2('cat', "Inline Chat"); + + constructor(desc: IAction2Options) { + super({ + ...desc, + category: AbstractInline2ChatAction.category, + precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_HAS_AGENT2, desc.precondition) + }); + } + + override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) { + const editorService = accessor.get(IEditorService); + const logService = accessor.get(ILogService); + + let ctrl = InlineChatController2.get(editor); + if (!ctrl) { + const { activeTextEditorControl } = editorService; + if (isCodeEditor(activeTextEditorControl)) { + editor = activeTextEditorControl; + } else if (isDiffEditor(activeTextEditorControl)) { + editor = activeTextEditorControl.getModifiedEditor(); + } + ctrl = InlineChatController2.get(editor); + } + + if (!ctrl) { + logService.warn('[IE] NO controller found for action', this.desc.id, editor.getModel()?.uri); + return; + } + + if (editor instanceof EmbeddedCodeEditorWidget) { + editor = editor.getParentEditor(); + } + if (!ctrl) { + for (const diffEditor of accessor.get(ICodeEditorService).listDiffEditors()) { + if (diffEditor.getOriginalEditor() === editor || diffEditor.getModifiedEditor() === editor) { + if (diffEditor instanceof EmbeddedDiffEditorWidget) { + this.runEditorCommand(accessor, diffEditor.getParentEditor(), ..._args); + } + } + } + return; + } + this.runInlineChatCommand(accessor, ctrl, editor, ..._args); + } + + abstract runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController2, editor: ICodeEditor, ...args: any[]): void; +} + +export class StopSessionAction2 extends AbstractInline2ChatAction { + constructor() { + super({ + id: 'inlineChat2.stop', + title: localize2('stop', "Stop"), + f1: true, + precondition: ContextKeyExpr.and(CTX_HAS_SESSION.isEqualTo('empty'), CTX_INLINE_CHAT_VISIBLE), + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyCode.Escape, + secondary: [KeyMod.CtrlCmd | KeyCode.KeyI] + }, + }); + } + + runInlineChatCommand(accessor: ServicesAccessor, _ctrl: InlineChatController2, editor: ICodeEditor, ...args: any[]): void { + const inlineChatSessions = accessor.get(IInlineChatSessionService); + if (!editor.hasModel()) { + return; + } + const textModel = editor.getModel(); + inlineChatSessions.getSession2(textModel.uri)?.dispose(); + } +} + +export class RevealWidget extends AbstractInline2ChatAction { + constructor() { + super({ + id: 'inlineChat2.reveal', + title: localize2('reveal', "Toggle Inline Chat"), + f1: true, + icon: Codicon.copilot, + precondition: ContextKeyExpr.and( + CTX_HAS_SESSION, + ), + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.CtrlCmd | KeyCode.KeyI + }, + menu: { + id: MenuId.ChatEditingEditorContent, + when: ContextKeyExpr.and( + CTX_HAS_SESSION, + ctxIsGlobalEditingSession.negate(), + ), + group: 'navigate', + order: 4, + } + }); + } + + runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController2, _editor: ICodeEditor): void { + ctrl.toggleWidgetUntilNextRequest(); + ctrl.markActiveController(); + } +} diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 95b2f907292..a28735ca7ae 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -12,12 +12,13 @@ import { Emitter, Event } from '../../../../base/common/event.js'; import { Lazy } from '../../../../base/common/lazy.js'; import { DisposableStore, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; import { MovingAverage } from '../../../../base/common/numbers.js'; -import { autorun } from '../../../../base/common/observable.js'; +import { autorun, autorunWithStore, constObservable, derived, IObservable, observableFromEvent, observableSignalFromEvent, observableValue, transaction } from '../../../../base/common/observable.js'; import { isEqual } from '../../../../base/common/resources.js'; import { StopWatch } from '../../../../base/common/stopwatch.js'; import { assertType } from '../../../../base/common/types.js'; import { generateUuid } from '../../../../base/common/uuid.js'; import { ICodeEditor, isCodeEditor } from '../../../../editor/browser/editorBrowser.js'; +import { observableCodeEditor } from '../../../../editor/browser/observableCodeEditor.js'; import { EditorOption } from '../../../../editor/common/config/editorOptions.js'; import { IPosition, Position } from '../../../../editor/common/core/position.js'; import { IRange, Range } from '../../../../editor/common/core/range.js'; @@ -38,6 +39,7 @@ import { ILogService } from '../../../../platform/log/common/log.js'; import { IEditorService, SIDE_GROUP } from '../../../services/editor/common/editorService.js'; import { IViewsService } from '../../../services/views/common/viewsService.js'; import { showChatView } from '../../chat/browser/chat.js'; +import { ChatEditorOverlayController } from '../../chat/browser/chatEditing/chatEditingEditorOverlay.js'; import { IChatWidgetLocationOptions } from '../../chat/browser/chatWidget.js'; import { ChatAgentLocation } from '../../chat/common/chatAgents.js'; import { ChatContextKeys } from '../../chat/common/chatContextKeys.js'; @@ -45,9 +47,9 @@ import { IChatEditingService, WorkingSetEntryState } from '../../chat/common/cha import { ChatModel, ChatRequestRemovalReason, IChatRequestModel, IChatTextEditGroup, IChatTextEditGroupState, IResponse } from '../../chat/common/chatModel.js'; import { IChatService } from '../../chat/common/chatService.js'; import { INotebookEditorService } from '../../notebook/browser/services/notebookEditorService.js'; -import { CTX_INLINE_CHAT_EDITING, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, CTX_INLINE_CHAT_RESPONSE_TYPE, CTX_INLINE_CHAT_VISIBLE, INLINE_CHAT_ID, InlineChatConfigKeys, InlineChatResponseType } from '../common/inlineChat.js'; +import { CTX_HAS_SESSION, CTX_INLINE_CHAT_EDITING, CTX_INLINE_CHAT_HAS_AGENT2, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, CTX_INLINE_CHAT_RESPONSE_TYPE, CTX_INLINE_CHAT_VISIBLE, INLINE_CHAT_ID, InlineChatConfigKeys, InlineChatResponseType } from '../common/inlineChat.js'; import { HunkInformation, Session, StashedSession } from './inlineChatSession.js'; -import { IInlineChatSessionService } from './inlineChatSessionService.js'; +import { IInlineChatSession2, IInlineChatSessionService } from './inlineChatSessionService.js'; import { InlineChatError } from './inlineChatSessionServiceImpl.js'; import { HunkAction, IEditObserver, LiveStrategy, ProgressingEditsOptions } from './inlineChatStrategies.js'; import { EditorBasedInlineChatWidget } from './inlineChatWidget.js'; @@ -99,8 +101,66 @@ export abstract class InlineChatRunOptions { export class InlineChatController implements IEditorContribution { + static ID = 'editor.contrib.inlineChatController'; + static get(editor: ICodeEditor) { - return editor.getContribution(INLINE_CHAT_ID); + return editor.getContribution(InlineChatController.ID); + } + + private readonly _delegate: IObservable; + + constructor( + editor: ICodeEditor, + @IContextKeyService contextKeyService: IContextKeyService, + ) { + + const inlineChat2 = observableFromEvent(this, Event.filter(contextKeyService.onDidChangeContext, e => e.affectsSome(new Set(CTX_INLINE_CHAT_HAS_AGENT2.keys()))), () => contextKeyService.contextMatchesRules(CTX_INLINE_CHAT_HAS_AGENT2)); + + this._delegate = derived(r => { + if (inlineChat2.read(r)) { + return InlineChatController2.get(editor)!; + } else { + return InlineChatController1.get(editor)!; + } + }); + } + + dispose(): void { + + } + + get isActive(): boolean { + return this._delegate.get().isActive; + } + + async run(arg?: InlineChatRunOptions): Promise { + return this._delegate.get().run(arg); + } + + focus() { + return this._delegate.get().focus(); + } + + get widget(): EditorBasedInlineChatWidget { + return this._delegate.get().widget; + } + + getWidgetPosition() { + return this._delegate.get().getWidgetPosition(); + } + + acceptSession() { + return this._delegate.get().acceptSession(); + } +} + +/** + * @deprecated + */ +export class InlineChatController1 implements IEditorContribution { + + static get(editor: ICodeEditor) { + return editor.getContribution(INLINE_CHAT_ID); } private _isDisposed: boolean = false; @@ -645,7 +705,7 @@ export class InlineChatController implements IEditorContribution { CancellationToken.None); // TODO@ulugbekna: add proper cancellation? - InlineChatController.get(newEditor)?.run({ existingSession: newSession }); + InlineChatController1.get(newEditor)?.run({ existingSession: newSession }); next = State.CANCEL; responsePromise.complete(); @@ -1102,8 +1162,252 @@ export class InlineChatController implements IEditorContribution { joinCurrentRun(): Promise | undefined { return this._currentRun; } + + get isActive() { + return Boolean(this._currentRun); + } } +export class InlineChatController2 implements IEditorContribution { + + static readonly ID = 'editor.contrib.inlineChatController2'; + + static get(editor: ICodeEditor): InlineChatController2 | undefined { + return editor.getContribution(InlineChatController2.ID) ?? undefined; + } + + private readonly _store = new DisposableStore(); + private readonly _showWidgetOverrideObs = observableValue(this, false); + private readonly _isActiveController = observableValue(this, false); + private readonly _zone: Lazy; + + private readonly _currentSession: IObservable; + + get widget(): EditorBasedInlineChatWidget { + return this._zone.value.widget; + } + + get isActive() { + return Boolean(this._currentSession.get()); + } + + constructor( + private readonly _editor: ICodeEditor, + @IInstantiationService private readonly _instaService: IInstantiationService, + @INotebookEditorService private readonly _notebookEditorService: INotebookEditorService, + @IInlineChatSessionService private readonly _inlineChatSessions: IInlineChatSessionService, + @IContextKeyService contextKeyService: IContextKeyService, + ) { + + const ctxHasSession = CTX_HAS_SESSION.bindTo(contextKeyService); + const ctxInlineChatVisible = CTX_INLINE_CHAT_VISIBLE.bindTo(contextKeyService); + + this._zone = new Lazy(() => { + + + const location: IChatWidgetLocationOptions = { + location: ChatAgentLocation.Editor, + resolveData: () => { + assertType(this._editor.hasModel()); + + return { + type: ChatAgentLocation.Editor, + selection: this._editor.getSelection(), + document: this._editor.getModel().uri, + wholeRange: this._editor.getSelection(), + }; + } + }; + + // inline chat in notebooks + // check if this editor is part of a notebook editor + // and iff so, use the notebook location but keep the resolveData + // talk about editor data + for (const notebookEditor of this._notebookEditorService.listNotebookEditors()) { + for (const [, codeEditor] of notebookEditor.codeEditors) { + if (codeEditor === this._editor) { + location.location = ChatAgentLocation.Notebook; + break; + } + } + } + + const result = this._instaService.createInstance(InlineChatZoneWidget, + location, + { + enableWorkingSet: 'implicit', + rendererOptions: { + renderCodeBlockPills: true, + renderTextEditsAsSummary: uri => isEqual(uri, _editor.getModel()?.uri) + } + }, + this._editor + ); + + result.domNode.classList.add('inline-chat-2'); + + return result; + }); + + + const editorObs = observableCodeEditor(_editor); + + const sessionsSignal = observableSignalFromEvent(this, _inlineChatSessions.onDidChangeSessions); + + this._currentSession = derived(r => { + sessionsSignal.read(r); + const model = editorObs.model.read(r); + const value = model && _inlineChatSessions.getSession2(model.uri); + return value ?? undefined; + }); + + + this._store.add(autorunWithStore((r, store) => { + const session = this._currentSession.read(r); + + if (!session) { + ctxHasSession.set(undefined); + this._isActiveController.set(false, undefined); + } else { + const checkRequests = () => ctxHasSession.set(session.chatModel.getRequests().length === 0 ? 'empty' : 'active'); + store.add(session.chatModel.onDidChange(checkRequests)); + checkRequests(); + } + })); + + const visibleSessionObs = observableValue(this, undefined); + + this._store.add(autorunWithStore((r, store) => { + + const session = this._currentSession.read(r); + const isActive = this._isActiveController.read(r); + + if (!session || !isActive) { + visibleSessionObs.set(undefined, undefined); + return; + } + + const { chatModel } = session; + const showShowUntil = this._showWidgetOverrideObs.read(r); + const hasNoRequests = chatModel.getRequests().length === 0; + + store.add(chatModel.onDidChange(e => { + if (e.kind === 'addRequest') { + transaction(tx => { + this._showWidgetOverrideObs.set(false, tx); + visibleSessionObs.set(undefined, tx); + }); + } + })); + + if (showShowUntil || hasNoRequests) { + visibleSessionObs.set(session, undefined); + } else { + visibleSessionObs.set(undefined, undefined); + } + })); + + this._store.add(autorun(r => { + + const session = visibleSessionObs.read(r); + + if (!session) { + this._zone.rawValue?.hide(); + _editor.focus(); + ctxInlineChatVisible.reset(); + } else { + ctxInlineChatVisible.set(true); + this._zone.value.widget.setChatModel(session.chatModel); + if (!this._zone.value.position) { + this._zone.value.show(session.initialPosition); + } + this._zone.value.reveal(this._zone.value.position!); + this._zone.value.widget.focus(); + session.editingSession.getEntry(session.uri)?.autoAcceptController.get()?.cancel(); + } + })); + + this._store.add(autorun(r => { + + const overlay = ChatEditorOverlayController.get(_editor)!; + const session = this._currentSession.read(r); + const model = editorObs.model.read(r); + if (!session || !model) { + overlay.hide(); + return; + } + + const lastResponse = observableFromEvent(this, session.chatModel.onDidChange, () => session.chatModel.getRequests().at(-1)?.response); + const response = lastResponse.read(r); + + const isInProgress = response + ? observableFromEvent(this, response.onDidChange, () => !response.isComplete) + : constObservable(false); + + if (isInProgress.read(r)) { + overlay.showRequest(session.editingSession); + } else if (session.editingSession.getEntry(session.uri)?.state.get() !== WorkingSetEntryState.Modified) { + overlay.hide(); + } + })); + } + + dispose(): void { + this._store.dispose(); + } + + toggleWidgetUntilNextRequest() { + const value = this._showWidgetOverrideObs.get(); + this._showWidgetOverrideObs.set(!value, undefined); + } + + + getWidgetPosition(): Position | undefined { + return this._zone.rawValue?.position; + } + + focus() { + this._zone.rawValue?.widget.focus(); + } + + markActiveController() { + this._isActiveController.set(true, undefined); + } + + async run(arg?: InlineChatRunOptions): Promise { + assertType(this._editor.hasModel()); + + const uri = this._editor.getModel().uri; + const session = await this._inlineChatSessions.createSession2(this._editor, uri, CancellationToken.None); + + this.markActiveController(); + + if (arg && InlineChatRunOptions.isInlineChatRunOptions(arg)) { + if (arg.initialRange) { + this._editor.revealRange(arg.initialRange); + } + if (arg.initialSelection) { + this._editor.setSelection(arg.initialSelection); + } + if (arg.message) { + this._zone.value.widget.chatWidget.setInput(arg.message); + if (arg.autoSend) { + await this._zone.value.widget.chatWidget.acceptInput(); + } + } + } + + await Event.toPromise(session.editingSession.onDidDispose); + + const rejected = session.editingSession.getEntry(uri)?.state.get() === WorkingSetEntryState.Rejected; + return !rejected; + } + + acceptSession() { + const value = this._currentSession.get(); + value?.editingSession.accept(); + } +} export async function reviewEdits(accessor: ServicesAccessor, editor: ICodeEditor, stream: AsyncIterable, token: CancellationToken): Promise { if (!editor.hasModel()) { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController2.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController2.ts deleted file mode 100644 index 458683cab4b..00000000000 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController2.ts +++ /dev/null @@ -1,427 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { CancellationToken } from '../../../../base/common/cancellation.js'; -import { Codicon } from '../../../../base/common/codicons.js'; -import { Event } from '../../../../base/common/event.js'; -import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; -import { Lazy } from '../../../../base/common/lazy.js'; -import { DisposableStore } from '../../../../base/common/lifecycle.js'; -import { autorun, autorunWithStore, constObservable, derived, IObservable, observableFromEvent, observableSignalFromEvent, observableValue, transaction } from '../../../../base/common/observable.js'; -import { isEqual } from '../../../../base/common/resources.js'; -import { assertType } from '../../../../base/common/types.js'; -import { ICodeEditor, isCodeEditor, isDiffEditor } from '../../../../editor/browser/editorBrowser.js'; -import { EditorAction2, ServicesAccessor } from '../../../../editor/browser/editorExtensions.js'; -import { observableCodeEditor } from '../../../../editor/browser/observableCodeEditor.js'; -import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js'; -import { EmbeddedCodeEditorWidget } from '../../../../editor/browser/widget/codeEditor/embeddedCodeEditorWidget.js'; -import { EmbeddedDiffEditorWidget } from '../../../../editor/browser/widget/diffEditor/embeddedDiffEditorWidget.js'; -import { Position } from '../../../../editor/common/core/position.js'; -import { IEditorContribution } from '../../../../editor/common/editorCommon.js'; -import { EditorContextKeys } from '../../../../editor/common/editorContextKeys.js'; -import { localize, localize2 } from '../../../../nls.js'; -import { IAction2Options, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; -import { ContextKeyExpr, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; -import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; -import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; -import { ILogService } from '../../../../platform/log/common/log.js'; -import { IEditorService } from '../../../services/editor/common/editorService.js'; -import { ctxIsGlobalEditingSession } from '../../chat/browser/chatEditing/chatEditingEditorController.js'; -import { ChatEditorOverlayController } from '../../chat/browser/chatEditing/chatEditingEditorOverlay.js'; -import { IChatWidgetLocationOptions } from '../../chat/browser/chatWidget.js'; -import { ChatAgentLocation } from '../../chat/common/chatAgents.js'; -import { WorkingSetEntryState } from '../../chat/common/chatEditingService.js'; -import { INotebookEditorService } from '../../notebook/browser/services/notebookEditorService.js'; -import { CTX_INLINE_CHAT_HAS_AGENT2, CTX_INLINE_CHAT_POSSIBLE, CTX_INLINE_CHAT_VISIBLE } from '../common/inlineChat.js'; -import { InlineChatRunOptions } from './inlineChatController.js'; -import { IInlineChatSession2, IInlineChatSessionService } from './inlineChatSessionService.js'; -import { EditorBasedInlineChatWidget } from './inlineChatWidget.js'; -import { InlineChatZoneWidget } from './inlineChatZoneWidget.js'; - - -export const CTX_HAS_SESSION = new RawContextKey('inlineChatHasSession', undefined, localize('chat.hasInlineChatSession', "The current editor has an active inline chat session")); - - -export class InlineChatController2 implements IEditorContribution { - - static readonly ID = 'editor.contrib.inlineChatController2'; - - static get(editor: ICodeEditor): InlineChatController2 | undefined { - return editor.getContribution(InlineChatController2.ID) ?? undefined; - } - - private readonly _store = new DisposableStore(); - private readonly _showWidgetOverrideObs = observableValue(this, false); - private readonly _isActiveController = observableValue(this, false); - private readonly _zone: Lazy; - - private readonly _currentSession: IObservable; - - get widget(): EditorBasedInlineChatWidget { - return this._zone.value.widget; - } - - constructor( - private readonly _editor: ICodeEditor, - @IInstantiationService private readonly _instaService: IInstantiationService, - @INotebookEditorService private readonly _notebookEditorService: INotebookEditorService, - @IInlineChatSessionService private readonly _inlineChatSessions: IInlineChatSessionService, - @IContextKeyService contextKeyService: IContextKeyService, - ) { - - const ctxHasSession = CTX_HAS_SESSION.bindTo(contextKeyService); - const ctxInlineChatVisible = CTX_INLINE_CHAT_VISIBLE.bindTo(contextKeyService); - - this._zone = new Lazy(() => { - - - const location: IChatWidgetLocationOptions = { - location: ChatAgentLocation.Editor, - resolveData: () => { - assertType(this._editor.hasModel()); - - return { - type: ChatAgentLocation.Editor, - selection: this._editor.getSelection(), - document: this._editor.getModel().uri, - wholeRange: this._editor.getSelection(), - }; - } - }; - - // inline chat in notebooks - // check if this editor is part of a notebook editor - // and iff so, use the notebook location but keep the resolveData - // talk about editor data - for (const notebookEditor of this._notebookEditorService.listNotebookEditors()) { - for (const [, codeEditor] of notebookEditor.codeEditors) { - if (codeEditor === this._editor) { - location.location = ChatAgentLocation.Notebook; - break; - } - } - } - - const result = this._instaService.createInstance(InlineChatZoneWidget, - location, - { - enableWorkingSet: 'implicit', - rendererOptions: { - renderCodeBlockPills: true, - renderTextEditsAsSummary: uri => isEqual(uri, _editor.getModel()?.uri) - } - }, - this._editor - ); - - result.domNode.classList.add('inline-chat-2'); - - return result; - }); - - - const editorObs = observableCodeEditor(_editor); - - const sessionsSignal = observableSignalFromEvent(this, _inlineChatSessions.onDidChangeSessions); - - this._currentSession = derived(r => { - sessionsSignal.read(r); - const model = editorObs.model.read(r); - const value = model && _inlineChatSessions.getSession2(model.uri); - return value ?? undefined; - }); - - - this._store.add(autorunWithStore((r, store) => { - const session = this._currentSession.read(r); - - if (!session) { - ctxHasSession.set(undefined); - this._isActiveController.set(false, undefined); - } else { - const checkRequests = () => ctxHasSession.set(session.chatModel.getRequests().length === 0 ? 'empty' : 'active'); - store.add(session.chatModel.onDidChange(checkRequests)); - checkRequests(); - } - })); - - const visibleSessionObs = observableValue(this, undefined); - - this._store.add(autorunWithStore((r, store) => { - - const session = this._currentSession.read(r); - const isActive = this._isActiveController.read(r); - - if (!session || !isActive) { - visibleSessionObs.set(undefined, undefined); - return; - } - - const { chatModel } = session; - const showShowUntil = this._showWidgetOverrideObs.read(r); - const hasNoRequests = chatModel.getRequests().length === 0; - - store.add(chatModel.onDidChange(e => { - if (e.kind === 'addRequest') { - transaction(tx => { - this._showWidgetOverrideObs.set(false, tx); - visibleSessionObs.set(undefined, tx); - }); - } - })); - - if (showShowUntil || hasNoRequests) { - visibleSessionObs.set(session, undefined); - } else { - visibleSessionObs.set(undefined, undefined); - } - })); - - this._store.add(autorun(r => { - - const session = visibleSessionObs.read(r); - - if (!session) { - this._zone.rawValue?.hide(); - _editor.focus(); - ctxInlineChatVisible.reset(); - } else { - ctxInlineChatVisible.set(true); - this._zone.value.widget.setChatModel(session.chatModel); - if (!this._zone.value.position) { - this._zone.value.show(session.initialPosition); - } - this._zone.value.reveal(this._zone.value.position!); - this._zone.value.widget.focus(); - session.editingSession.getEntry(session.uri)?.autoAcceptController.get()?.cancel(); - } - })); - - this._store.add(autorun(r => { - - const overlay = ChatEditorOverlayController.get(_editor)!; - const session = this._currentSession.read(r); - const model = editorObs.model.read(r); - if (!session || !model) { - overlay.hide(); - return; - } - - const lastResponse = observableFromEvent(this, session.chatModel.onDidChange, () => session.chatModel.getRequests().at(-1)?.response); - const response = lastResponse.read(r); - - const isInProgress = response - ? observableFromEvent(this, response.onDidChange, () => !response.isComplete) - : constObservable(false); - - if (isInProgress.read(r)) { - overlay.showRequest(session.editingSession); - } else if (session.editingSession.getEntry(session.uri)?.state.get() !== WorkingSetEntryState.Modified) { - overlay.hide(); - } - })); - } - - dispose(): void { - this._store.dispose(); - } - - toggleWidgetUntilNextRequest() { - const value = this._showWidgetOverrideObs.get(); - this._showWidgetOverrideObs.set(!value, undefined); - } - - - getWidgetPosition(): Position | undefined { - return this._zone.rawValue?.position; - } - - focus() { - this._zone.rawValue?.widget.focus(); - } - - markActiveController() { - this._isActiveController.set(true, undefined); - } - - async run(arg?: InlineChatRunOptions): Promise { - assertType(this._editor.hasModel()); - - const uri = this._editor.getModel().uri; - const session = await this._inlineChatSessions.createSession2(this._editor, uri, CancellationToken.None); - - this.markActiveController(); - - if (arg && InlineChatRunOptions.isInlineChatRunOptions(arg)) { - if (arg.initialRange) { - this._editor.revealRange(arg.initialRange); - } - if (arg.initialSelection) { - this._editor.setSelection(arg.initialSelection); - } - if (arg.message) { - this._zone.value.widget.chatWidget.setInput(arg.message); - if (arg.autoSend) { - await this._zone.value.widget.chatWidget.acceptInput(); - } - } - } - - await Event.toPromise(session.editingSession.onDidDispose); - - const rejected = session.editingSession.getEntry(uri)?.state.get() === WorkingSetEntryState.Rejected; - return !rejected; - } - - acceptSession() { - const value = this._currentSession.get(); - value?.editingSession.accept(); - } -} - -export class StartSessionAction2 extends EditorAction2 { - - constructor() { - super({ - id: 'inlineChat2.start', - title: localize2('start', "Inline Chat"), - precondition: ContextKeyExpr.and( - CTX_INLINE_CHAT_HAS_AGENT2, - CTX_INLINE_CHAT_POSSIBLE, - CTX_HAS_SESSION.negate(), - EditorContextKeys.writable, - EditorContextKeys.editorSimpleInput.negate() - ), - f1: true, - category: AbstractInlineChatAction.category, - keybinding: { - when: EditorContextKeys.focus, - weight: KeybindingWeight.WorkbenchContrib, - primary: KeyMod.CtrlCmd | KeyCode.KeyI - }, - menu: { - id: MenuId.ChatCommandCenter, - group: 'd_inlineChat', - order: 10, - when: CTX_INLINE_CHAT_HAS_AGENT2 - } - }); - } - - override async runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]) { - const ctrl = InlineChatController2.get(editor); - ctrl?.run(); - } -} - -abstract class AbstractInlineChatAction extends EditorAction2 { - - static readonly category = localize2('cat', "Inline Chat"); - - constructor(desc: IAction2Options) { - super({ - ...desc, - category: AbstractInlineChatAction.category, - precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_HAS_AGENT2, desc.precondition) - }); - } - - override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) { - const editorService = accessor.get(IEditorService); - const logService = accessor.get(ILogService); - - let ctrl = InlineChatController2.get(editor); - if (!ctrl) { - const { activeTextEditorControl } = editorService; - if (isCodeEditor(activeTextEditorControl)) { - editor = activeTextEditorControl; - } else if (isDiffEditor(activeTextEditorControl)) { - editor = activeTextEditorControl.getModifiedEditor(); - } - ctrl = InlineChatController2.get(editor); - } - - if (!ctrl) { - logService.warn('[IE] NO controller found for action', this.desc.id, editor.getModel()?.uri); - return; - } - - if (editor instanceof EmbeddedCodeEditorWidget) { - editor = editor.getParentEditor(); - } - if (!ctrl) { - for (const diffEditor of accessor.get(ICodeEditorService).listDiffEditors()) { - if (diffEditor.getOriginalEditor() === editor || diffEditor.getModifiedEditor() === editor) { - if (diffEditor instanceof EmbeddedDiffEditorWidget) { - this.runEditorCommand(accessor, diffEditor.getParentEditor(), ..._args); - } - } - } - return; - } - this.runInlineChatCommand(accessor, ctrl, editor, ..._args); - } - - abstract runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController2, editor: ICodeEditor, ...args: any[]): void; -} - -export class StopSessionAction2 extends AbstractInlineChatAction { - constructor() { - super({ - id: 'inlineChat2.stop', - title: localize2('stop', "Stop"), - f1: true, - precondition: ContextKeyExpr.and(CTX_HAS_SESSION.isEqualTo('empty'), CTX_INLINE_CHAT_VISIBLE), - keybinding: { - weight: KeybindingWeight.WorkbenchContrib, - primary: KeyCode.Escape, - secondary: [KeyMod.CtrlCmd | KeyCode.KeyI] - }, - }); - } - - runInlineChatCommand(accessor: ServicesAccessor, _ctrl: InlineChatController2, editor: ICodeEditor, ...args: any[]): void { - const inlineChatSessions = accessor.get(IInlineChatSessionService); - if (!editor.hasModel()) { - return; - } - const textModel = editor.getModel(); - inlineChatSessions.getSession2(textModel.uri)?.dispose(); - } -} - -class RevealWidget extends AbstractInlineChatAction { - constructor() { - super({ - id: 'inlineChat2.reveal', - title: localize2('reveal', "Toggle Inline Chat"), - f1: true, - icon: Codicon.copilot, - precondition: ContextKeyExpr.and( - CTX_HAS_SESSION, - ), - keybinding: { - weight: KeybindingWeight.WorkbenchContrib, - primary: KeyMod.CtrlCmd | KeyCode.KeyI - }, - menu: { - id: MenuId.ChatEditingEditorContent, - when: ContextKeyExpr.and( - CTX_HAS_SESSION, - ctxIsGlobalEditingSession.negate(), - ), - group: 'navigate', - order: 4, - } - }); - } - - runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController2, _editor: ICodeEditor): void { - ctrl.toggleWidgetUntilNextRequest(); - ctrl.markActiveController(); - } -} - -registerAction2(RevealWidget); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts index 5c6cee1541f..e4143ceff4a 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatCurrentLine.ts @@ -14,7 +14,7 @@ import { EditorAction2, ServicesAccessor } from '../../../../editor/browser/edit import { EditOperation } from '../../../../editor/common/core/editOperation.js'; import { Range } from '../../../../editor/common/core/range.js'; import { IPosition, Position } from '../../../../editor/common/core/position.js'; -import { AbstractInlineChatAction } from './inlineChatActions.js'; +import { AbstractInline1ChatAction } from './inlineChatActions.js'; import { EditorContextKeys } from '../../../../editor/common/editorContextKeys.js'; import { IValidEditOperation, TrackedRangeStickiness } from '../../../../editor/common/model.js'; import { URI } from '../../../../base/common/uri.js'; @@ -48,7 +48,7 @@ export class InlineChatExpandLineAction extends EditorAction2 { constructor() { super({ id: _inlineChatActionId, - category: AbstractInlineChatAction.category, + category: AbstractInline1ChatAction.category, title: localize2('startWithCurrentLine', "Start in Editor with Current Line"), f1: true, precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_VISIBLE.negate(), CTX_INLINE_CHAT_HAS_AGENT, EditorContextKeys.writable), @@ -101,7 +101,7 @@ export class ShowInlineChatHintAction extends EditorAction2 { constructor() { super({ id: 'inlineChat.showHint', - category: AbstractInlineChatAction.category, + category: AbstractInline1ChatAction.category, title: localize2('showHint', "Show Inline Chat Hint"), f1: false, precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_VISIBLE.negate(), CTX_INLINE_CHAT_HAS_AGENT, EditorContextKeys.writable), diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index 7215c80e416..d289e6111f5 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -89,6 +89,10 @@ export const CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF = new RawContextKey('inl export const CTX_INLINE_CHAT_REQUEST_IN_PROGRESS = new RawContextKey('inlineChatRequestInProgress', false, localize('inlineChatRequestInProgress', "Whether an inline chat request is currently in progress")); export const CTX_INLINE_CHAT_RESPONSE_TYPE = new RawContextKey('inlineChatResponseType', InlineChatResponseType.None, localize('inlineChatResponseTypes', "What type was the responses have been receieved, nothing yet, just messages, or messaged and local edits")); + +export const CTX_HAS_SESSION = new RawContextKey('inlineChatHasSession', undefined, localize('chat.hasInlineChatSession', "The current editor has an active inline chat session")); + + // --- (selected) action identifier export const ACTION_START = 'inlineChat.start'; diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts index cc0bb05c1dc..54754b0b91d 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts @@ -8,7 +8,7 @@ import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextke import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; import { InlineChatController } from '../browser/inlineChatController.js'; -import { AbstractInlineChatAction, setHoldForSpeech } from '../browser/inlineChatActions.js'; +import { AbstractInline1ChatAction, setHoldForSpeech } from '../browser/inlineChatActions.js'; import { disposableTimeout } from '../../../../base/common/async.js'; import { EditorContextKeys } from '../../../../editor/common/editorContextKeys.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; @@ -20,12 +20,14 @@ import { HasSpeechProvider, ISpeechService } from '../../speech/common/speechSer import { localize2 } from '../../../../nls.js'; import { Action2 } from '../../../../platform/actions/common/actions.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { EditorAction2 } from '../../../../editor/browser/editorExtensions.js'; -export class HoldToSpeak extends AbstractInlineChatAction { +export class HoldToSpeak extends EditorAction2 { constructor() { super({ id: 'inlineChat.holdForSpeech', + category: AbstractInline1ChatAction.category, precondition: ContextKeyExpr.and(HasSpeechProvider, CTX_INLINE_CHAT_VISIBLE), title: localize2('holdForSpeech', "Hold for Speech"), keybinding: { @@ -36,8 +38,11 @@ export class HoldToSpeak extends AbstractInlineChatAction { }); } - override runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController, editor: ICodeEditor, ...args: any[]): void { - holdForSpeech(accessor, ctrl, this); + override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) { + const ctrl = InlineChatController.get(editor); + if (ctrl) { + holdForSpeech(accessor, ctrl, this); + } } } @@ -67,7 +72,7 @@ function holdForSpeech(accessor: ServicesAccessor, ctrl: InlineChatController, a holdMode.finally(() => { if (listening) { commandService.executeCommand(StopListeningAction.ID).finally(() => { - ctrl.chatWidget.acceptInput(); + ctrl.widget.chatWidget.acceptInput(); }); } handle.dispose(); diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index 20282488d48..185476fb7f8 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -33,7 +33,7 @@ import { IAccessibleViewService } from '../../../../../platform/accessibility/br import { IChatAccessibilityService, IChatWidget, IChatWidgetService } from '../../../chat/browser/chat.js'; import { ChatAgentLocation, ChatAgentService, IChatAgentData, IChatAgentNameService, IChatAgentService } from '../../../chat/common/chatAgents.js'; import { IChatResponseViewModel } from '../../../chat/common/chatViewModel.js'; -import { InlineChatController, State } from '../../browser/inlineChatController.js'; +import { InlineChatController1, State } from '../../browser/inlineChatController.js'; import { CTX_INLINE_CHAT_RESPONSE_TYPE, InlineChatConfigKeys, InlineChatResponseType } from '../../common/inlineChat.js'; import { TestViewsService, workbenchInstantiationService } from '../../../../test/browser/workbenchTestServices.js'; import { IExtensionService, nullExtensionDescription } from '../../../../services/extensions/common/extensions.js'; @@ -88,7 +88,7 @@ suite('InlineChatController', function () { disambiguation: [], }; - class TestController extends InlineChatController { + class TestController extends InlineChatController1 { static INIT_SEQUENCE: readonly State[] = [State.CREATE_SESSION, State.INIT_UI, State.WAIT_FOR_INPUT]; static INIT_SEQUENCE_AUTO_SEND: readonly State[] = [...this.INIT_SEQUENCE, State.SHOW_REQUEST, State.WAIT_FOR_INPUT]; diff --git a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts index 752af5fb88d..47fb9d3d0d2 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts @@ -533,7 +533,7 @@ registerAction2(class extends Action2 { const ctrl = InlineChatController.get(editorControl.activeCodeEditor); if (ctrl) { - ctrl.acceptHunk(); + ctrl.acceptSession(); } historyService.replaceLast(notebookDocument.uri, value); diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts index 669b094d675..36920ec0a03 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts @@ -407,7 +407,7 @@ export class InteractiveEditor extends EditorPane implements IEditorPaneWithScro ContentHoverController.ID, GlyphHoverController.ID, MarkerController.ID, - INLINE_CHAT_ID + INLINE_CHAT_ID, ]) } }); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts index c367f5a8988..7c52036cb9a 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts @@ -27,9 +27,8 @@ import { Iterable } from '../../../../../../base/common/iterator.js'; import { ICodeEditor } from '../../../../../../editor/browser/editorBrowser.js'; import { IEditorService } from '../../../../../services/editor/common/editorService.js'; import { ChatContextKeys } from '../../../../chat/common/chatContextKeys.js'; -import { AbstractInlineChatAction } from '../../../../inlineChat/browser/inlineChatActions.js'; import { InlineChatController } from '../../../../inlineChat/browser/inlineChatController.js'; -import { HunkInformation } from '../../../../inlineChat/browser/inlineChatSession.js'; +import { EditorAction2 } from '../../../../../../editor/browser/editorExtensions.js'; registerAction2(class extends NotebookAction { constructor() { @@ -671,7 +670,7 @@ registerAction2(class extends NotebookCellAction { }); -export class AcceptChangesAndRun extends AbstractInlineChatAction { +export class AcceptChangesAndRun extends EditorAction2 { constructor() { super({ @@ -700,10 +699,11 @@ export class AcceptChangesAndRun extends AbstractInlineChatAction { }); } - override async runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController, codeEditor: ICodeEditor, hunk?: HunkInformation | any): Promise { + override runEditorCommand(accessor: ServicesAccessor, codeEditor: ICodeEditor) { const editor = getContextFromActiveEditor(accessor.get(IEditorService)); + const ctrl = InlineChatController.get(codeEditor); - if (!editor) { + if (!editor || !ctrl) { return; } diff --git a/src/vs/workbench/contrib/replNotebook/browser/repl.contribution.ts b/src/vs/workbench/contrib/replNotebook/browser/repl.contribution.ts index f06bc5a4193..61e6351ad26 100644 --- a/src/vs/workbench/contrib/replNotebook/browser/repl.contribution.ts +++ b/src/vs/workbench/contrib/replNotebook/browser/repl.contribution.ts @@ -434,7 +434,7 @@ async function executeReplInput( // Just accept any existing inline chat hunk const ctrl = InlineChatController.get(editorControl.activeCodeEditor); if (ctrl) { - ctrl.acceptHunk(); + ctrl.acceptSession(); } historyService.replaceLast(notebookDocument.uri, value); diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts index 0d6fdd35361..935f2731a33 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatActions.ts @@ -11,7 +11,7 @@ import { KeybindingWeight } from '../../../../../platform/keybinding/common/keyb import { IChatWidgetService } from '../../../chat/browser/chat.js'; import { ChatAgentLocation } from '../../../chat/common/chatAgents.js'; import { IChatService } from '../../../chat/common/chatService.js'; -import { AbstractInlineChatAction } from '../../../inlineChat/browser/inlineChatActions.js'; +import { AbstractInline1ChatAction } from '../../../inlineChat/browser/inlineChatActions.js'; import { isDetachedTerminalInstance } from '../../../terminal/browser/terminal.js'; import { registerActiveXtermAction } from '../../../terminal/browser/terminalActions.js'; import { TerminalContextKeys } from '../../../terminal/common/terminalContextKey.js'; @@ -21,7 +21,7 @@ import { TerminalChatController } from './terminalChatController.js'; registerActiveXtermAction({ id: TerminalChatCommandId.Start, title: localize2('startChat', 'Terminal Inline Chat'), - category: AbstractInlineChatAction.category, + category: AbstractInline1ChatAction.category, keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyI, when: ContextKeyExpr.and(TerminalContextKeys.focusInAny), @@ -58,7 +58,7 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalChatCommandId.Close, title: localize2('closeChat', 'Close'), - category: AbstractInlineChatAction.category, + category: AbstractInline1ChatAction.category, keybinding: { primary: KeyCode.Escape, when: ContextKeyExpr.and( @@ -89,7 +89,7 @@ registerActiveXtermAction({ id: TerminalChatCommandId.RunCommand, title: localize2('runCommand', 'Run Chat Command'), shortTitle: localize2('run', 'Run'), - category: AbstractInlineChatAction.category, + category: AbstractInline1ChatAction.category, precondition: ContextKeyExpr.and( ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalChatContextKeys.requestActive.negate(), @@ -121,7 +121,7 @@ registerActiveXtermAction({ id: TerminalChatCommandId.RunFirstCommand, title: localize2('runFirstCommand', 'Run First Chat Command'), shortTitle: localize2('runFirst', 'Run First'), - category: AbstractInlineChatAction.category, + category: AbstractInline1ChatAction.category, precondition: ContextKeyExpr.and( ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalChatContextKeys.requestActive.negate(), @@ -152,7 +152,7 @@ registerActiveXtermAction({ id: TerminalChatCommandId.InsertCommand, title: localize2('insertCommand', 'Insert Chat Command'), shortTitle: localize2('insert', 'Insert'), - category: AbstractInlineChatAction.category, + category: AbstractInline1ChatAction.category, icon: Codicon.insert, precondition: ContextKeyExpr.and( ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), @@ -185,7 +185,7 @@ registerActiveXtermAction({ id: TerminalChatCommandId.InsertFirstCommand, title: localize2('insertFirstCommand', 'Insert First Chat Command'), shortTitle: localize2('insertFirst', 'Insert First'), - category: AbstractInlineChatAction.category, + category: AbstractInline1ChatAction.category, precondition: ContextKeyExpr.and( ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalChatContextKeys.requestActive.negate(), @@ -217,7 +217,7 @@ registerActiveXtermAction({ title: localize2('chat.rerun.label', "Rerun Request"), f1: false, icon: Codicon.refresh, - category: AbstractInlineChatAction.category, + category: AbstractInline1ChatAction.category, precondition: ContextKeyExpr.and( ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalChatContextKeys.requestActive.negate(), @@ -258,7 +258,7 @@ registerActiveXtermAction({ registerActiveXtermAction({ id: TerminalChatCommandId.ViewInChat, title: localize2('viewInChat', 'View in Chat'), - category: AbstractInlineChatAction.category, + category: AbstractInline1ChatAction.category, precondition: ContextKeyExpr.and( ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalChatContextKeys.requestActive.negate(),