Skip to content

Commit

Permalink
Rewrites the inline completion feature.
Browse files Browse the repository at this point in the history
Fixes #172162
Fixes #174329
Fixes #172153
Fixes #164716
Fixes #170527
  • Loading branch information
hediet committed Apr 17, 2023
1 parent 5b5b1c3 commit fb1af29
Show file tree
Hide file tree
Showing 31 changed files with 2,103 additions and 2,687 deletions.
50 changes: 37 additions & 13 deletions src/vs/base/common/observableImpl/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { IDisposable } from 'vs/base/common/lifecycle';
import type { derived } from 'vs/base/common/observableImpl/derived';
import { getLogger } from 'vs/base/common/observableImpl/logging';

Expand All @@ -25,9 +26,9 @@ export interface IObservable<T, TChange = void> {
/**
* Subscribes the reader to this observable and returns the current value of this observable.
*/
read(reader: IReader): T;
read(reader: IReader | undefined): T;

map<TNew>(fn: (value: T) => TNew): IObservable<TNew>;
map<TNew>(fn: (value: T, reader: IReader) => TNew): IObservable<TNew>;

readonly debugName: string;
}
Expand Down Expand Up @@ -100,19 +101,19 @@ export abstract class ConvenientObservable<T, TChange> implements IObservable<T,
public abstract removeObserver(observer: IObserver): void;

