Skip to content

Commit

Permalink
Fixes #179134. Adds flag suppressSuggestions to inline completion res…
Browse files Browse the repository at this point in the history
…ult. (#180129)

* Fixes #179134. Adds flag suppressSuggestions to inline completion result.

* Fixes memory leak.

* Implements hidden keepOnBlur editor flag for inline suggestions to make debugging easier.

* Fixes inline suggestion visibility bug

* Implements experimental forward stability flag (defaults to false).
  • Loading branch information
hediet authored Apr 17, 2023
1 parent 34f65dc commit 732f21c
Show file tree
Hide file tree
Showing 13 changed files with 150 additions and 82 deletions.
7 changes: 7 additions & 0 deletions src/vs/editor/common/config/editorOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3807,6 +3807,11 @@ export interface IInlineSuggestOptions {
showToolbar?: 'always' | 'onHover';

suppressSuggestions?: boolean;

/**
* Does not clear active inline suggestions when the editor loses focus.
*/
keepOnBlur?: boolean;
}

/**
Expand All @@ -3824,6 +3829,7 @@ class InlineEditorSuggest extends BaseEditorOption<EditorOption.inlineSuggest, I
mode: 'subwordSmart',
showToolbar: 'onHover',
suppressSuggestions: false,
keepOnBlur: false,
};

super(
Expand Down Expand Up @@ -3863,6 +3869,7 @@ class InlineEditorSuggest extends BaseEditorOption<EditorOption.inlineSuggest, I
mode: stringSet(input.mode, this.defaultValue.mode, ['prefix', 'subword', 'subwordSmart']),
showToolbar: stringSet(input.showToolbar, this.defaultValue.showToolbar, ['always', 'onHover']),
suppressSuggestions: boolean(input.suppressSuggestions, this.defaultValue.suppressSuggestions),
keepOnBlur: boolean(input.keepOnBlur, this.defaultValue.keepOnBlur),
};
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/vs/editor/common/languages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -691,6 +691,13 @@ export interface InlineCompletions<TItem extends InlineCompletion = InlineComple
* A list of commands associated with the inline completions of this list.
*/
readonly commands?: Command[];

readonly suppressSuggestions?: boolean | undefined;

/**
* When set and the user types a suggestion without derivating from it, the inline suggestion is not updated.
*/
readonly enableForwardStability?: boolean | undefined;
}

