Skip to content

Commit 071e031

Browse files
authored
1 parent 5f22745 commit 071e031

File tree

9 files changed

+174
-168
lines changed

9 files changed

+174
-168
lines changed

src/vs/base/common/async.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2082,6 +2082,8 @@ export class AsyncIterableObject<T> implements AsyncIterable<T> {
20822082
});
20832083
}
20842084

2085+
public filter<T2 extends T>(filterFn: (item: T) => item is T2): AsyncIterableObject<T2>;
2086+
public filter(filterFn: (item: T) => boolean): AsyncIterableObject<T>;
20852087
public filter(filterFn: (item: T) => boolean): AsyncIterableObject<T> {
20862088
return AsyncIterableObject.filter(this, filterFn);
20872089
}

src/vs/editor/common/languages.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -918,7 +918,7 @@ export interface InlineCompletionsProvider<T extends InlineCompletions = InlineC
918918
toString?(): string;
919919
}
920920

921-
export type InlineCompletionsDisposeReason = { kind: 'lostRace' | 'tokenCancellation' | 'other' };
921+
export type InlineCompletionsDisposeReason = { kind: 'lostRace' | 'tokenCancellation' | 'other' | 'empty' | 'notTaken' };
922922

923923
export enum InlineCompletionEndOfLifeReasonKind {
924924
Accepted = 0,

src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,14 @@ export class InlineCompletionsModel extends Disposable {
9292
@ICodeEditorService private readonly _codeEditorService: ICodeEditorService,
9393
) {
9494
super();
95-
this._source = this._register(this._instantiationService.createInstance(InlineCompletionsSource, this.textModel, this._textModelVersionId, this._debounceValue));
95+
this.primaryPosition = derived(this, reader => this._positions.read(reader)[0] ?? new Position(1, 1));
96+
this._source = this._register(this._instantiationService.createInstance(InlineCompletionsSource, this.textModel, this._textModelVersionId, this._debounceValue, this.primaryPosition));
9697
this._isActive = observableValue<boolean>(this, false);
9798
this._onlyRequestInlineEditsSignal = observableSignal(this);
9899
this._forceUpdateExplicitlySignal = observableSignal(this);
99100
this._noDelaySignal = observableSignal(this);
100101
this._fetchSpecificProviderSignal = observableSignal<InlineCompletionsProvider | undefined>(this);
101102
this._selectedInlineCompletionId = observableValue<string | undefined>(this, undefined);
102-
this.primaryPosition = derived(this, reader => this._positions.read(reader)[0] ?? new Position(1, 1));
103103
this._isAcceptingPartially = false;
104104
this._onDidAccept = new Emitter<void>();
105105
this.onDidAccept = this._onDidAccept.event;
@@ -188,7 +188,6 @@ export class InlineCompletionsModel extends Disposable {
188188
this._source.seedInlineCompletionsWithSuggestWidget();
189189
}
190190

191-
const cursorPosition = this.primaryPosition.get();
192191
if (changeSummary.dontRefetch) {
193192
return Promise.resolve(true);
194193
}
@@ -228,8 +227,9 @@ export class InlineCompletionsModel extends Disposable {
228227
const suppressedProviderGroupIds = this._suppressedInlineCompletionGroupIds.get();
229228
const availableProviders = providers.filter(provider => !(provider.groupId && suppressedProviderGroupIds.has(provider.groupId)));
230229

231-
return this._source.fetch(availableProviders, cursorPosition, context, itemToPreserve?.identity, changeSummary.shouldDebounce, userJumpedToActiveCompletion, !!changeSummary.provider, this.editorType);
230+
return this._source.fetch(availableProviders, context, itemToPreserve?.identity, changeSummary.shouldDebounce, userJumpedToActiveCompletion, !!changeSummary.provider, this.editorType);
232231
});
232+
233233
this._inlineCompletionItems = derivedOpts({ owner: this }, reader => {
234234
const c = this._source.inlineCompletions.read(reader);
235235
if (!c) { return undefined; }

src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts

Lines changed: 91 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { compareUndefinedSmallest, numberComparator } from '../../../../../base/
77
import { findLastMax } from '../../../../../base/common/arraysFind.js';
88
import { CancellationTokenSource } from '../../../../../base/common/cancellation.js';
99
import { equalsIfDefined, itemEquals } from '../../../../../base/common/equals.js';
10-
import { Disposable, IDisposable, MutableDisposable } from '../../../../../base/common/lifecycle.js';
10+
import { Disposable, DisposableStore, IDisposable, MutableDisposable, toDisposable } from '../../../../../base/common/lifecycle.js';
1111
import { derived, IObservable, IObservableWithChange, ITransaction, observableValue, recordChanges, transaction } from '../../../../../base/common/observable.js';
1212
// eslint-disable-next-line local/code-no-deep-import-of-internal
1313
import { observableReducerSettable } from '../../../../../base/common/observableInternal/experimental/reducer.js';
@@ -27,7 +27,7 @@ import { IModelContentChangedEvent } from '../../../../common/textModelEvents.js
2727
import { formatRecordableLogEntry, IRecordableEditorLogEntry, IRecordableLogEntry, StructuredLogger } from '../structuredLogger.js';
2828
import { wait } from '../utils.js';
2929
import { InlineSuggestionIdentity, InlineSuggestionItem } from './inlineSuggestionItem.js';
30-
import { InlineCompletionContextWithoutUuid, InlineCompletionEditorType, InlineCompletionProviderResult, provideInlineCompletions } from './provideInlineCompletions.js';
30+
import { InlineCompletionContextWithoutUuid, InlineCompletionEditorType, provideInlineCompletions, runWhenCancelled } from './provideInlineCompletions.js';
3131

3232
export class InlineCompletionsSource extends Disposable {
3333
private static _requestId = 0;
@@ -47,6 +47,7 @@ export class InlineCompletionsSource extends Disposable {
4747
private readonly _textModel: ITextModel,
4848
private readonly _versionId: IObservableWithChange<number | null, IModelContentChangedEvent | undefined>,
4949
private readonly _debounceValue: IFeatureDebounceInformation,
50+
private readonly _cursorPosition: IObservable<Position>,
5051
@ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService,
5152
@ILogService private readonly _logService: ILogService,
5253
@IConfigurationService private readonly _configurationService: IConfigurationService,
@@ -105,7 +106,7 @@ export class InlineCompletionsSource extends Disposable {
105106

106107
private _log(entry:
107108
{ sourceId: string; kind: 'start'; requestId: number; context: unknown } & IRecordableEditorLogEntry
108-
| { sourceId: string; kind: 'end'; error: unknown; durationMs: number; result: unknown; requestId: number } & IRecordableLogEntry
109+
| { sourceId: string; kind: 'end'; error: unknown; durationMs: number; result: unknown; requestId: number; didAllProvidersReturn: boolean } & IRecordableLogEntry
109110
) {
110111
if (this._loggingEnabled.get()) {
111112
this._logService.info(formatRecordableLogEntry(entry));
@@ -116,7 +117,8 @@ export class InlineCompletionsSource extends Disposable {
116117
private readonly _loadingCount;
117118
public readonly loading;
118119

119-
public fetch(providers: InlineCompletionsProvider[], position: Position, context: InlineCompletionContextWithoutUuid, activeInlineCompletion: InlineSuggestionIdentity | undefined, withDebounce: boolean, userJumpedToActiveCompletion: IObservable<boolean>, providerhasChangedCompletion: boolean, editorType: InlineCompletionEditorType): Promise<boolean> {
120+
public fetch(providers: InlineCompletionsProvider[], context: InlineCompletionContextWithoutUuid, activeInlineCompletion: InlineSuggestionIdentity | undefined, withDebounce: boolean, userJumpedToActiveCompletion: IObservable<boolean>, providerhasChangedCompletion: boolean, editorType: InlineCompletionEditorType): Promise<boolean> {
121+
const position = this._cursorPosition.get();
120122
const request = new UpdateRequest(position, context, this._textModel.getVersionId());
121123

122124
const target = context.selectedSuggestionInfo ? this.suggestWidgetInlineCompletions.get() : this.inlineCompletions.get();
@@ -134,6 +136,7 @@ export class InlineCompletionsSource extends Disposable {
134136

135137
const promise = (async () => {
136138
this._loadingCount.set(this._loadingCount.get() + 1, undefined);
139+
const store = new DisposableStore();
137140
try {
138141
const recommendedDebounceValue = this._debounceValue.get(this._textModel);
139142
const debounceValue = findLastMax(
@@ -158,67 +161,92 @@ export class InlineCompletionsSource extends Disposable {
158161
}
159162

160163
const startTime = new Date();
161-
let providerResult: InlineCompletionProviderResult | undefined = undefined;
162-
let error: unknown = undefined;
163-
try {
164-
providerResult = await provideInlineCompletions(
165-
providers,
166-
position,
167-
this._textModel,
168-
context,
169-
editorType,
170-
source.token,
171-
this._languageConfigurationService
172-
);
173-
} catch (e) {
174-
error = e;
175-
throw e;
176-
} finally {
177-
if (this._loggingEnabled.get() || this._structuredFetchLogger.isEnabled.get()) {
178-
if (source.token.isCancellationRequested || this._store.isDisposed || this._textModel.getVersionId() !== request.versionId) {
179-
error = 'canceled';
164+
const providerResult = provideInlineCompletions(providers, this._cursorPosition.get(), this._textModel, context, editorType, this._languageConfigurationService);
165+
166+
runWhenCancelled(source.token, () => providerResult.cancelAndDispose({ kind: 'tokenCancellation' }));
167+
168+
let shouldStopEarly = false;
169+
170+
const suggestions: InlineSuggestionItem[] = [];
171+
for await (const list of providerResult.lists) {
172+
if (!list) {
173+
continue;
174+
}
175+
list.addRef();
176+
store.add(toDisposable(() => list.removeRef(list.inlineSuggestionsData.length === 0 ? { kind: 'empty' } : { kind: 'notTaken' })));
177+
178+
for (const item of list.inlineSuggestionsData) {
179+
if (!context.includeInlineEdits && (item.isInlineEdit || item.showInlineEditMenu)) {
180+
continue;
180181
}
181-
const result = providerResult?.completions.map(c => ({
182-
range: c.range.toString(),
183-
text: c.insertText,
184-
isInlineEdit: !!c.isInlineEdit,
185-
source: c.source.provider.groupId,
186-
}));
187-
this._log({ sourceId: 'InlineCompletions.fetch', kind: 'end', requestId, durationMs: (Date.now() - startTime.getTime()), error, result, time: Date.now() });
182+
if (!context.includeInlineCompletions && !(item.isInlineEdit || item.showInlineEditMenu)) {
183+
continue;
184+
}
185+
186+
const i = InlineSuggestionItem.create(item, this._textModel);
187+
suggestions.push(i);
188+
// Stop after first visible inline completion
189+
if (!i.isInlineEdit && !i.showInlineEditMenu && context.triggerKind === InlineCompletionTriggerKind.Automatic) {
190+
if (i.isVisible(this._textModel, this._cursorPosition.get())) {
191+
shouldStopEarly = true;
192+
}
193+
}
194+
}
195+
196+
if (shouldStopEarly) {
197+
break;
198+
}
199+
}
200+
201+
providerResult.cancelAndDispose({ kind: 'lostRace' });
202+
203+
if (this._loggingEnabled.get() || this._structuredFetchLogger.isEnabled.get()) {
204+
const didAllProvidersReturn = providerResult.didAllProvidersReturn;
205+
let error: string | undefined = undefined;
206+
if (source.token.isCancellationRequested || this._store.isDisposed || this._textModel.getVersionId() !== request.versionId) {
207+
error = 'canceled';
188208
}
209+
const result = suggestions.map(c => ({
210+
range: c.editRange.toString(),
211+
text: c.insertText,
212+
isInlineEdit: !!c.isInlineEdit,
213+
source: c.source.provider.groupId,
214+
}));
215+
this._log({ sourceId: 'InlineCompletions.fetch', kind: 'end', requestId, durationMs: (Date.now() - startTime.getTime()), error, result, time: Date.now(), didAllProvidersReturn });
189216
}
190217

191-
if (source.token.isCancellationRequested || this._store.isDisposed || this._textModel.getVersionId() !== request.versionId || userJumpedToActiveCompletion.get() /* In the meantime the user showed interest for the active completion so dont hide it */) {
192-
providerResult.dispose();
218+
if (source.token.isCancellationRequested || this._store.isDisposed || this._textModel.getVersionId() !== request.versionId
219+
|| userJumpedToActiveCompletion.get() /* In the meantime the user showed interest for the active completion so dont hide it */) {
193220
return false;
194221
}
195222

196223
const endTime = new Date();
197224
this._debounceValue.update(this._textModel, endTime.getTime() - startTime.getTime());
198225

226+
const cursorPosition = this._cursorPosition.get();
199227
this._updateOperation.clear();
200228
transaction(tx => {
201-
const v = this._state.get();
202229
/** @description Update completions with provider result */
230+
const v = this._state.get();
231+
203232
if (context.selectedSuggestionInfo) {
204233
this._state.set({
205234
inlineCompletions: InlineCompletionsState.createEmpty(),
206-
suggestWidgetInlineCompletions: v.suggestWidgetInlineCompletions.createStateWithAppliedResults(providerResult, request, this._textModel, activeInlineCompletion),
235+
suggestWidgetInlineCompletions: v.suggestWidgetInlineCompletions.createStateWithAppliedResults(suggestions, request, this._textModel, cursorPosition, activeInlineCompletion),
207236
}, tx);
208237
} else {
209238
this._state.set({
210-
inlineCompletions: v.inlineCompletions.createStateWithAppliedResults(providerResult, request, this._textModel, activeInlineCompletion),
239+
inlineCompletions: v.inlineCompletions.createStateWithAppliedResults(suggestions, request, this._textModel, cursorPosition, activeInlineCompletion),
211240
suggestWidgetInlineCompletions: InlineCompletionsState.createEmpty(),
212241
}, tx);
213242
}
214243

215-
providerResult.dispose();
216244
v.inlineCompletions.dispose();
217245
v.suggestWidgetInlineCompletions.dispose();
218246
});
219-
220247
} finally {
221248
this._loadingCount.set(this._loadingCount.get() - 1, undefined);
249+
store.dispose();
222250
}
223251

224252
return true;
@@ -348,28 +376,43 @@ class InlineCompletionsState extends Disposable {
348376
return new InlineCompletionsState(newInlineCompletions, this.request);
349377
}
350378

351-
public createStateWithAppliedResults(update: InlineCompletionProviderResult, request: UpdateRequest, textModel: ITextModel, itemToPreserve: InlineSuggestionIdentity | undefined): InlineCompletionsState {
352-
const items: InlineSuggestionItem[] = [];
379+
public createStateWithAppliedResults(updatedSuggestions: InlineSuggestionItem[], request: UpdateRequest, textModel: ITextModel, cursorPosition: Position, itemIdToPreserve: InlineSuggestionIdentity | undefined): InlineCompletionsState {
380+
let updatedItems: InlineSuggestionItem[] = [];
353381

354-
for (const item of update.completions) {
355-
const i = InlineSuggestionItem.create(item, textModel);
382+
let itemToPreserve: InlineSuggestionItem | undefined = undefined;
383+
if (itemIdToPreserve) {
384+
const preserveCandidate = this._findById(itemIdToPreserve);
385+
if (preserveCandidate) {
386+
const updatedSuggestionsHasItemToPreserve = updatedSuggestions.some(i => i.hash === preserveCandidate.hash);
387+
if (!updatedSuggestionsHasItemToPreserve && preserveCandidate.canBeReused(textModel, request.position)) {
388+
itemToPreserve = preserveCandidate;
389+
}
390+
}
391+
}
392+
393+
const preferInlineCompletions = itemToPreserve
394+
// itemToPreserve has precedence
395+
? !itemToPreserve.isInlineEdit
396+
// Otherwise: prefer inline completion if there is a visible one
397+
: updatedSuggestions.some(i => !i.isInlineEdit && i.isVisible(textModel, cursorPosition));
398+
399+
for (const i of updatedSuggestions) {
356400
const oldItem = this._findByHash(i.hash);
357401
if (oldItem) {
358-
items.push(i.withIdentity(oldItem.identity));
402+
updatedItems.push(i.withIdentity(oldItem.identity));
359403
oldItem.setEndOfLifeReason({ kind: InlineCompletionEndOfLifeReasonKind.Ignored, userTypingDisagreed: false, supersededBy: i.getSourceCompletion() });
360404
} else {
361-
items.push(i);
405+
updatedItems.push(i);
362406
}
363407
}
364408

365409
if (itemToPreserve) {
366-
const item = this._findById(itemToPreserve);
367-
if (item && !update.has(item.getSingleTextEdit()) && item.canBeReused(textModel, request.position)) {
368-
items.unshift(item);
369-
}
410+
updatedItems.unshift(itemToPreserve);
370411
}
371412

372-
return new InlineCompletionsState(items, request);
413+
updatedItems = preferInlineCompletions ? updatedItems.filter(i => !i.isInlineEdit) : updatedItems.filter(i => i.isInlineEdit);
414+
415+
return new InlineCompletionsState(updatedItems, request);
373416
}
374417

375418
public clone(): InlineCompletionsState {

0 commit comments

Comments
 (0)