/** @sealed */
public read(reader: IReader): T {
reader.subscribeTo(this);
public read(reader: IReader | undefined): T {
reader?.subscribeTo(this);
return this.get();
}

/** @sealed */
public map<TNew>(fn: (value: T) => TNew): IObservable<TNew> {
public map<TNew>(fn: (value: T, reader: IReader) => TNew): IObservable<TNew> {
return _derived(
() => {
const name = getFunctionName(fn);
return name !== undefined ? name : `${this.debugName} (mapped)`;
},
(reader) => fn(this.read(reader))
(reader) => fn(this.read(reader), reader)
);
}

Expand Down Expand Up @@ -204,19 +205,19 @@ export class ObservableValue<T, TChange = void>
extends BaseObservable<T, TChange>
implements ISettableObservable<T, TChange>
{
private value: T;
protected _value: T;

constructor(public readonly debugName: string, initialValue: T) {
super();
this.value = initialValue;
this._value = initialValue;
}

public get(): T {
return this.value;
return this._value;
}

public set(value: T, tx: ITransaction | undefined, change: TChange): void {
if (this.value === value) {
if (this._value === value) {
return;
}

Expand All @@ -227,8 +228,8 @@ export class ObservableValue<T, TChange = void>
return;
}

const oldValue = this.value;
this.value = value;
const oldValue = this._value;
this._setValue(value);
getLogger()?.handleObservableChanged(this, { oldValue, newValue: value, change, didChange: true });

for (const observer of this.observers) {
Expand All @@ -238,7 +239,30 @@ export class ObservableValue<T, TChange = void>
}

override toString(): string {
return `${this.debugName}: ${this.value}`;
return `${this.debugName}: ${this._value}`;
}

protected _setValue(newValue: T): void {
this._value = newValue;
}
}

export function disposableObservableValue<T extends IDisposable | undefined, TChange = void>(name: string, initialValue: T): ISettableObservable<T, TChange> & IDisposable {
return new DisposableObservableValue(name, initialValue);
}

export class DisposableObservableValue<T extends IDisposable | undefined, TChange = void> extends ObservableValue<T, TChange> implements IDisposable {
protected override _setValue(newValue: T): void {
if (this._value === newValue) {
return;
}
if (this._value) {
this._value.dispose();
}
this._value = newValue;
}

public dispose(): void {
this._value?.dispose();
}
}
22 changes: 16 additions & 6 deletions src/vs/editor/common/languages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -621,19 +621,29 @@ export enum InlineCompletionTriggerKind {
}

export interface InlineCompletionContext {

/**
* How the completion was triggered.
*/
readonly triggerKind: InlineCompletionTriggerKind;

readonly selectedSuggestionInfo: SelectedSuggestionInfo | undefined;
}

export interface SelectedSuggestionInfo {
range: IRange;
text: string;
isSnippetText: boolean;
completionKind: CompletionItemKind;
export class SelectedSuggestionInfo {
constructor(
public readonly range: IRange,
public readonly text: string,
public readonly completionKind: CompletionItemKind,
public readonly isSnippetText: boolean,
) {
}

public equals(other: SelectedSuggestionInfo) {
return Range.lift(this.range).equalsRange(other.range)
&& this.text === other.text
&& this.completionKind === other.completionKind
&& this.isSnippetText === other.isSnippetText;
}
}

export interface InlineCompletion {
Expand Down
2 changes: 1 addition & 1 deletion src/vs/editor/contrib/hover/browser/hover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { HoverParticipantRegistry } from 'vs/editor/contrib/hover/browser/hoverT
import { MarkdownHoverParticipant } from 'vs/editor/contrib/hover/browser/markdownHoverParticipant';
import { MarkerHoverParticipant } from 'vs/editor/contrib/hover/browser/markerHoverParticipant';
import 'vs/css!./hover';
import { InlineSuggestionHintsContentWidget } from 'vs/editor/contrib/inlineCompletions/browser/inlineSuggestionHintsWidget';
import { InlineSuggestionHintsContentWidget } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsHintsWidget';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ResultKind } from 'vs/platform/keybinding/common/keybindingResolver';

Expand Down
185 changes: 185 additions & 0 deletions src/vs/editor/contrib/inlineCompletions/browser/commands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { transaction } from 'vs/base/common/observable';
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 * as nls from 'vs/nls';
import { MenuId, Action2 } from 'vs/platform/actions/common/actions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';

export class ShowNextInlineSuggestionAction extends EditorAction {
public static ID = showNextInlineSuggestionActionId;
constructor() {
super({
id: ShowNextInlineSuggestionAction.ID,
label: nls.localize('action.inlineSuggest.showNext', "Show Next Inline Suggestion"),
alias: 'Show Next Inline Suggestion',
precondition: ContextKeyExpr.and(EditorContextKeys.writable, InlineCompletionContextKeys.inlineSuggestionVisible),
kbOpts: {
weight: 100,
primary: KeyMod.Alt | KeyCode.BracketRight,
},
});
}

public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor): Promise<void> {
const controller = InlineCompletionsController.get(editor);
controller?.model.get()?.next();
}
}

export class ShowPreviousInlineSuggestionAction extends EditorAction {
public static ID = showPreviousInlineSuggestionActionId;
constructor() {
super({
id: ShowPreviousInlineSuggestionAction.ID,
label: nls.localize('action.inlineSuggest.showPrevious', "Show Previous Inline Suggestion"),
alias: 'Show Previous Inline Suggestion',
precondition: ContextKeyExpr.and(EditorContextKeys.writable, InlineCompletionContextKeys.inlineSuggestionVisible),
kbOpts: {
weight: 100,
primary: KeyMod.Alt | KeyCode.BracketLeft,
},
});
}

public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor): Promise<void> {
const controller = InlineCompletionsController.get(editor);
controller?.model.get()?.previous();
}
}

export class TriggerInlineSuggestionAction extends EditorAction {
constructor() {
super({
id: 'editor.action.inlineSuggest.trigger',
label: nls.localize('action.inlineSuggest.trigger', "Trigger Inline Suggestion"),
alias: 'Trigger Inline Suggestion',
precondition: EditorContextKeys.writable
});
}

public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor): Promise<void> {
const controller = InlineCompletionsController.get(editor);
controller?.model.get()?.trigger(undefined);
}
}

export class AcceptNextWordOfInlineCompletion extends EditorAction {
constructor() {
super({
id: 'editor.action.inlineSuggest.acceptNextWord',
label: nls.localize('action.inlineSuggest.acceptNextWord', "Accept Next Word Of Inline Suggestion"),
alias: 'Accept Next Word Of Inline Suggestion',
precondition: ContextKeyExpr.and(EditorContextKeys.writable, InlineCompletionContextKeys.inlineSuggestionVisible),
kbOpts: {
weight: KeybindingWeight.EditorContrib + 1,
primary: KeyMod.CtrlCmd | KeyCode.RightArrow,
},
menuOpts: [{
menuId: MenuId.InlineSuggestionToolbar,
title: nls.localize('acceptWord', 'Accept Word'),
group: 'primary',
order: 2,
}],
});
}

public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor): Promise<void> {
const controller = InlineCompletionsController.get(editor);
controller?.model.get()?.acceptNextWord(controller.editor);
}
}

export class AcceptInlineCompletion extends EditorAction {
constructor() {
super({
id: inlineSuggestCommitId,
label: nls.localize('action.inlineSuggest.accept', "Accept Inline Suggestion"),
alias: 'Accept Inline Suggestion',
precondition: InlineCompletionContextKeys.inlineSuggestionVisible,
menuOpts: [{
menuId: MenuId.InlineSuggestionToolbar,
title: nls.localize('accept', "Accept"),
group: 'primary',
order: 1,
}],
kbOpts: {
primary: KeyCode.Tab,
weight: 200,
kbExpr: ContextKeyExpr.and(
InlineCompletionContextKeys.inlineSuggestionVisible,
EditorContextKeys.tabMovesFocus.toNegated(),
InlineCompletionContextKeys.inlineSuggestionHasIndentationLessThanTabSize
),
}
});
}

public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor): Promise<void> {
const controller = InlineCompletionsController.get(editor);
if (controller) {
controller.model.get()?.accept(controller.editor);
controller.editor.focus();
}
}
}

