Merge pull request #124828 from microsoft/alex/ghost-text

More Ghost Text Improvements
This commit is contained in:
Alexandru Dima 2021-05-28 21:03:23 +02:00 committed by GitHub
commit 24143e9132
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 133 additions and 69 deletions

View file

@ -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<EditorOption.suggest, InternalSugge
showIcons: true,
showStatusBar: false,
showSuggestionPreview: false,
suggestionPreviewExpanded: true,
ghostTextExpanded: true,
showInlineCompletions: false,
showInlineDetails: true,
showMethods: true,
showFunctions: true,
@ -3394,12 +3399,16 @@ class EditorSuggest extends BaseEditorOption<EditorOption.suggest, InternalSugge
default: defaults.showSuggestionPreview,
description: nls.localize('suggest.showSuggestionPreview', "Controls whether to preview the suggestion outcome in the editor.")
},
'editor.suggest.suggestionPreviewExpanded': {
'editor.suggest.showInlineCompletions': {
type: 'boolean',
default: defaults.suggestionPreviewExpanded,
description: nls.localize('suggest.suggestionPreviewExpanded', "Controls whether the suggestiion preview is expanded by default.")
default: defaults.showInlineCompletions,
description: nls.localize('suggest.showInlineCompletions', "Controls whether to show inline completions in the editor.")
},
'editor.suggest.ghostTextExpanded': {
type: 'boolean',
default: defaults.ghostTextExpanded,
description: nls.localize('suggest.ghostTextExpanded', "Controls whether the ghost text that is used by the suggestion preview or the inline completions is expanted by default.")
},
'editor.suggest.showInlineDetails': {
type: 'boolean',
default: defaults.showInlineDetails,
@ -3576,7 +3585,8 @@ class EditorSuggest extends BaseEditorOption<EditorOption.suggest, InternalSugge
showIcons: boolean(input.showIcons, this.defaultValue.showIcons),
showStatusBar: boolean(input.showStatusBar, this.defaultValue.showStatusBar),
showSuggestionPreview: boolean(input.showSuggestionPreview, this.defaultValue.showSuggestionPreview),
suggestionPreviewExpanded: boolean(input.suggestionPreviewExpanded, this.defaultValue.suggestionPreviewExpanded),
ghostTextExpanded: boolean(input.ghostTextExpanded, this.defaultValue.ghostTextExpanded),
showInlineCompletions: boolean(input.showInlineCompletions, this.defaultValue.showInlineCompletions),
showInlineDetails: boolean(input.showInlineDetails, this.defaultValue.showInlineDetails),
showMethods: boolean(input.showMethods, this.defaultValue.showMethods),
showFunctions: boolean(input.showFunctions, this.defaultValue.showFunctions),

View file

@ -30,8 +30,8 @@ export class GhostTextController extends Disposable {
private readonly widget: GhostTextWidget;
private readonly activeController = this._register(new MutableDisposable<ActiveGhostTextController>());
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 {
@ -72,15 +80,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 {
@ -104,8 +116,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,
@ -118,7 +137,6 @@ export class ActiveGhostTextController extends Disposable {
this._register(this.suggestWidgetAdapterModel.onDidChange(() => {
this.updateModel();
}));
this.updateModel();
this._register(toDisposable(() => {
@ -127,18 +145,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;
@ -155,48 +174,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);
}
}
@ -239,7 +250,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,
},
});
}
@ -262,7 +273,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,
},
});
}

View file

@ -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;
}

View file

@ -11,10 +11,11 @@ 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';
import { IViewZoneData } from 'vs/editor/browser/controller/mouseTarget';
export class InlineCompletionsHover implements IHoverPart {
constructor(
public readonly owner: IEditorHoverParticipant<InlineCompletionsHover>,
public readonly range: Range
@ -27,15 +28,15 @@ export class InlineCompletionsHover implements IHoverPart {
&& this.range.endColumn >= anchor.range.endColumn
);
}
}
export class InlineCompletionsHoverParticipant implements IEditorHoverParticipant<InlineCompletionsHover> {
constructor(
private readonly _editor: ICodeEditor,
hover: IEditorHover,
@ICommandService private readonly _commandService: ICommandService,
@IMenuService private readonly _menuService: IMenuService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
) { }
suggestHoverAnchor(mouseEvent: IEditorMouseEvent): HoverAnchor | null {
@ -72,17 +73,34 @@ export class InlineCompletionsHoverParticipant implements IEditorHoverParticipan
}
renderHoverParts(hoverParts: InlineCompletionsHover[], fragment: DocumentFragment, statusBar: IEditorHoverStatusBar): 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;
}
}

View file

@ -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<void>();
@ -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();
}
}

View file

@ -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;
}
}

12
src/vs/monaco.d.ts vendored
View file

@ -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.
*/

View file

@ -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;

View file

@ -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 {