Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts
5346 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { assertNever } from '../../../../../base/common/assert.js';
7
import { AsyncIterableProducer } from '../../../../../base/common/async.js';
8
import { CancellationToken, CancellationTokenSource } from '../../../../../base/common/cancellation.js';
9
import { BugIndicatingError, onUnexpectedExternalError } from '../../../../../base/common/errors.js';
10
import { Disposable, IDisposable } from '../../../../../base/common/lifecycle.js';
11
import { prefixedUuid } from '../../../../../base/common/uuid.js';
12
import { ICommandService } from '../../../../../platform/commands/common/commands.js';
13
import { ISingleEditOperation } from '../../../../common/core/editOperation.js';
14
import { StringReplacement } from '../../../../common/core/edits/stringEdit.js';
15
import { OffsetRange } from '../../../../common/core/ranges/offsetRange.js';
16
import { Position } from '../../../../common/core/position.js';
17
import { Range } from '../../../../common/core/range.js';
18
import { TextReplacement } from '../../../../common/core/edits/textEdit.js';
19
import { InlineCompletionEndOfLifeReason, InlineCompletionEndOfLifeReasonKind, InlineCompletion, InlineCompletionContext, InlineCompletions, InlineCompletionsProvider, PartialAcceptInfo, InlineCompletionsDisposeReason, LifetimeSummary, ProviderId, IInlineCompletionHint, InlineCompletionTriggerKind } from '../../../../common/languages.js';
20
import { ILanguageConfigurationService } from '../../../../common/languages/languageConfigurationRegistry.js';
21
import { ITextModel } from '../../../../common/model.js';
22
import { fixBracketsInLine } from '../../../../common/model/bracketPairsTextModelPart/fixBrackets.js';
23
import { SnippetParser, Text } from '../../../snippet/browser/snippetParser.js';
24
import { ErrorResult, getReadonlyEmptyArray } from '../utils.js';
25
import { groupByMap } from '../../../../../base/common/collections.js';
26
import { DirectedGraph } from './graph.js';
27
import { CachedFunction } from '../../../../../base/common/cache.js';
28
import { InlineCompletionViewData, InlineCompletionViewKind } from '../view/inlineEdits/inlineEditsViewInterface.js';
29
import { isDefined } from '../../../../../base/common/types.js';
30
import { inlineCompletionIsVisible } from './inlineCompletionIsVisible.js';
31
import { EditDeltaInfo } from '../../../../common/textModelEditSource.js';
32
import { URI } from '../../../../../base/common/uri.js';
33
import { InlineSuggestionEditKind } from './editKind.js';
34
import { InlineSuggestAlternativeAction } from './InlineSuggestAlternativeAction.js';
35
36
export type InlineCompletionContextWithoutUuid = Omit<InlineCompletionContext, 'requestUuid'>;
37
38
export function provideInlineCompletions(
39
providers: InlineCompletionsProvider[],
40
position: Position,
41
model: ITextModel,
42
context: InlineCompletionContextWithoutUuid,
43
requestInfo: InlineSuggestRequestInfo,
44
languageConfigurationService?: ILanguageConfigurationService,
45
): IInlineCompletionProviderResult {
46
const requestUuid = prefixedUuid('icr');
47
48
const cancellationTokenSource = new CancellationTokenSource();
49
let cancelReason: InlineCompletionsDisposeReason | undefined = undefined;
50
51
const contextWithUuid: InlineCompletionContext = { ...context, requestUuid: requestUuid };
52
53
const defaultReplaceRange = getDefaultRange(position, model);
54
55
const providersByGroupId = groupByMap(providers, p => p.groupId);
56
const yieldsToGraph = DirectedGraph.from(providers, p => {
57
return p.yieldsToGroupIds?.flatMap(groupId => providersByGroupId.get(groupId) ?? []) ?? [];
58
});
59
const { foundCycles } = yieldsToGraph.removeCycles();
60
if (foundCycles.length > 0) {
61
onUnexpectedExternalError(new Error(`Inline completions: cyclic yield-to dependency detected.`
62
+ ` Path: ${foundCycles.map(s => s.toString ? s.toString() : ('' + s)).join(' -> ')}`));
63
}
64
65
let runningCount = 0;
66
67
const queryProvider = new CachedFunction(async (provider: InlineCompletionsProvider<InlineCompletions>): Promise<InlineSuggestionList | undefined> => {
68
try {
69
runningCount++;
70
if (cancellationTokenSource.token.isCancellationRequested) {
71
return undefined;
72
}
73
74
const yieldsTo = yieldsToGraph.getOutgoing(provider);
75
for (const p of yieldsTo) {
76
// We know there is no cycle, so no recursion here
77
const result = await queryProvider.get(p);
78
if (result) {
79
for (const item of result.inlineSuggestions.items) {
80
if (item.isInlineEdit || typeof item.insertText !== 'string' && item.insertText !== undefined) {
81
return undefined;
82
}
83
if (item.insertText !== undefined) {
84
const t = new TextReplacement(Range.lift(item.range) ?? defaultReplaceRange, item.insertText);
85
if (inlineCompletionIsVisible(t, undefined, model, position)) {
86
return undefined;
87
}
88
}
89
90
// else: inline completion is not visible, so lets not block
91
}
92
}
93
}
94
95
let result: InlineCompletions | null | undefined;
96
const providerStartTime = Date.now();
97
try {
98
result = await provider.provideInlineCompletions(model, position, contextWithUuid, cancellationTokenSource.token);
99
} catch (e) {
100
onUnexpectedExternalError(e);
101
return undefined;
102
}
103
const providerEndTime = Date.now();
104
105
if (!result) {
106
return undefined;
107
}
108
109
const data: InlineSuggestData[] = [];
110
const list = new InlineSuggestionList(result, data, provider);
111
list.addRef();
112
runWhenCancelled(cancellationTokenSource.token, () => {
113
return list.removeRef(cancelReason);
114
});
115
if (cancellationTokenSource.token.isCancellationRequested) {
116
return undefined; // The list is disposed now, so we cannot return the items!
117
}
118
119
for (const item of result.items) {
120
const r = toInlineSuggestData(item, list, defaultReplaceRange, model, languageConfigurationService, contextWithUuid, requestInfo, { startTime: providerStartTime, endTime: providerEndTime });
121
if (ErrorResult.is(r)) {
122
r.logError();
123
continue;
124
}
125
data.push(r);
126
}
127
128
return list;
129
} finally {
130
runningCount--;
131
}
132
});
133
134
const inlineCompletionLists = AsyncIterableProducer.fromPromisesResolveOrder(providers.map(p => queryProvider.get(p))).filter(isDefined);
135
136
return {
137
contextWithUuid,
138
get didAllProvidersReturn() { return runningCount === 0; },
139
lists: inlineCompletionLists,
140
cancelAndDispose: reason => {
141
if (cancelReason !== undefined) {
142
return;
143
}
144
cancelReason = reason;
145
cancellationTokenSource.dispose(true);
146
}
147
};
148
}
149
150
/** If the token is eventually cancelled, this will not leak either. */
151
export function runWhenCancelled(token: CancellationToken, callback: () => void): IDisposable {
152
if (token.isCancellationRequested) {
153
callback();
154
return Disposable.None;
155
} else {
156
const listener = token.onCancellationRequested(() => {
157
listener.dispose();
158
callback();
159
});
160
return { dispose: () => listener.dispose() };
161
}
162
}
163
164
export interface IInlineCompletionProviderResult {
165
get didAllProvidersReturn(): boolean;
166
167
contextWithUuid: InlineCompletionContext;
168
169
cancelAndDispose(reason: InlineCompletionsDisposeReason): void;
170
171
lists: AsyncIterableProducer<InlineSuggestionList>;
172
}
173
174
function toInlineSuggestData(
175
inlineCompletion: InlineCompletion,
176
source: InlineSuggestionList,
177
defaultReplaceRange: Range,
178
textModel: ITextModel,
179
languageConfigurationService: ILanguageConfigurationService | undefined,
180
context: InlineCompletionContext,
181
requestInfo: InlineSuggestRequestInfo,
182
providerRequestInfo: InlineSuggestProviderRequestInfo,
183
): InlineSuggestData | ErrorResult {
184
185
let action: IInlineSuggestDataAction | undefined;
186
const uri = inlineCompletion.uri ? URI.revive(inlineCompletion.uri) : undefined;
187
188
if (inlineCompletion.jumpToPosition !== undefined) {
189
action = {
190
kind: 'jumpTo',
191
position: Position.lift(inlineCompletion.jumpToPosition),
192
uri,
193
};
194
} else if (inlineCompletion.insertText !== undefined) {
195
let insertText: string;
196
let snippetInfo: SnippetInfo | undefined;
197
let range = inlineCompletion.range ? Range.lift(inlineCompletion.range) : defaultReplaceRange;
198
199
if (typeof inlineCompletion.insertText === 'string') {
200
insertText = inlineCompletion.insertText;
201
202
if (languageConfigurationService && inlineCompletion.completeBracketPairs) {
203
insertText = closeBrackets(
204
insertText,
205
range.getStartPosition(),
206
textModel,
207
languageConfigurationService
208
);
209
210
// Modify range depending on if brackets are added or removed
211
const diff = insertText.length - inlineCompletion.insertText.length;
212
if (diff !== 0) {
213
range = new Range(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn + diff);
214
}
215
}
216
217
snippetInfo = undefined;
218
} else if ('snippet' in inlineCompletion.insertText) {
219
const preBracketCompletionLength = inlineCompletion.insertText.snippet.length;
220
221
if (languageConfigurationService && inlineCompletion.completeBracketPairs) {
222
inlineCompletion.insertText.snippet = closeBrackets(
223
inlineCompletion.insertText.snippet,
224
range.getStartPosition(),
225
textModel,
226
languageConfigurationService
227
);
228
229
// Modify range depending on if brackets are added or removed
230
const diff = inlineCompletion.insertText.snippet.length - preBracketCompletionLength;
231
if (diff !== 0) {
232
range = new Range(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn + diff);
233
}
234
}
235
236
const snippet = new SnippetParser().parse(inlineCompletion.insertText.snippet);
237
238
if (snippet.children.length === 1 && snippet.children[0] instanceof Text) {
239
insertText = snippet.children[0].value;
240
snippetInfo = undefined;
241
} else {
242
insertText = snippet.toString();
243
snippetInfo = {
244
snippet: inlineCompletion.insertText.snippet,
245
range: range
246
};
247
}
248
} else {
249
assertNever(inlineCompletion.insertText);
250
}
251
action = {
252
kind: 'edit',
253
range,
254
insertText,
255
snippetInfo,
256
uri,
257
alternativeAction: undefined,
258
};
259
} else {
260
action = undefined;
261
if (!inlineCompletion.hint) {
262
return ErrorResult.message('Inline completion has no insertText, jumpToPosition nor hint.');
263
}
264
}
265
266
return new InlineSuggestData(
267
action,
268
inlineCompletion.hint,
269
inlineCompletion.additionalTextEdits || getReadonlyEmptyArray(),
270
inlineCompletion,
271
source,
272
context,
273
inlineCompletion.isInlineEdit ?? false,
274
inlineCompletion.supportsRename ?? false,
275
requestInfo,
276
providerRequestInfo,
277
inlineCompletion.correlationId,
278
);
279
}
280
281
export type InlineSuggestSku = { type: string; plan: string };
282
283
export type InlineSuggestRequestInfo = {
284
startTime: number;
285
editorType: InlineCompletionEditorType;
286
languageId: string;
287
reason: string;
288
typingInterval: number;
289
typingIntervalCharacterCount: number;
290
availableProviders: ProviderId[];
291
sku: InlineSuggestSku | undefined;
292
};
293
294
export type InlineSuggestProviderRequestInfo = {
295
startTime: number;
296
endTime: number;
297
};
298
299
export type PartialAcceptance = {
300
characters: number;
301
count: number;
302
ratio: number;
303
};
304
305
export type RenameInfo = {
306
createdRename: boolean;
307
duration: number;
308
timedOut?: boolean;
309
droppedOtherEdits?: number;
310
droppedRenameEdits?: number;
311
};
312
313
export type InlineSuggestViewData = {
314
editorType: InlineCompletionEditorType;
315
renderData?: InlineCompletionViewData;
316
viewKind?: InlineCompletionViewKind;
317
};
318
319
export type IInlineSuggestDataAction = IInlineSuggestDataActionEdit | IInlineSuggestDataActionJumpTo;
320
321
export interface IInlineSuggestDataActionEdit {
322
kind: 'edit';
323
range: Range;
324
insertText: string;
325
snippetInfo: SnippetInfo | undefined;
326
uri: URI | undefined;
327
alternativeAction: InlineSuggestAlternativeAction | undefined;
328
}
329
330
export interface IInlineSuggestDataActionJumpTo {
331
kind: 'jumpTo';
332
position: Position;
333
uri: URI | undefined;
334
}
335
336
export class InlineSuggestData {
337
public static createForTest(action: IInlineSuggestDataAction | undefined, targetUri: URI): InlineSuggestData {
338
const mockInlineCompletion: InlineCompletion = {
339
insertText: action?.kind === 'edit' ? action.insertText : '',
340
range: action?.kind === 'edit' ? action.range : undefined,
341
isInlineEdit: true,
342
};
343
const mockProvider: InlineCompletionsProvider = {
344
provideInlineCompletions: () => ({ items: [] }),
345
disposeInlineCompletions: () => { },
346
};
347
const mockSource = new InlineSuggestionList(
348
{ items: [mockInlineCompletion] },
349
[],
350
mockProvider
351
);
352
const mockContext: InlineCompletionContext = {
353
triggerKind: InlineCompletionTriggerKind.Explicit,
354
selectedSuggestionInfo: undefined,
355
requestUuid: 'test-' + Date.now(),
356
earliestShownDateTime: 0,
357
includeInlineCompletions: true,
358
includeInlineEdits: false,
359
requestIssuedDateTime: Date.now(),
360
};
361
const mockRequestInfo: InlineSuggestRequestInfo = {
362
startTime: Date.now(),
363
sku: undefined,
364
editorType: InlineCompletionEditorType.TextEditor,
365
languageId: 'plaintext',
366
availableProviders: [],
367
reason: '',
368
typingInterval: 0,
369
typingIntervalCharacterCount: 0,
370
};
371
const mockProviderRequestInfo: InlineSuggestProviderRequestInfo = {
372
startTime: Date.now(),
373
endTime: Date.now(),
374
};
375
376
return new InlineSuggestData(
377
action,
378
undefined,
379
[],
380
mockInlineCompletion,
381
mockSource,
382
mockContext,
383
true,
384
false,
385
mockRequestInfo,
386
mockProviderRequestInfo,
387
undefined
388
);
389
}
390
391
private _didShow = false;
392
private _timeUntilShown: number | undefined = undefined;
393
private _timeUntilActuallyShown: number | undefined = undefined;
394
private _showStartTime: number | undefined = undefined;
395
private _shownDuration: number = 0;
396
private _showUncollapsedStartTime: number | undefined = undefined;
397
private _showUncollapsedDuration: number = 0;
398
private _notShownReason: string | undefined = undefined;
399
400
private _viewData: InlineSuggestViewData;
401
private _didReportEndOfLife = false;
402
private _lastSetEndOfLifeReason: InlineCompletionEndOfLifeReason | undefined = undefined;
403
private _isPreceeded = false;
404
private _partiallyAcceptedCount = 0;
405
private _partiallyAcceptedSinceOriginal: PartialAcceptance = { characters: 0, ratio: 0, count: 0 };
406
private _renameInfo: RenameInfo | undefined = undefined;
407
private _editKind: InlineSuggestionEditKind | undefined = undefined;
408
409
get action(): IInlineSuggestDataAction | undefined {
410
return this._action;
411
}
412
413
constructor(
414
private _action: IInlineSuggestDataAction | undefined,
415
public readonly hint: IInlineCompletionHint | undefined,
416
public readonly additionalTextEdits: readonly ISingleEditOperation[],
417
public readonly sourceInlineCompletion: InlineCompletion,
418
public readonly source: InlineSuggestionList,
419
public readonly context: InlineCompletionContext,
420
public readonly isInlineEdit: boolean,
421
public readonly supportsRename: boolean,
422
private readonly _requestInfo: InlineSuggestRequestInfo,
423
private readonly _providerRequestInfo: InlineSuggestProviderRequestInfo,
424
private readonly _correlationId: string | undefined,
425
) {
426
this._viewData = { editorType: _requestInfo.editorType };
427
}
428
429
public get showInlineEditMenu() { return this.sourceInlineCompletion.showInlineEditMenu ?? false; }
430
431
public get partialAccepts(): PartialAcceptance { return this._partiallyAcceptedSinceOriginal; }
432
433
434
public async reportInlineEditShown(commandService: ICommandService, updatedInsertText: string, viewKind: InlineCompletionViewKind, viewData: InlineCompletionViewData, editKind: InlineSuggestionEditKind | undefined, timeWhenShown: number): Promise<void> {
435
this.updateShownDuration(viewKind);
436
437
if (this._didShow || this._didReportEndOfLife) {
438
return;
439
}
440
this.addPerformanceMarker('shown');
441
this._didShow = true;
442
this._editKind = editKind;
443
this._viewData.viewKind = viewKind;
444
this._viewData.renderData = viewData;
445
this._timeUntilShown = timeWhenShown - this._requestInfo.startTime;
446
this._timeUntilActuallyShown = Date.now() - this._requestInfo.startTime;
447
448
const editDeltaInfo = new EditDeltaInfo(viewData.lineCountModified, viewData.lineCountOriginal, viewData.characterCountModified, viewData.characterCountOriginal);
449
this.source.provider.handleItemDidShow?.(this.source.inlineSuggestions, this.sourceInlineCompletion, updatedInsertText, editDeltaInfo);
450
451
if (this.sourceInlineCompletion.shownCommand) {
452
await commandService.executeCommand(this.sourceInlineCompletion.shownCommand.id, ...(this.sourceInlineCompletion.shownCommand.arguments || []));
453
}
454
}
455
456
public reportPartialAccept(acceptedCharacters: number, info: PartialAcceptInfo, partialAcceptance: PartialAcceptance) {
457
this._partiallyAcceptedCount++;
458
this._partiallyAcceptedSinceOriginal.characters += partialAcceptance.characters;
459
this._partiallyAcceptedSinceOriginal.ratio = Math.min(this._partiallyAcceptedSinceOriginal.ratio + (1 - this._partiallyAcceptedSinceOriginal.ratio) * partialAcceptance.ratio, 1);
460
this._partiallyAcceptedSinceOriginal.count += partialAcceptance.count;
461
462
this.source.provider.handlePartialAccept?.(
463
this.source.inlineSuggestions,
464
this.sourceInlineCompletion,
465
acceptedCharacters,
466
info
467
);
468
}
469
470
/**
471
* Sends the end of life event to the provider.
472
* If no reason is provided, the last set reason is used.
473
* If no reason was set, the default reason is used.
474
*/
475
public reportEndOfLife(reason?: InlineCompletionEndOfLifeReason): void {
476
if (this._didReportEndOfLife) {
477
return;
478
}
479
this._didReportEndOfLife = true;
480
this.reportInlineEditHidden();
481
482
if (!reason) {
483
reason = this._lastSetEndOfLifeReason ?? { kind: InlineCompletionEndOfLifeReasonKind.Ignored, userTypingDisagreed: false, supersededBy: undefined };
484
}
485
486
// A suggestion can only be "rejected" if it was actually shown to the user.
487
// If the suggestion was never shown, downgrade to "ignored".
488
if (reason.kind === InlineCompletionEndOfLifeReasonKind.Rejected && !this._didShow) {
489
reason = { kind: InlineCompletionEndOfLifeReasonKind.Ignored, userTypingDisagreed: false, supersededBy: undefined };
490
}
491
492
if (reason.kind === InlineCompletionEndOfLifeReasonKind.Rejected && this.source.provider.handleRejection) {
493
this.source.provider.handleRejection(this.source.inlineSuggestions, this.sourceInlineCompletion);
494
}
495
496
if (this.source.provider.handleEndOfLifetime) {
497
const summary: LifetimeSummary = {
498
requestUuid: this.context.requestUuid,
499
correlationId: this._correlationId,
500
selectedSuggestionInfo: !!this.context.selectedSuggestionInfo,
501
partiallyAccepted: this._partiallyAcceptedCount,
502
partiallyAcceptedCountSinceOriginal: this._partiallyAcceptedSinceOriginal.count,
503
partiallyAcceptedRatioSinceOriginal: this._partiallyAcceptedSinceOriginal.ratio,
504
partiallyAcceptedCharactersSinceOriginal: this._partiallyAcceptedSinceOriginal.characters,
505
shown: this._didShow,
506
shownDuration: this._shownDuration,
507
shownDurationUncollapsed: this._showUncollapsedDuration,
508
editKind: this._editKind?.toString(),
509
preceeded: this._isPreceeded,
510
timeUntilShown: this._timeUntilShown,
511
timeUntilActuallyShown: this._timeUntilActuallyShown,
512
timeUntilProviderRequest: this._providerRequestInfo.startTime - this._requestInfo.startTime,
513
timeUntilProviderResponse: this._providerRequestInfo.endTime - this._requestInfo.startTime,
514
editorType: this._viewData.editorType,
515
languageId: this._requestInfo.languageId,
516
requestReason: this._requestInfo.reason,
517
viewKind: this._viewData.viewKind,
518
notShownReason: this._notShownReason,
519
performanceMarkers: this.performance.toString(),
520
renameCreated: this._renameInfo?.createdRename,
521
renameDuration: this._renameInfo?.duration,
522
renameTimedOut: this._renameInfo?.timedOut,
523
renameDroppedOtherEdits: this._renameInfo?.droppedOtherEdits,
524
renameDroppedRenameEdits: this._renameInfo?.droppedRenameEdits,
525
typingInterval: this._requestInfo.typingInterval,
526
typingIntervalCharacterCount: this._requestInfo.typingIntervalCharacterCount,
527
skuPlan: this._requestInfo.sku?.plan,
528
skuType: this._requestInfo.sku?.type,
529
availableProviders: this._requestInfo.availableProviders.map(p => p.toString()).join(','),
530
...this._viewData.renderData?.getData(),
531
};
532
this.source.provider.handleEndOfLifetime(this.source.inlineSuggestions, this.sourceInlineCompletion, reason, summary);
533
}
534
}
535
536
public setIsPreceeded(partialAccepts: PartialAcceptance): void {
537
this._isPreceeded = true;
538
539
if (this._partiallyAcceptedSinceOriginal.characters !== 0 || this._partiallyAcceptedSinceOriginal.ratio !== 0 || this._partiallyAcceptedSinceOriginal.count !== 0) {
540
console.warn('Expected partiallyAcceptedCountSinceOriginal to be { characters: 0, rate: 0, partialAcceptances: 0 } before setIsPreceeded.');
541
}
542
this._partiallyAcceptedSinceOriginal = partialAccepts;
543
}
544
545
public setNotShownReason(reason: string): void {
546
this._notShownReason ??= reason;
547
}
548
549
/**
550
* Sets the end of life reason, but does not send the event to the provider yet.
551
*/
552
public setEndOfLifeReason(reason: InlineCompletionEndOfLifeReason): void {
553
this.reportInlineEditHidden();
554
this._lastSetEndOfLifeReason = reason;
555
}
556
557
private updateShownDuration(viewKind: InlineCompletionViewKind) {
558
const timeNow = Date.now();
559
if (!this._showStartTime) {
560
this._showStartTime = timeNow;
561
}
562
563
const isCollapsed = viewKind === InlineCompletionViewKind.Collapsed;
564
if (!isCollapsed && this._showUncollapsedStartTime === undefined) {
565
this._showUncollapsedStartTime = timeNow;
566
}
567
568
if (isCollapsed && this._showUncollapsedStartTime !== undefined) {
569
this._showUncollapsedDuration += timeNow - this._showUncollapsedStartTime;
570
}
571
}
572
573
private reportInlineEditHidden() {
574
if (this._showStartTime === undefined) {
575
return;
576
}
577
const timeNow = Date.now();
578
this._shownDuration += timeNow - this._showStartTime;
579
this._showStartTime = undefined;
580
581
if (this._showUncollapsedStartTime === undefined) {
582
return;
583
}
584
this._showUncollapsedDuration += timeNow - this._showUncollapsedStartTime;
585
this._showUncollapsedStartTime = undefined;
586
}
587
588
public setRenameProcessingInfo(info: RenameInfo): void {
589
if (this._renameInfo) {
590
throw new BugIndicatingError('Rename info has already been set.');
591
}
592
this._renameInfo = info;
593
}
594
595
public withAction(action: IInlineSuggestDataAction): InlineSuggestData {
596
this._action = action;
597
return this;
598
}
599
600
private performance = new InlineSuggestionsPerformance();
601
public addPerformanceMarker(marker: string): void {
602
this.performance.mark(marker);
603
}
604
}
605
606
class InlineSuggestionsPerformance {
607
private markers: { name: string; timeStamp: number }[] = [];
608
constructor() {
609
this.markers.push({ name: 'start', timeStamp: Date.now() });
610
}
611
612
mark(marker: string): void {
613
this.markers.push({ name: marker, timeStamp: Date.now() });
614
}
615
616
toString(): string {
617
const deltas = [];
618
for (let i = 1; i < this.markers.length; i++) {
619
const delta = this.markers[i].timeStamp - this.markers[i - 1].timeStamp;
620
deltas.push({ [this.markers[i].name]: delta });
621
}
622
return JSON.stringify(deltas);
623
}
624
}
625
626
export interface SnippetInfo {
627
snippet: string;
628
/* Could be different than the main range */
629
range: Range;
630
}
631
632
export enum InlineCompletionEditorType {
633
TextEditor = 'textEditor',
634
DiffEditor = 'diffEditor',
635
Notebook = 'notebook',
636
}
637
638
/**
639
* A ref counted pointer to the computed `InlineCompletions` and the `InlineCompletionsProvider` that
640
* computed them.
641
*/
642
export class InlineSuggestionList {
643
private refCount = 0;
644
constructor(
645
public readonly inlineSuggestions: InlineCompletions,
646
public readonly inlineSuggestionsData: readonly InlineSuggestData[],
647
public readonly provider: InlineCompletionsProvider,
648
) { }
649
650
addRef(): void {
651
this.refCount++;
652
}
653
654
removeRef(reason: InlineCompletionsDisposeReason = { kind: 'other' }): void {
655
this.refCount--;
656
if (this.refCount === 0) {
657
for (const item of this.inlineSuggestionsData) {
658
// Fallback if it has not been called before
659
item.reportEndOfLife();
660
}
661
this.provider.disposeInlineCompletions(this.inlineSuggestions, reason);
662
}
663
}
664
}
665
666
function getDefaultRange(position: Position, model: ITextModel): Range {
667
const word = model.getWordAtPosition(position);
668
const maxColumn = model.getLineMaxColumn(position.lineNumber);
669
// By default, always replace up until the end of the current line.
670
// This default might be subject to change!
671
return word
672
? new Range(position.lineNumber, word.startColumn, position.lineNumber, maxColumn)
673
: Range.fromPositions(position, position.with(undefined, maxColumn));
674
}
675
676
function closeBrackets(text: string, position: Position, model: ITextModel, languageConfigurationService: ILanguageConfigurationService): string {
677
const currentLine = model.getLineContent(position.lineNumber);
678
const edit = StringReplacement.replace(new OffsetRange(position.column - 1, currentLine.length), text);
679
680
const proposedLineTokens = model.tokenization.tokenizeLinesAt(position.lineNumber, [edit.replace(currentLine)]);
681
const textTokens = proposedLineTokens?.[0].sliceZeroCopy(edit.getRangeAfterReplace());
682
if (!textTokens) {
683
return text;
684
}
685
686
const fixedText = fixBracketsInLine(textTokens, languageConfigurationService);
687
return fixedText;
688
}
689
690