export class HideInlineCompletion extends EditorAction {
public static ID = 'editor.action.inlineSuggest.hide';

constructor() {
super({
id: HideInlineCompletion.ID,
label: nls.localize('action.inlineSuggest.hide', "Hide Inline Suggestion"),
alias: 'Hide Inline Suggestion',
precondition: InlineCompletionContextKeys.inlineSuggestionVisible,
kbOpts: {
weight: 100,
primary: KeyCode.Escape,
}
});
}

public async run(accessor: ServicesAccessor | undefined, editor: ICodeEditor): Promise<void> {
const controller = InlineCompletionsController.get(editor);
transaction(tx => {
controller?.model.get()?.stop(tx);
});
}
}

export class ToggleAlwaysShowInlineSuggestionToolbar extends Action2 {
public static ID = 'editor.action.inlineSuggest.toggleAlwaysShowToolbar';

constructor() {
super({
id: ToggleAlwaysShowInlineSuggestionToolbar.ID,
title: nls.localize('action.inlineSuggest.alwaysShowToolbar', "Always Show Toolbar"),
f1: false,
precondition: undefined,
menu: [{
id: MenuId.InlineSuggestionToolbar,
group: 'secondary',
order: 10,
}],
toggled: InlineCompletionContextKeys.alwaysShowInlineSuggestionToolbar,
});
}

public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
const configService = accessor.get(IConfigurationService);
const currentValue = configService.getValue<'always' | 'onHover'>('editor.inlineSuggest.showToolbar');
const newValue = currentValue === 'always' ? 'onHover' : 'always';
configService.updateValue('editor.inlineSuggest.showToolbar', newValue);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,7 @@
font-size: 0;
}

.monaco-editor .ghost-text-decoration {
font-style: italic;
}

.monaco-editor .suggest-preview-text {
.monaco-editor .ghost-text-decoration, .monaco-editor .suggest-preview-text .ghost-text {
font-style: italic;
}

Expand Down
Loading

0 comments on commit fb1af29

Please sign in to comment.