Path: blob/main/src/vs/editor/contrib/inlineCompletions/browser/controller/commands.ts
5283 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, vUnchecked, 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()),82changeHintData: vOptionalProp(vUnchecked()),83}), vUndefined());8485export class TriggerInlineSuggestionAction extends EditorAction {86constructor() {87super({88id: 'editor.action.inlineSuggest.trigger',89label: nls.localize2('action.inlineSuggest.trigger', "Trigger Inline Suggestion"),90precondition: EditorContextKeys.writable,91metadata: {92description: nls.localize('inlineSuggest.trigger.description', "Triggers an inline suggestion in the editor."),93args: [{94name: 'args',95description: nls.localize('inlineSuggest.trigger.args', "Options for triggering inline suggestions."),96isOptional: true,97schema: argsValidator.getJSONSchema(),98}]99}100});101}102103public override async run(accessor: ServicesAccessor, editor: ICodeEditor, args: unknown): Promise<void> {104const notificationService = accessor.get(INotificationService);105const languageFeaturesService = accessor.get(ILanguageFeaturesService);106107const controller = InlineCompletionsController.get(editor);108109const validatedArgs = argsValidator.validateOrThrow(args);110111const provider = validatedArgs?.providerId ?112languageFeaturesService.inlineCompletionsProvider.all(editor.getModel()!)113.find(p => inlineCompletionProviderGetMatcher(p).some(m => m === validatedArgs.providerId))114: undefined;115116await asyncTransaction(async tx => {117/** @description triggerExplicitly from command */118await controller?.model.get()?.trigger(tx, {119provider: provider,120explicit: validatedArgs?.explicit ?? true,121changeHint: validatedArgs?.changeHintData ? { data: validatedArgs.changeHintData } : undefined,122});123controller?.playAccessibilitySignal(tx);124});125126if (validatedArgs?.showNoResultNotification) {127if (!controller?.model.get()?.state.get()) {128notificationService.notify({129severity: Severity.Info,130message: nls.localize('noInlineSuggestionAvailable', "No inline suggestion is available.")131});132}133}134}135}136137export class AcceptNextWordOfInlineCompletion extends EditorAction {138constructor() {139super({140id: 'editor.action.inlineSuggest.acceptNextWord',141label: nls.localize2('action.inlineSuggest.acceptNextWord', "Accept Next Word Of Inline Suggestion"),142precondition: ContextKeyExpr.and(EditorContextKeys.writable, InlineCompletionContextKeys.inlineSuggestionVisible),143kbOpts: {144weight: KeybindingWeight.EditorContrib + 1,145primary: KeyMod.CtrlCmd | KeyCode.RightArrow,146kbExpr: ContextKeyExpr.and(EditorContextKeys.writable, InlineCompletionContextKeys.inlineSuggestionVisible, InlineCompletionContextKeys.cursorBeforeGhostText, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()),147},148menuOpts: [{149menuId: MenuId.InlineSuggestionToolbar,150title: nls.localize('acceptWord', 'Accept Word'),151group: 'primary',152order: 2,153}],154});155}156157public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {158const controller = InlineCompletionsController.get(editor);159await controller?.model.get()?.acceptNextWord();160}161}162163export class AcceptNextLineOfInlineCompletion extends EditorAction {164constructor() {165super({166id: 'editor.action.inlineSuggest.acceptNextLine',167label: nls.localize2('action.inlineSuggest.acceptNextLine', "Accept Next Line Of Inline Suggestion"),168precondition: ContextKeyExpr.and(EditorContextKeys.writable, InlineCompletionContextKeys.inlineSuggestionVisible),169kbOpts: {170weight: KeybindingWeight.EditorContrib + 1,171},172menuOpts: [{173menuId: MenuId.InlineSuggestionToolbar,174title: nls.localize('acceptLine', 'Accept Line'),175group: 'secondary',176order: 2,177}],178});179}180181public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {182const controller = InlineCompletionsController.get(editor);183await controller?.model.get()?.acceptNextLine();184}185}186187export class AcceptInlineCompletion extends EditorAction {188constructor() {189super({190id: inlineSuggestCommitId,191label: nls.localize2('action.inlineSuggest.accept', "Accept Inline Suggestion"),192precondition: ContextKeyExpr.or(InlineCompletionContextKeys.inlineSuggestionVisible, InlineCompletionContextKeys.inlineEditVisible),193menuOpts: [{194menuId: MenuId.InlineSuggestionToolbar,195title: nls.localize('accept', "Accept"),196group: 'primary',197order: 2,198}, {199menuId: MenuId.InlineEditsActions,200title: nls.localize('accept', "Accept"),201group: 'primary',202order: 2,203}],204kbOpts: [205{206primary: KeyCode.Tab,207weight: 200,208kbExpr: ContextKeyExpr.or(209ContextKeyExpr.and(210InlineCompletionContextKeys.inlineSuggestionVisible,211EditorContextKeys.tabMovesFocus.toNegated(),212SuggestContext.Visible.toNegated(),213EditorContextKeys.hoverFocused.toNegated(),214InlineCompletionContextKeys.hasSelection.toNegated(),215216InlineCompletionContextKeys.inlineSuggestionHasIndentationLessThanTabSize,217),218ContextKeyExpr.and(219InlineCompletionContextKeys.inlineEditVisible,220EditorContextKeys.tabMovesFocus.toNegated(),221SuggestContext.Visible.toNegated(),222EditorContextKeys.hoverFocused.toNegated(),223224InlineCompletionContextKeys.tabShouldAcceptInlineEdit,225)226),227}228],229});230}231232public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {233const controller = InlineCompletionsController.getInFocusedEditorOrParent(accessor);234if (controller) {235controller.model.get()?.accept(controller.editor);236controller.editor.focus();237}238}239}240KeybindingsRegistry.registerKeybindingRule({241id: inlineSuggestCommitId,242weight: 202, // greater than jump243primary: KeyCode.Tab,244when: ContextKeyExpr.and(InlineCompletionContextKeys.inInlineEditsPreviewEditor)245});246247export class AcceptInlineCompletionAlternativeAction extends EditorAction {248constructor() {249super({250id: inlineSuggestCommitAlternativeActionId,251label: nls.localize2('action.inlineSuggest.acceptAlternativeAction', "Accept Inline Suggestion Alternative Action"),252precondition: ContextKeyExpr.and(InlineCompletionContextKeys.inlineSuggestionAlternativeActionVisible, InlineCompletionContextKeys.inlineEditVisible),253menuOpts: [],254kbOpts: [255{256primary: KeyMod.Shift | KeyCode.Tab,257weight: 203,258}259],260});261}262263public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {264const controller = InlineCompletionsController.getInFocusedEditorOrParent(accessor);265if (controller) {266controller.model.get()?.accept(controller.editor, true);267controller.editor.focus();268}269}270}271KeybindingsRegistry.registerKeybindingRule({272id: inlineSuggestCommitAlternativeActionId,273weight: 203,274primary: KeyMod.Shift | KeyCode.Tab,275when: ContextKeyExpr.and(InlineCompletionContextKeys.inInlineEditsPreviewEditor)276});277278export class JumpToNextInlineEdit extends EditorAction {279constructor() {280super({281id: jumpToNextInlineEditId,282label: nls.localize2('action.inlineSuggest.jump', "Jump to next inline edit"),283precondition: InlineCompletionContextKeys.inlineEditVisible,284menuOpts: [{285menuId: MenuId.InlineEditsActions,286title: nls.localize('jump', "Jump"),287group: 'primary',288order: 1,289when: InlineCompletionContextKeys.cursorAtInlineEdit.toNegated(),290}],291kbOpts: {292primary: KeyCode.Tab,293weight: 201,294kbExpr: ContextKeyExpr.and(295InlineCompletionContextKeys.inlineEditVisible,296EditorContextKeys.tabMovesFocus.toNegated(),297SuggestContext.Visible.toNegated(),298EditorContextKeys.hoverFocused.toNegated(),299InlineCompletionContextKeys.tabShouldJumpToInlineEdit,300),301}302});303}304305public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {306const controller = InlineCompletionsController.get(editor);307if (controller) {308controller.jump();309}310}311}312313export class HideInlineCompletion extends EditorAction {314public static ID = hideInlineCompletionId;315316constructor() {317super({318id: HideInlineCompletion.ID,319label: nls.localize2('action.inlineSuggest.hide', "Hide Inline Suggestion"),320precondition: ContextKeyExpr.or(InlineCompletionContextKeys.inlineSuggestionVisible, InlineCompletionContextKeys.inlineEditVisible),321kbOpts: {322weight: KeybindingWeight.EditorContrib + 90, // same as hiding the suggest widget323primary: KeyCode.Escape,324},325menuOpts: [{326menuId: MenuId.InlineEditsActions,327title: nls.localize('reject', "Reject"),328group: 'primary',329order: 3,330}]331});332}333334public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {335const controller = InlineCompletionsController.getInFocusedEditorOrParent(accessor);336transaction(tx => {337controller?.model.get()?.stop('explicitCancel', tx);338});339controller?.editor.focus();340}341}342343export class ToggleInlineCompletionShowCollapsed extends EditorAction {344public static ID = toggleShowCollapsedId;345346constructor() {347super({348id: ToggleInlineCompletionShowCollapsed.ID,349label: nls.localize2('action.inlineSuggest.toggleShowCollapsed', "Toggle Inline Suggestions Show Collapsed"),350precondition: ContextKeyExpr.true(),351});352}353354public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {355const configurationService = accessor.get(IConfigurationService);356const showCollapsed = configurationService.getValue<boolean>('editor.inlineSuggest.edits.showCollapsed');357configurationService.updateValue('editor.inlineSuggest.edits.showCollapsed', !showCollapsed);358}359}360361KeybindingsRegistry.registerKeybindingRule({362id: HideInlineCompletion.ID,363weight: -1, // very weak364primary: KeyCode.Escape,365secondary: [KeyMod.Shift | KeyCode.Escape],366when: ContextKeyExpr.and(InlineCompletionContextKeys.inInlineEditsPreviewEditor)367});368369export class ToggleAlwaysShowInlineSuggestionToolbar extends Action2 {370public static ID = 'editor.action.inlineSuggest.toggleAlwaysShowToolbar';371372constructor() {373super({374id: ToggleAlwaysShowInlineSuggestionToolbar.ID,375title: nls.localize('action.inlineSuggest.alwaysShowToolbar', "Always Show Toolbar"),376f1: false,377precondition: undefined,378menu: [{379id: MenuId.InlineSuggestionToolbar,380group: 'secondary',381order: 10,382}],383toggled: ContextKeyExpr.equals('config.editor.inlineSuggest.showToolbar', 'always')384});385}386387public async run(accessor: ServicesAccessor): Promise<void> {388const configService = accessor.get(IConfigurationService);389const currentValue = configService.getValue<'always' | 'onHover'>('editor.inlineSuggest.showToolbar');390const newValue = currentValue === 'always' ? 'onHover' : 'always';391configService.updateValue('editor.inlineSuggest.showToolbar', newValue);392}393}394395export class DevExtractReproSample extends EditorAction {396constructor() {397super({398id: 'editor.action.inlineSuggest.dev.extractRepro',399label: nls.localize('action.inlineSuggest.dev.extractRepro', "Developer: Extract Inline Suggest State"),400alias: 'Developer: Inline Suggest Extract Repro',401precondition: ContextKeyExpr.or(InlineCompletionContextKeys.inlineEditVisible, InlineCompletionContextKeys.inlineSuggestionVisible),402});403}404405public override async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<any> {406const clipboardService = accessor.get(IClipboardService);407408const controller = InlineCompletionsController.get(editor);409const m = controller?.model.get();410if (!m) { return; }411const repro = m.extractReproSample();412413const inlineCompletionLines = splitLines(JSON.stringify({ inlineCompletion: repro.inlineCompletion }, null, 4));414415const json = inlineCompletionLines.map(l => '// ' + l).join('\n');416417const reproStr = `${repro.documentValue}\n\n// <json>\n${json}\n// </json>\n`;418419await clipboardService.writeText(reproStr);420421return { reproCase: reproStr };422}423}424425426