Improves inline completion provider change event (#252312)

This commit is contained in:
Henning Dieterichs
2025-06-24 20:05:40 +02:00
committed by GitHub
parent 848883ca9c
commit cacbd7f8ac
3 changed files with 36 additions and 30 deletions
@@ -8,7 +8,7 @@ import { itemsEquals } from '../../../../../base/common/equals.js';
import { BugIndicatingError, onUnexpectedError, onUnexpectedExternalError } from '../../../../../base/common/errors.js';
import { Emitter } from '../../../../../base/common/event.js';
import { Disposable } from '../../../../../base/common/lifecycle.js';
import { IObservable, IObservableWithChange, IReader, ITransaction, autorun, autorunWithStore, constObservable, derived, derivedHandleChanges, derivedOpts, observableFromEvent, observableSignal, observableValue, recomputeInitiallyAndOnChange, subtransaction, transaction } from '../../../../../base/common/observable.js';
import { IObservable, IObservableWithChange, IReader, ITransaction, autorun, constObservable, derived, derivedHandleChanges, derivedOpts, mapObservableArrayCached, observableFromEvent, observableSignal, observableValue, recomputeInitiallyAndOnChange, subtransaction, transaction } from '../../../../../base/common/observable.js';
import { commonPrefixLength, firstNonWhitespaceIndex } from '../../../../../base/common/strings.js';
import { isDefined } from '../../../../../base/common/types.js';
import { IAccessibilityService } from '../../../../../platform/accessibility/common/accessibility.js';
@@ -150,11 +150,15 @@ export class InlineCompletionsModel extends Disposable {
onlyRequestInlineEdits: false,
shouldDebounce: true,
provider: undefined as InlineCompletionsProvider | undefined,
textChange: false,
}),
handleChange: (ctx, changeSummary) => {
/** @description fetch inline completions */
if (ctx.didChange(this._textModelVersionId) && this._preserveCurrentCompletionReasons.has(this._getReason(ctx.change))) {
changeSummary.preserveCurrentCompletion = true;
if (ctx.didChange(this._textModelVersionId)) {
if (this._preserveCurrentCompletionReasons.has(this._getReason(ctx.change))) {
changeSummary.preserveCurrentCompletion = true;
}
changeSummary.textChange = true;
} else if (ctx.didChange(this._forceUpdateExplicitlySignal)) {
changeSummary.inlineCompletionTriggerKind = InlineCompletionTriggerKind.Explicit;
} else if (ctx.didChange(this.dontRefetchSignal)) {
@@ -206,7 +210,7 @@ export class InlineCompletionsModel extends Disposable {
includeInlineEdits: this._inlineEditsEnabled.read(reader),
};
if (context.triggerKind === InlineCompletionTriggerKind.Automatic) {
if (context.triggerKind === InlineCompletionTriggerKind.Automatic && changeSummary.textChange) {
if (this.textModel.getAlternativeVersionId() === this._lastShownInlineCompletionInfo?.alternateTextModelVersionId) {
// When undoing back to a version where an inline edit/completion was shown,
// we want to show an inline edit (or completion) again if it was originally an inline edit (or completion).
@@ -572,32 +576,29 @@ export class InlineCompletionsModel extends Disposable {
}));
const inlineCompletionProviders = observableFromEvent(this._languageFeaturesService.inlineCompletionsProvider.onDidChange, () => this._languageFeaturesService.inlineCompletionsProvider.all(textModel));
this._register(autorunWithStore((reader, store) => {
const providers = inlineCompletionProviders.read(reader);
for (const provider of providers) {
if (!provider.onDidChangeInlineCompletions) {
continue;
mapObservableArrayCached(this, inlineCompletionProviders, (provider, store) => {
if (!provider.onDidChangeInlineCompletions) {
return;
}
store.add(provider.onDidChangeInlineCompletions(() => {
if (!this._enabled.get()) {
return;
}
store.add(provider.onDidChangeInlineCompletions(() => {
if (!this._enabled.get()) {
return;
}
// If there is an active suggestion from a different provider, we ignore the update
const activeState = this.state.get();
if (activeState && (activeState.inlineCompletion || activeState.edits) && activeState.inlineCompletion?.source.provider !== provider) {
return;
}
// If there is an active suggestion from a different provider, we ignore the update
const activeState = this.state.get();
if (activeState && (activeState.inlineCompletion || activeState.edits) && activeState.inlineCompletion?.source.provider !== provider) {
return;
}
transaction(tx => {
this._fetchSpecificProviderSignal.trigger(tx, provider);
this.trigger(tx);
});
transaction(tx => {
this._fetchSpecificProviderSignal.trigger(tx, provider);
this.trigger(tx);
});
}));
}
}));
}));
}).recomputeInitiallyAndOnChange(this._store);
this._didUndoInlineEdits.recomputeInitiallyAndOnChange(this._store);
}
@@ -119,7 +119,7 @@ export class InlineCompletionsSource extends Disposable {
public fetch(providers: InlineCompletionsProvider[], context: InlineCompletionContextWithoutUuid, activeInlineCompletion: InlineSuggestionIdentity | undefined, withDebounce: boolean, userJumpedToActiveCompletion: IObservable<boolean>, providerhasChangedCompletion: boolean, editorType: InlineCompletionEditorType): Promise<boolean> {
const position = this._cursorPosition.get();
const request = new UpdateRequest(position, context, this._textModel.getVersionId());
const request = new UpdateRequest(position, context, this._textModel.getVersionId(), new Set(providers));
const target = context.selectedSuggestionInfo ? this.suggestWidgetInlineCompletions.get() : this.inlineCompletions.get();
@@ -307,6 +307,7 @@ class UpdateRequest {
public readonly position: Position,
public readonly context: InlineCompletionContextWithoutUuid,
public readonly versionId: number,
public readonly providers: Set<InlineCompletionsProvider>,
) {
}
@@ -315,7 +316,8 @@ class UpdateRequest {
&& equalsIfDefined(this.context.selectedSuggestionInfo, other.context.selectedSuggestionInfo, itemEquals())
&& (other.context.triggerKind === InlineCompletionTriggerKind.Automatic
|| this.context.triggerKind === InlineCompletionTriggerKind.Explicit)
&& this.versionId === other.versionId;
&& this.versionId === other.versionId
&& isSubset(other.providers, this.providers);
}
public get isExplicitRequest() {
@@ -323,6 +325,10 @@ class UpdateRequest {
}
}
function isSubset<T>(set1: Set<T>, set2: Set<T>): boolean {
return [...set1].every(item => set2.has(item));
}
class UpdateOperation implements IDisposable {
constructor(
public readonly request: UpdateRequest,
@@ -680,13 +680,12 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread
return `InlineCompletionsProvider(${extensionId})`;
},
};
this._registrations.set(handle, this._languageFeaturesService.inlineCompletionsProvider.register(selector, provider));
if (typeof eventHandle === 'number') {
const emitter = new Emitter<void>();
this._registrations.set(eventHandle, emitter);
provider.onDidChangeInlineCompletions = emitter.event;
}
this._registrations.set(handle, this._languageFeaturesService.inlineCompletionsProvider.register(selector, provider));
}
$emitInlineCompletionsChange(handle: number): void {