mirror of
https://github.com/Microsoft/vscode
synced 2024-07-05 01:08:57 +00:00
Inline completions refactoring (#214272)
This commit is contained in:
parent
d9148b110e
commit
ad4f719bd5
17
src/vs/base/browser/domObservable.ts
Normal file
17
src/vs/base/browser/domObservable.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { createStyleSheet2 } from 'vs/base/browser/dom';
|
||||
import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { autorun, IObservable } from 'vs/base/common/observable';
|
||||
|
||||
export function createStyleSheetFromObservable(css: IObservable<string>): IDisposable {
|
||||
const store = new DisposableStore();
|
||||
const w = store.add(createStyleSheet2());
|
||||
store.add(autorun(reader => {
|
||||
w.setStyle(css.read(reader));
|
||||
}));
|
||||
return store;
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import { equalsIfDefined, itemsEquals } from 'vs/base/common/equals';
|
||||
import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IObservable, ITransaction, autorunOpts, derived, derivedOpts, observableFromEvent, observableSignal, observableValue, observableValueOpts } from 'vs/base/common/observable';
|
||||
import { IObservable, ITransaction, autorunOpts, autorunWithStoreHandleChanges, derived, derivedOpts, observableFromEvent, observableSignal, observableValue, observableValueOpts } from 'vs/base/common/observable';
|
||||
import { TransactionImpl } from 'vs/base/common/observableInternal/base';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
|
@ -189,3 +189,39 @@ class ObservableCodeEditor extends Disposable {
|
|||
return d;
|
||||
}
|
||||
}
|
||||
|
||||
type RemoveUndefined<T> = T extends undefined ? never : T;
|
||||
export function reactToChange<T, TChange>(observable: IObservable<T, TChange>, cb: (value: T, deltas: RemoveUndefined<TChange>[]) => void): IDisposable {
|
||||
return autorunWithStoreHandleChanges({
|
||||
createEmptyChangeSummary: () => ({ deltas: [] as RemoveUndefined<TChange>[], didChange: false }),
|
||||
handleChange: (context, changeSummary) => {
|
||||
if (context.didChange(observable)) {
|
||||
const e = context.change;
|
||||
if (e !== undefined) {
|
||||
changeSummary.deltas.push(e as RemoveUndefined<TChange>);
|
||||
}
|
||||
changeSummary.didChange = true;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
}, (reader, changeSummary) => {
|
||||
const value = observable.read(reader);
|
||||
if (changeSummary.didChange) {
|
||||
cb(value, changeSummary.deltas);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function reactToChangeWithStore<T, TChange>(observable: IObservable<T, TChange>, cb: (value: T, deltas: RemoveUndefined<TChange>[], store: DisposableStore) => void): IDisposable {
|
||||
const store = new DisposableStore();
|
||||
const disposable = reactToChange(observable, (value, deltas) => {
|
||||
store.clear();
|
||||
cb(value, deltas, store);
|
||||
});
|
||||
return {
|
||||
dispose() {
|
||||
disposable.dispose();
|
||||
store.dispose();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -3,18 +3,19 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { createStyleSheet2 } from 'vs/base/browser/dom';
|
||||
import { createStyleSheetFromObservable } from 'vs/base/browser/domObservable';
|
||||
import { alert } from 'vs/base/browser/ui/aria/aria';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { cancelOnDispose } from 'vs/base/common/cancellation';
|
||||
import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IObservable, ITransaction, autorun, autorunHandleChanges, autorunWithStoreHandleChanges, constObservable, derived, disposableObservableValue, observableFromEvent, observableSignal, observableValue, transaction, waitForState } from 'vs/base/common/observable';
|
||||
import { IObservable, ITransaction, autorun, constObservable, derived, observableFromEvent, observableSignal, observableValue, transaction, waitForState } from 'vs/base/common/observable';
|
||||
import { ISettableObservable } from 'vs/base/common/observableInternal/base';
|
||||
import { mapObservableArrayCached } from 'vs/base/common/observableInternal/utils';
|
||||
import { derivedDisposable } from 'vs/base/common/observableInternal/derived';
|
||||
import { derivedObservableWithCache, mapObservableArrayCached } from 'vs/base/common/observableInternal/utils';
|
||||
import { isUndefined } from 'vs/base/common/types';
|
||||
import { CoreEditingCommands } from 'vs/editor/browser/coreCommands';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { obsCodeEditor } from 'vs/editor/browser/observableUtilities';
|
||||
import { obsCodeEditor, reactToChange, reactToChangeWithStore } from 'vs/editor/browser/observableUtilities';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
|
@ -43,25 +44,26 @@ export class InlineCompletionsController extends Disposable {
|
|||
return editor.getContribution<InlineCompletionsController>(InlineCompletionsController.ID);
|
||||
}
|
||||
|
||||
public readonly model = this._register(disposableObservableValue<InlineCompletionsModel | undefined>(this, undefined));
|
||||
private readonly _editorObs = obsCodeEditor(this.editor);
|
||||
private readonly _positions = derived(this, reader => this._editorObs.positions.read(reader) ?? [new Position(1, 1)]);
|
||||
|
||||
private readonly _suggestWidgetAdaptor = this._register(new SuggestWidgetAdaptor(
|
||||
this.editor,
|
||||
() => {
|
||||
this._editorObs.forceUpdate();
|
||||
return this.model.get()?.selectedInlineCompletion.get()?.toSingleTextEdit(undefined);
|
||||
},
|
||||
(item) => {
|
||||
this._editorObs.forceUpdate(tx => {
|
||||
/** @description InlineCompletionsController.handleSuggestAccepted */
|
||||
this.model.get()?.handleSuggestAccepted(item);
|
||||
});
|
||||
}
|
||||
(item) => this._editorObs.forceUpdate(_tx => {
|
||||
/** @description InlineCompletionsController.handleSuggestAccepted */
|
||||
this.model.get()?.handleSuggestAccepted(item);
|
||||
})
|
||||
));
|
||||
|
||||
private readonly _suggestWidgetSelectedItem = observableFromEvent(cb => this._suggestWidgetAdaptor.onDidSelectedItemChange(() => {
|
||||
this._editorObs.forceUpdate(_tx => cb(undefined));
|
||||
}), () => this._suggestWidgetAdaptor.selectedItem);
|
||||
|
||||
|
||||
private readonly _enabledInConfig = observableFromEvent(this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.inlineSuggest).enabled);
|
||||
private readonly _isScreenReaderEnabled = observableFromEvent(this._accessibilityService.onDidChangeScreenReaderOptimized, () => this._accessibilityService.isScreenReaderOptimized());
|
||||
private readonly _editorDictationInProgress = observableFromEvent(
|
||||
|
@ -69,7 +71,32 @@ export class InlineCompletionsController extends Disposable {
|
|||
() => this._contextKeyService.getContext(this.editor.getDomNode()).getValue('editorDictation.inProgress') === true
|
||||
);
|
||||
private readonly _enabled = derived(this, reader => this._enabledInConfig.read(reader) && (!this._isScreenReaderEnabled.read(reader) || !this._editorDictationInProgress.read(reader)));
|
||||
private readonly _fontFamily = observableFromEvent(this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.inlineSuggest).fontFamily);
|
||||
|
||||
private readonly _debounceValue = this._debounceService.for(
|
||||
this._languageFeaturesService.inlineCompletionsProvider,
|
||||
'InlineCompletionsDebounce',
|
||||
{ min: 50, max: 50 }
|
||||
);
|
||||
|
||||
public readonly model = derivedDisposable<InlineCompletionsModel | undefined>(this, reader => {
|
||||
if (this._editorObs.isReadonly.read(reader)) { return undefined; }
|
||||
const textModel = this._editorObs.model.read(reader);
|
||||
if (!textModel) { return undefined; }
|
||||
|
||||
const model: InlineCompletionsModel = this._instantiationService.createInstance(
|
||||
InlineCompletionsModel,
|
||||
textModel,
|
||||
this._suggestWidgetSelectedItem,
|
||||
this._editorObs.versionId,
|
||||
this._positions,
|
||||
this._debounceValue,
|
||||
observableFromEvent(this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.suggest).preview),
|
||||
observableFromEvent(this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.suggest).previewMode),
|
||||
observableFromEvent(this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.inlineSuggest).mode),
|
||||
this._enabled,
|
||||
);
|
||||
return model;
|
||||
}).recomputeInitiallyAndOnChange(this._store);
|
||||
|
||||
private readonly _ghostTexts = derived(this, (reader) => {
|
||||
const model = this.model.read(reader);
|
||||
|
@ -85,14 +112,9 @@ export class InlineCompletionsController extends Disposable {
|
|||
}))
|
||||
).recomputeInitiallyAndOnChange(this._store);
|
||||
|
||||
private readonly _debounceValue = this._debounceService.for(
|
||||
this._languageFeaturesService.inlineCompletionsProvider,
|
||||
'InlineCompletionsDebounce',
|
||||
{ min: 50, max: 50 }
|
||||
);
|
||||
|
||||
private readonly _playAccessibilitySignal = observableSignal(this);
|
||||
private readonly _textModelIfWritable = derived(reader => this._editorObs.isReadonly.read(reader) ? undefined : this._editorObs.model.read(reader));
|
||||
|
||||
private readonly _fontFamily = observableFromEvent(this.editor.onDidChangeConfiguration, () => this.editor.getOption(EditorOption.inlineSuggest).fontFamily);
|
||||
|
||||
constructor(
|
||||
public readonly editor: ICodeEditor,
|
||||
|
@ -110,65 +132,11 @@ export class InlineCompletionsController extends Disposable {
|
|||
|
||||
this._register(new InlineCompletionContextKeys(this._contextKeyService, this.model));
|
||||
|
||||
this._register(autorun(reader => {
|
||||
/** @description InlineCompletionsController.update model */
|
||||
const textModel = this._textModelIfWritable.read(reader);
|
||||
transaction(tx => {
|
||||
/** @description InlineCompletionsController.onDidChangeModel/readonly */
|
||||
this.model.set(undefined, tx);
|
||||
if (textModel) {
|
||||
const model = _instantiationService.createInstance(
|
||||
InlineCompletionsModel,
|
||||
textModel,
|
||||
this._suggestWidgetSelectedItem,
|
||||
this._editorObs.versionId,
|
||||
this._positions,
|
||||
this._debounceValue,
|
||||
observableFromEvent(editor.onDidChangeConfiguration, () => editor.getOption(EditorOption.suggest).preview),
|
||||
observableFromEvent(editor.onDidChangeConfiguration, () => editor.getOption(EditorOption.suggest).previewMode),
|
||||
observableFromEvent(editor.onDidChangeConfiguration, () => editor.getOption(EditorOption.inlineSuggest).mode),
|
||||
this._enabled,
|
||||
);
|
||||
this.model.set(model, tx);
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
const styleElement = this._register(createStyleSheet2());
|
||||
this._register(autorun(reader => {
|
||||
const fontFamily = this._fontFamily.read(reader);
|
||||
styleElement.setStyle(fontFamily === '' || fontFamily === 'default' ? `` : `
|
||||
.monaco-editor .ghost-text-decoration,
|
||||
.monaco-editor .ghost-text-decoration-preview,
|
||||
.monaco-editor .ghost-text {
|
||||
font-family: ${fontFamily};
|
||||
}`);
|
||||
}));
|
||||
|
||||
this._register(autorunWithStoreHandleChanges({
|
||||
createEmptyChangeSummary: () => ({ shouldStop: false }),
|
||||
handleChange: (context, changeSummary) => {
|
||||
if (context.didChange(this._editorObs.selections)) {
|
||||
const e = context.change;
|
||||
if (e && (e.reason === CursorChangeReason.Explicit || e.source === 'api')) {
|
||||
changeSummary.shouldStop = true;
|
||||
}
|
||||
}
|
||||
return changeSummary.shouldStop;
|
||||
},
|
||||
}, (reader, changeSummary) => {
|
||||
this._editorObs.selections.read(reader);
|
||||
if (changeSummary.shouldStop) {
|
||||
this.model.get()?.stop(undefined);
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(editor.onDidType(() => this._editorObs.forceUpdate(tx => {
|
||||
/** @description InlineCompletionsController.onDidType */
|
||||
this._register(reactToChange(this._editorObs.onDidType, (_value, _changes) => {
|
||||
if (this._enabled.get()) {
|
||||
this.model.get()?.trigger(tx);
|
||||
this.model.get()?.trigger();
|
||||
}
|
||||
})));
|
||||
}));
|
||||
|
||||
this._register(this._commandService.onDidExecuteCommand((e) => {
|
||||
// These commands don't trigger onDidType.
|
||||
|
@ -187,15 +155,21 @@ export class InlineCompletionsController extends Disposable {
|
|||
}
|
||||
}));
|
||||
|
||||
this._register(reactToChange(this._editorObs.selections, (_value, changes) => {
|
||||
if (changes.some(e => e.reason === CursorChangeReason.Explicit || e.source === 'api')) {
|
||||
this.model.get()?.stop();
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this.editor.onDidBlurEditorWidget(() => {
|
||||
// This is a hidden setting very useful for debugging
|
||||
if (this._contextKeyService.getContextKeyValue<boolean>('accessibleViewIsShown') || this._configurationService.getValue('editor.inlineSuggest.keepOnBlur') ||
|
||||
editor.getOption(EditorOption.inlineSuggest).keepOnBlur) {
|
||||
return;
|
||||
}
|
||||
if (InlineSuggestionHintsContentWidget.dropDownVisible) {
|
||||
if (this._contextKeyService.getContextKeyValue<boolean>('accessibleViewIsShown')
|
||||
|| this._configurationService.getValue('editor.inlineSuggest.keepOnBlur')
|
||||
|| editor.getOption(EditorOption.inlineSuggest).keepOnBlur
|
||||
|| InlineSuggestionHintsContentWidget.dropDownVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
transaction(tx => {
|
||||
/** @description InlineCompletionsController.onDidBlurEditorWidget */
|
||||
this.model.get()?.stop(tx);
|
||||
|
@ -217,44 +191,47 @@ export class InlineCompletionsController extends Disposable {
|
|||
this._suggestWidgetAdaptor.stopForceRenderingAbove();
|
||||
}));
|
||||
|
||||
const cancellationStore = this._register(new DisposableStore());
|
||||
let lastInlineCompletionId: string | undefined = undefined;
|
||||
this._register(autorunHandleChanges({
|
||||
handleChange: (context, changeSummary) => {
|
||||
if (context.didChange(this._playAccessibilitySignal)) {
|
||||
lastInlineCompletionId = undefined;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
}, async (reader, _) => {
|
||||
/** @description InlineCompletionsController.playAccessibilitySignalAndReadSuggestion */
|
||||
this._playAccessibilitySignal.read(reader);
|
||||
|
||||
const currentInlineCompletionBySemanticId = derivedObservableWithCache<string | undefined>(this, (reader, last) => {
|
||||
const model = this.model.read(reader);
|
||||
const state = model?.state.read(reader);
|
||||
if (!model || !state || !state.inlineCompletion) {
|
||||
lastInlineCompletionId = undefined;
|
||||
return;
|
||||
if (this._suggestWidgetSelectedItem.get()) {
|
||||
return last;
|
||||
}
|
||||
return state?.inlineCompletion?.semanticId;
|
||||
});
|
||||
this._register(reactToChangeWithStore(derived(reader => {
|
||||
this._playAccessibilitySignal.read(reader);
|
||||
currentInlineCompletionBySemanticId.read(reader);
|
||||
return {};
|
||||
}), async (_value, _deltas, store) => {
|
||||
/** @description InlineCompletionsController.playAccessibilitySignalAndReadSuggestion */
|
||||
const model = this.model.get();
|
||||
const state = model?.state.get();
|
||||
if (!state || !model) { return; }
|
||||
const lineText = model.textModel.getLineContent(state.primaryGhostText.lineNumber);
|
||||
|
||||
if (state.inlineCompletion.semanticId !== lastInlineCompletionId) {
|
||||
cancellationStore.clear();
|
||||
lastInlineCompletionId = state.inlineCompletion.semanticId;
|
||||
const lineText = model.textModel.getLineContent(state.primaryGhostText.lineNumber);
|
||||
await timeout(50, cancelOnDispose(store));
|
||||
await waitForState(this._suggestWidgetSelectedItem, isUndefined, () => false, cancelOnDispose(store));
|
||||
|
||||
await timeout(50, cancelOnDispose(cancellationStore));
|
||||
await waitForState(this._suggestWidgetSelectedItem, isUndefined, () => false, cancelOnDispose(cancellationStore));
|
||||
|
||||
await this._accessibilitySignalService.playSignal(AccessibilitySignal.inlineSuggestion);
|
||||
|
||||
if (this.editor.getOption(EditorOption.screenReaderAnnounceInlineSuggestion)) {
|
||||
this.provideScreenReaderUpdate(state.primaryGhostText.renderForScreenReader(lineText));
|
||||
}
|
||||
await this._accessibilitySignalService.playSignal(AccessibilitySignal.inlineSuggestion);
|
||||
if (this.editor.getOption(EditorOption.screenReaderAnnounceInlineSuggestion)) {
|
||||
this._provideScreenReaderUpdate(state.primaryGhostText.renderForScreenReader(lineText));
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(new InlineCompletionsHintsWidget(this.editor, this.model, this._instantiationService));
|
||||
|
||||
this._register(createStyleSheetFromObservable(derived(reader => {
|
||||
const fontFamily = this._fontFamily.read(reader);
|
||||
if (fontFamily === '' || fontFamily === 'default') { return ''; }
|
||||
return `
|
||||
.monaco-editor .ghost-text-decoration,
|
||||
.monaco-editor .ghost-text-decoration-preview,
|
||||
.monaco-editor .ghost-text {
|
||||
font-family: ${fontFamily};
|
||||
}`;
|
||||
})));
|
||||
|
||||
// TODO@hediet
|
||||
this._register(this._configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('accessibility.verbosity.inlineCompletions')) {
|
||||
|
@ -268,14 +245,14 @@ export class InlineCompletionsController extends Disposable {
|
|||
this._playAccessibilitySignal.trigger(tx);
|
||||
}
|
||||
|
||||
private provideScreenReaderUpdate(content: string): void {
|
||||
private _provideScreenReaderUpdate(content: string): void {
|
||||
const accessibleViewShowing = this._contextKeyService.getContextKeyValue<boolean>('accessibleViewIsShown');
|
||||
const accessibleViewKeybinding = this._keybindingService.lookupKeybinding('editor.action.accessibleView');
|
||||
let hint: string | undefined;
|
||||
if (!accessibleViewShowing && accessibleViewKeybinding && this.editor.getOption(EditorOption.inlineCompletionsAccessibilityVerbose)) {
|
||||
hint = localize('showAccessibleViewHint', "Inspect this in the accessible view ({0})", accessibleViewKeybinding.getAriaLabel());
|
||||
}
|
||||
hint ? alert(content + ', ' + hint) : alert(content);
|
||||
alert(hint ? content + ', ' + hint : content);
|
||||
}
|
||||
|
||||
public shouldShowHoverAt(range: Range) {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Permutation } from 'vs/base/common/arrays';
|
||||
import { compareBy, Permutation } from 'vs/base/common/arrays';
|
||||
import { mapFindFirst } from 'vs/base/common/arraysFind';
|
||||
import { itemsEquals } from 'vs/base/common/equals';
|
||||
import { BugIndicatingError, onUnexpectedError, onUnexpectedExternalError } from 'vs/base/common/errors';
|
||||
|
@ -33,17 +33,10 @@ import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetCon
|
|||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export enum VersionIdChangeReason {
|
||||
Undo,
|
||||
Redo,
|
||||
AcceptWord,
|
||||
Other,
|
||||
}
|
||||
|
||||
export class InlineCompletionsModel extends Disposable {
|
||||
private readonly _source = this._register(this._instantiationService.createInstance(InlineCompletionsSource, this.textModel, this.textModelVersionId, this._debounceValue));
|
||||
private readonly _source = this._register(this._instantiationService.createInstance(InlineCompletionsSource, this.textModel, this._textModelVersionId, this._debounceValue));
|
||||
private readonly _isActive = observableValue<boolean>(this, false);
|
||||
readonly _forceUpdateExplicitlySignal = observableSignal(this);
|
||||
private readonly _forceUpdateExplicitlySignal = observableSignal(this);
|
||||
|
||||
// We use a semantic id to keep the same inline completion selected even if the provider reorders the completions.
|
||||
private readonly _selectedInlineCompletionId = observableValue<string | undefined>(this, undefined);
|
||||
|
@ -55,7 +48,7 @@ export class InlineCompletionsModel extends Disposable {
|
|||
constructor(
|
||||
public readonly textModel: ITextModel,
|
||||
public readonly selectedSuggestItem: IObservable<SuggestItemInfo | undefined>,
|
||||
public readonly textModelVersionId: IObservable<number | null, IModelContentChangedEvent | undefined>,
|
||||
public readonly _textModelVersionId: IObservable<number | null, IModelContentChangedEvent | undefined>,
|
||||
private readonly _positions: IObservable<readonly Position[]>,
|
||||
private readonly _debounceValue: IFeatureDebounceInformation,
|
||||
private readonly _suggestPreviewEnabled: IObservable<boolean>,
|
||||
|
@ -107,7 +100,7 @@ export class InlineCompletionsModel extends Disposable {
|
|||
}),
|
||||
handleChange: (ctx, changeSummary) => {
|
||||
/** @description fetch inline completions */
|
||||
if (ctx.didChange(this.textModelVersionId) && this._preserveCurrentCompletionReasons.has(this._getReason(ctx.change))) {
|
||||
if (ctx.didChange(this._textModelVersionId) && this._preserveCurrentCompletionReasons.has(this._getReason(ctx.change))) {
|
||||
changeSummary.preserveCurrentCompletion = true;
|
||||
} else if (ctx.didChange(this._forceUpdateExplicitlySignal)) {
|
||||
changeSummary.inlineCompletionTriggerKind = InlineCompletionTriggerKind.Explicit;
|
||||
|
@ -122,7 +115,7 @@ export class InlineCompletionsModel extends Disposable {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
this.textModelVersionId.read(reader); // Refetch on text change
|
||||
this._textModelVersionId.read(reader); // Refetch on text change
|
||||
|
||||
const suggestWidgetInlineCompletions = this._source.suggestWidgetInlineCompletions.get();
|
||||
const suggestItem = this.selectedSuggestItem.read(reader);
|
||||
|
@ -272,26 +265,24 @@ export class InlineCompletionsModel extends Disposable {
|
|||
|
||||
const augmentedCompletion = mapFindFirst(candidateInlineCompletions, completion => {
|
||||
let r = completion.toSingleTextEdit(reader);
|
||||
r = singleTextRemoveCommonPrefix(r, model, Range.fromPositions(r.range.getStartPosition(), suggestCompletion.range.getEndPosition()));
|
||||
r = singleTextRemoveCommonPrefix(
|
||||
r,
|
||||
model,
|
||||
Range.fromPositions(r.range.getStartPosition(), suggestCompletion.range.getEndPosition())
|
||||
);
|
||||
return singleTextEditAugments(r, suggestCompletion) ? { completion, edit: r } : undefined;
|
||||
});
|
||||
|
||||
return augmentedCompletion;
|
||||
}
|
||||
|
||||
public readonly ghostTexts = derivedOpts({
|
||||
owner: this,
|
||||
equalsFn: ghostTextsOrReplacementsEqual
|
||||
}, reader => {
|
||||
public readonly ghostTexts = derivedOpts({ owner: this, equalsFn: ghostTextsOrReplacementsEqual }, reader => {
|
||||
const v = this.state.read(reader);
|
||||
if (!v) { return undefined; }
|
||||
return v.ghostTexts;
|
||||
});
|
||||
|
||||
public readonly primaryGhostText = derivedOpts({
|
||||
owner: this,
|
||||
equalsFn: ghostTextOrReplacementEquals
|
||||
}, reader => {
|
||||
public readonly primaryGhostText = derivedOpts({ owner: this, equalsFn: ghostTextOrReplacementEquals }, reader => {
|
||||
const v = this.state.read(reader);
|
||||
if (!v) { return undefined; }
|
||||
return v?.primaryGhostText;
|
||||
|
@ -328,6 +319,11 @@ export class InlineCompletionsModel extends Disposable {
|
|||
}
|
||||
const completion = state.inlineCompletion.toInlineCompletion(undefined);
|
||||
|
||||
if (completion.command) {
|
||||
// Make sure the completion list will not be disposed.
|
||||
completion.source.addRef();
|
||||
}
|
||||
|
||||
editor.pushUndoStop();
|
||||
if (completion.snippetInfo) {
|
||||
editor.executeEdits(
|
||||
|
@ -349,18 +345,8 @@ export class InlineCompletionsModel extends Disposable {
|
|||
editor.setSelections(selections, 'inlineCompletionAccept');
|
||||
}
|
||||
|
||||
if (completion.command) {
|
||||
// Make sure the completion list will not be disposed.
|
||||
completion.source.addRef();
|
||||
}
|
||||
|
||||
// Reset before invoking the command, since the command might cause a follow up trigger.
|
||||
transaction(tx => {
|
||||
this._source.clear(tx);
|
||||
// Potentially, isActive will get set back to true by the typing or accept inline suggest event
|
||||
// if automatic inline suggestions are enabled.
|
||||
this._isActive.set(false, tx);
|
||||
});
|
||||
// Reset before invoking the command, as the command might cause a follow up trigger (which we don't want to reset).
|
||||
this.stop();
|
||||
|
||||
if (completion.command) {
|
||||
await this._commandService
|
||||
|
@ -466,9 +452,7 @@ export class InlineCompletionsModel extends Disposable {
|
|||
completion.source.inlineCompletions,
|
||||
completion.sourceInlineCompletion,
|
||||
text.length,
|
||||
{
|
||||
kind,
|
||||
}
|
||||
{ kind, }
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
|
@ -493,6 +477,13 @@ export class InlineCompletionsModel extends Disposable {
|
|||
}
|
||||
}
|
||||
|
||||
export enum VersionIdChangeReason {
|
||||
Undo,
|
||||
Redo,
|
||||
AcceptWord,
|
||||
Other,
|
||||
}
|
||||
|
||||
export function getSecondaryEdits(textModel: ITextModel, positions: readonly Position[], primaryEdit: SingleTextEdit): SingleTextEdit[] {
|
||||
if (positions.length === 1) {
|
||||
// No secondary cursor positions
|
||||
|
@ -535,7 +526,7 @@ function substringPos(text: string, pos: Position): string {
|
|||
}
|
||||
|
||||
function getEndPositionsAfterApplying(edits: readonly SingleTextEdit[]): Position[] {
|
||||
const sortPerm = Permutation.createSortPermutation(edits, (edit1, edit2) => Range.compareRangesUsingStarts(edit1.range, edit2.range));
|
||||
const sortPerm = Permutation.createSortPermutation(edits, compareBy(e => e.range, Range.compareRangesUsingStarts));
|
||||
const edit = new TextEdit(sortPerm.apply(edits));
|
||||
const sortedNewRanges = edit.getNewRanges();
|
||||
const newRanges = sortPerm.inverse().apply(sortedNewRanges);
|
||||
|
|
Loading…
Reference in New Issue
Block a user