From 098dfd56e3cbdf406d5207bd522e2604dfa3b0e6 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 28 May 2021 14:21:55 +0200 Subject: [PATCH 1/3] Changes shortcuts of next/previous inline completion to Alt + Open/Close Square Bracket. --- .../editor/contrib/inlineCompletions/ghostTextController.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/ghostTextController.ts b/src/vs/editor/contrib/inlineCompletions/ghostTextController.ts index 936268a0993..ab55f8b46e8 100644 --- a/src/vs/editor/contrib/inlineCompletions/ghostTextController.ts +++ b/src/vs/editor/contrib/inlineCompletions/ghostTextController.ts @@ -235,7 +235,7 @@ export class ShowNextInlineCompletionAction extends EditorAction { precondition: EditorContextKeys.writable, kbOpts: { weight: 100, - primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift | KeyCode.RightArrow, + primary: KeyMod.Alt | KeyCode.US_CLOSE_SQUARE_BRACKET, }, }); } @@ -258,7 +258,7 @@ export class ShowPreviousInlineCompletionAction extends EditorAction { precondition: EditorContextKeys.writable, kbOpts: { weight: 100, - primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift | KeyCode.LeftArrow, + primary: KeyMod.Alt | KeyCode.US_OPEN_SQUARE_BRACKET, }, }); } From a2d1bd0d1586427fc0add2877df4181503cb1dfc Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 28 May 2021 14:30:21 +0200 Subject: [PATCH 2/3] Adds menu 'editor/inlineCompletions/actions' that extensions can contribute to. --- .../inlineCompletionsHoverParticipant.ts | 30 +++++++++++++++---- src/vs/platform/actions/common/actions.ts | 1 + .../api/common/menusExtensionPoint.ts | 8 ++++- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/inlineCompletionsHoverParticipant.ts b/src/vs/editor/contrib/inlineCompletions/inlineCompletionsHoverParticipant.ts index 5781d62dd00..348a5b8d852 100644 --- a/src/vs/editor/contrib/inlineCompletions/inlineCompletionsHoverParticipant.ts +++ b/src/vs/editor/contrib/inlineCompletions/inlineCompletionsHoverParticipant.ts @@ -11,9 +11,10 @@ import { IModelDecoration } from 'vs/editor/common/model'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { GhostTextController, ShowNextInlineCompletionAction, ShowPreviousInlineCompletionAction } from 'vs/editor/contrib/inlineCompletions/ghostTextController'; import { ICommandService } from 'vs/platform/commands/common/commands'; +import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; export class InlineCompletionsHover implements IHoverPart { - constructor( public readonly owner: IEditorHoverParticipant, public readonly range: Range @@ -25,15 +26,15 @@ export class InlineCompletionsHover implements IHoverPart { && this.range.endColumn >= hoverRange.endColumn ); } - } export class InlineCompletionsHoverParticipant implements IEditorHoverParticipant { - constructor( private readonly _editor: ICodeEditor, hover: IEditorHover, @ICommandService private readonly _commandService: ICommandService, + @IMenuService private readonly _menuService: IMenuService, + @IContextKeyService private readonly _contextKeyService: IContextKeyService, ) { } computeSync(hoverRange: Range, lineDecorations: IModelDecoration[]): InlineCompletionsHover[] { @@ -45,17 +46,34 @@ export class InlineCompletionsHoverParticipant implements IEditorHoverParticipan } renderHoverParts(hoverParts: InlineCompletionsHover[], fragment: DocumentFragment, statusBar: EditorHoverStatusBar): IDisposable { + const menu = this._menuService.createMenu( + MenuId.InlineCompletionsActions, + this._contextKeyService + ); + statusBar.addAction({ - label: nls.localize('showPreviousInlineCompletion', "⬅️ Previous"), + label: nls.localize('showPreviousInlineCompletion', "Previous"), commandId: ShowPreviousInlineCompletionAction.ID, run: () => this._commandService.executeCommand(ShowPreviousInlineCompletionAction.ID) }); statusBar.addAction({ - label: nls.localize('showNextInlineCompletion', "Next ➡️"), + label: nls.localize('showNextInlineCompletion', "Next"), commandId: ShowNextInlineCompletionAction.ID, run: () => this._commandService.executeCommand(ShowNextInlineCompletionAction.ID) }); + + for (const [_, group] of menu.getActions()) { + for (const action of group) { + if (action instanceof MenuItemAction) { + statusBar.addAction({ + label: action.label, + commandId: action.item.id, + run: () => this._commandService.executeCommand(action.item.id) + }); + } + } + } + return Disposable.None; } - } diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 27f03169442..f0e548fea19 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -171,6 +171,7 @@ export class MenuId { static readonly TerminalTabEmptyAreaContext = new MenuId('TerminalTabEmptyAreaContext'); static readonly TerminalInlineTabContext = new MenuId('TerminalInlineTabContext'); static readonly WebviewContext = new MenuId('WebviewContext'); + static readonly InlineCompletionsActions = new MenuId('InlineCompletionsActions'); readonly id: number; readonly _debugName: string; diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index 71aa38a9fee..b7e24d0765e 100644 --- a/src/vs/workbench/api/common/menusExtensionPoint.ts +++ b/src/vs/workbench/api/common/menusExtensionPoint.ts @@ -229,7 +229,13 @@ const apiMenus: IAPIMenu[] = [ key: 'ports/item/port/inline', id: MenuId.TunnelPortInline, description: localize('view.tunnelPortInline', "The Ports view item port inline menu") - } + }, + { + key: 'editor/inlineCompletions/actions', + id: MenuId.InlineCompletionsActions, + description: localize('inlineCompletions.actions', "The actions shown when hovering on an inline completion"), + supportsSubmenus: false + }, ]; namespace schema { From b16b45fecb84ef1b059810445dde300340b1b903 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 28 May 2021 17:37:49 +0200 Subject: [PATCH 3/3] Tweaks the effect of showSuggestionPreview and introduces showInlineCompletions. Explicitly triggering inline completions will now work even if settings are disabled. --- src/vs/editor/common/config/editorOptions.ts | 30 ++++-- .../inlineCompletions/ghostTextController.ts | 93 +++++++++++-------- .../inlineCompletions/ghostTextWidget.ts | 2 +- .../inlineCompletionsModel.ts | 10 +- .../suggestWidgetAdapterModel.ts | 12 ++- src/vs/monaco.d.ts | 12 ++- 6 files changed, 99 insertions(+), 60 deletions(-) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 48cb7125c14..d3a46c0ad6f 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -3175,14 +3175,18 @@ export interface ISuggestOptions { */ showStatusBar?: boolean; /** - * Enable or disable the rendering of the suggestion inline. + * Enable or disable the rendering of the suggestion preview. */ showSuggestionPreview?: boolean; /** - * Enable or disable the default expansion of the suggestion preview. - * Defaults to false. + * Enable or disable the rendering of automatic inline completions. + */ + showInlineCompletions?: boolean; + /** + * Enable or disable the default expansion of the ghost text as used + * by the suggestion preview or the inline completions. */ - suggestionPreviewExpanded?: boolean; + ghostTextExpanded?: boolean; /** * Show details inline with the label. Defaults to true. */ @@ -3315,7 +3319,8 @@ class EditorSuggest extends BaseEditorOption()); - private readonly contextKeys: GhostTextContextKeys; + private triggeredExplicitly = false; constructor( private readonly editor: ICodeEditor, @@ -54,13 +54,21 @@ export class GhostTextController extends Disposable { this.updateModelController(); } + // Don't call this method when not neccessary. It will recreate the activeController. private updateModelController(): void { const suggestOptions = this.editor.getOption(EditorOption.suggest); this.activeController.value = undefined; - this.activeController.value = this.editor.hasModel() && suggestOptions.showSuggestionPreview - ? this.instantiationService.createInstance(ActiveGhostTextController, this.editor, this.widget, this.contextKeys) - : undefined; + // ActiveGhostTextController is only created if one of those settings is set or if the inline completions are triggered explicitly. + this.activeController.value = + this.editor.hasModel() && (suggestOptions.showSuggestionPreview || suggestOptions.showInlineCompletions || this.triggeredExplicitly) + ? this.instantiationService.createInstance( + ActiveGhostTextController, + this.editor, + this.widget, + this.contextKeys + ) + : undefined; } public shouldShowHoverAt(hoverRange: Range): boolean { @@ -68,15 +76,19 @@ export class GhostTextController extends Disposable { } public trigger(): void { - this.activeController.value?.trigger(); + this.triggeredExplicitly = true; + if (!this.activeController.value) { + this.updateModelController(); + } + this.activeController.value?.triggerInlineCompletion(); } public commit(): void { - this.activeController.value?.commit(); + this.activeController.value?.commitInlineCompletion(); } public hide(): void { - this.activeController.value?.hide(); + this.activeController.value?.hideInlineCompletion(); } public showNextInlineCompletion(): void { @@ -100,8 +112,15 @@ class GhostTextContextKeys { * The controller for a text editor with an initialized text model. */ export class ActiveGhostTextController extends Disposable { - private readonly suggestWidgetAdapterModel = new SuggestWidgetAdapterModel(this.editor); - private readonly inlineCompletionsModel = new InlineCompletionsModel(this.editor, this.commandService); + private readonly suggestWidgetAdapterModel = this._register(new SuggestWidgetAdapterModel(this.editor)); + private readonly inlineCompletionsModel = this._register(new InlineCompletionsModel(this.editor, this.commandService)); + + private get activeInlineCompletionsModel(): InlineCompletionsModel | undefined { + if (this.widget.model === this.inlineCompletionsModel) { + return this.inlineCompletionsModel; + } + return undefined; + } constructor( private readonly editor: IActiveCodeEditor, @@ -114,7 +133,6 @@ export class ActiveGhostTextController extends Disposable { this._register(this.suggestWidgetAdapterModel.onDidChange(() => { this.updateModel(); })); - this.updateModel(); this._register(toDisposable(() => { @@ -123,18 +141,19 @@ export class ActiveGhostTextController extends Disposable { } })); - this._register(this.inlineCompletionsModel.onDidChange(() => { - this.updateContextKeys(); - })); + if (this.inlineCompletionsModel) { + this._register(this.inlineCompletionsModel.onDidChange(() => { + this.updateContextKeys(); + })); + } } private updateContextKeys(): void { this.contextKeys.inlineCompletionVisible.set( - this.widget.model === this.inlineCompletionsModel - && this.inlineCompletionsModel.ghostText !== undefined + this.activeInlineCompletionsModel?.ghostText !== undefined ); - if (this.inlineCompletionsModel.ghostText) { + if (this.inlineCompletionsModel?.ghostText) { const firstLine = this.inlineCompletionsModel.ghostText.lines[0] || ''; const suggestionStartsWithWs = firstLine.startsWith(' ') || firstLine.startsWith('\t'); const p = this.inlineCompletionsModel.ghostText.position; @@ -151,48 +170,40 @@ export class ActiveGhostTextController extends Disposable { } public shouldShowHoverAt(hoverRange: Range): boolean { - if (this.widget.model === this.inlineCompletionsModel) { - const ghostText = this.widget.model.ghostText; - if (ghostText) { - return hoverRange.containsPosition(ghostText.position); - } + const ghostText = this.activeInlineCompletionsModel?.ghostText; + if (ghostText) { + return hoverRange.containsPosition(ghostText.position); } return false; } - public trigger(): void { - if (this.widget.model === this.inlineCompletionsModel) { - this.inlineCompletionsModel.startSession(); - } + public triggerInlineCompletion(): void { + this.activeInlineCompletionsModel?.startSession(); } - public commit(): void { - if (this.widget.model === this.inlineCompletionsModel) { - this.inlineCompletionsModel.commitCurrentSuggestion(); - } + public commitInlineCompletion(): void { + this.activeInlineCompletionsModel?.commitCurrentSuggestion(); } - public hide(): void { - if (this.widget.model === this.inlineCompletionsModel) { - this.inlineCompletionsModel.hide(); - } + public hideInlineCompletion(): void { + this.activeInlineCompletionsModel?.hide(); } public showNextInlineCompletion(): void { - if (this.widget.model === this.inlineCompletionsModel) { - this.inlineCompletionsModel.showNextInlineCompletion(); - } + this.activeInlineCompletionsModel?.showNext(); } public showPreviousInlineCompletion(): void { - if (this.widget.model === this.inlineCompletionsModel) { - this.inlineCompletionsModel.showPreviousInlineCompletion(); - } + this.activeInlineCompletionsModel?.showPrevious(); } private updateModel() { - this.widget.setModel(this.suggestWidgetAdapterModel.isActive ? this.suggestWidgetAdapterModel : this.inlineCompletionsModel); - this.inlineCompletionsModel.setActive(this.widget.model === this.inlineCompletionsModel); + this.widget.setModel( + this.suggestWidgetAdapterModel.isActive + ? this.suggestWidgetAdapterModel + : this.inlineCompletionsModel + ); + this.inlineCompletionsModel?.setActive(this.widget.model === this.inlineCompletionsModel); } } diff --git a/src/vs/editor/contrib/inlineCompletions/ghostTextWidget.ts b/src/vs/editor/contrib/inlineCompletions/ghostTextWidget.ts index e3d22ae67bc..bc6bdf57941 100644 --- a/src/vs/editor/contrib/inlineCompletions/ghostTextWidget.ts +++ b/src/vs/editor/contrib/inlineCompletions/ghostTextWidget.ts @@ -52,7 +52,7 @@ export abstract class BaseGhostTextWidgetModel extends Disposable implements Gho public get expanded() { if (this._expanded === undefined) { - return this.editor.getOption(EditorOption.suggest).suggestionPreviewExpanded; + return this.editor.getOption(EditorOption.suggest).ghostTextExpanded; } return this._expanded; } diff --git a/src/vs/editor/contrib/inlineCompletions/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/inlineCompletionsModel.ts index d1b7b2d0ecf..30cdad8f57c 100644 --- a/src/vs/editor/contrib/inlineCompletions/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/inlineCompletionsModel.ts @@ -17,6 +17,7 @@ import { InlineCompletion, InlineCompletionContext, InlineCompletions, InlineCom import { BaseGhostTextWidgetModel, GhostText, GhostTextWidgetModel } from 'vs/editor/contrib/inlineCompletions/ghostTextWidget'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { ICommandService } from 'vs/platform/commands/common/commands'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; export class InlineCompletionsModel extends Disposable implements GhostTextWidgetModel { protected readonly onDidChangeEmitter = new Emitter(); @@ -77,6 +78,11 @@ export class InlineCompletionsModel extends Disposable implements GhostTextWidge } private startSessionIfTriggered(): void { + const suggestOptions = this.editor.getOption(EditorOption.suggest); + if (!suggestOptions.showInlineCompletions) { + return; + } + if (this.session && this.session.isValid) { return; } @@ -105,11 +111,11 @@ export class InlineCompletionsModel extends Disposable implements GhostTextWidge this.session?.commitCurrentCompletion(); } - public showNextInlineCompletion(): void { + public showNext(): void { this.session?.showNextInlineCompletion(); } - public showPreviousInlineCompletion(): void { + public showPrevious(): void { this.session?.showPreviousInlineCompletion(); } } diff --git a/src/vs/editor/contrib/inlineCompletions/suggestWidgetAdapterModel.ts b/src/vs/editor/contrib/inlineCompletions/suggestWidgetAdapterModel.ts index 5d3d30d83c9..92e3fa107c8 100644 --- a/src/vs/editor/contrib/inlineCompletions/suggestWidgetAdapterModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/suggestWidgetAdapterModel.ts @@ -6,6 +6,7 @@ import { Event } from 'vs/base/common/event'; import { toDisposable } from 'vs/base/common/lifecycle'; import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { CompletionItemInsertTextRule } from 'vs/editor/common/modes'; @@ -66,6 +67,11 @@ export class SuggestWidgetAdapterModel extends BaseGhostTextWidgetModel { })); } + private isSuggestionPreviewEnabled(): boolean { + const suggestOptions = this.editor.getOption(EditorOption.suggest); + return suggestOptions.showSuggestionPreview; + } + private updateFromSuggestion(): void { const suggestController = SuggestController.get(this.editor); if (!suggestController) { @@ -109,7 +115,7 @@ export class SuggestWidgetAdapterModel extends BaseGhostTextWidgetModel { const suggestController = SuggestController.get(this.editor); if (suggestController) { - if (this.minReservedLineCount >= 1) { + if (this.minReservedLineCount >= 1 && this.isSuggestionPreviewEnabled()) { suggestController.forceRenderingAbove(); } else { suggestController.stopForceRenderingAbove(); @@ -120,7 +126,9 @@ export class SuggestWidgetAdapterModel extends BaseGhostTextWidgetModel { } public override get ghostText(): GhostText | undefined { - return this.currentGhostText; + return this.isSuggestionPreviewEnabled() + ? this.currentGhostText + : undefined; } } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 61608d03849..d8dbc2f8e90 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3845,14 +3845,18 @@ declare namespace monaco.editor { */ showStatusBar?: boolean; /** - * Enable or disable the rendering of the suggestion inline. + * Enable or disable the rendering of the suggestion preview. */ showSuggestionPreview?: boolean; /** - * Enable or disable the default expansion of the suggestion preview. - * Defaults to false. + * Enable or disable the rendering of automatic inline completions. + */ + showInlineCompletions?: boolean; + /** + * Enable or disable the default expansion of the ghost text as used + * by the suggestion preview or the inline completions. */ - suggestionPreviewExpanded?: boolean; + ghostTextExpanded?: boolean; /** * Show details inline with the label. Defaults to true. */