From 54d6e2b0ef5a8819ba0741f613ab98fd44ec83d4 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 24 Jun 2025 19:52:42 +0200 Subject: [PATCH] Improves inline completion provider change event --- .../browser/model/inlineCompletionsModel.ts | 53 ++++++++++--------- .../browser/model/inlineCompletionsSource.ts | 10 +++- .../api/browser/mainThreadLanguageFeatures.ts | 3 +- 3 files changed, 36 insertions(+), 30 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts index 87b2ba47a154f..5a64d8466c730 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts @@ -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); } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts index a82b809d8fada..636c3b7897948 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts @@ -119,7 +119,7 @@ export class InlineCompletionsSource extends Disposable { public fetch(providers: InlineCompletionsProvider[], context: InlineCompletionContextWithoutUuid, activeInlineCompletion: InlineSuggestionIdentity | undefined, withDebounce: boolean, userJumpedToActiveCompletion: IObservable, providerhasChangedCompletion: boolean, editorType: InlineCompletionEditorType): Promise { 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, ) { } @@ -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(set1: Set, set2: Set): boolean { + return [...set1].every(item => set2.has(item)); +} + class UpdateOperation implements IDisposable { constructor( public readonly request: UpdateRequest, diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 964a69fa433b7..7cab7df73bb31 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -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(); this._registrations.set(eventHandle, emitter); provider.onDidChangeInlineCompletions = emitter.event; } + this._registrations.set(handle, this._languageFeaturesService.inlineCompletionsProvider.register(selector, provider)); } $emitInlineCompletionsChange(handle: number): void {