export interface InlineCompletionsProvider<T extends InlineCompletions = InlineCompletions> {
Expand Down
3 changes: 2 additions & 1 deletion src/vs/editor/contrib/inlineCompletions/browser/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorAction, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { showNextInlineSuggestionActionId, showPreviousInlineSuggestionActionId, inlineSuggestCommitId } from 'vs/editor/contrib/inlineCompletions/browser/commandIds';
import { InlineCompletionContextKeys, InlineCompletionsController } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController';
import { InlineCompletionContextKeys } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionContextKeys';
import { InlineCompletionsController } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController';
import * as nls from 'vs/nls';
import { MenuId, Action2 } from 'vs/platform/actions/common/actions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { IObservable, autorun } from 'vs/base/common/observable';
import { firstNonWhitespaceIndex } from 'vs/base/common/strings';
import { CursorColumns } from 'vs/editor/common/core/cursorColumns';
import { InlineCompletionsModel } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel';
import { RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { Disposable } from 'vs/base/common/lifecycle';
import { localize } from 'vs/nls';

export class InlineCompletionContextKeys extends Disposable {
public static readonly inlineSuggestionVisible = new RawContextKey<boolean>('inlineSuggestionVisible', false, localize('inlineSuggestionVisible', "Whether an inline suggestion is visible"));
public static readonly inlineSuggestionHasIndentation = new RawContextKey<boolean>('inlineSuggestionHasIndentation', false, localize('inlineSuggestionHasIndentation', "Whether the inline suggestion starts with whitespace"));
public static readonly inlineSuggestionHasIndentationLessThanTabSize = new RawContextKey<boolean>('inlineSuggestionHasIndentationLessThanTabSize', true, localize('inlineSuggestionHasIndentationLessThanTabSize', "Whether the inline suggestion starts with whitespace that is less than what would be inserted by tab"));
public static readonly alwaysShowInlineSuggestionToolbar = new RawContextKey<boolean>('alwaysShowInlineSuggestionToolbar', false, localize('alwaysShowInlineSuggestionToolbar', "Whether the inline suggestion toolbar should always be visible"));
public static readonly suppressSuggestions = new RawContextKey<boolean | undefined>('inlineSuggestionSuppressSuggestions', undefined, localize('suppressSuggestions', "Whether suggestions should be suppressed for the current suggestion"));

public readonly inlineCompletionVisible = InlineCompletionContextKeys.inlineSuggestionVisible.bindTo(this.contextKeyService);
public readonly inlineCompletionSuggestsIndentation = InlineCompletionContextKeys.inlineSuggestionHasIndentation.bindTo(this.contextKeyService);
public readonly inlineCompletionSuggestsIndentationLessThanTabSize = InlineCompletionContextKeys.inlineSuggestionHasIndentationLessThanTabSize.bindTo(this.contextKeyService);
public readonly suppressSuggestions = InlineCompletionContextKeys.suppressSuggestions.bindTo(this.contextKeyService);

constructor(
private readonly contextKeyService: IContextKeyService,
private readonly model: IObservable<InlineCompletionsModel | undefined>,
) {
super();

this._register(autorun('update context key: inlineCompletionVisible, suppressSuggestions', (reader) => {
const model = this.model.read(reader);
const suggestion = model?.currentInlineCompletion.read(reader);
const ghostText = model?.ghostText.read(reader);
const selectedSuggestItem = model?.selectedSuggestItem.read(reader);
this.inlineCompletionVisible.set(selectedSuggestItem === undefined && ghostText !== undefined);

if (ghostText && suggestion) {
this.suppressSuggestions.set(suggestion.inlineCompletion.source.inlineCompletions.suppressSuggestions);
}
}));

this._register(autorun('update context key: inlineCompletionSuggestsIndentation, inlineCompletionSuggestsIndentationLessThanTabSize', (reader) => {
const model = this.model.read(reader);

let startsWithIndentation = false;
let startsWithIndentationLessThanTabSize = true;

const ghostText = model?.ghostText.read(reader);
if (!!model?.selectedSuggestItem && ghostText && ghostText.parts.length > 0) {
const { column, lines } = ghostText.parts[0];

const firstLine = lines[0];

const indentationEndColumn = model.textModel.getLineIndentColumn(ghostText.lineNumber);
const inIndentation = column <= indentationEndColumn;

if (inIndentation) {
let firstNonWsIdx = firstNonWhitespaceIndex(firstLine);
if (firstNonWsIdx === -1) {
firstNonWsIdx = firstLine.length - 1;
}
startsWithIndentation = firstNonWsIdx > 0;

const tabSize = model.textModel.getOptions().tabSize;
const visibleColumnIndentation = CursorColumns.visibleColumnFromColumn(firstLine, firstNonWsIdx + 1, tabSize);
startsWithIndentationLessThanTabSize = visibleColumnIndentation < tabSize;
}
}

this.inlineCompletionSuggestsIndentation.set(startsWithIndentation);
this.inlineCompletionSuggestsIndentationLessThanTabSize.set(startsWithIndentationLessThanTabSize);
}));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,30 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { alert } from 'vs/base/browser/ui/aria/aria';
import { Event } from 'vs/base/common/event';
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { autorun, constObservable, observableFromEvent, observableValue } from 'vs/base/common/observable';
import { IObservable, ITransaction, disposableObservableValue, transaction } from 'vs/base/common/observableImpl/base';
import { ITransaction, disposableObservableValue, transaction } from 'vs/base/common/observableImpl/base';
import { CoreEditingCommands } from 'vs/editor/browser/coreCommands';
import { ICodeEditor } 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 { CursorChangeReason } from 'vs/editor/common/cursorEvents';
import { ILanguageFeatureDebounceService } from 'vs/editor/common/services/languageFeatureDebounce';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
import { inlineSuggestCommitId } from 'vs/editor/contrib/inlineCompletions/browser/commandIds';
import { GhostTextWidget } from 'vs/editor/contrib/inlineCompletions/browser/ghostTextWidget';
import { InlineCompletionContextKeys } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionContextKeys';
import { InlineSuggestionHintsContentWidget } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsHintsWidget';
import { InlineCompletionsModel, VersionIdChangeReason } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel';
import { SuggestWidgetAdaptor } from 'vs/editor/contrib/inlineCompletions/browser/suggestWidgetInlineCompletionProvider';
import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import * as nls from 'vs/nls';
import { CursorColumns } from 'vs/editor/common/core/cursorColumns';
import { firstNonWhitespaceIndex } from 'vs/base/common/strings';
import { InlineSuggestionHintsContentWidget } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsHintsWidget';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { CoreEditingCommands } from 'vs/editor/browser/coreCommands';
import { inlineSuggestCommitId } from 'vs/editor/contrib/inlineCompletions/browser/commandIds';
import { ILanguageFeatureDebounceService } from 'vs/editor/common/services/languageFeatureDebounce';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService';
import { alert } from 'vs/base/browser/ui/aria/aria';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';

export class InlineCompletionsController extends Disposable {
static ID = 'editor.contrib.inlineCompletionsController';
Expand Down Expand Up @@ -141,7 +139,8 @@ export class InlineCompletionsController extends Disposable {

this._register(this.editor.onDidBlurEditorWidget(() => {
// This is a hidden setting very useful for debugging
if (this.configurationService.getValue('editor.inlineSuggest.keepOnBlur')) {
if (this.configurationService.getValue('editor.inlineSuggest.keepOnBlur') ||
editor.getOption(EditorOption.inlineSuggest).keepOnBlur) {
return;
}
if (InlineSuggestionHintsContentWidget.dropDownVisible) {
Expand Down Expand Up @@ -207,60 +206,3 @@ export class InlineCompletionsController extends Disposable {
return this.ghostTextWidget.ownsViewZone(viewZoneId);
}
}

export class InlineCompletionContextKeys extends Disposable {
public static readonly inlineSuggestionVisible = new RawContextKey<boolean>('inlineSuggestionVisible', false, nls.localize('inlineSuggestionVisible', "Whether an inline suggestion is visible"));
public static readonly inlineSuggestionHasIndentation = new RawContextKey<boolean>('inlineSuggestionHasIndentation', false, nls.localize('inlineSuggestionHasIndentation', "Whether the inline suggestion starts with whitespace"));
public static readonly inlineSuggestionHasIndentationLessThanTabSize = new RawContextKey<boolean>('inlineSuggestionHasIndentationLessThanTabSize', true, nls.localize('inlineSuggestionHasIndentationLessThanTabSize', "Whether the inline suggestion starts with whitespace that is less than what would be inserted by tab"));
public static readonly alwaysShowInlineSuggestionToolbar = new RawContextKey<boolean>('alwaysShowInlineSuggestionToolbar', false, nls.localize('alwaysShowInlineSuggestionToolbar', "Whether the inline suggestion toolbar should always be visible"));

public readonly inlineCompletionVisible = InlineCompletionContextKeys.inlineSuggestionVisible.bindTo(this.contextKeyService);
public readonly inlineCompletionSuggestsIndentation = InlineCompletionContextKeys.inlineSuggestionHasIndentation.bindTo(this.contextKeyService);
public readonly inlineCompletionSuggestsIndentationLessThanTabSize = InlineCompletionContextKeys.inlineSuggestionHasIndentationLessThanTabSize.bindTo(this.contextKeyService);

constructor(
private readonly contextKeyService: IContextKeyService,
private readonly model: IObservable<InlineCompletionsModel | undefined>,
) {
super();

this._register(autorun('update context key: inlineCompletionVisible', (reader) => {
const model = this.model.read(reader);
const ghostText = model?.ghostText.read(reader);
const selectedSuggestItem = model?.selectedSuggestItem.read(reader);
this.inlineCompletionVisible.set(selectedSuggestItem === undefined && ghostText !== undefined);
}));

this._register(autorun('update context key: inlineCompletionSuggestsIndentation, inlineCompletionSuggestsIndentationLessThanTabSize', (reader) => {
const model = this.model.read(reader);

let startsWithIndentation = false;
let startsWithIndentationLessThanTabSize = true;

const ghostText = model?.ghostText.read(reader);
if (!!model?.selectedSuggestItem && ghostText && ghostText.parts.length > 0) {
const { column, lines } = ghostText.parts[0];

const firstLine = lines[0];

const indentationEndColumn = model.textModel.getLineIndentColumn(ghostText.lineNumber);
const inIndentation = column <= indentationEndColumn;

if (inIndentation) {
let firstNonWsIdx = firstNonWhitespaceIndex(firstLine);
if (firstNonWsIdx === -1) {
firstNonWsIdx = firstLine.length - 1;
}
startsWithIndentation = firstNonWsIdx > 0;

const tabSize = model.textModel.getOptions().tabSize;
const visibleColumnIndentation = CursorColumns.visibleColumnFromColumn(firstLine, firstNonWsIdx + 1, tabSize);
startsWithIndentationLessThanTabSize = visibleColumnIndentation < tabSize;
}
}

this.inlineCompletionSuggestsIndentation.set(startsWithIndentation);
this.inlineCompletionSuggestsIndentationLessThanTabSize.set(startsWithIndentationLessThanTabSize);
}));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ export class InlineCompletionsModel extends Disposable {
}

private async _update(reader: IReader | undefined, triggerKind: InlineCompletionTriggerKind, preserveCurrentCompletion: boolean = false): Promise<void> {
preserveCurrentCompletion = preserveCurrentCompletion || (this.currentInlineCompletion.get()?.inlineCompletion.source.inlineCompletions.enableForwardStability ?? false);

const suggestItem = this.selectedSuggestItem.read(reader);
const cursorPosition = this.cursorPosition.read(reader);
this.textModelVersionId.read(reader);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ export class InlineCompletionWithUpdatedRange {
}

const originalValue = model.getValueInRange(minimizedReplacement.range, EndOfLinePreference.LF).toLowerCase();
const filterText = this.inlineCompletion.filterText.toLowerCase();
const filterText = minimizedReplacement.text.toLowerCase();

const cursorPosIndex = Math.max(0, cursorPosition.column - minimizedReplacement.range.startColumn);

Expand All @@ -300,7 +300,7 @@ export class InlineCompletionWithUpdatedRange {
let originalValueAfter = originalValue.substring(cursorPosIndex);

const originalValueIndent = model.getLineIndentColumn(minimizedReplacement.range.startLineNumber);
if (this.updatedRange.startColumn <= originalValueIndent) {
if (minimizedReplacement.range.startColumn <= originalValueIndent) {
// Remove indentation
originalValueBefore = originalValueBefore.trimStart();
if (originalValueBefore.length === 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export class InlineCompletionProviderResult implements IDisposable {
* computed them.
*/
export class InlineCompletionList {
private refCount = 0;
private refCount = 1;
constructor(
public readonly inlineCompletions: InlineCompletions,
public readonly provider: InlineCompletionsProvider,
Expand Down
13 changes: 10 additions & 3 deletions src/vs/editor/contrib/suggest/browser/suggestModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { IWordAtPosition } from 'vs/editor/common/core/wordHelper';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
import { FuzzyScoreOptions } from 'vs/base/common/filters';
import { assertType } from 'vs/base/common/types';
import { InlineCompletionContextKeys } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionContextKeys';

export interface ICancelEvent {
readonly retrigger: boolean;
Expand Down Expand Up @@ -102,11 +103,14 @@ export const enum State {
}

function canShowQuickSuggest(editor: ICodeEditor, contextKeyService: IContextKeyService, configurationService: IConfigurationService): boolean {
if (!Boolean(contextKeyService.getContextKeyValue('inlineSuggestionVisible'))) {
if (!Boolean(contextKeyService.getContextKeyValue(InlineCompletionContextKeys.inlineSuggestionVisible.key))) {
// Allow if there is no inline suggestion.
return true;
}

const suppressSuggestions = contextKeyService.getContextKeyValue<boolean | undefined>(InlineCompletionContextKeys.suppressSuggestions.key);
if (suppressSuggestions !== undefined) {
return !suppressSuggestions;
}
return !editor.getOption(EditorOption.inlineSuggest).suppressSuggestions;
}

Expand All @@ -115,7 +119,10 @@ function canShowSuggestOnTriggerCharacters(editor: ICodeEditor, contextKeyServic
// Allow if there is no inline suggestion.
return true;
}

const suppressSuggestions = contextKeyService.getContextKeyValue<boolean | undefined>(InlineCompletionContextKeys.suppressSuggestions.key);
if (suppressSuggestions !== undefined) {
return !suppressSuggestions;
}
return !editor.getOption(EditorOption.inlineSuggest).suppressSuggestions;
}

Expand Down
9 changes: 9 additions & 0 deletions src/vs/monaco.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4401,6 +4401,10 @@ declare namespace monaco.editor {
mode?: 'prefix' | 'subword' | 'subwordSmart';
showToolbar?: 'always' | 'onHover';
suppressSuggestions?: boolean;
/**
* Does not clear active inline suggestions when the editor loses focus.
*/
keepOnBlur?: boolean;
}

export interface IBracketPairColorizationOptions {
Expand Down Expand Up @@ -6837,6 +6841,11 @@ declare namespace monaco.languages {
* A list of commands associated with the inline completions of this list.
*/
readonly commands?: Command[];
readonly suppressSuggestions?: boolean | undefined;
/**
* When set and the user types a suggestion without derivating from it, the inline suggestion is not updated.
*/
readonly enableForwardStability?: boolean | undefined;
}

export interface InlineCompletionsProvider<T extends InlineCompletions = InlineCompletions> {
Expand Down
Loading

0 comments on commit 732f21c

Please sign in to comment.