Path: blob/main/src/vs/editor/contrib/inlineCompletions/browser/controller/commands.ts
4780 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js';6import { asyncTransaction, transaction } from '../../../../../base/common/observable.js';7import { splitLines } from '../../../../../base/common/strings.js';8import { vBoolean, vObj, vOptionalProp, vString, vUndefined, vUnion, vWithJsonSchemaRef } from '../../../../../base/common/validation.js';9import * as nls from '../../../../../nls.js';10import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from '../../../../../platform/accessibility/common/accessibility.js';11import { Action2, MenuId } from '../../../../../platform/actions/common/actions.js';12import { IClipboardService } from '../../../../../platform/clipboard/common/clipboardService.js';13import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';14import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js';15import { KeybindingsRegistry, KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js';16import { INotificationService, Severity } from '../../../../../platform/notification/common/notification.js';17import { ICodeEditor } from '../../../../browser/editorBrowser.js';18import { EditorAction, ServicesAccessor } from '../../../../browser/editorExtensions.js';19import { EditorContextKeys } from '../../../../common/editorContextKeys.js';20import { InlineCompletionsProvider } from '../../../../common/languages.js';21import { ILanguageFeaturesService } from '../../../../common/services/languageFeatures.js';22import { Context as SuggestContext } from '../../../suggest/browser/suggest.js';23import { hideInlineCompletionId, inlineSuggestCommitAlternativeActionId, inlineSuggestCommitId, jumpToNextInlineEditId, showNextInlineSuggestionActionId, showPreviousInlineSuggestionActionId, toggleShowCollapsedId } from './commandIds.js';24import { InlineCompletionContextKeys } from './inlineCompletionContextKeys.js';25import { InlineCompletionsController } from './inlineCompletionsController.js';2627export class ShowNextInlineSuggestionAction extends EditorAction {28public static ID = showNextInlineSuggestionActionId;29constructor() {30super({31id: ShowNextInlineSuggestionAction.ID,32label: nls.localize2('action.inlineSuggest.showNext', "Show Next Inline Suggestion"),33precondition: ContextKeyExpr.and(EditorContextKeys.writable, InlineCompletionContextKeys.inlineSuggestionVisible),34kbOpts: {35weight: 100,36primary: KeyMod.Alt | KeyCode.BracketRight,37},38});39}4041public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {42const controller = InlineCompletionsController.get(editor);43controller?.model.get()?.next();44}45}4647export class ShowPreviousInlineSuggestionAction extends EditorAction {48public static ID = showPreviousInlineSuggestionActionId;49constructor() {50super({51id: ShowPreviousInlineSuggestionAction.ID,52label: nls.localize2('action.inlineSuggest.showPrevious', "Show Previous Inline Suggestion"),53precondition: ContextKeyExpr.and(EditorContextKeys.writable, InlineCompletionContextKeys.inlineSuggestionVisible),54kbOpts: {55weight: 100,56primary: KeyMod.Alt | KeyCode.BracketLeft,57},58});59}6061public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {62const controller = InlineCompletionsController.get(editor);63controller?.model.get()?.previous();64}65}6667export const providerIdSchemaUri = 'vscode://schemas/inlineCompletionProviderIdArgs';6869export function inlineCompletionProviderGetMatcher(provider: InlineCompletionsProvider): string[] {70const result: string[] = [];71if (provider.providerId) {72result.push(provider.providerId.toStringWithoutVersion());73result.push(provider.providerId.extensionId + ':*');74}75return result;76}7778const argsValidator = vUnion(vObj({79showNoResultNotification: vOptionalProp(vBoolean()),80providerId: vOptionalProp(vWithJsonSchemaRef(providerIdSchemaUri, vString())),81explicit: vOptionalProp(vBoolean()),82}), vUndefined());8384export class TriggerInlineSuggestionAction extends EditorAction {85constructor() {86super({87id: 'editor.action.inlineSuggest.trigger',88label: nls.localize2('action.inlineSuggest.trigger', "Trigger Inline Suggestion"),89precondition: EditorContextKeys.writable,90metadata: {91description: nls.localize('inlineSuggest.trigger.description', "Triggers an inline suggestion in the editor."),92args: [{93name: 'args',94description: nls.localize('inlineSuggest.trigger.args', "Options for triggering inline suggestions."),95isOptional: true,96schema: argsValidator.getJSONSchema(),97}]98}99});100}101102public override async run(accessor: ServicesAccessor, editor: ICodeEditor, args: unknown): Promise<void> {103const notificationService = accessor.get(INotificationService);104const languageFeaturesService = accessor.get(ILanguageFeaturesService);105106const controller = InlineCompletionsController.get(editor);107108const validatedArgs = argsValidator.validateOrThrow(args);109110const provider = validatedArgs?.providerId ?111languageFeaturesService.inlineCompletionsProvider.all(editor.getModel()!)112.find(p => inlineCompletionProviderGetMatcher(p).some(m => m === validatedArgs.providerId))113: undefined;114115await asyncTransaction(async tx => {116/** @description triggerExplicitly from command */117await controller?.model.get()?.trigger(tx, {118provider: provider,119explicit: validatedArgs?.explicit ?? true,120});121controller?.playAccessibilitySignal(tx);122});123124if (validatedArgs?.showNoResultNotification) {125if (!controller?.model.get()?.state.get()) {126notificationService.notify({127severity: Severity.Info,128message: nls.localize('noInlineSuggestionAvailable', "No inline suggestion is available.")129});130}131}132}133}134135export class AcceptNextWordOfInlineCompletion extends EditorAction {136constructor() {137super({138id: 'editor.action.inlineSuggest.acceptNextWord',139label: nls.localize2('action.inlineSuggest.acceptNextWord', "Accept Next Word Of Inline Suggestion"),140precondition: ContextKeyExpr.and(EditorContextKeys.writable, InlineCompletionContextKeys.inlineSuggestionVisible),141kbOpts: {142weight: KeybindingWeight.EditorContrib + 1,143primary: KeyMod.CtrlCmd | KeyCode.RightArrow,144kbExpr: ContextKeyExpr.and(EditorContextKeys.writable, InlineCompletionContextKeys.inlineSuggestionVisible, InlineCompletionContextKeys.cursorBeforeGhostText, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()),145},146menuOpts: [{147menuId: MenuId.InlineSuggestionToolbar,148title: nls.localize('acceptWord', 'Accept Word'),149group: 'primary',150order: 2,151}],152});153}154155public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {156const controller = InlineCompletionsController.get(editor);157await controller?.model.get()?.acceptNextWord();158}159}160161export class AcceptNextLineOfInlineCompletion extends EditorAction {162constructor() {163super({164id: 'editor.action.inlineSuggest.acceptNextLine',165label: nls.localize2('action.inlineSuggest.acceptNextLine', "Accept Next Line Of Inline Suggestion"),166precondition: ContextKeyExpr.and(EditorContextKeys.writable, InlineCompletionContextKeys.inlineSuggestionVisible),167kbOpts: {168weight: KeybindingWeight.EditorContrib + 1,169},170menuOpts: [{171menuId: MenuId.InlineSuggestionToolbar,172title: nls.localize('acceptLine', 'Accept Line'),173group: 'secondary',174order: 2,175}],176});177}178179public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {180const controller = InlineCompletionsController.get(editor);181await controller?.model.get()?.acceptNextLine();182}183}184185export class AcceptInlineCompletion extends EditorAction {186constructor() {187super({188id: inlineSuggestCommitId,189label: nls.localize2('action.inlineSuggest.accept', "Accept Inline Suggestion"),190precondition: ContextKeyExpr.or(InlineCompletionContextKeys.inlineSuggestionVisible, InlineCompletionContextKeys.inlineEditVisible),191menuOpts: [{192menuId: MenuId.InlineSuggestionToolbar,193title: nls.localize('accept', "Accept"),194group: 'primary',195order: 2,196}, {197menuId: MenuId.InlineEditsActions,198title: nls.localize('accept', "Accept"),199group: 'primary',200order: 2,201}],202kbOpts: [203{204primary: KeyCode.Tab,205weight: 200,206kbExpr: ContextKeyExpr.or(207ContextKeyExpr.and(208InlineCompletionContextKeys.inlineSuggestionVisible,209EditorContextKeys.tabMovesFocus.toNegated(),210SuggestContext.Visible.toNegated(),211EditorContextKeys.hoverFocused.toNegated(),212InlineCompletionContextKeys.hasSelection.toNegated(),213214InlineCompletionContextKeys.inlineSuggestionHasIndentationLessThanTabSize,215),216ContextKeyExpr.and(217InlineCompletionContextKeys.inlineEditVisible,218EditorContextKeys.tabMovesFocus.toNegated(),219SuggestContext.Visible.toNegated(),220EditorContextKeys.hoverFocused.toNegated(),221222InlineCompletionContextKeys.tabShouldAcceptInlineEdit,223)224),225}226],227});228}229230public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {231const controller = InlineCompletionsController.getInFocusedEditorOrParent(accessor);232if (controller) {233controller.model.get()?.accept(controller.editor);234controller.editor.focus();235}236}237}238KeybindingsRegistry.registerKeybindingRule({239id: inlineSuggestCommitId,240weight: 202, // greater than jump241primary: KeyCode.Tab,242when: ContextKeyExpr.and(InlineCompletionContextKeys.inInlineEditsPreviewEditor)243});244245export class AcceptInlineCompletionAlternativeAction extends EditorAction {246constructor() {247super({248id: inlineSuggestCommitAlternativeActionId,249label: nls.localize2('action.inlineSuggest.acceptAlternativeAction', "Accept Inline Suggestion Alternative Action"),250precondition: ContextKeyExpr.and(InlineCompletionContextKeys.inlineSuggestionAlternativeActionVisible, InlineCompletionContextKeys.inlineEditVisible),251menuOpts: [],252kbOpts: [253{254primary: KeyMod.Shift | KeyCode.Tab,255weight: 203,256}257],258});259}260261public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {262const controller = InlineCompletionsController.getInFocusedEditorOrParent(accessor);263if (controller) {264controller.model.get()?.accept(controller.editor, true);265controller.editor.focus();266}267}268}269KeybindingsRegistry.registerKeybindingRule({270id: inlineSuggestCommitAlternativeActionId,271weight: 203,272primary: KeyMod.Shift | KeyCode.Tab,273when: ContextKeyExpr.and(InlineCompletionContextKeys.inInlineEditsPreviewEditor)274});275276export class JumpToNextInlineEdit extends EditorAction {277constructor() {278super({279id: jumpToNextInlineEditId,280label: nls.localize2('action.inlineSuggest.jump', "Jump to next inline edit"),281precondition: InlineCompletionContextKeys.inlineEditVisible,282menuOpts: [{283menuId: MenuId.InlineEditsActions,284title: nls.localize('jump', "Jump"),285group: 'primary',286order: 1,287when: InlineCompletionContextKeys.cursorAtInlineEdit.toNegated(),288}],289kbOpts: {290primary: KeyCode.Tab,291weight: 201,292kbExpr: ContextKeyExpr.and(293InlineCompletionContextKeys.inlineEditVisible,294EditorContextKeys.tabMovesFocus.toNegated(),295SuggestContext.Visible.toNegated(),296EditorContextKeys.hoverFocused.toNegated(),297InlineCompletionContextKeys.tabShouldJumpToInlineEdit,298),299}300});301}302303public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {304const controller = InlineCompletionsController.get(editor);305if (controller) {306controller.jump();307}308}309}310311export class HideInlineCompletion extends EditorAction {312public static ID = hideInlineCompletionId;313314constructor() {315super({316id: HideInlineCompletion.ID,317label: nls.localize2('action.inlineSuggest.hide', "Hide Inline Suggestion"),318precondition: ContextKeyExpr.or(InlineCompletionContextKeys.inlineSuggestionVisible, InlineCompletionContextKeys.inlineEditVisible),319kbOpts: {320weight: KeybindingWeight.EditorContrib + 90, // same as hiding the suggest widget321primary: KeyCode.Escape,322},323menuOpts: [{324menuId: MenuId.InlineEditsActions,325title: nls.localize('reject', "Reject"),326group: 'primary',327order: 3,328}]329});330}331332public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {333const controller = InlineCompletionsController.getInFocusedEditorOrParent(accessor);334transaction(tx => {335controller?.model.get()?.stop('explicitCancel', tx);336});337controller?.editor.focus();338}339}340341export class ToggleInlineCompletionShowCollapsed extends EditorAction {342public static ID = toggleShowCollapsedId;343344constructor() {345super({346id: ToggleInlineCompletionShowCollapsed.ID,347label: nls.localize2('action.inlineSuggest.toggleShowCollapsed', "Toggle Inline Suggestions Show Collapsed"),348precondition: ContextKeyExpr.true(),349});350}351352public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {353const configurationService = accessor.get(IConfigurationService);354const showCollapsed = configurationService.getValue<boolean>('editor.inlineSuggest.edits.showCollapsed');355configurationService.updateValue('editor.inlineSuggest.edits.showCollapsed', !showCollapsed);356}357}358359KeybindingsRegistry.registerKeybindingRule({360id: HideInlineCompletion.ID,361weight: -1, // very weak362primary: KeyCode.Escape,363secondary: [KeyMod.Shift | KeyCode.Escape],364when: ContextKeyExpr.and(InlineCompletionContextKeys.inInlineEditsPreviewEditor)365});366367export class ToggleAlwaysShowInlineSuggestionToolbar extends Action2 {368public static ID = 'editor.action.inlineSuggest.toggleAlwaysShowToolbar';369370constructor() {371super({372id: ToggleAlwaysShowInlineSuggestionToolbar.ID,373title: nls.localize('action.inlineSuggest.alwaysShowToolbar', "Always Show Toolbar"),374f1: false,375precondition: undefined,376menu: [{377id: MenuId.InlineSuggestionToolbar,378group: 'secondary',379order: 10,380}],381toggled: ContextKeyExpr.equals('config.editor.inlineSuggest.showToolbar', 'always')382});383}384385public async run(accessor: ServicesAccessor): Promise<void> {386const configService = accessor.get(IConfigurationService);387const currentValue = configService.getValue<'always' | 'onHover'>('editor.inlineSuggest.showToolbar');388const newValue = currentValue === 'always' ? 'onHover' : 'always';389configService.updateValue('editor.inlineSuggest.showToolbar', newValue);390}391}392393export class DevExtractReproSample extends EditorAction {394constructor() {395super({396id: 'editor.action.inlineSuggest.dev.extractRepro',397label: nls.localize('action.inlineSuggest.dev.extractRepro', "Developer: Extract Inline Suggest State"),398alias: 'Developer: Inline Suggest Extract Repro',399precondition: ContextKeyExpr.or(InlineCompletionContextKeys.inlineEditVisible, InlineCompletionContextKeys.inlineSuggestionVisible),400});401}402403public override async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<any> {404const clipboardService = accessor.get(IClipboardService);405406const controller = InlineCompletionsController.get(editor);407const m = controller?.model.get();408if (!m) { return; }409const repro = m.extractReproSample();410411const inlineCompletionLines = splitLines(JSON.stringify({ inlineCompletion: repro.inlineCompletion }, null, 4));412413const json = inlineCompletionLines.map(l => '// ' + l).join('\n');414415const reproStr = `${repro.documentValue}\n\n// <json>\n${json}\n// </json>\n`;416417await clipboardService.writeText(reproStr);418419return { reproCase: reproStr };420}421}422423424