Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/contrib/inlineCompletions/browser/controller/commands.ts
4780 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 { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js';
7
import { asyncTransaction, transaction } from '../../../../../base/common/observable.js';
8
import { splitLines } from '../../../../../base/common/strings.js';
9
import { vBoolean, vObj, vOptionalProp, vString, vUndefined, vUnion, vWithJsonSchemaRef } from '../../../../../base/common/validation.js';
10
import * as nls from '../../../../../nls.js';
11
import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from '../../../../../platform/accessibility/common/accessibility.js';
12
import { Action2, MenuId } from '../../../../../platform/actions/common/actions.js';
13
import { IClipboardService } from '../../../../../platform/clipboard/common/clipboardService.js';
14
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
15
import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js';
16
import { KeybindingsRegistry, KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js';
17
import { INotificationService, Severity } from '../../../../../platform/notification/common/notification.js';
18
import { ICodeEditor } from '../../../../browser/editorBrowser.js';
19
import { EditorAction, ServicesAccessor } from '../../../../browser/editorExtensions.js';
20
import { EditorContextKeys } from '../../../../common/editorContextKeys.js';
21
import { InlineCompletionsProvider } from '../../../../common/languages.js';
22
import { ILanguageFeaturesService } from '../../../../common/services/languageFeatures.js';
23
import { Context as SuggestContext } from '../../../suggest/browser/suggest.js';
24
import { hideInlineCompletionId, inlineSuggestCommitAlternativeActionId, inlineSuggestCommitId, jumpToNextInlineEditId, showNextInlineSuggestionActionId, showPreviousInlineSuggestionActionId, toggleShowCollapsedId } from './commandIds.js';
25
import { InlineCompletionContextKeys } from './inlineCompletionContextKeys.js';
26
import { InlineCompletionsController } from './inlineCompletionsController.js';
27
28
export class ShowNextInlineSuggestionAction extends EditorAction {
29
public static ID = showNextInlineSuggestionActionId;
30
constructor() {
31
super({
32
id: ShowNextInlineSuggestionAction.ID,
33
label: nls.localize2('action.inlineSuggest.showNext', "Show Next Inline Suggestion"),
34
precondition: ContextKeyExpr.and(EditorContextKeys.writable, InlineCompletionContextKeys.inlineSuggestionVisible),
35
kbOpts: {
36
weight: 100,
37
primary: KeyMod.Alt | KeyCode.BracketRight,
38
},
39
});
40
}
41
42
public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
43
const controller = InlineCompletionsController.get(editor);
44
controller?.model.get()?.next();
45
}
46
}
47
48
export class ShowPreviousInlineSuggestionAction extends EditorAction {
49
public static ID = showPreviousInlineSuggestionActionId;
50
constructor() {
51
super({
52
id: ShowPreviousInlineSuggestionAction.ID,
53
label: nls.localize2('action.inlineSuggest.showPrevious', "Show Previous Inline Suggestion"),
54
precondition: ContextKeyExpr.and(EditorContextKeys.writable, InlineCompletionContextKeys.inlineSuggestionVisible),
55
kbOpts: {
56
weight: 100,
57
primary: KeyMod.Alt | KeyCode.BracketLeft,
58
},
59
});
60
}
61
62
public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
63
const controller = InlineCompletionsController.get(editor);
64
controller?.model.get()?.previous();
65
}
66
}
67
68
export const providerIdSchemaUri = 'vscode://schemas/inlineCompletionProviderIdArgs';
69
70
export function inlineCompletionProviderGetMatcher(provider: InlineCompletionsProvider): string[] {
71
const result: string[] = [];
72
if (provider.providerId) {
73
result.push(provider.providerId.toStringWithoutVersion());
74
result.push(provider.providerId.extensionId + ':*');
75
}
76
return result;
77
}
78
79
const argsValidator = vUnion(vObj({
80
showNoResultNotification: vOptionalProp(vBoolean()),
81
providerId: vOptionalProp(vWithJsonSchemaRef(providerIdSchemaUri, vString())),
82
explicit: vOptionalProp(vBoolean()),
83
}), vUndefined());
84
85
export class TriggerInlineSuggestionAction extends EditorAction {
86
constructor() {
87
super({
88
id: 'editor.action.inlineSuggest.trigger',
89
label: nls.localize2('action.inlineSuggest.trigger', "Trigger Inline Suggestion"),
90
precondition: EditorContextKeys.writable,
91
metadata: {
92
description: nls.localize('inlineSuggest.trigger.description', "Triggers an inline suggestion in the editor."),
93
args: [{
94
name: 'args',
95
description: nls.localize('inlineSuggest.trigger.args', "Options for triggering inline suggestions."),
96
isOptional: true,
97
schema: argsValidator.getJSONSchema(),
98
}]
99
}
100
});
101
}
102
103
public override async run(accessor: ServicesAccessor, editor: ICodeEditor, args: unknown): Promise<void> {
104
const notificationService = accessor.get(INotificationService);
105
const languageFeaturesService = accessor.get(ILanguageFeaturesService);
106
107
const controller = InlineCompletionsController.get(editor);
108
109
const validatedArgs = argsValidator.validateOrThrow(args);
110
111
const provider = validatedArgs?.providerId ?
112
languageFeaturesService.inlineCompletionsProvider.all(editor.getModel()!)
113
.find(p => inlineCompletionProviderGetMatcher(p).some(m => m === validatedArgs.providerId))
114
: undefined;
115
116
await asyncTransaction(async tx => {
117
/** @description triggerExplicitly from command */
118
await controller?.model.get()?.trigger(tx, {
119
provider: provider,
120
explicit: validatedArgs?.explicit ?? true,
121
});
122
controller?.playAccessibilitySignal(tx);
123
});
124
125
if (validatedArgs?.showNoResultNotification) {
126
if (!controller?.model.get()?.state.get()) {
127
notificationService.notify({
128
severity: Severity.Info,
129
message: nls.localize('noInlineSuggestionAvailable', "No inline suggestion is available.")
130
});
131
}
132
}
133
}
134
}
135
136
export class AcceptNextWordOfInlineCompletion extends EditorAction {
137
constructor() {
138
super({
139
id: 'editor.action.inlineSuggest.acceptNextWord',
140
label: nls.localize2('action.inlineSuggest.acceptNextWord', "Accept Next Word Of Inline Suggestion"),
141
precondition: ContextKeyExpr.and(EditorContextKeys.writable, InlineCompletionContextKeys.inlineSuggestionVisible),
142
kbOpts: {
143
weight: KeybindingWeight.EditorContrib + 1,
144
primary: KeyMod.CtrlCmd | KeyCode.RightArrow,
145
kbExpr: ContextKeyExpr.and(EditorContextKeys.writable, InlineCompletionContextKeys.inlineSuggestionVisible, InlineCompletionContextKeys.cursorBeforeGhostText, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()),
146
},
147
menuOpts: [{
148
menuId: MenuId.InlineSuggestionToolbar,
149
title: nls.localize('acceptWord', 'Accept Word'),
150
group: 'primary',
151
order: 2,
152
}],
153
});
154
}
155
156
public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
157
const controller = InlineCompletionsController.get(editor);
158
await controller?.model.get()?.acceptNextWord();
159
}
160
}
161
162
export class AcceptNextLineOfInlineCompletion extends EditorAction {
163
constructor() {
164
super({
165
id: 'editor.action.inlineSuggest.acceptNextLine',
166
label: nls.localize2('action.inlineSuggest.acceptNextLine', "Accept Next Line Of Inline Suggestion"),
167
precondition: ContextKeyExpr.and(EditorContextKeys.writable, InlineCompletionContextKeys.inlineSuggestionVisible),
168
kbOpts: {
169
weight: KeybindingWeight.EditorContrib + 1,
170
},
171
menuOpts: [{
172
menuId: MenuId.InlineSuggestionToolbar,
173
title: nls.localize('acceptLine', 'Accept Line'),
174
group: 'secondary',
175
order: 2,
176
}],
177
});
178
}
179
180
public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
181
const controller = InlineCompletionsController.get(editor);
182
await controller?.model.get()?.acceptNextLine();
183
}
184
}
185
186
export class AcceptInlineCompletion extends EditorAction {
187
constructor() {
188
super({
189
id: inlineSuggestCommitId,
190
label: nls.localize2('action.inlineSuggest.accept', "Accept Inline Suggestion"),
191
precondition: ContextKeyExpr.or(InlineCompletionContextKeys.inlineSuggestionVisible, InlineCompletionContextKeys.inlineEditVisible),
192
menuOpts: [{
193
menuId: MenuId.InlineSuggestionToolbar,
194
title: nls.localize('accept', "Accept"),
195
group: 'primary',
196
order: 2,
197
}, {
198
menuId: MenuId.InlineEditsActions,
199
title: nls.localize('accept', "Accept"),
200
group: 'primary',
201
order: 2,
202
}],
203
kbOpts: [
204
{
205
primary: KeyCode.Tab,
206
weight: 200,
207
kbExpr: ContextKeyExpr.or(
208
ContextKeyExpr.and(
209
InlineCompletionContextKeys.inlineSuggestionVisible,
210
EditorContextKeys.tabMovesFocus.toNegated(),
211
SuggestContext.Visible.toNegated(),
212
EditorContextKeys.hoverFocused.toNegated(),
213
InlineCompletionContextKeys.hasSelection.toNegated(),
214
215
InlineCompletionContextKeys.inlineSuggestionHasIndentationLessThanTabSize,
216
),
217
ContextKeyExpr.and(
218
InlineCompletionContextKeys.inlineEditVisible,
219
EditorContextKeys.tabMovesFocus.toNegated(),
220
SuggestContext.Visible.toNegated(),
221
EditorContextKeys.hoverFocused.toNegated(),
222
223
InlineCompletionContextKeys.tabShouldAcceptInlineEdit,
224
)
225
),
226
}
227
],
228
});
229
}
230
231
public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
232
const controller = InlineCompletionsController.getInFocusedEditorOrParent(accessor);
233
if (controller) {
234
controller.model.get()?.accept(controller.editor);
235
controller.editor.focus();
236
}
237
}
238
}
239
KeybindingsRegistry.registerKeybindingRule({
240
id: inlineSuggestCommitId,
241
weight: 202, // greater than jump
242
primary: KeyCode.Tab,
243
when: ContextKeyExpr.and(InlineCompletionContextKeys.inInlineEditsPreviewEditor)
244
});
245
246
export class AcceptInlineCompletionAlternativeAction extends EditorAction {
247
constructor() {
248
super({
249
id: inlineSuggestCommitAlternativeActionId,
250
label: nls.localize2('action.inlineSuggest.acceptAlternativeAction', "Accept Inline Suggestion Alternative Action"),
251
precondition: ContextKeyExpr.and(InlineCompletionContextKeys.inlineSuggestionAlternativeActionVisible, InlineCompletionContextKeys.inlineEditVisible),
252
menuOpts: [],
253
kbOpts: [
254
{
255
primary: KeyMod.Shift | KeyCode.Tab,
256
weight: 203,
257
}
258
],
259
});
260
}
261
262
public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
263
const controller = InlineCompletionsController.getInFocusedEditorOrParent(accessor);
264
if (controller) {
265
controller.model.get()?.accept(controller.editor, true);
266
controller.editor.focus();
267
}
268
}
269
}
270
KeybindingsRegistry.registerKeybindingRule({
271
id: inlineSuggestCommitAlternativeActionId,
272
weight: 203,
273
primary: KeyMod.Shift | KeyCode.Tab,
274
when: ContextKeyExpr.and(InlineCompletionContextKeys.inInlineEditsPreviewEditor)
275
});
276
277
export class JumpToNextInlineEdit extends EditorAction {
278
constructor() {
279
super({
280
id: jumpToNextInlineEditId,
281
label: nls.localize2('action.inlineSuggest.jump', "Jump to next inline edit"),
282
precondition: InlineCompletionContextKeys.inlineEditVisible,
283
menuOpts: [{
284
menuId: MenuId.InlineEditsActions,
285
title: nls.localize('jump', "Jump"),
286
group: 'primary',
287
order: 1,
288
when: InlineCompletionContextKeys.cursorAtInlineEdit.toNegated(),
289
}],
290
kbOpts: {
291
primary: KeyCode.Tab,
292
weight: 201,
293
kbExpr: ContextKeyExpr.and(
294
InlineCompletionContextKeys.inlineEditVisible,
295
EditorContextKeys.tabMovesFocus.toNegated(),
296
SuggestContext.Visible.toNegated(),
297
EditorContextKeys.hoverFocused.toNegated(),
298
InlineCompletionContextKeys.tabShouldJumpToInlineEdit,
299
),
300
}
301
});
302
}
303
304
public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
305
const controller = InlineCompletionsController.get(editor);
306
if (controller) {
307
controller.jump();
308
}
309
}
310
}
311
312
export class HideInlineCompletion extends EditorAction {
313
public static ID = hideInlineCompletionId;
314
315
constructor() {
316
super({
317
id: HideInlineCompletion.ID,
318
label: nls.localize2('action.inlineSuggest.hide', "Hide Inline Suggestion"),
319
precondition: ContextKeyExpr.or(InlineCompletionContextKeys.inlineSuggestionVisible, InlineCompletionContextKeys.inlineEditVisible),
320
kbOpts: {
321
weight: KeybindingWeight.EditorContrib + 90, // same as hiding the suggest widget
322
primary: KeyCode.Escape,
323
},
324
menuOpts: [{
325
menuId: MenuId.InlineEditsActions,
326
title: nls.localize('reject', "Reject"),
327
group: 'primary',
328
order: 3,
329
}]
330
});
331
}
332
333
public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
334
const controller = InlineCompletionsController.getInFocusedEditorOrParent(accessor);
335
transaction(tx => {
336
controller?.model.get()?.stop('explicitCancel', tx);
337
});
338
controller?.editor.focus();
339
}
340
}
341
342
export class ToggleInlineCompletionShowCollapsed extends EditorAction {
343
public static ID = toggleShowCollapsedId;
344
345
constructor() {
346
super({
347
id: ToggleInlineCompletionShowCollapsed.ID,
348
label: nls.localize2('action.inlineSuggest.toggleShowCollapsed', "Toggle Inline Suggestions Show Collapsed"),
349
precondition: ContextKeyExpr.true(),
350
});
351
}
352
353
public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
354
const configurationService = accessor.get(IConfigurationService);
355
const showCollapsed = configurationService.getValue<boolean>('editor.inlineSuggest.edits.showCollapsed');
356
configurationService.updateValue('editor.inlineSuggest.edits.showCollapsed', !showCollapsed);
357
}
358
}
359
360
KeybindingsRegistry.registerKeybindingRule({
361
id: HideInlineCompletion.ID,
362
weight: -1, // very weak
363
primary: KeyCode.Escape,
364
secondary: [KeyMod.Shift | KeyCode.Escape],
365
when: ContextKeyExpr.and(InlineCompletionContextKeys.inInlineEditsPreviewEditor)
366
});
367
368
export class ToggleAlwaysShowInlineSuggestionToolbar extends Action2 {
369
public static ID = 'editor.action.inlineSuggest.toggleAlwaysShowToolbar';
370
371
constructor() {
372
super({
373
id: ToggleAlwaysShowInlineSuggestionToolbar.ID,
374
title: nls.localize('action.inlineSuggest.alwaysShowToolbar', "Always Show Toolbar"),
375
f1: false,
376
precondition: undefined,
377
menu: [{
378
id: MenuId.InlineSuggestionToolbar,
379
group: 'secondary',
380
order: 10,
381
}],
382
toggled: ContextKeyExpr.equals('config.editor.inlineSuggest.showToolbar', 'always')
383
});
384
}
385
386
public async run(accessor: ServicesAccessor): Promise<void> {
387
const configService = accessor.get(IConfigurationService);
388
const currentValue = configService.getValue<'always' | 'onHover'>('editor.inlineSuggest.showToolbar');
389
const newValue = currentValue === 'always' ? 'onHover' : 'always';
390
configService.updateValue('editor.inlineSuggest.showToolbar', newValue);
391
}
392
}
393
394
export class DevExtractReproSample extends EditorAction {
395
constructor() {
396
super({
397
id: 'editor.action.inlineSuggest.dev.extractRepro',
398
label: nls.localize('action.inlineSuggest.dev.extractRepro', "Developer: Extract Inline Suggest State"),
399
alias: 'Developer: Inline Suggest Extract Repro',
400
precondition: ContextKeyExpr.or(InlineCompletionContextKeys.inlineEditVisible, InlineCompletionContextKeys.inlineSuggestionVisible),
401
});
402
}
403
404
public override async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<any> {
405
const clipboardService = accessor.get(IClipboardService);
406
407
const controller = InlineCompletionsController.get(editor);
408
const m = controller?.model.get();
409
if (!m) { return; }
410
const repro = m.extractReproSample();
411
412
const inlineCompletionLines = splitLines(JSON.stringify({ inlineCompletion: repro.inlineCompletion }, null, 4));
413
414
const json = inlineCompletionLines.map(l => '// ' + l).join('\n');
415
416
const reproStr = `${repro.documentValue}\n\n// <json>\n${json}\n// </json>\n`;
417
418
await clipboardService.writeText(reproStr);
419
420
return { reproCase: reproStr };
421
}
422
}
423
424