Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { Codicon } from '../../../../base/common/codicons.js';
7
import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
8
import { ICodeEditor, isCodeEditor, isDiffEditor } from '../../../../editor/browser/editorBrowser.js';
9
import { EditorAction2 } from '../../../../editor/browser/editorExtensions.js';
10
import { EmbeddedDiffEditorWidget } from '../../../../editor/browser/widget/diffEditor/embeddedDiffEditorWidget.js';
11
import { EmbeddedCodeEditorWidget } from '../../../../editor/browser/widget/codeEditor/embeddedCodeEditorWidget.js';
12
import { EditorContextKeys } from '../../../../editor/common/editorContextKeys.js';
13
import { InlineChatController, InlineChatController1, InlineChatController2, InlineChatRunOptions } from './inlineChatController.js';
14
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, MENU_INLINE_CHAT_SIDE } from '../common/inlineChat.js';
15
import { ctxHasEditorModification, ctxHasRequestInProgress, ctxIsGlobalEditingSession, ctxRequestCount } from '../../chat/browser/chatEditing/chatEditingEditorContextKeys.js';
16
import { localize, localize2 } from '../../../../nls.js';
17
import { Action2, IAction2Options, MenuId } from '../../../../platform/actions/common/actions.js';
18
import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
19
import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
20
import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
21
import { IEditorService } from '../../../services/editor/common/editorService.js';
22
import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js';
23
import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from '../../../../platform/accessibility/common/accessibility.js';
24
import { CommandsRegistry } from '../../../../platform/commands/common/commands.js';
25
import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js';
26
import { IPreferencesService } from '../../../services/preferences/common/preferences.js';
27
import { ILogService } from '../../../../platform/log/common/log.js';
28
import { IChatService } from '../../chat/common/chatService.js';
29
import { ChatContextKeys } from '../../chat/common/chatContextKeys.js';
30
import { HunkInformation } from './inlineChatSession.js';
31
import { IChatWidgetService } from '../../chat/browser/chat.js';
32
import { IInlineChatSessionService } from './inlineChatSessionService.js';
33
34
35
CommandsRegistry.registerCommandAlias('interactiveEditor.start', 'inlineChat.start');
36
CommandsRegistry.registerCommandAlias('interactive.acceptChanges', ACTION_ACCEPT_CHANGES);
37
38
39
export const START_INLINE_CHAT = registerIcon('start-inline-chat', Codicon.sparkle, localize('startInlineChat', 'Icon which spawns the inline chat from the editor toolbar.'));
40
41
// some gymnastics to enable hold for speech without moving the StartSessionAction into the electron-layer
42
43
export interface IHoldForSpeech {
44
(accessor: ServicesAccessor, controller: InlineChatController, source: Action2): void;
45
}
46
let _holdForSpeech: IHoldForSpeech | undefined = undefined;
47
export function setHoldForSpeech(holdForSpeech: IHoldForSpeech) {
48
_holdForSpeech = holdForSpeech;
49
}
50
51
const inlineChatContextKey = ContextKeyExpr.and(
52
ContextKeyExpr.or(CTX_INLINE_CHAT_HAS_AGENT, CTX_INLINE_CHAT_HAS_AGENT2),
53
CTX_INLINE_CHAT_POSSIBLE,
54
EditorContextKeys.writable,
55
EditorContextKeys.editorSimpleInput.negate()
56
);
57
58
export class StartSessionAction extends Action2 {
59
60
constructor() {
61
super({
62
id: ACTION_START,
63
title: localize2('run', 'Open Inline Chat'),
64
category: AbstractInline1ChatAction.category,
65
f1: true,
66
precondition: inlineChatContextKey,
67
keybinding: {
68
when: EditorContextKeys.focus,
69
weight: KeybindingWeight.WorkbenchContrib,
70
primary: KeyMod.CtrlCmd | KeyCode.KeyI
71
},
72
icon: START_INLINE_CHAT,
73
menu: [{
74
id: MenuId.EditorContext,
75
group: '1_chat',
76
order: 3,
77
when: inlineChatContextKey
78
}, {
79
id: MenuId.ChatTitleBarMenu,
80
group: 'a_open',
81
order: 3,
82
}]
83
});
84
}
85
override run(accessor: ServicesAccessor, ...args: any[]): any {
86
87
const codeEditorService = accessor.get(ICodeEditorService);
88
const editor = codeEditorService.getActiveCodeEditor();
89
if (!editor || editor.isSimpleWidget) {
90
// well, at least we tried...
91
return;
92
}
93
94
95
// precondition does hold
96
return editor.invokeWithinContext((editorAccessor) => {
97
const kbService = editorAccessor.get(IContextKeyService);
98
const logService = editorAccessor.get(ILogService);
99
const enabled = kbService.contextMatchesRules(this.desc.precondition ?? undefined);
100
if (!enabled) {
101
logService.debug(`[EditorAction2] NOT running command because its precondition is FALSE`, this.desc.id, this.desc.precondition?.serialize());
102
return;
103
}
104
return this._runEditorCommand(editorAccessor, editor, ...args);
105
});
106
}
107
108
private _runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) {
109
110
const ctrl = InlineChatController.get(editor);
111
if (!ctrl) {
112
return;
113
}
114
115
if (_holdForSpeech) {
116
accessor.get(IInstantiationService).invokeFunction(_holdForSpeech, ctrl, this);
117
}
118
119
let options: InlineChatRunOptions | undefined;
120
const arg = _args[0];
121
if (arg && InlineChatRunOptions.isInlineChatRunOptions(arg)) {
122
options = arg;
123
}
124
InlineChatController.get(editor)?.run({ ...options });
125
}
126
}
127
128
export class FocusInlineChat extends EditorAction2 {
129
130
constructor() {
131
super({
132
id: 'inlineChat.focus',
133
title: localize2('focus', "Focus Input"),
134
f1: true,
135
category: AbstractInline1ChatAction.category,
136
precondition: ContextKeyExpr.and(EditorContextKeys.editorTextFocus, CTX_INLINE_CHAT_VISIBLE, CTX_INLINE_CHAT_FOCUSED.negate(), CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()),
137
keybinding: [{
138
weight: KeybindingWeight.EditorCore + 10, // win against core_command
139
when: ContextKeyExpr.and(CTX_INLINE_CHAT_OUTER_CURSOR_POSITION.isEqualTo('above'), EditorContextKeys.isEmbeddedDiffEditor.negate()),
140
primary: KeyMod.CtrlCmd | KeyCode.DownArrow,
141
}, {
142
weight: KeybindingWeight.EditorCore + 10, // win against core_command
143
when: ContextKeyExpr.and(CTX_INLINE_CHAT_OUTER_CURSOR_POSITION.isEqualTo('below'), EditorContextKeys.isEmbeddedDiffEditor.negate()),
144
primary: KeyMod.CtrlCmd | KeyCode.UpArrow,
145
}]
146
});
147
}
148
149
override runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) {
150
InlineChatController.get(editor)?.focus();
151
}
152
}
153
154
//#region --- VERSION 1
155
156
export class UnstashSessionAction extends EditorAction2 {
157
constructor() {
158
super({
159
id: 'inlineChat.unstash',
160
title: localize2('unstash', "Resume Last Dismissed Inline Chat"),
161
category: AbstractInline1ChatAction.category,
162
precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_HAS_STASHED_SESSION, EditorContextKeys.writable),
163
keybinding: {
164
weight: KeybindingWeight.WorkbenchContrib,
165
primary: KeyMod.CtrlCmd | KeyCode.KeyZ,
166
}
167
});
168
}
169
170
override async runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) {
171
const ctrl = InlineChatController1.get(editor);
172
if (ctrl) {
173
const session = ctrl.unstashLastSession();
174
if (session) {
175
ctrl.run({
176
existingSession: session,
177
});
178
}
179
}
180
}
181
}
182
183
export abstract class AbstractInline1ChatAction extends EditorAction2 {
184
185
static readonly category = localize2('cat', "Inline Chat");
186
187
constructor(desc: IAction2Options) {
188
189
const massageMenu = (menu: IAction2Options['menu'] | undefined) => {
190
if (Array.isArray(menu)) {
191
for (const entry of menu) {
192
entry.when = ContextKeyExpr.and(CTX_INLINE_CHAT_HAS_AGENT, entry.when);
193
}
194
} else if (menu) {
195
menu.when = ContextKeyExpr.and(CTX_INLINE_CHAT_HAS_AGENT, menu.when);
196
}
197
};
198
if (Array.isArray(desc.menu)) {
199
massageMenu(desc.menu);
200
} else {
201
massageMenu(desc.menu);
202
}
203
204
super({
205
...desc,
206
category: AbstractInline1ChatAction.category,
207
precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_HAS_AGENT, desc.precondition)
208
});
209
}
210
211
override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) {
212
const editorService = accessor.get(IEditorService);
213
const logService = accessor.get(ILogService);
214
215
let ctrl = InlineChatController1.get(editor);
216
if (!ctrl) {
217
const { activeTextEditorControl } = editorService;
218
if (isCodeEditor(activeTextEditorControl)) {
219
editor = activeTextEditorControl;
220
} else if (isDiffEditor(activeTextEditorControl)) {
221
editor = activeTextEditorControl.getModifiedEditor();
222
}
223
ctrl = InlineChatController1.get(editor);
224
}
225
226
if (!ctrl) {
227
logService.warn('[IE] NO controller found for action', this.desc.id, editor.getModel()?.uri);
228
return;
229
}
230
231
if (editor instanceof EmbeddedCodeEditorWidget) {
232
editor = editor.getParentEditor();
233
}
234
if (!ctrl) {
235
for (const diffEditor of accessor.get(ICodeEditorService).listDiffEditors()) {
236
if (diffEditor.getOriginalEditor() === editor || diffEditor.getModifiedEditor() === editor) {
237
if (diffEditor instanceof EmbeddedDiffEditorWidget) {
238
this.runEditorCommand(accessor, diffEditor.getParentEditor(), ..._args);
239
}
240
}
241
}
242
return;
243
}
244
this.runInlineChatCommand(accessor, ctrl, editor, ..._args);
245
}
246
247
abstract runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController1, editor: ICodeEditor, ...args: any[]): void;
248
}
249
250
export class ArrowOutUpAction extends AbstractInline1ChatAction {
251
constructor() {
252
super({
253
id: 'inlineChat.arrowOutUp',
254
title: localize('arrowUp', 'Cursor Up'),
255
precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, EditorContextKeys.isEmbeddedDiffEditor.negate(), CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()),
256
keybinding: {
257
weight: KeybindingWeight.EditorCore,
258
primary: KeyMod.CtrlCmd | KeyCode.UpArrow
259
}
260
});
261
}
262
263
runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, ..._args: any[]): void {
264
ctrl.arrowOut(true);
265
}
266
}
267
268
export class ArrowOutDownAction extends AbstractInline1ChatAction {
269
constructor() {
270
super({
271
id: 'inlineChat.arrowOutDown',
272
title: localize('arrowDown', 'Cursor Down'),
273
precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_LAST, EditorContextKeys.isEmbeddedDiffEditor.negate(), CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()),
274
keybinding: {
275
weight: KeybindingWeight.EditorCore,
276
primary: KeyMod.CtrlCmd | KeyCode.DownArrow
277
}
278
});
279
}
280
281
runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, ..._args: any[]): void {
282
ctrl.arrowOut(false);
283
}
284
}
285
286
export class AcceptChanges extends AbstractInline1ChatAction {
287
288
constructor() {
289
super({
290
id: ACTION_ACCEPT_CHANGES,
291
title: localize2('apply1', "Accept Changes"),
292
shortTitle: localize('apply2', 'Accept'),
293
icon: Codicon.check,
294
f1: true,
295
precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_VISIBLE),
296
keybinding: [{
297
weight: KeybindingWeight.WorkbenchContrib + 10,
298
primary: KeyMod.CtrlCmd | KeyCode.Enter,
299
}],
300
menu: [{
301
id: MENU_INLINE_CHAT_WIDGET_STATUS,
302
group: '0_main',
303
order: 1,
304
when: ContextKeyExpr.and(
305
ChatContextKeys.inputHasText.toNegated(),
306
CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.toNegated(),
307
CTX_INLINE_CHAT_RESPONSE_TYPE.isEqualTo(InlineChatResponseType.MessagesAndEdits)
308
),
309
}, {
310
id: MENU_INLINE_CHAT_ZONE,
311
group: 'navigation',
312
order: 1,
313
}]
314
});
315
}
316
317
override async runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, hunk?: HunkInformation | any): Promise<void> {
318
ctrl.acceptHunk(hunk);
319
}
320
}
321
322
export class DiscardHunkAction extends AbstractInline1ChatAction {
323
324
constructor() {
325
super({
326
id: ACTION_DISCARD_CHANGES,
327
title: localize('discard', 'Discard'),
328
icon: Codicon.chromeClose,
329
precondition: CTX_INLINE_CHAT_VISIBLE,
330
menu: [{
331
id: MENU_INLINE_CHAT_ZONE,
332
group: 'navigation',
333
order: 2
334
}],
335
keybinding: {
336
weight: KeybindingWeight.EditorContrib,
337
primary: KeyCode.Escape,
338
when: CTX_INLINE_CHAT_RESPONSE_TYPE.isEqualTo(InlineChatResponseType.MessagesAndEdits)
339
}
340
});
341
}
342
343
async runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, hunk?: HunkInformation | any): Promise<void> {
344
return ctrl.discardHunk(hunk);
345
}
346
}
347
348
export class RerunAction extends AbstractInline1ChatAction {
349
constructor() {
350
super({
351
id: ACTION_REGENERATE_RESPONSE,
352
title: localize2('chat.rerun.label', "Rerun Request"),
353
shortTitle: localize('rerun', 'Rerun'),
354
f1: false,
355
icon: Codicon.refresh,
356
precondition: CTX_INLINE_CHAT_VISIBLE,
357
menu: {
358
id: MENU_INLINE_CHAT_WIDGET_STATUS,
359
group: '0_main',
360
order: 5,
361
when: ContextKeyExpr.and(
362
ChatContextKeys.inputHasText.toNegated(),
363
CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.negate(),
364
CTX_INLINE_CHAT_RESPONSE_TYPE.notEqualsTo(InlineChatResponseType.None)
365
)
366
},
367
keybinding: {
368
weight: KeybindingWeight.WorkbenchContrib,
369
primary: KeyMod.CtrlCmd | KeyCode.KeyR
370
}
371
});
372
}
373
374
override async runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, ..._args: any[]): Promise<void> {
375
const chatService = accessor.get(IChatService);
376
const chatWidgetService = accessor.get(IChatWidgetService);
377
const model = ctrl.chatWidget.viewModel?.model;
378
if (!model) {
379
return;
380
}
381
382
const lastRequest = model.getRequests().at(-1);
383
if (lastRequest) {
384
const widget = chatWidgetService.getWidgetBySessionId(model.sessionId);
385
await chatService.resendRequest(lastRequest, {
386
noCommandDetection: false,
387
attempt: lastRequest.attempt + 1,
388
location: ctrl.chatWidget.location,
389
userSelectedModelId: widget?.input.currentLanguageModel
390
});
391
}
392
}
393
}
394
395
export class CloseAction extends AbstractInline1ChatAction {
396
397
constructor() {
398
super({
399
id: 'inlineChat.close',
400
title: localize('close', 'Close'),
401
icon: Codicon.close,
402
precondition: CTX_INLINE_CHAT_VISIBLE,
403
keybinding: {
404
weight: KeybindingWeight.EditorContrib + 1,
405
primary: KeyCode.Escape,
406
},
407
menu: [{
408
id: MENU_INLINE_CHAT_WIDGET_STATUS,
409
group: '0_main',
410
order: 1,
411
when: CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.negate()
412
}, {
413
id: MENU_INLINE_CHAT_SIDE,
414
group: 'navigation',
415
when: CTX_INLINE_CHAT_RESPONSE_TYPE.isEqualTo(InlineChatResponseType.None)
416
}]
417
});
418
}
419
420
async runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, ..._args: any[]): Promise<void> {
421
ctrl.cancelSession();
422
}
423
}
424
425
export class ConfigureInlineChatAction extends AbstractInline1ChatAction {
426
constructor() {
427
super({
428
id: 'inlineChat.configure',
429
title: localize2('configure', 'Configure Inline Chat'),
430
icon: Codicon.settingsGear,
431
precondition: CTX_INLINE_CHAT_VISIBLE,
432
f1: true,
433
menu: {
434
id: MENU_INLINE_CHAT_WIDGET_STATUS,
435
group: 'zzz',
436
order: 5
437
}
438
});
439
}
440
441
async runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, ..._args: any[]): Promise<void> {
442
accessor.get(IPreferencesService).openSettings({ query: 'inlineChat' });
443
}
444
}
445
446
export class MoveToNextHunk extends AbstractInline1ChatAction {
447
448
constructor() {
449
super({
450
id: 'inlineChat.moveToNextHunk',
451
title: localize2('moveToNextHunk', 'Move to Next Change'),
452
precondition: CTX_INLINE_CHAT_VISIBLE,
453
f1: true,
454
keybinding: {
455
weight: KeybindingWeight.WorkbenchContrib,
456
primary: KeyCode.F7
457
}
458
});
459
}
460
461
override runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController1, editor: ICodeEditor, ...args: any[]): void {
462
ctrl.moveHunk(true);
463
}
464
}
465
466
export class MoveToPreviousHunk extends AbstractInline1ChatAction {
467
468
constructor() {
469
super({
470
id: 'inlineChat.moveToPreviousHunk',
471
title: localize2('moveToPreviousHunk', 'Move to Previous Change'),
472
f1: true,
473
precondition: CTX_INLINE_CHAT_VISIBLE,
474
keybinding: {
475
weight: KeybindingWeight.WorkbenchContrib,
476
primary: KeyMod.Shift | KeyCode.F7
477
}
478
});
479
}
480
481
override runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController1, editor: ICodeEditor, ...args: any[]): void {
482
ctrl.moveHunk(false);
483
}
484
}
485
486
export class ViewInChatAction extends AbstractInline1ChatAction {
487
constructor() {
488
super({
489
id: ACTION_VIEW_IN_CHAT,
490
title: localize('viewInChat', 'View in Chat'),
491
icon: Codicon.chatSparkle,
492
precondition: CTX_INLINE_CHAT_VISIBLE,
493
menu: [{
494
id: MENU_INLINE_CHAT_WIDGET_STATUS,
495
group: 'more',
496
order: 1,
497
when: CTX_INLINE_CHAT_RESPONSE_TYPE.notEqualsTo(InlineChatResponseType.Messages)
498
}, {
499
id: MENU_INLINE_CHAT_WIDGET_STATUS,
500
group: '0_main',
501
order: 1,
502
when: ContextKeyExpr.and(
503
ChatContextKeys.inputHasText.toNegated(),
504
CTX_INLINE_CHAT_RESPONSE_TYPE.isEqualTo(InlineChatResponseType.Messages),
505
CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.negate()
506
)
507
}],
508
keybinding: {
509
weight: KeybindingWeight.WorkbenchContrib,
510
primary: KeyMod.CtrlCmd | KeyCode.DownArrow,
511
when: ChatContextKeys.inChatInput
512
}
513
});
514
}
515
override runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, ..._args: any[]) {
516
return ctrl.viewInChat();
517
}
518
}
519
520
export class ToggleDiffForChange extends AbstractInline1ChatAction {
521
522
constructor() {
523
super({
524
id: ACTION_TOGGLE_DIFF,
525
precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_VISIBLE, CTX_INLINE_CHAT_CHANGE_HAS_DIFF),
526
title: localize2('showChanges', 'Toggle Changes'),
527
icon: Codicon.diffSingle,
528
toggled: {
529
condition: CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF,
530
},
531
menu: [{
532
id: MENU_INLINE_CHAT_WIDGET_STATUS,
533
group: 'zzz',
534
order: 1,
535
}, {
536
id: MENU_INLINE_CHAT_ZONE,
537
group: 'navigation',
538
when: CTX_INLINE_CHAT_CHANGE_HAS_DIFF,
539
order: 2
540
}]
541
});
542
}
543
544
override runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController1, _editor: ICodeEditor, hunkInfo: HunkInformation | any): void {
545
ctrl.toggleDiff(hunkInfo);
546
}
547
}
548
549
//#endregion
550
551
552
//#region --- VERSION 2
553
abstract class AbstractInline2ChatAction extends EditorAction2 {
554
555
static readonly category = localize2('cat', "Inline Chat");
556
557
constructor(desc: IAction2Options) {
558
const massageMenu = (menu: IAction2Options['menu'] | undefined) => {
559
if (Array.isArray(menu)) {
560
for (const entry of menu) {
561
entry.when = ContextKeyExpr.and(CTX_INLINE_CHAT_HAS_AGENT2, entry.when);
562
}
563
} else if (menu) {
564
menu.when = ContextKeyExpr.and(CTX_INLINE_CHAT_HAS_AGENT2, menu.when);
565
}
566
};
567
if (Array.isArray(desc.menu)) {
568
massageMenu(desc.menu);
569
} else {
570
massageMenu(desc.menu);
571
}
572
573
super({
574
...desc,
575
category: AbstractInline2ChatAction.category,
576
precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_HAS_AGENT2, desc.precondition)
577
});
578
}
579
580
override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) {
581
const editorService = accessor.get(IEditorService);
582
const logService = accessor.get(ILogService);
583
584
let ctrl = InlineChatController2.get(editor);
585
if (!ctrl) {
586
const { activeTextEditorControl } = editorService;
587
if (isCodeEditor(activeTextEditorControl)) {
588
editor = activeTextEditorControl;
589
} else if (isDiffEditor(activeTextEditorControl)) {
590
editor = activeTextEditorControl.getModifiedEditor();
591
}
592
ctrl = InlineChatController2.get(editor);
593
}
594
595
if (!ctrl) {
596
logService.warn('[IE] NO controller found for action', this.desc.id, editor.getModel()?.uri);
597
return;
598
}
599
600
if (editor instanceof EmbeddedCodeEditorWidget) {
601
editor = editor.getParentEditor();
602
}
603
if (!ctrl) {
604
for (const diffEditor of accessor.get(ICodeEditorService).listDiffEditors()) {
605
if (diffEditor.getOriginalEditor() === editor || diffEditor.getModifiedEditor() === editor) {
606
if (diffEditor instanceof EmbeddedDiffEditorWidget) {
607
this.runEditorCommand(accessor, diffEditor.getParentEditor(), ..._args);
608
}
609
}
610
}
611
return;
612
}
613
this.runInlineChatCommand(accessor, ctrl, editor, ..._args);
614
}
615
616
abstract runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController2, editor: ICodeEditor, ...args: any[]): void;
617
}
618
619
class KeepOrUndoSessionAction extends AbstractInline2ChatAction {
620
621
constructor(id: string, private readonly _keep: boolean) {
622
super({
623
id,
624
title: _keep
625
? localize2('Keep', "Keep")
626
: localize2('Undo', "Undo"),
627
f1: true,
628
icon: _keep ? Codicon.check : Codicon.discard,
629
precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_VISIBLE, ctxHasRequestInProgress.negate()),
630
keybinding: [{
631
weight: KeybindingWeight.WorkbenchContrib + 10, // win over new-window-action
632
primary: _keep
633
? KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyY
634
: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyN
635
}],
636
menu: [{
637
id: MENU_INLINE_CHAT_WIDGET_STATUS,
638
group: '0_main',
639
order: 1,
640
when: ContextKeyExpr.and(CTX_INLINE_CHAT_HAS_AGENT2, ContextKeyExpr.greater(ctxRequestCount.key, 0), ctxHasEditorModification),
641
}]
642
});
643
}
644
645
override async runInlineChatCommand(accessor: ServicesAccessor, _ctrl: InlineChatController2, editor: ICodeEditor, ..._args: any[]): Promise<void> {
646
const inlineChatSessions = accessor.get(IInlineChatSessionService);
647
if (!editor.hasModel()) {
648
return;
649
}
650
const textModel = editor.getModel();
651
const session = inlineChatSessions.getSession2(textModel.uri);
652
if (session) {
653
if (this._keep) {
654
await session.editingSession.accept();
655
} else {
656
await session.editingSession.reject();
657
}
658
session.dispose();
659
}
660
}
661
}
662
663
export class KeepSessionAction2 extends KeepOrUndoSessionAction {
664
constructor() {
665
super('inlineChat2.keep', true);
666
}
667
}
668
669
export class UndoSessionAction2 extends KeepOrUndoSessionAction {
670
constructor() {
671
super('inlineChat2.undo', false);
672
}
673
}
674
675
export class CloseSessionAction2 extends AbstractInline2ChatAction {
676
677
constructor() {
678
super({
679
id: 'inlineChat2.close',
680
title: localize2('close2', "Close"),
681
f1: true,
682
icon: Codicon.close,
683
precondition: ContextKeyExpr.and(
684
CTX_INLINE_CHAT_VISIBLE,
685
ctxHasRequestInProgress.negate(),
686
ContextKeyExpr.or(ctxRequestCount.isEqualTo(0), ctxHasEditorModification.negate())
687
),
688
keybinding: [{
689
when: ctxRequestCount.isEqualTo(0),
690
weight: KeybindingWeight.WorkbenchContrib,
691
primary: KeyMod.CtrlCmd | KeyCode.KeyI,
692
}, {
693
weight: KeybindingWeight.WorkbenchContrib,
694
primary: KeyCode.Escape,
695
}],
696
menu: [{
697
id: MENU_INLINE_CHAT_SIDE,
698
group: 'navigation',
699
when: ContextKeyExpr.and(CTX_INLINE_CHAT_HAS_AGENT2, ctxRequestCount.isEqualTo(0)),
700
}, {
701
id: MENU_INLINE_CHAT_WIDGET_STATUS,
702
group: '0_main',
703
order: 1,
704
when: ContextKeyExpr.and(CTX_INLINE_CHAT_HAS_AGENT2, ctxHasEditorModification.negate()),
705
}]
706
});
707
}
708
709
runInlineChatCommand(accessor: ServicesAccessor, _ctrl: InlineChatController2, editor: ICodeEditor, ...args: any[]): void {
710
const inlineChatSessions = accessor.get(IInlineChatSessionService);
711
if (editor.hasModel()) {
712
const textModel = editor.getModel();
713
inlineChatSessions.getSession2(textModel.uri)?.dispose();
714
}
715
}
716
}
717
718
export class RevealWidget extends AbstractInline2ChatAction {
719
constructor() {
720
super({
721
id: 'inlineChat2.reveal',
722
title: localize2('reveal', "Toggle Inline Chat"),
723
f1: true,
724
icon: Codicon.chatSparkle,
725
precondition: ContextKeyExpr.and(ctxIsGlobalEditingSession.negate(), ContextKeyExpr.greaterEquals(ctxRequestCount.key, 1)),
726
toggled: {
727
condition: CTX_INLINE_CHAT_VISIBLE,
728
},
729
keybinding: {
730
weight: KeybindingWeight.WorkbenchContrib,
731
primary: KeyMod.CtrlCmd | KeyCode.KeyI
732
},
733
menu: {
734
id: MenuId.ChatEditingEditorContent,
735
when: ContextKeyExpr.and(
736
ContextKeyExpr.greaterEquals(ctxRequestCount.key, 1),
737
ctxIsGlobalEditingSession.negate(),
738
),
739
group: 'navigate',
740
order: 4,
741
}
742
});
743
}
744
745
runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController2, _editor: ICodeEditor): void {
746
ctrl.toggleWidgetUntilNextRequest();
747
ctrl.markActiveController();
748
}
749
}
750
751
export class CancelRequestAction extends AbstractInline2ChatAction {
752
constructor() {
753
super({
754
id: 'inlineChat2.cancelRequest',
755
title: localize2('cancel', "Cancel Request"),
756
f1: true,
757
icon: Codicon.stopCircle,
758
precondition: ContextKeyExpr.and(ctxIsGlobalEditingSession.negate(), ctxHasRequestInProgress),
759
toggled: CTX_INLINE_CHAT_VISIBLE,
760
menu: {
761
id: MenuId.ChatEditingEditorContent,
762
when: ContextKeyExpr.and(ctxIsGlobalEditingSession.negate(), ctxHasRequestInProgress),
763
group: 'a_request',
764
order: 1,
765
}
766
});
767
}
768
769
runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController2, _editor: ICodeEditor): void {
770
const chatService = accessor.get(IChatService);
771
772
const { viewModel } = ctrl.widget.chatWidget;
773
if (viewModel) {
774
ctrl.toggleWidgetUntilNextRequest();
775
ctrl.markActiveController();
776
chatService.cancelCurrentRequestForSession(viewModel.sessionId);
777
}
778
}
779
}
780
781