mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-08 17:19:48 +01:00
inline completions code cleanup (#228328)
* inline completions code cleanup * Fixes CI
This commit is contained in:
committed by
GitHub
parent
1d3895d045
commit
8bb2e41e2e
@@ -783,7 +783,7 @@ export interface InlineCompletionsProvider<T extends InlineCompletions = InlineC
|
||||
* @experimental
|
||||
* @internal
|
||||
*/
|
||||
provideInlineEdits?(model: model.ITextModel, range: Range, context: InlineCompletionContext, token: CancellationToken): ProviderResult<T>;
|
||||
provideInlineEditsForRange?(model: model.ITextModel, range: Range, context: InlineCompletionContext, token: CancellationToken): ProviderResult<T>;
|
||||
|
||||
/**
|
||||
* Will be called when an item is shown.
|
||||
|
||||
+3
-26
@@ -8,8 +8,8 @@ import { alert } from '../../../../../base/browser/ui/aria/aria.js';
|
||||
import { timeout } from '../../../../../base/common/async.js';
|
||||
import { cancelOnDispose } from '../../../../../base/common/cancellation.js';
|
||||
import { readHotReloadableExport } from '../../../../../base/common/hotReloadHelpers.js';
|
||||
import { Disposable, DisposableStore, toDisposable } from '../../../../../base/common/lifecycle.js';
|
||||
import { IObservable, ISettableObservable, ITransaction, autorun, constObservable, derived, derivedDisposable, derivedObservableWithCache, mapObservableArrayCached, observableFromEvent, observableSignal, observableValue, runOnChange, runOnChangeWithStore, transaction, waitForState } from '../../../../../base/common/observable.js';
|
||||
import { Disposable, toDisposable } from '../../../../../base/common/lifecycle.js';
|
||||
import { ITransaction, autorun, constObservable, derived, derivedDisposable, derivedObservableWithCache, mapObservableArrayCached, observableFromEvent, observableSignal, runOnChange, runOnChangeWithStore, transaction, waitForState } from '../../../../../base/common/observable.js';
|
||||
import { isUndefined } from '../../../../../base/common/types.js';
|
||||
import { localize } from '../../../../../nls.js';
|
||||
import { IAccessibilityService } from '../../../../../platform/accessibility/common/accessibility.js';
|
||||
@@ -31,6 +31,7 @@ import { ILanguageFeaturesService } from '../../../../common/services/languageFe
|
||||
import { InlineCompletionsHintsWidget, InlineSuggestionHintsContentWidget } from '../hintsWidget/inlineCompletionsHintsWidget.js';
|
||||
import { InlineCompletionsModel } from '../model/inlineCompletionsModel.js';
|
||||
import { SuggestWidgetAdaptor } from '../model/suggestWidgetAdaptor.js';
|
||||
import { convertItemsToStableObservables } from '../utils.js';
|
||||
import { GhostTextView } from '../view/ghostTextView.js';
|
||||
import { inlineSuggestCommitId } from './commandIds.js';
|
||||
import { InlineCompletionContextKeys } from './inlineCompletionContextKeys.js';
|
||||
@@ -273,27 +274,3 @@ export class InlineCompletionsController extends Disposable {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function convertItemsToStableObservables<T>(items: IObservable<readonly T[]>, store: DisposableStore): IObservable<IObservable<T>[]> {
|
||||
const result = observableValue<IObservable<T>[]>('result', []);
|
||||
const innerObservables: ISettableObservable<T>[] = [];
|
||||
|
||||
store.add(autorun(reader => {
|
||||
const itemsValue = items.read(reader);
|
||||
|
||||
transaction(tx => {
|
||||
if (itemsValue.length !== innerObservables.length) {
|
||||
innerObservables.length = itemsValue.length;
|
||||
for (let i = 0; i < innerObservables.length; i++) {
|
||||
if (!innerObservables[i]) {
|
||||
innerObservables[i] = observableValue<T>('item', itemsValue[i]);
|
||||
}
|
||||
}
|
||||
result.set([...innerObservables], tx);
|
||||
}
|
||||
innerObservables.forEach((o, i) => o.set(itemsValue[i], tx));
|
||||
});
|
||||
}));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
+11
-38
@@ -4,31 +4,13 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IDiffChange, LcsDiff } from '../../../../../base/common/diff/diff.js';
|
||||
import { commonPrefixLength, getLeadingWhitespace } from '../../../../../base/common/strings.js';
|
||||
import { getLeadingWhitespace } from '../../../../../base/common/strings.js';
|
||||
import { Position } from '../../../../common/core/position.js';
|
||||
import { Range } from '../../../../common/core/range.js';
|
||||
import { TextLength } from '../../../../common/core/textLength.js';
|
||||
import { SingleTextEdit } from '../../../../common/core/textEdit.js';
|
||||
import { EndOfLinePreference, ITextModel } from '../../../../common/model.js';
|
||||
import { ITextModel } from '../../../../common/model.js';
|
||||
import { GhostText, GhostTextPart } from './ghostText.js';
|
||||
|
||||
export function singleTextRemoveCommonPrefix(edit: SingleTextEdit, model: ITextModel, validModelRange?: Range): SingleTextEdit {
|
||||
const modelRange = validModelRange ? edit.range.intersectRanges(validModelRange) : edit.range;
|
||||
if (!modelRange) {
|
||||
return edit;
|
||||
}
|
||||
const valueToReplace = model.getValueInRange(modelRange, EndOfLinePreference.LF);
|
||||
const commonPrefixLen = commonPrefixLength(valueToReplace, edit.text);
|
||||
const start = TextLength.ofText(valueToReplace.substring(0, commonPrefixLen)).addToPosition(edit.range.getStartPosition());
|
||||
const text = edit.text.substring(commonPrefixLen);
|
||||
const range = Range.fromPositions(start, edit.range.getEndPosition());
|
||||
return new SingleTextEdit(range, text);
|
||||
}
|
||||
|
||||
export function singleTextEditAugments(edit: SingleTextEdit, base: SingleTextEdit): boolean {
|
||||
// The augmented completion must replace the base range, but can replace even more
|
||||
return edit.text.startsWith(base.text) && rangeExtends(edit.range, base.range);
|
||||
}
|
||||
import { singleTextRemoveCommonPrefix } from './singleTextEditHelpers.js';
|
||||
|
||||
/**
|
||||
* @param previewSuffixLength Sets where to split `inlineCompletion.text`.
|
||||
@@ -58,27 +40,23 @@ export function computeGhostText(
|
||||
// ^^^^^^^^^^ ^^^^^^ sourceIndentationLength
|
||||
// ^^^^^^ replacedIndentation.length
|
||||
// ^^^ rangeThatDoesNotReplaceIndentation
|
||||
|
||||
// inlineCompletion.text: '··foo'
|
||||
// ^^ suggestionAddedIndentationLength
|
||||
|
||||
const suggestionAddedIndentationLength = getLeadingWhitespace(e.text).length;
|
||||
|
||||
const replacedIndentation = sourceLine.substring(e.range.startColumn - 1, sourceIndentationLength);
|
||||
|
||||
const [startPosition, endPosition] = [e.range.getStartPosition(), e.range.getEndPosition()];
|
||||
const newStartPosition =
|
||||
startPosition.column + replacedIndentation.length <= endPosition.column
|
||||
? startPosition.delta(0, replacedIndentation.length)
|
||||
: endPosition;
|
||||
const newStartPosition = startPosition.column + replacedIndentation.length <= endPosition.column
|
||||
? startPosition.delta(0, replacedIndentation.length)
|
||||
: endPosition;
|
||||
const rangeThatDoesNotReplaceIndentation = Range.fromPositions(newStartPosition, endPosition);
|
||||
|
||||
const suggestionWithoutIndentationChange =
|
||||
e.text.startsWith(replacedIndentation)
|
||||
// Adds more indentation without changing existing indentation: We can add ghost text for this
|
||||
? e.text.substring(replacedIndentation.length)
|
||||
// Changes or removes existing indentation. Only add ghost text for the non-indentation part.
|
||||
: e.text.substring(suggestionAddedIndentationLength);
|
||||
const suggestionWithoutIndentationChange = e.text.startsWith(replacedIndentation)
|
||||
// Adds more indentation without changing existing indentation: We can add ghost text for this
|
||||
? e.text.substring(replacedIndentation.length)
|
||||
// Changes or removes existing indentation. Only add ghost text for the non-indentation part.
|
||||
: e.text.substring(suggestionAddedIndentationLength);
|
||||
|
||||
e = new SingleTextEdit(rangeThatDoesNotReplaceIndentation, suggestionWithoutIndentationChange);
|
||||
}
|
||||
@@ -139,11 +117,6 @@ export function computeGhostText(
|
||||
return new GhostText(lineNumber, parts);
|
||||
}
|
||||
|
||||
function rangeExtends(extendingRange: Range, rangeToExtend: Range): boolean {
|
||||
return rangeToExtend.getStartPosition().equals(extendingRange.getStartPosition())
|
||||
&& rangeToExtend.getEndPosition().isBeforeOrEqual(extendingRange.getEndPosition());
|
||||
}
|
||||
|
||||
let lastRequest: { originalValue: string; newValue: string; changes: readonly IDiffChange[] | undefined } | undefined = undefined;
|
||||
function cachingDiff(originalValue: string, newValue: string): readonly IDiffChange[] | undefined {
|
||||
if (lastRequest?.originalValue === originalValue && lastRequest?.newValue === newValue) {
|
||||
@@ -3,20 +3,21 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { compareBy, Permutation } from '../../../../../base/common/arrays.js';
|
||||
import { mapFindFirst } from '../../../../../base/common/arraysFind.js';
|
||||
import { itemsEquals } from '../../../../../base/common/equals.js';
|
||||
import { BugIndicatingError, onUnexpectedError, onUnexpectedExternalError } from '../../../../../base/common/errors.js';
|
||||
import { Disposable } from '../../../../../base/common/lifecycle.js';
|
||||
import { IObservable, IReader, ITransaction, autorun, derived, derivedHandleChanges, derivedOpts, observableSignal, observableValue, recomputeInitiallyAndOnChange, subtransaction, transaction } from '../../../../../base/common/observable.js';
|
||||
import { commonPrefixLength, splitLinesIncludeSeparators } from '../../../../../base/common/strings.js';
|
||||
import { commonPrefixLength } from '../../../../../base/common/strings.js';
|
||||
import { isDefined } from '../../../../../base/common/types.js';
|
||||
import { ICommandService } from '../../../../../platform/commands/common/commands.js';
|
||||
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
|
||||
import { ICodeEditor } from '../../../../browser/editorBrowser.js';
|
||||
import { EditOperation } from '../../../../common/core/editOperation.js';
|
||||
import { Position } from '../../../../common/core/position.js';
|
||||
import { Range } from '../../../../common/core/range.js';
|
||||
import { Selection } from '../../../../common/core/selection.js';
|
||||
import { SingleTextEdit, TextEdit } from '../../../../common/core/textEdit.js';
|
||||
import { SingleTextEdit } from '../../../../common/core/textEdit.js';
|
||||
import { TextLength } from '../../../../common/core/textLength.js';
|
||||
import { ScrollType } from '../../../../common/editorCommon.js';
|
||||
import { Command, InlineCompletionContext, InlineCompletionTriggerKind, PartialAcceptTriggerKind } from '../../../../common/languages.js';
|
||||
@@ -24,14 +25,13 @@ import { ILanguageConfigurationService } from '../../../../common/languages/lang
|
||||
import { EndOfLinePreference, ITextModel } from '../../../../common/model.js';
|
||||
import { IFeatureDebounceInformation } from '../../../../common/services/languageFeatureDebounce.js';
|
||||
import { IModelContentChangedEvent } from '../../../../common/textModelEvents.js';
|
||||
import { SnippetController2 } from '../../../snippet/browser/snippetController2.js';
|
||||
import { addPositions, getEndPositionsAfterApplying, substringPos, subtractPositions } from '../utils.js';
|
||||
import { computeGhostText } from './computeGhostText.js';
|
||||
import { GhostText, GhostTextOrReplacement, ghostTextOrReplacementEquals, ghostTextsOrReplacementsEqual } from './ghostText.js';
|
||||
import { InlineCompletionWithUpdatedRange, InlineCompletionsSource } from './inlineCompletionsSource.js';
|
||||
import { computeGhostText, singleTextEditAugments, singleTextRemoveCommonPrefix } from './singleTextEdit.js';
|
||||
import { singleTextEditAugments, singleTextRemoveCommonPrefix } from './singleTextEditHelpers.js';
|
||||
import { SuggestItemInfo } from './suggestWidgetAdaptor.js';
|
||||
import { addPositions, subtractPositions } from '../utils.js';
|
||||
import { SnippetController2 } from '../../../snippet/browser/snippetController2.js';
|
||||
import { ICommandService } from '../../../../../platform/commands/common/commands.js';
|
||||
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
|
||||
|
||||
export class InlineCompletionsModel extends Disposable {
|
||||
private readonly _source = this._register(this._instantiationService.createInstance(InlineCompletionsSource, this.textModel, this._textModelVersionId, this._debounceValue));
|
||||
@@ -515,20 +515,3 @@ export function getSecondaryEdits(textModel: ITextModel, positions: readonly Pos
|
||||
return new SingleTextEdit(range, secondaryEditText);
|
||||
});
|
||||
}
|
||||
|
||||
function substringPos(text: string, pos: Position): string {
|
||||
let subtext = '';
|
||||
const lines = splitLinesIncludeSeparators(text);
|
||||
for (let i = pos.lineNumber - 1; i < lines.length; i++) {
|
||||
subtext += lines[i].substring(i === pos.lineNumber - 1 ? pos.column - 1 : 0);
|
||||
}
|
||||
return subtext;
|
||||
}
|
||||
|
||||
function getEndPositionsAfterApplying(edits: readonly SingleTextEdit[]): Position[] {
|
||||
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);
|
||||
return newRanges.map(range => range.getEndPosition());
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import { EndOfLinePreference, ITextModel } from '../../../../common/model.js';
|
||||
import { IFeatureDebounceInformation } from '../../../../common/services/languageFeatureDebounce.js';
|
||||
import { ILanguageFeaturesService } from '../../../../common/services/languageFeatures.js';
|
||||
import { InlineCompletionItem, InlineCompletionProviderResult, provideInlineCompletions } from './provideInlineCompletions.js';
|
||||
import { singleTextRemoveCommonPrefix } from './singleTextEdit.js';
|
||||
import { singleTextRemoveCommonPrefix } from './singleTextEditHelpers.js';
|
||||
|
||||
export class InlineCompletionsSource extends Disposable {
|
||||
private readonly _updateOperation = this._register(new MutableDisposable<UpdateOperation>());
|
||||
@@ -26,21 +26,21 @@ export class InlineCompletionsSource extends Disposable {
|
||||
public readonly suggestWidgetInlineCompletions = disposableObservableValue<UpToDateInlineCompletions | undefined>('suggestWidgetInlineCompletions', undefined);
|
||||
|
||||
constructor(
|
||||
private readonly textModel: ITextModel,
|
||||
private readonly versionId: IObservable<number | null>,
|
||||
private readonly _textModel: ITextModel,
|
||||
private readonly _versionId: IObservable<number | null>,
|
||||
private readonly _debounceValue: IFeatureDebounceInformation,
|
||||
@ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService,
|
||||
@ILanguageConfigurationService private readonly languageConfigurationService: ILanguageConfigurationService,
|
||||
@ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,
|
||||
@ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(this.textModel.onDidChangeContent(() => {
|
||||
this._register(this._textModel.onDidChangeContent(() => {
|
||||
this._updateOperation.clear();
|
||||
}));
|
||||
}
|
||||
|
||||
public fetch(position: Position, context: InlineCompletionContext, activeInlineCompletion: InlineCompletionWithUpdatedRange | undefined): Promise<boolean> {
|
||||
const request = new UpdateRequest(position, context, this.textModel.getVersionId());
|
||||
const request = new UpdateRequest(position, context, this._textModel.getVersionId());
|
||||
|
||||
const target = context.selectedSuggestionInfo ? this.suggestWidgetInlineCompletions : this.inlineCompletions;
|
||||
|
||||
@@ -59,34 +59,34 @@ export class InlineCompletionsSource extends Disposable {
|
||||
const shouldDebounce = updateOngoing || context.triggerKind === InlineCompletionTriggerKind.Automatic;
|
||||
if (shouldDebounce) {
|
||||
// This debounces the operation
|
||||
await wait(this._debounceValue.get(this.textModel), source.token);
|
||||
await wait(this._debounceValue.get(this._textModel), source.token);
|
||||
}
|
||||
|
||||
if (source.token.isCancellationRequested || this._store.isDisposed || this.textModel.getVersionId() !== request.versionId) {
|
||||
if (source.token.isCancellationRequested || this._store.isDisposed || this._textModel.getVersionId() !== request.versionId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const startTime = new Date();
|
||||
const updatedCompletions = await provideInlineCompletions(
|
||||
this.languageFeaturesService.inlineCompletionsProvider,
|
||||
this._languageFeaturesService.inlineCompletionsProvider,
|
||||
position,
|
||||
this.textModel,
|
||||
this._textModel,
|
||||
context,
|
||||
source.token,
|
||||
this.languageConfigurationService
|
||||
this._languageConfigurationService
|
||||
);
|
||||
|
||||
if (source.token.isCancellationRequested || this._store.isDisposed || this.textModel.getVersionId() !== request.versionId) {
|
||||
if (source.token.isCancellationRequested || this._store.isDisposed || this._textModel.getVersionId() !== request.versionId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const endTime = new Date();
|
||||
this._debounceValue.update(this.textModel, endTime.getTime() - startTime.getTime());
|
||||
this._debounceValue.update(this._textModel, endTime.getTime() - startTime.getTime());
|
||||
|
||||
const completions = new UpToDateInlineCompletions(updatedCompletions, request, this.textModel, this.versionId);
|
||||
const completions = new UpToDateInlineCompletions(updatedCompletions, request, this._textModel, this._versionId);
|
||||
if (activeInlineCompletion) {
|
||||
const asInlineCompletion = activeInlineCompletion.toInlineCompletion(undefined);
|
||||
if (activeInlineCompletion.canBeReused(this.textModel, position) && !updatedCompletions.has(asInlineCompletion)) {
|
||||
if (activeInlineCompletion.canBeReused(this._textModel, position) && !updatedCompletions.has(asInlineCompletion)) {
|
||||
completions.prepend(activeInlineCompletion.inlineCompletion, asInlineCompletion.range, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ export async function provideInlineCompletions(
|
||||
const completions = await provider.provideInlineCompletions(model, positionOrRange, context, token);
|
||||
return completions;
|
||||
} else {
|
||||
const completions = await provider.provideInlineEdits?.(model, positionOrRange, context, token);
|
||||
const completions = await provider.provideInlineEditsForRange?.(model, positionOrRange, context, token);
|
||||
return completions;
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { commonPrefixLength } from '../../../../../base/common/strings.js';
|
||||
import { Range } from '../../../../common/core/range.js';
|
||||
import { TextLength } from '../../../../common/core/textLength.js';
|
||||
import { SingleTextEdit } from '../../../../common/core/textEdit.js';
|
||||
import { EndOfLinePreference, ITextModel } from '../../../../common/model.js';
|
||||
|
||||
export function singleTextRemoveCommonPrefix(edit: SingleTextEdit, model: ITextModel, validModelRange?: Range): SingleTextEdit {
|
||||
const modelRange = validModelRange ? edit.range.intersectRanges(validModelRange) : edit.range;
|
||||
if (!modelRange) {
|
||||
return edit;
|
||||
}
|
||||
const valueToReplace = model.getValueInRange(modelRange, EndOfLinePreference.LF);
|
||||
const commonPrefixLen = commonPrefixLength(valueToReplace, edit.text);
|
||||
const start = TextLength.ofText(valueToReplace.substring(0, commonPrefixLen)).addToPosition(edit.range.getStartPosition());
|
||||
const text = edit.text.substring(commonPrefixLen);
|
||||
const range = Range.fromPositions(start, edit.range.getEndPosition());
|
||||
return new SingleTextEdit(range, text);
|
||||
}
|
||||
|
||||
export function singleTextEditAugments(edit: SingleTextEdit, base: SingleTextEdit): boolean {
|
||||
// The augmented completion must replace the base range, but can replace even more
|
||||
return edit.text.startsWith(base.text) && rangeExtends(edit.range, base.range);
|
||||
}
|
||||
|
||||
function rangeExtends(extendingRange: Range, rangeToExtend: Range): boolean {
|
||||
return rangeToExtend.getStartPosition().equals(extendingRange.getStartPosition())
|
||||
&& rangeToExtend.getEndPosition().isBeforeOrEqual(extendingRange.getEndPosition());
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import { Range } from '../../../../common/core/range.js';
|
||||
import { SingleTextEdit } from '../../../../common/core/textEdit.js';
|
||||
import { CompletionItemInsertTextRule, CompletionItemKind, SelectedSuggestionInfo } from '../../../../common/languages.js';
|
||||
import { ITextModel } from '../../../../common/model.js';
|
||||
import { singleTextEditAugments, singleTextRemoveCommonPrefix } from './singleTextEdit.js';
|
||||
import { singleTextEditAugments, singleTextRemoveCommonPrefix } from './singleTextEditHelpers.js';
|
||||
import { SnippetParser } from '../../../snippet/browser/snippetParser.js';
|
||||
import { SnippetSession } from '../../../snippet/browser/snippetSession.js';
|
||||
import { CompletionItem } from '../../../suggest/browser/suggest.js';
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Permutation, compareBy } from '../../../../base/common/arrays.js';
|
||||
import { BugIndicatingError } from '../../../../base/common/errors.js';
|
||||
import { DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { IObservable, autorunOpts } from '../../../../base/common/observable.js';
|
||||
import { ICodeEditor } from '../../../browser/editorBrowser.js';
|
||||
import { DisposableStore } from '../../../../base/common/lifecycle.js';
|
||||
import { IObservable, observableValue, ISettableObservable, autorun, transaction } from '../../../../base/common/observable.js';
|
||||
import { splitLinesIncludeSeparators } from '../../../../base/common/strings.js';
|
||||
import { Position } from '../../../common/core/position.js';
|
||||
import { Range } from '../../../common/core/range.js';
|
||||
import { IModelDeltaDecoration } from '../../../common/model.js';
|
||||
import { SingleTextEdit, TextEdit } from '../../../common/core/textEdit.js';
|
||||
|
||||
const array: ReadonlyArray<any> = [];
|
||||
export function getReadonlyEmptyArray<T>(): readonly T[] {
|
||||
@@ -36,25 +37,6 @@ export class ColumnRange {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use observableCodeEditor(editor).applyDecorations(decorations) instead.
|
||||
* @deprecated
|
||||
*/
|
||||
export function applyObservableDecorations(editor: ICodeEditor, decorations: IObservable<IModelDeltaDecoration[]>): IDisposable {
|
||||
const d = new DisposableStore();
|
||||
const decorationsCollection = editor.createDecorationsCollection();
|
||||
d.add(autorunOpts({ debugName: () => `Apply decorations from ${decorations.debugName}` }, reader => {
|
||||
const d = decorations.read(reader);
|
||||
decorationsCollection.set(d);
|
||||
}));
|
||||
d.add({
|
||||
dispose: () => {
|
||||
decorationsCollection.clear();
|
||||
}
|
||||
});
|
||||
return d;
|
||||
}
|
||||
|
||||
export function addPositions(pos1: Position, pos2: Position): Position {
|
||||
return new Position(pos1.lineNumber + pos2.lineNumber - 1, pos2.lineNumber === 1 ? pos1.column + pos2.column - 1 : pos2.column);
|
||||
}
|
||||
@@ -62,3 +44,44 @@ export function addPositions(pos1: Position, pos2: Position): Position {
|
||||
export function subtractPositions(pos1: Position, pos2: Position): Position {
|
||||
return new Position(pos1.lineNumber - pos2.lineNumber + 1, pos1.lineNumber - pos2.lineNumber === 0 ? pos1.column - pos2.column + 1 : pos1.column);
|
||||
}
|
||||
|
||||
export function substringPos(text: string, pos: Position): string {
|
||||
let subtext = '';
|
||||
const lines = splitLinesIncludeSeparators(text);
|
||||
for (let i = pos.lineNumber - 1; i < lines.length; i++) {
|
||||
subtext += lines[i].substring(i === pos.lineNumber - 1 ? pos.column - 1 : 0);
|
||||
}
|
||||
return subtext;
|
||||
}
|
||||
|
||||
export function getEndPositionsAfterApplying(edits: readonly SingleTextEdit[]): Position[] {
|
||||
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);
|
||||
return newRanges.map(range => range.getEndPosition());
|
||||
}
|
||||
|
||||
export function convertItemsToStableObservables<T>(items: IObservable<readonly T[]>, store: DisposableStore): IObservable<IObservable<T>[]> {
|
||||
const result = observableValue<IObservable<T>[]>('result', []);
|
||||
const innerObservables: ISettableObservable<T>[] = [];
|
||||
|
||||
store.add(autorun(reader => {
|
||||
const itemsValue = items.read(reader);
|
||||
|
||||
transaction(tx => {
|
||||
if (itemsValue.length !== innerObservables.length) {
|
||||
innerObservables.length = itemsValue.length;
|
||||
for (let i = 0; i < innerObservables.length; i++) {
|
||||
if (!innerObservables[i]) {
|
||||
innerObservables[i] = observableValue<T>('item', itemsValue[i]);
|
||||
}
|
||||
}
|
||||
result.set([...innerObservables], tx);
|
||||
}
|
||||
innerObservables.forEach((o, i) => o.set(itemsValue[i], tx));
|
||||
});
|
||||
}));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ import { createTextModel } from '../../../../test/common/testTextModel.js';
|
||||
import { IAccessibilitySignalService } from '../../../../../platform/accessibilitySignal/browser/accessibilitySignalService.js';
|
||||
import { ServiceCollection } from '../../../../../platform/instantiation/common/serviceCollection.js';
|
||||
import { Selection } from '../../../../common/core/selection.js';
|
||||
import { computeGhostText } from '../../browser/model/singleTextEdit.js';
|
||||
import { computeGhostText } from '../../browser/model/computeGhostText.js';
|
||||
|
||||
suite('Inline Completions', () => {
|
||||
ensureNoDisposablesAreLeakedInTestSuite();
|
||||
|
||||
@@ -15,9 +15,10 @@ import { LineDecoration } from '../../../common/viewLayout/lineDecorations.js';
|
||||
import { InlineDecorationType } from '../../../common/viewModel.js';
|
||||
import { AdditionalLinesWidget, LineData } from '../../inlineCompletions/browser/view/ghostTextView.js';
|
||||
import { GhostText } from '../../inlineCompletions/browser/model/ghostText.js';
|
||||
import { ColumnRange, applyObservableDecorations } from '../../inlineCompletions/browser/utils.js';
|
||||
import { ColumnRange } from '../../inlineCompletions/browser/utils.js';
|
||||
import { diffDeleteDecoration, diffLineDeleteDecorationBackgroundWithIndicator } from '../../../browser/widget/diffEditor/registrations.contribution.js';
|
||||
import { LineTokens } from '../../../common/tokens/lineTokens.js';
|
||||
import { observableCodeEditor } from '../../../browser/observableCodeEditor.js';
|
||||
|
||||
export const INLINE_EDIT_DESCRIPTION = 'inline-edit';
|
||||
export interface IGhostTextWidgetModel {
|
||||
@@ -29,17 +30,20 @@ export interface IGhostTextWidgetModel {
|
||||
|
||||
export class GhostTextWidget extends Disposable {
|
||||
private readonly isDisposed = observableValue(this, false);
|
||||
private readonly currentTextModel = observableFromEvent(this, this.editor.onDidChangeModel, () => /** @description editor.model */ this.editor.getModel());
|
||||
private readonly currentTextModel = observableFromEvent(this, this._editor.onDidChangeModel, () => /** @description editor.model */ this._editor.getModel());
|
||||
|
||||
private readonly _editorObs = observableCodeEditor(this._editor);
|
||||
|
||||
constructor(
|
||||
private readonly editor: ICodeEditor,
|
||||
private readonly _editor: ICodeEditor,
|
||||
readonly model: IGhostTextWidgetModel,
|
||||
@ILanguageService private readonly languageService: ILanguageService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(toDisposable(() => { this.isDisposed.set(true, undefined); }));
|
||||
this._register(applyObservableDecorations(this.editor, this.decorations));
|
||||
|
||||
this._register(this._editorObs.setDecorations(this.decorations));
|
||||
}
|
||||
|
||||
private readonly uiState = derived(this, reader => {
|
||||
@@ -214,7 +218,7 @@ export class GhostTextWidget extends Disposable {
|
||||
|
||||
private readonly additionalLinesWidget = this._register(
|
||||
new AdditionalLinesWidget(
|
||||
this.editor,
|
||||
this._editor,
|
||||
derived(reader => {
|
||||
/** @description lines */
|
||||
const uiState = this.uiState.read(reader);
|
||||
|
||||
@@ -613,8 +613,8 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread
|
||||
provideInlineCompletions: async (model: ITextModel, position: EditorPosition, context: languages.InlineCompletionContext, token: CancellationToken): Promise<IdentifiableInlineCompletions | undefined> => {
|
||||
return this._proxy.$provideInlineCompletions(handle, model.uri, position, context, token);
|
||||
},
|
||||
provideInlineEdits: async (model: ITextModel, range: EditorRange, context: languages.InlineCompletionContext, token: CancellationToken): Promise<IdentifiableInlineCompletions | undefined> => {
|
||||
return this._proxy.$provideInlineEdits(handle, model.uri, range, context, token);
|
||||
provideInlineEditsForRange: async (model: ITextModel, range: EditorRange, context: languages.InlineCompletionContext, token: CancellationToken): Promise<IdentifiableInlineCompletions | undefined> => {
|
||||
return this._proxy.$provideInlineEditsForRange(handle, model.uri, range, context, token);
|
||||
},
|
||||
handleItemDidShow: async (completions: IdentifiableInlineCompletions, item: IdentifiableInlineCompletion, updatedInsertText: string): Promise<void> => {
|
||||
if (supportsHandleEvents) {
|
||||
|
||||
@@ -2227,7 +2227,7 @@ export interface ExtHostLanguageFeaturesShape {
|
||||
$resolveCompletionItem(handle: number, id: ChainedCacheId, token: CancellationToken): Promise<ISuggestDataDto | undefined>;
|
||||
$releaseCompletionItems(handle: number, id: number): void;
|
||||
$provideInlineCompletions(handle: number, resource: UriComponents, position: IPosition, context: languages.InlineCompletionContext, token: CancellationToken): Promise<IdentifiableInlineCompletions | undefined>;
|
||||
$provideInlineEdits(handle: number, resource: UriComponents, range: IRange, context: languages.InlineCompletionContext, token: CancellationToken): Promise<IdentifiableInlineCompletions | undefined>;
|
||||
$provideInlineEditsForRange(handle: number, resource: UriComponents, range: IRange, context: languages.InlineCompletionContext, token: CancellationToken): Promise<IdentifiableInlineCompletions | undefined>;
|
||||
$handleInlineCompletionDidShow(handle: number, pid: number, idx: number, updatedInsertText: string): void;
|
||||
$handleInlineCompletionPartialAccept(handle: number, pid: number, idx: number, acceptedCharacters: number, info: languages.PartialAcceptInfo): void;
|
||||
$freeInlineCompletionsList(handle: number, pid: number): void;
|
||||
|
||||
@@ -1287,7 +1287,7 @@ class InlineCompletionAdapterBase {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async provideInlineEdits(resource: URI, range: IRange, context: languages.InlineCompletionContext, token: CancellationToken): Promise<extHostProtocol.IdentifiableInlineCompletions | undefined> {
|
||||
async provideInlineEditsForRange(resource: URI, range: IRange, context: languages.InlineCompletionContext, token: CancellationToken): Promise<extHostProtocol.IdentifiableInlineCompletions | undefined> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -1396,8 +1396,8 @@ class InlineCompletionAdapter extends InlineCompletionAdapterBase {
|
||||
};
|
||||
}
|
||||
|
||||
override async provideInlineEdits(resource: URI, range: IRange, context: languages.InlineCompletionContext, token: CancellationToken): Promise<extHostProtocol.IdentifiableInlineCompletions | undefined> {
|
||||
if (!this._provider.provideInlineEdits) {
|
||||
override async provideInlineEditsForRange(resource: URI, range: IRange, context: languages.InlineCompletionContext, token: CancellationToken): Promise<extHostProtocol.IdentifiableInlineCompletions | undefined> {
|
||||
if (!this._provider.provideInlineEditsForRange) {
|
||||
return undefined;
|
||||
}
|
||||
checkProposedApiEnabled(this._extension, 'inlineCompletionsAdditions');
|
||||
@@ -1405,7 +1405,7 @@ class InlineCompletionAdapter extends InlineCompletionAdapterBase {
|
||||
const doc = this._documents.getDocument(resource);
|
||||
const r = typeConvert.Range.to(range);
|
||||
|
||||
const result = await this._provider.provideInlineEdits(doc, r, {
|
||||
const result = await this._provider.provideInlineEditsForRange(doc, r, {
|
||||
selectedCompletionInfo:
|
||||
context.selectedSuggestionInfo
|
||||
? {
|
||||
@@ -2674,8 +2674,8 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
|
||||
return this._withAdapter(handle, InlineCompletionAdapterBase, adapter => adapter.provideInlineCompletions(URI.revive(resource), position, context, token), undefined, token);
|
||||
}
|
||||
|
||||
$provideInlineEdits(handle: number, resource: UriComponents, range: IRange, context: languages.InlineCompletionContext, token: CancellationToken): Promise<extHostProtocol.IdentifiableInlineCompletions | undefined> {
|
||||
return this._withAdapter(handle, InlineCompletionAdapterBase, adapter => adapter.provideInlineEdits(URI.revive(resource), range, context, token), undefined, token);
|
||||
$provideInlineEditsForRange(handle: number, resource: UriComponents, range: IRange, context: languages.InlineCompletionContext, token: CancellationToken): Promise<extHostProtocol.IdentifiableInlineCompletions | undefined> {
|
||||
return this._withAdapter(handle, InlineCompletionAdapterBase, adapter => adapter.provideInlineEditsForRange(URI.revive(resource), range, context, token), undefined, token);
|
||||
}
|
||||
|
||||
$handleInlineCompletionDidShow(handle: number, pid: number, idx: number, updatedInsertText: string): void {
|
||||
|
||||
@@ -61,7 +61,7 @@ declare module 'vscode' {
|
||||
// eslint-disable-next-line local/vscode-dts-provider-naming
|
||||
handleDidPartiallyAcceptCompletionItem?(completionItem: InlineCompletionItem, info: PartialAcceptInfo): void;
|
||||
|
||||
provideInlineEdits?(document: TextDocument, range: Range, context: InlineCompletionContext, token: CancellationToken): ProviderResult<InlineCompletionItem[] | InlineCompletionList>;
|
||||
provideInlineEditsForRange?(document: TextDocument, range: Range, context: InlineCompletionContext, token: CancellationToken): ProviderResult<InlineCompletionItem[] | InlineCompletionList>;
|
||||
}
|
||||
|
||||
export interface InlineCompletionContext {
|
||||
|
||||
Reference in New Issue
Block a user