Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts
5222 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 { VSBuffer } from '../../../base/common/buffer.js';
7
import { CancellationToken } from '../../../base/common/cancellation.js';
8
import { createStringDataTransferItem, IReadonlyVSDataTransfer, VSDataTransfer } from '../../../base/common/dataTransfer.js';
9
import { CancellationError } from '../../../base/common/errors.js';
10
import { Emitter, Event } from '../../../base/common/event.js';
11
import { HierarchicalKind } from '../../../base/common/hierarchicalKind.js';
12
import { combinedDisposable, Disposable, DisposableMap, toDisposable } from '../../../base/common/lifecycle.js';
13
import { ResourceMap } from '../../../base/common/map.js';
14
import { revive } from '../../../base/common/marshalling.js';
15
import { mixin } from '../../../base/common/objects.js';
16
import { URI } from '../../../base/common/uri.js';
17
import { Position as EditorPosition, IPosition } from '../../../editor/common/core/position.js';
18
import { Range as EditorRange, IRange } from '../../../editor/common/core/range.js';
19
import { Selection } from '../../../editor/common/core/selection.js';
20
import * as languages from '../../../editor/common/languages.js';
21
import { ILanguageService } from '../../../editor/common/languages/language.js';
22
import { IndentationRule, LanguageConfiguration, OnEnterRule } from '../../../editor/common/languages/languageConfiguration.js';
23
import { ILanguageConfigurationService } from '../../../editor/common/languages/languageConfigurationRegistry.js';
24
import { ITextModel } from '../../../editor/common/model.js';
25
import { ILanguageFeaturesService } from '../../../editor/common/services/languageFeatures.js';
26
import { decodeSemanticTokensDto } from '../../../editor/common/services/semanticTokensDto.js';
27
import { ExtensionIdentifier } from '../../../platform/extensions/common/extensions.js';
28
import { IUriIdentityService } from '../../../platform/uriIdentity/common/uriIdentity.js';
29
import { reviveWorkspaceEditDto } from './mainThreadBulkEdits.js';
30
import * as typeConvert from '../common/extHostTypeConverters.js';
31
import { DataTransferFileCache } from '../common/shared/dataTransferCache.js';
32
import * as callh from '../../contrib/callHierarchy/common/callHierarchy.js';
33
import * as search from '../../contrib/search/common/search.js';
34
import * as typeh from '../../contrib/typeHierarchy/common/typeHierarchy.js';
35
import { extHostNamedCustomer, IExtHostContext } from '../../services/extensions/common/extHostCustomers.js';
36
import { ExtHostContext, ExtHostLanguageFeaturesShape, HoverWithId, ICallHierarchyItemDto, ICodeActionDto, ICodeActionProviderMetadataDto, IdentifiableInlineCompletion, IdentifiableInlineCompletions, IDocumentDropEditDto, IDocumentDropEditProviderMetadata, IDocumentFilterDto, IIndentationRuleDto, IInlayHintDto, IInlineCompletionChangeHintDto, IInlineCompletionModelInfoDto, ILanguageConfigurationDto, ILanguageWordDefinitionDto, ILinkDto, ILocationDto, ILocationLinkDto, IOnEnterRuleDto, IPasteEditDto, IPasteEditProviderMetadataDto, IRegExpDto, ISignatureHelpProviderMetadataDto, ISuggestDataDto, ISuggestDataDtoField, ISuggestResultDtoField, ITypeHierarchyItemDto, IWorkspaceSymbolDto, MainContext, MainThreadLanguageFeaturesShape } from '../common/extHost.protocol.js';
37
import { InlineCompletionEndOfLifeReasonKind } from '../common/extHostTypes.js';
38
import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js';
39
import { DataChannelForwardingTelemetryService, forwardToChannelIf, isCopilotLikeExtension } from '../../../platform/dataChannel/browser/forwardingTelemetryService.js';
40
import { IAiEditTelemetryService } from '../../contrib/editTelemetry/browser/telemetry/aiEditTelemetry/aiEditTelemetryService.js';
41
import { EditDeltaInfo } from '../../../editor/common/textModelEditSource.js';
42
import { IInlineCompletionsUnificationService } from '../../services/inlineCompletions/common/inlineCompletionsUnification.js';
43
import { InlineCompletionEndOfLifeEvent, sendInlineCompletionsEndOfLifeTelemetry } from '../../../editor/contrib/inlineCompletions/browser/telemetry.js';
44
45
@extHostNamedCustomer(MainContext.MainThreadLanguageFeatures)
46
export class MainThreadLanguageFeatures extends Disposable implements MainThreadLanguageFeaturesShape {
47
48
private readonly _proxy: ExtHostLanguageFeaturesShape;
49
private readonly _registrations = this._register(new DisposableMap<number>());
50
51
constructor(
52
extHostContext: IExtHostContext,
53
@ILanguageService private readonly _languageService: ILanguageService,
54
@ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService,
55
@ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,
56
@IUriIdentityService private readonly _uriIdentService: IUriIdentityService,
57
@IInstantiationService private readonly _instantiationService: IInstantiationService,
58
@IInlineCompletionsUnificationService private readonly _inlineCompletionsUnificationService: IInlineCompletionsUnificationService,
59
) {
60
super();
61
62
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostLanguageFeatures);
63
64
if (this._languageService) {
65
const updateAllWordDefinitions = () => {
66
const wordDefinitionDtos: ILanguageWordDefinitionDto[] = [];
67
for (const languageId of _languageService.getRegisteredLanguageIds()) {
68
const wordDefinition = this._languageConfigurationService.getLanguageConfiguration(languageId).getWordDefinition();
69
wordDefinitionDtos.push({
70
languageId: languageId,
71
regexSource: wordDefinition.source,
72
regexFlags: wordDefinition.flags
73
});
74
}
75
this._proxy.$setWordDefinitions(wordDefinitionDtos);
76
};
77
this._register(this._languageConfigurationService.onDidChange((e) => {
78
if (!e.languageId) {
79
updateAllWordDefinitions();
80
} else {
81
const wordDefinition = this._languageConfigurationService.getLanguageConfiguration(e.languageId).getWordDefinition();
82
this._proxy.$setWordDefinitions([{
83
languageId: e.languageId,
84
regexSource: wordDefinition.source,
85
regexFlags: wordDefinition.flags
86
}]);
87
}
88
}));
89
updateAllWordDefinitions();
90
}
91
92
if (this._inlineCompletionsUnificationService) {
93
this._register(this._inlineCompletionsUnificationService.onDidStateChange(() => {
94
this._proxy.$acceptInlineCompletionsUnificationState(this._inlineCompletionsUnificationService.state);
95
}));
96
this._proxy.$acceptInlineCompletionsUnificationState(this._inlineCompletionsUnificationService.state);
97
}
98
}
99
100
$unregister(handle: number): void {
101
this._registrations.deleteAndDispose(handle);
102
}
103
104
//#region --- revive functions
105
106
private static _reviveLocationDto(data?: ILocationDto): languages.Location;
107
private static _reviveLocationDto(data?: ILocationDto[]): languages.Location[];
108
private static _reviveLocationDto(data: ILocationDto | ILocationDto[] | undefined): languages.Location | languages.Location[] | undefined {
109
if (!data) {
110
return data;
111
} else if (Array.isArray(data)) {
112
data.forEach(l => MainThreadLanguageFeatures._reviveLocationDto(l));
113
return <languages.Location[]>data;
114
} else {
115
data.uri = URI.revive(data.uri);
116
return <languages.Location>data;
117
}
118
}
119
120
private static _reviveLocationLinkDto(data: ILocationLinkDto): languages.LocationLink;
121
private static _reviveLocationLinkDto(data: ILocationLinkDto[]): languages.LocationLink[];
122
private static _reviveLocationLinkDto(data: ILocationLinkDto | ILocationLinkDto[]): languages.LocationLink | languages.LocationLink[] {
123
if (!data) {
124
return <languages.LocationLink>data;
125
} else if (Array.isArray(data)) {
126
data.forEach(l => MainThreadLanguageFeatures._reviveLocationLinkDto(l));
127
return <languages.LocationLink[]>data;
128
} else {
129
data.uri = URI.revive(data.uri);
130
return <languages.LocationLink>data;
131
}
132
}
133
134
private static _reviveWorkspaceSymbolDto(data: IWorkspaceSymbolDto): search.IWorkspaceSymbol;
135
private static _reviveWorkspaceSymbolDto(data: IWorkspaceSymbolDto[]): search.IWorkspaceSymbol[];
136
private static _reviveWorkspaceSymbolDto(data: undefined): undefined;
137
private static _reviveWorkspaceSymbolDto(data: IWorkspaceSymbolDto | IWorkspaceSymbolDto[] | undefined): search.IWorkspaceSymbol | search.IWorkspaceSymbol[] | undefined {
138
if (!data) {
139
return data;
140
} else if (Array.isArray(data)) {
141
data.forEach(MainThreadLanguageFeatures._reviveWorkspaceSymbolDto);
142
return <search.IWorkspaceSymbol[]>data;
143
} else {
144
data.location = MainThreadLanguageFeatures._reviveLocationDto(data.location);
145
return <search.IWorkspaceSymbol>data;
146
}
147
}
148
149
private static _reviveCodeActionDto(data: ReadonlyArray<ICodeActionDto>, uriIdentService: IUriIdentityService): languages.CodeAction[] {
150
data?.forEach(code => reviveWorkspaceEditDto(code.edit, uriIdentService));
151
return <languages.CodeAction[]>data;
152
}
153
154
private static _reviveLinkDTO(data: ILinkDto): languages.ILink {
155
if (data.url && typeof data.url !== 'string') {
156
data.url = URI.revive(data.url);
157
}
158
return <languages.ILink>data;
159
}
160
161
private static _reviveCallHierarchyItemDto(data: ICallHierarchyItemDto | undefined): callh.CallHierarchyItem {
162
if (data) {
163
data.uri = URI.revive(data.uri);
164
}
165
return data as callh.CallHierarchyItem;
166
}
167
168
private static _reviveTypeHierarchyItemDto(data: ITypeHierarchyItemDto | undefined): typeh.TypeHierarchyItem {
169
if (data) {
170
data.uri = URI.revive(data.uri);
171
}
172
return data as typeh.TypeHierarchyItem;
173
}
174
175
//#endregion
176
177
// --- outline
178
179
$registerDocumentSymbolProvider(handle: number, selector: IDocumentFilterDto[], displayName: string): void {
180
this._registrations.set(handle, this._languageFeaturesService.documentSymbolProvider.register(selector, {
181
displayName,
182
provideDocumentSymbols: (model: ITextModel, token: CancellationToken): Promise<languages.DocumentSymbol[] | undefined> => {
183
return this._proxy.$provideDocumentSymbols(handle, model.uri, token);
184
}
185
}));
186
}
187
188
// --- code lens
189
190
$registerCodeLensSupport(handle: number, selector: IDocumentFilterDto[], eventHandle: number | undefined): void {
191
192
const provider: languages.CodeLensProvider = {
193
provideCodeLenses: async (model: ITextModel, token: CancellationToken): Promise<languages.CodeLensList | undefined> => {
194
const listDto = await this._proxy.$provideCodeLenses(handle, model.uri, token);
195
if (!listDto) {
196
return undefined;
197
}
198
return {
199
lenses: listDto.lenses,
200
dispose: () => listDto.cacheId && this._proxy.$releaseCodeLenses(handle, listDto.cacheId)
201
};
202
},
203
resolveCodeLens: async (model: ITextModel, codeLens: languages.CodeLens, token: CancellationToken): Promise<languages.CodeLens | undefined> => {
204
const result = await this._proxy.$resolveCodeLens(handle, codeLens, token);
205
if (!result || token.isCancellationRequested) {
206
return undefined;
207
}
208
209
return {
210
...result,
211
range: model.validateRange(result.range),
212
};
213
}
214
};
215
216
if (typeof eventHandle === 'number') {
217
const emitter = new Emitter<languages.CodeLensProvider>();
218
this._registrations.set(eventHandle, emitter);
219
provider.onDidChange = emitter.event;
220
}
221
222
this._registrations.set(handle, this._languageFeaturesService.codeLensProvider.register(selector, provider));
223
}
224
225
$emitCodeLensEvent(eventHandle: number, event?: unknown): void {
226
const obj = this._registrations.get(eventHandle);
227
if (obj instanceof Emitter) {
228
obj.fire(event);
229
}
230
}
231
232
// --- declaration
233
234
$registerDefinitionSupport(handle: number, selector: IDocumentFilterDto[]): void {
235
this._registrations.set(handle, this._languageFeaturesService.definitionProvider.register(selector, {
236
provideDefinition: (model, position, token): Promise<languages.LocationLink[]> => {
237
return this._proxy.$provideDefinition(handle, model.uri, position, token).then(MainThreadLanguageFeatures._reviveLocationLinkDto);
238
}
239
}));
240
}
241
242
$registerDeclarationSupport(handle: number, selector: IDocumentFilterDto[]): void {
243
this._registrations.set(handle, this._languageFeaturesService.declarationProvider.register(selector, {
244
provideDeclaration: (model, position, token) => {
245
return this._proxy.$provideDeclaration(handle, model.uri, position, token).then(MainThreadLanguageFeatures._reviveLocationLinkDto);
246
}
247
}));
248
}
249
250
$registerImplementationSupport(handle: number, selector: IDocumentFilterDto[]): void {
251
this._registrations.set(handle, this._languageFeaturesService.implementationProvider.register(selector, {
252
provideImplementation: (model, position, token): Promise<languages.LocationLink[]> => {
253
return this._proxy.$provideImplementation(handle, model.uri, position, token).then(MainThreadLanguageFeatures._reviveLocationLinkDto);
254
}
255
}));
256
}
257
258
$registerTypeDefinitionSupport(handle: number, selector: IDocumentFilterDto[]): void {
259
this._registrations.set(handle, this._languageFeaturesService.typeDefinitionProvider.register(selector, {
260
provideTypeDefinition: (model, position, token): Promise<languages.LocationLink[]> => {
261
return this._proxy.$provideTypeDefinition(handle, model.uri, position, token).then(MainThreadLanguageFeatures._reviveLocationLinkDto);
262
}
263
}));
264
}
265
266
// --- extra info
267
268
$registerHoverProvider(handle: number, selector: IDocumentFilterDto[]): void {
269
/*
270
const hoverFinalizationRegistry = new FinalizationRegistry((hoverId: number) => {
271
this._proxy.$releaseHover(handle, hoverId);
272
});
273
*/
274
this._registrations.set(handle, this._languageFeaturesService.hoverProvider.register(selector, {
275
provideHover: async (model: ITextModel, position: EditorPosition, token: CancellationToken, context?: languages.HoverContext<HoverWithId>): Promise<HoverWithId | undefined> => {
276
const serializedContext: languages.HoverContext<{ id: number }> = {
277
verbosityRequest: context?.verbosityRequest ? {
278
verbosityDelta: context.verbosityRequest.verbosityDelta,
279
previousHover: { id: context.verbosityRequest.previousHover.id }
280
} : undefined,
281
};
282
const hover = await this._proxy.$provideHover(handle, model.uri, position, serializedContext, token);
283
// hoverFinalizationRegistry.register(hover, hover.id);
284
return hover;
285
}
286
}));
287
}
288
289
// --- debug hover
290
291
$registerEvaluatableExpressionProvider(handle: number, selector: IDocumentFilterDto[]): void {
292
this._registrations.set(handle, this._languageFeaturesService.evaluatableExpressionProvider.register(selector, {
293
provideEvaluatableExpression: (model: ITextModel, position: EditorPosition, token: CancellationToken): Promise<languages.EvaluatableExpression | undefined> => {
294
return this._proxy.$provideEvaluatableExpression(handle, model.uri, position, token);
295
}
296
}));
297
}
298
299
// --- inline values
300
301
$registerInlineValuesProvider(handle: number, selector: IDocumentFilterDto[], eventHandle: number | undefined): void {
302
const provider: languages.InlineValuesProvider = {
303
provideInlineValues: (model: ITextModel, viewPort: EditorRange, context: languages.InlineValueContext, token: CancellationToken): Promise<languages.InlineValue[] | undefined> => {
304
return this._proxy.$provideInlineValues(handle, model.uri, viewPort, context, token);
305
}
306
};
307
308
if (typeof eventHandle === 'number') {
309
const emitter = new Emitter<void>();
310
this._registrations.set(eventHandle, emitter);
311
provider.onDidChangeInlineValues = emitter.event;
312
}
313
314
this._registrations.set(handle, this._languageFeaturesService.inlineValuesProvider.register(selector, provider));
315
}
316
317
$emitInlineValuesEvent(eventHandle: number, event?: unknown): void {
318
const obj = this._registrations.get(eventHandle);
319
if (obj instanceof Emitter) {
320
obj.fire(event);
321
}
322
}
323
324
// --- occurrences
325
326
$registerDocumentHighlightProvider(handle: number, selector: IDocumentFilterDto[]): void {
327
this._registrations.set(handle, this._languageFeaturesService.documentHighlightProvider.register(selector, {
328
provideDocumentHighlights: (model: ITextModel, position: EditorPosition, token: CancellationToken): Promise<languages.DocumentHighlight[] | undefined> => {
329
return this._proxy.$provideDocumentHighlights(handle, model.uri, position, token);
330
}
331
}));
332
}
333
334
$registerMultiDocumentHighlightProvider(handle: number, selector: IDocumentFilterDto[]): void {
335
this._registrations.set(handle, this._languageFeaturesService.multiDocumentHighlightProvider.register(selector, {
336
selector: selector,
337
provideMultiDocumentHighlights: (model: ITextModel, position: EditorPosition, otherModels: ITextModel[], token: CancellationToken): Promise<Map<URI, languages.DocumentHighlight[]> | undefined> => {
338
return this._proxy.$provideMultiDocumentHighlights(handle, model.uri, position, otherModels.map(model => model.uri), token).then(dto => {
339
// dto should be non-null + non-undefined
340
// dto length of 0 is valid, just no highlights, pass this through.
341
if (dto === undefined || dto === null) {
342
return undefined;
343
}
344
const result = new ResourceMap<languages.DocumentHighlight[]>();
345
dto?.forEach(value => {
346
// check if the URI exists already, if so, combine the highlights, otherwise create a new entry
347
const uri = URI.revive(value.uri);
348
if (result.has(uri)) {
349
result.get(uri)!.push(...value.highlights);
350
} else {
351
result.set(uri, value.highlights);
352
}
353
});
354
return result;
355
});
356
}
357
}));
358
}
359
360
// --- linked editing
361
362
$registerLinkedEditingRangeProvider(handle: number, selector: IDocumentFilterDto[]): void {
363
this._registrations.set(handle, this._languageFeaturesService.linkedEditingRangeProvider.register(selector, {
364
provideLinkedEditingRanges: async (model: ITextModel, position: EditorPosition, token: CancellationToken): Promise<languages.LinkedEditingRanges | undefined> => {
365
const res = await this._proxy.$provideLinkedEditingRanges(handle, model.uri, position, token);
366
if (res) {
367
return {
368
ranges: res.ranges,
369
wordPattern: res.wordPattern ? MainThreadLanguageFeatures._reviveRegExp(res.wordPattern) : undefined
370
};
371
}
372
return undefined;
373
}
374
}));
375
}
376
377
// --- references
378
379
$registerReferenceSupport(handle: number, selector: IDocumentFilterDto[]): void {
380
this._registrations.set(handle, this._languageFeaturesService.referenceProvider.register(selector, {
381
provideReferences: (model: ITextModel, position: EditorPosition, context: languages.ReferenceContext, token: CancellationToken): Promise<languages.Location[]> => {
382
return this._proxy.$provideReferences(handle, model.uri, position, context, token).then(MainThreadLanguageFeatures._reviveLocationDto);
383
}
384
}));
385
}
386
387
// --- code actions
388
389
$registerCodeActionSupport(handle: number, selector: IDocumentFilterDto[], metadata: ICodeActionProviderMetadataDto, displayName: string, extensionId: string, supportsResolve: boolean): void {
390
const provider: languages.CodeActionProvider = {
391
provideCodeActions: async (model: ITextModel, rangeOrSelection: EditorRange | Selection, context: languages.CodeActionContext, token: CancellationToken): Promise<languages.CodeActionList | undefined> => {
392
const listDto = await this._proxy.$provideCodeActions(handle, model.uri, rangeOrSelection, context, token);
393
if (!listDto) {
394
return undefined;
395
}
396
return {
397
actions: MainThreadLanguageFeatures._reviveCodeActionDto(listDto.actions, this._uriIdentService),
398
dispose: () => {
399
if (typeof listDto.cacheId === 'number') {
400
this._proxy.$releaseCodeActions(handle, listDto.cacheId);
401
}
402
}
403
};
404
},
405
providedCodeActionKinds: metadata.providedKinds,
406
documentation: metadata.documentation,
407
displayName,
408
extensionId,
409
};
410
411
if (supportsResolve) {
412
provider.resolveCodeAction = async (codeAction: languages.CodeAction, token: CancellationToken): Promise<languages.CodeAction> => {
413
const resolved = await this._proxy.$resolveCodeAction(handle, (<ICodeActionDto>codeAction).cacheId!, token);
414
if (resolved.edit) {
415
codeAction.edit = reviveWorkspaceEditDto(resolved.edit, this._uriIdentService);
416
}
417
418
if (resolved.command) {
419
codeAction.command = resolved.command;
420
}
421
422
return codeAction;
423
};
424
}
425
426
this._registrations.set(handle, this._languageFeaturesService.codeActionProvider.register(selector, provider));
427
}
428
429
// --- copy paste action provider
430
431
private readonly _pasteEditProviders = new Map<number, MainThreadPasteEditProvider>();
432
433
$registerPasteEditProvider(handle: number, selector: IDocumentFilterDto[], metadata: IPasteEditProviderMetadataDto): void {
434
const provider = new MainThreadPasteEditProvider(handle, this._proxy, metadata, this._uriIdentService);
435
this._pasteEditProviders.set(handle, provider);
436
this._registrations.set(handle, combinedDisposable(
437
this._languageFeaturesService.documentPasteEditProvider.register(selector, provider),
438
toDisposable(() => this._pasteEditProviders.delete(handle)),
439
));
440
}
441
442
$resolvePasteFileData(handle: number, requestId: number, dataId: string): Promise<VSBuffer> {
443
const provider = this._pasteEditProviders.get(handle);
444
if (!provider) {
445
throw new Error('Could not find provider');
446
}
447
return provider.resolveFileData(requestId, dataId);
448
}
449
450
// --- formatting
451
452
$registerDocumentFormattingSupport(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier, displayName: string): void {
453
this._registrations.set(handle, this._languageFeaturesService.documentFormattingEditProvider.register(selector, {
454
extensionId,
455
displayName,
456
provideDocumentFormattingEdits: (model: ITextModel, options: languages.FormattingOptions, token: CancellationToken): Promise<languages.TextEdit[] | undefined> => {
457
return this._proxy.$provideDocumentFormattingEdits(handle, model.uri, options, token);
458
}
459
}));
460
}
461
462
$registerRangeFormattingSupport(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier, displayName: string, supportsRanges: boolean): void {
463
this._registrations.set(handle, this._languageFeaturesService.documentRangeFormattingEditProvider.register(selector, {
464
extensionId,
465
displayName,
466
provideDocumentRangeFormattingEdits: (model: ITextModel, range: EditorRange, options: languages.FormattingOptions, token: CancellationToken): Promise<languages.TextEdit[] | undefined> => {
467
return this._proxy.$provideDocumentRangeFormattingEdits(handle, model.uri, range, options, token);
468
},
469
provideDocumentRangesFormattingEdits: !supportsRanges
470
? undefined
471
: (model, ranges, options, token) => {
472
return this._proxy.$provideDocumentRangesFormattingEdits(handle, model.uri, ranges, options, token);
473
},
474
}));
475
}
476
477
$registerOnTypeFormattingSupport(handle: number, selector: IDocumentFilterDto[], autoFormatTriggerCharacters: string[], extensionId: ExtensionIdentifier): void {
478
this._registrations.set(handle, this._languageFeaturesService.onTypeFormattingEditProvider.register(selector, {
479
extensionId,
480
autoFormatTriggerCharacters,
481
provideOnTypeFormattingEdits: (model: ITextModel, position: EditorPosition, ch: string, options: languages.FormattingOptions, token: CancellationToken): Promise<languages.TextEdit[] | undefined> => {
482
return this._proxy.$provideOnTypeFormattingEdits(handle, model.uri, position, ch, options, token);
483
}
484
}));
485
}
486
487
// --- navigate type
488
489
$registerNavigateTypeSupport(handle: number, supportsResolve: boolean): void {
490
let lastResultId: number | undefined;
491
492
const provider: search.IWorkspaceSymbolProvider = {
493
provideWorkspaceSymbols: async (search: string, token: CancellationToken): Promise<search.IWorkspaceSymbol[]> => {
494
const result = await this._proxy.$provideWorkspaceSymbols(handle, search, token);
495
if (lastResultId !== undefined) {
496
this._proxy.$releaseWorkspaceSymbols(handle, lastResultId);
497
}
498
lastResultId = result.cacheId;
499
return MainThreadLanguageFeatures._reviveWorkspaceSymbolDto(result.symbols);
500
}
501
};
502
if (supportsResolve) {
503
provider.resolveWorkspaceSymbol = async (item: search.IWorkspaceSymbol, token: CancellationToken): Promise<search.IWorkspaceSymbol | undefined> => {
504
const resolvedItem = await this._proxy.$resolveWorkspaceSymbol(handle, item, token);
505
return resolvedItem && MainThreadLanguageFeatures._reviveWorkspaceSymbolDto(resolvedItem);
506
};
507
}
508
this._registrations.set(handle, search.WorkspaceSymbolProviderRegistry.register(provider));
509
}
510
511
// --- rename
512
513
$registerRenameSupport(handle: number, selector: IDocumentFilterDto[], supportResolveLocation: boolean): void {
514
this._registrations.set(handle, this._languageFeaturesService.renameProvider.register(selector, {
515
provideRenameEdits: (model: ITextModel, position: EditorPosition, newName: string, token: CancellationToken) => {
516
return this._proxy.$provideRenameEdits(handle, model.uri, position, newName, token).then(data => reviveWorkspaceEditDto(data, this._uriIdentService));
517
},
518
resolveRenameLocation: supportResolveLocation
519
? (model: ITextModel, position: EditorPosition, token: CancellationToken): Promise<languages.RenameLocation | undefined> => this._proxy.$resolveRenameLocation(handle, model.uri, position, token)
520
: undefined
521
}));
522
}
523
524
$registerNewSymbolNamesProvider(handle: number, selector: IDocumentFilterDto[]): void {
525
this._registrations.set(handle, this._languageFeaturesService.newSymbolNamesProvider.register(selector, {
526
supportsAutomaticNewSymbolNamesTriggerKind: this._proxy.$supportsAutomaticNewSymbolNamesTriggerKind(handle),
527
provideNewSymbolNames: (model: ITextModel, range: IRange, triggerKind: languages.NewSymbolNameTriggerKind, token: CancellationToken): Promise<languages.NewSymbolName[] | undefined> => {
528
return this._proxy.$provideNewSymbolNames(handle, model.uri, range, triggerKind, token);
529
}
530
} satisfies languages.NewSymbolNamesProvider));
531
}
532
533
// --- semantic tokens
534
535
$registerDocumentSemanticTokensProvider(handle: number, selector: IDocumentFilterDto[], legend: languages.SemanticTokensLegend, eventHandle: number | undefined): void {
536
let event: Event<void> | undefined = undefined;
537
if (typeof eventHandle === 'number') {
538
const emitter = new Emitter<void>();
539
this._registrations.set(eventHandle, emitter);
540
event = emitter.event;
541
}
542
this._registrations.set(handle, this._languageFeaturesService.documentSemanticTokensProvider.register(selector, new MainThreadDocumentSemanticTokensProvider(this._proxy, handle, legend, event)));
543
}
544
545
$emitDocumentSemanticTokensEvent(eventHandle: number): void {
546
const obj = this._registrations.get(eventHandle);
547
if (obj instanceof Emitter) {
548
obj.fire(undefined);
549
}
550
}
551
552
$emitDocumentRangeSemanticTokensEvent(eventHandle: number): void {
553
const obj = this._registrations.get(eventHandle);
554
if (obj instanceof Emitter) {
555
obj.fire(undefined);
556
}
557
}
558
559
$registerDocumentRangeSemanticTokensProvider(handle: number, selector: IDocumentFilterDto[], legend: languages.SemanticTokensLegend, eventHandle: number | undefined): void {
560
let event: Event<void> | undefined = undefined;
561
if (typeof eventHandle === 'number') {
562
const emitter = new Emitter<void>();
563
this._registrations.set(eventHandle, emitter);
564
event = emitter.event;
565
}
566
this._registrations.set(handle, this._languageFeaturesService.documentRangeSemanticTokensProvider.register(selector, new MainThreadDocumentRangeSemanticTokensProvider(this._proxy, handle, legend, event)));
567
}
568
569
// --- suggest
570
571
private static _inflateSuggestDto(defaultRange: IRange | { insert: IRange; replace: IRange }, data: ISuggestDataDto, extensionId: ExtensionIdentifier): languages.CompletionItem {
572
573
const label = data[ISuggestDataDtoField.label];
574
const commandId = data[ISuggestDataDtoField.commandId];
575
const commandIdent = data[ISuggestDataDtoField.commandIdent];
576
const commitChars = data[ISuggestDataDtoField.commitCharacters];
577
578
type IdentCommand = languages.Command & { $ident: string | undefined };
579
580
let command: IdentCommand | undefined;
581
if (commandId) {
582
command = {
583
$ident: commandIdent,
584
id: commandId,
585
title: '',
586
arguments: commandIdent ? [commandIdent] : data[ISuggestDataDtoField.commandArguments], // Automatically fill in ident as first argument
587
};
588
}
589
590
return {
591
label,
592
extensionId,
593
kind: data[ISuggestDataDtoField.kind] ?? languages.CompletionItemKind.Property,
594
tags: data[ISuggestDataDtoField.kindModifier],
595
detail: data[ISuggestDataDtoField.detail],
596
documentation: data[ISuggestDataDtoField.documentation],
597
sortText: data[ISuggestDataDtoField.sortText],
598
filterText: data[ISuggestDataDtoField.filterText],
599
preselect: data[ISuggestDataDtoField.preselect],
600
insertText: data[ISuggestDataDtoField.insertText] ?? (typeof label === 'string' ? label : label.label),
601
range: data[ISuggestDataDtoField.range] ?? defaultRange,
602
insertTextRules: data[ISuggestDataDtoField.insertTextRules],
603
commitCharacters: commitChars ? Array.from(commitChars) : undefined,
604
additionalTextEdits: data[ISuggestDataDtoField.additionalTextEdits],
605
command,
606
// not-standard
607
_id: data.x,
608
};
609
}
610
611
$registerCompletionsProvider(handle: number, selector: IDocumentFilterDto[], triggerCharacters: string[], supportsResolveDetails: boolean, extensionId: ExtensionIdentifier): void {
612
const provider: languages.CompletionItemProvider = {
613
triggerCharacters,
614
_debugDisplayName: `${extensionId.value}(${triggerCharacters.join('')})`,
615
provideCompletionItems: async (model: ITextModel, position: EditorPosition, context: languages.CompletionContext, token: CancellationToken): Promise<languages.CompletionList | undefined> => {
616
const result = await this._proxy.$provideCompletionItems(handle, model.uri, position, context, token);
617
if (!result) {
618
return result;
619
}
620
return {
621
suggestions: result[ISuggestResultDtoField.completions].map(d => MainThreadLanguageFeatures._inflateSuggestDto(result[ISuggestResultDtoField.defaultRanges], d, extensionId)),
622
incomplete: result[ISuggestResultDtoField.isIncomplete] || false,
623
duration: result[ISuggestResultDtoField.duration],
624
dispose: () => {
625
if (typeof result.x === 'number') {
626
this._proxy.$releaseCompletionItems(handle, result.x);
627
}
628
}
629
};
630
}
631
};
632
if (supportsResolveDetails) {
633
provider.resolveCompletionItem = (suggestion, token) => {
634
return this._proxy.$resolveCompletionItem(handle, suggestion._id!, token).then(result => {
635
if (!result) {
636
return suggestion;
637
}
638
639
const newSuggestion = MainThreadLanguageFeatures._inflateSuggestDto(suggestion.range, result, extensionId);
640
return mixin(suggestion, newSuggestion, true);
641
});
642
};
643
}
644
this._registrations.set(handle, this._languageFeaturesService.completionProvider.register(selector, provider));
645
}
646
647
$registerInlineCompletionsSupport(
648
handle: number,
649
selector: IDocumentFilterDto[],
650
supportsHandleEvents: boolean,
651
extensionId: string,
652
extensionVersion: string,
653
groupId: string | undefined,
654
yieldsToExtensionIds: string[],
655
displayName: string | undefined,
656
debounceDelayMs: number | undefined,
657
excludesExtensionIds: string[],
658
supportsOnDidChange: boolean,
659
supportsSetModelId: boolean,
660
initialModelInfo: IInlineCompletionModelInfoDto | undefined,
661
supportsOnDidChangeModelInfo: boolean,
662
): void {
663
const providerId = new languages.ProviderId(extensionId, extensionVersion, groupId);
664
665
const provider = this._instantiationService.createInstance(
666
ExtensionBackedInlineCompletionsProvider,
667
handle,
668
groupId ?? extensionId,
669
providerId,
670
yieldsToExtensionIds,
671
excludesExtensionIds,
672
debounceDelayMs,
673
displayName,
674
initialModelInfo,
675
supportsHandleEvents,
676
supportsSetModelId,
677
supportsOnDidChange,
678
supportsOnDidChangeModelInfo,
679
selector,
680
this._proxy,
681
);
682
683
this._registrations.set(handle, provider);
684
}
685
686
$emitInlineCompletionsChange(handle: number, changeHint: IInlineCompletionChangeHintDto | undefined): void {
687
const obj = this._registrations.get(handle);
688
if (obj instanceof ExtensionBackedInlineCompletionsProvider) {
689
obj._emitDidChange(changeHint);
690
}
691
}
692
693
$emitInlineCompletionModelInfoChange(handle: number, data: IInlineCompletionModelInfoDto | undefined): void {
694
const obj = this._registrations.get(handle);
695
if (obj instanceof ExtensionBackedInlineCompletionsProvider) {
696
obj._setModelInfo(data);
697
}
698
}
699
700
// --- parameter hints
701
702
$registerSignatureHelpProvider(handle: number, selector: IDocumentFilterDto[], metadata: ISignatureHelpProviderMetadataDto): void {
703
this._registrations.set(handle, this._languageFeaturesService.signatureHelpProvider.register(selector, {
704
705
signatureHelpTriggerCharacters: metadata.triggerCharacters,
706
signatureHelpRetriggerCharacters: metadata.retriggerCharacters,
707
708
provideSignatureHelp: async (model: ITextModel, position: EditorPosition, token: CancellationToken, context: languages.SignatureHelpContext): Promise<languages.SignatureHelpResult | undefined> => {
709
const result = await this._proxy.$provideSignatureHelp(handle, model.uri, position, context, token);
710
if (!result) {
711
return undefined;
712
}
713
return {
714
value: result,
715
dispose: () => {
716
this._proxy.$releaseSignatureHelp(handle, result.id);
717
}
718
};
719
}
720
}));
721
}
722
723
// --- inline hints
724
725
$registerInlayHintsProvider(handle: number, selector: IDocumentFilterDto[], supportsResolve: boolean, eventHandle: number | undefined, displayName: string | undefined): void {
726
const provider: languages.InlayHintsProvider = {
727
displayName,
728
provideInlayHints: async (model: ITextModel, range: EditorRange, token: CancellationToken): Promise<languages.InlayHintList | undefined> => {
729
const result = await this._proxy.$provideInlayHints(handle, model.uri, range, token);
730
if (!result) {
731
return;
732
}
733
return {
734
hints: revive(result.hints),
735
dispose: () => {
736
if (result.cacheId) {
737
this._proxy.$releaseInlayHints(handle, result.cacheId);
738
}
739
}
740
};
741
}
742
};
743
if (supportsResolve) {
744
provider.resolveInlayHint = async (hint, token) => {
745
const dto: IInlayHintDto = hint;
746
if (!dto.cacheId) {
747
return hint;
748
}
749
const result = await this._proxy.$resolveInlayHint(handle, dto.cacheId, token);
750
if (token.isCancellationRequested) {
751
throw new CancellationError();
752
}
753
if (!result) {
754
return hint;
755
}
756
return {
757
...hint,
758
tooltip: result.tooltip,
759
label: revive<string | languages.InlayHintLabelPart[]>(result.label),
760
textEdits: result.textEdits
761
};
762
};
763
}
764
if (typeof eventHandle === 'number') {
765
const emitter = new Emitter<void>();
766
this._registrations.set(eventHandle, emitter);
767
provider.onDidChangeInlayHints = emitter.event;
768
}
769
770
this._registrations.set(handle, this._languageFeaturesService.inlayHintsProvider.register(selector, provider));
771
}
772
773
$emitInlayHintsEvent(eventHandle: number): void {
774
const obj = this._registrations.get(eventHandle);
775
if (obj instanceof Emitter) {
776
obj.fire(undefined);
777
}
778
}
779
780
// --- links
781
782
$registerDocumentLinkProvider(handle: number, selector: IDocumentFilterDto[], supportsResolve: boolean): void {
783
const provider: languages.LinkProvider = {
784
provideLinks: (model, token) => {
785
return this._proxy.$provideDocumentLinks(handle, model.uri, token).then(dto => {
786
if (!dto) {
787
return undefined;
788
}
789
return {
790
links: dto.links.map(MainThreadLanguageFeatures._reviveLinkDTO),
791
dispose: () => {
792
if (typeof dto.cacheId === 'number') {
793
this._proxy.$releaseDocumentLinks(handle, dto.cacheId);
794
}
795
}
796
};
797
});
798
}
799
};
800
if (supportsResolve) {
801
provider.resolveLink = (link, token) => {
802
const dto: ILinkDto = link;
803
if (!dto.cacheId) {
804
return link;
805
}
806
return this._proxy.$resolveDocumentLink(handle, dto.cacheId, token).then(obj => {
807
return obj && MainThreadLanguageFeatures._reviveLinkDTO(obj);
808
});
809
};
810
}
811
this._registrations.set(handle, this._languageFeaturesService.linkProvider.register(selector, provider));
812
}
813
814
// --- colors
815
816
$registerDocumentColorProvider(handle: number, selector: IDocumentFilterDto[]): void {
817
const proxy = this._proxy;
818
this._registrations.set(handle, this._languageFeaturesService.colorProvider.register(selector, {
819
provideDocumentColors: (model, token) => {
820
return proxy.$provideDocumentColors(handle, model.uri, token)
821
.then(documentColors => {
822
return documentColors.map(documentColor => {
823
const [red, green, blue, alpha] = documentColor.color;
824
const color = {
825
red: red,
826
green: green,
827
blue: blue,
828
alpha
829
};
830
831
return {
832
color,
833
range: documentColor.range
834
};
835
});
836
});
837
},
838
839
provideColorPresentations: (model, colorInfo, token) => {
840
return proxy.$provideColorPresentations(handle, model.uri, {
841
color: [colorInfo.color.red, colorInfo.color.green, colorInfo.color.blue, colorInfo.color.alpha],
842
range: colorInfo.range
843
}, token);
844
}
845
}));
846
}
847
848
// --- folding
849
850
$registerFoldingRangeProvider(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier, eventHandle: number | undefined): void {
851
const provider: languages.FoldingRangeProvider = {
852
id: extensionId.value,
853
provideFoldingRanges: (model, context, token) => {
854
return this._proxy.$provideFoldingRanges(handle, model.uri, context, token);
855
}
856
};
857
858
if (typeof eventHandle === 'number') {
859
const emitter = new Emitter<languages.FoldingRangeProvider>();
860
this._registrations.set(eventHandle, emitter);
861
provider.onDidChange = emitter.event;
862
}
863
864
this._registrations.set(handle, this._languageFeaturesService.foldingRangeProvider.register(selector, provider));
865
}
866
867
$emitFoldingRangeEvent(eventHandle: number, event?: unknown): void {
868
const obj = this._registrations.get(eventHandle);
869
if (obj instanceof Emitter) {
870
obj.fire(event);
871
}
872
}
873
874
// -- smart select
875
876
$registerSelectionRangeProvider(handle: number, selector: IDocumentFilterDto[]): void {
877
this._registrations.set(handle, this._languageFeaturesService.selectionRangeProvider.register(selector, {
878
provideSelectionRanges: (model, positions, token) => {
879
return this._proxy.$provideSelectionRanges(handle, model.uri, positions, token);
880
}
881
}));
882
}
883
884
// --- call hierarchy
885
886
$registerCallHierarchyProvider(handle: number, selector: IDocumentFilterDto[]): void {
887
this._registrations.set(handle, callh.CallHierarchyProviderRegistry.register(selector, {
888
889
prepareCallHierarchy: async (document, position, token) => {
890
const items = await this._proxy.$prepareCallHierarchy(handle, document.uri, position, token);
891
if (!items || items.length === 0) {
892
return undefined;
893
}
894
return {
895
dispose: () => {
896
for (const item of items) {
897
this._proxy.$releaseCallHierarchy(handle, item._sessionId);
898
}
899
},
900
roots: items.map(MainThreadLanguageFeatures._reviveCallHierarchyItemDto)
901
};
902
},
903
904
provideOutgoingCalls: async (item, token) => {
905
const outgoing = await this._proxy.$provideCallHierarchyOutgoingCalls(handle, item._sessionId, item._itemId, token);
906
if (!outgoing) {
907
return outgoing;
908
}
909
outgoing.forEach(value => {
910
value.to = MainThreadLanguageFeatures._reviveCallHierarchyItemDto(value.to);
911
});
912
// eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any
913
return <any>outgoing;
914
},
915
provideIncomingCalls: async (item, token) => {
916
const incoming = await this._proxy.$provideCallHierarchyIncomingCalls(handle, item._sessionId, item._itemId, token);
917
if (!incoming) {
918
return incoming;
919
}
920
incoming.forEach(value => {
921
value.from = MainThreadLanguageFeatures._reviveCallHierarchyItemDto(value.from);
922
});
923
// eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any
924
return <any>incoming;
925
}
926
}));
927
}
928
929
// --- configuration
930
931
private static _reviveRegExp(regExp: IRegExpDto): RegExp {
932
return new RegExp(regExp.pattern, regExp.flags);
933
}
934
935
private static _reviveIndentationRule(indentationRule: IIndentationRuleDto): IndentationRule {
936
return {
937
decreaseIndentPattern: MainThreadLanguageFeatures._reviveRegExp(indentationRule.decreaseIndentPattern),
938
increaseIndentPattern: MainThreadLanguageFeatures._reviveRegExp(indentationRule.increaseIndentPattern),
939
indentNextLinePattern: indentationRule.indentNextLinePattern ? MainThreadLanguageFeatures._reviveRegExp(indentationRule.indentNextLinePattern) : undefined,
940
unIndentedLinePattern: indentationRule.unIndentedLinePattern ? MainThreadLanguageFeatures._reviveRegExp(indentationRule.unIndentedLinePattern) : undefined,
941
};
942
}
943
944
private static _reviveOnEnterRule(onEnterRule: IOnEnterRuleDto): OnEnterRule {
945
return {
946
beforeText: MainThreadLanguageFeatures._reviveRegExp(onEnterRule.beforeText),
947
afterText: onEnterRule.afterText ? MainThreadLanguageFeatures._reviveRegExp(onEnterRule.afterText) : undefined,
948
previousLineText: onEnterRule.previousLineText ? MainThreadLanguageFeatures._reviveRegExp(onEnterRule.previousLineText) : undefined,
949
action: onEnterRule.action
950
};
951
}
952
953
private static _reviveOnEnterRules(onEnterRules: IOnEnterRuleDto[]): OnEnterRule[] {
954
return onEnterRules.map(MainThreadLanguageFeatures._reviveOnEnterRule);
955
}
956
957
$setLanguageConfiguration(handle: number, languageId: string, _configuration: ILanguageConfigurationDto): void {
958
959
const configuration: LanguageConfiguration = {
960
comments: _configuration.comments,
961
brackets: _configuration.brackets,
962
wordPattern: _configuration.wordPattern ? MainThreadLanguageFeatures._reviveRegExp(_configuration.wordPattern) : undefined,
963
indentationRules: _configuration.indentationRules ? MainThreadLanguageFeatures._reviveIndentationRule(_configuration.indentationRules) : undefined,
964
onEnterRules: _configuration.onEnterRules ? MainThreadLanguageFeatures._reviveOnEnterRules(_configuration.onEnterRules) : undefined,
965
966
autoClosingPairs: undefined,
967
surroundingPairs: undefined,
968
__electricCharacterSupport: undefined
969
};
970
971
if (_configuration.autoClosingPairs) {
972
configuration.autoClosingPairs = _configuration.autoClosingPairs;
973
} else if (_configuration.__characterPairSupport) {
974
// backwards compatibility
975
configuration.autoClosingPairs = _configuration.__characterPairSupport.autoClosingPairs;
976
}
977
978
if (_configuration.__electricCharacterSupport && _configuration.__electricCharacterSupport.docComment) {
979
configuration.__electricCharacterSupport = {
980
docComment: {
981
open: _configuration.__electricCharacterSupport.docComment.open,
982
close: _configuration.__electricCharacterSupport.docComment.close
983
}
984
};
985
}
986
987
if (this._languageService.isRegisteredLanguageId(languageId)) {
988
this._registrations.set(handle, this._languageConfigurationService.register(languageId, configuration, 100));
989
}
990
}
991
992
// --- type hierarchy
993
994
$registerTypeHierarchyProvider(handle: number, selector: IDocumentFilterDto[]): void {
995
this._registrations.set(handle, typeh.TypeHierarchyProviderRegistry.register(selector, {
996
997
prepareTypeHierarchy: async (document, position, token) => {
998
const items = await this._proxy.$prepareTypeHierarchy(handle, document.uri, position, token);
999
if (!items) {
1000
return undefined;
1001
}
1002
return {
1003
dispose: () => {
1004
for (const item of items) {
1005
this._proxy.$releaseTypeHierarchy(handle, item._sessionId);
1006
}
1007
},
1008
roots: items.map(MainThreadLanguageFeatures._reviveTypeHierarchyItemDto)
1009
};
1010
},
1011
1012
provideSupertypes: async (item, token) => {
1013
const supertypes = await this._proxy.$provideTypeHierarchySupertypes(handle, item._sessionId, item._itemId, token);
1014
if (!supertypes) {
1015
return supertypes;
1016
}
1017
return supertypes.map(MainThreadLanguageFeatures._reviveTypeHierarchyItemDto);
1018
},
1019
provideSubtypes: async (item, token) => {
1020
const subtypes = await this._proxy.$provideTypeHierarchySubtypes(handle, item._sessionId, item._itemId, token);
1021
if (!subtypes) {
1022
return subtypes;
1023
}
1024
return subtypes.map(MainThreadLanguageFeatures._reviveTypeHierarchyItemDto);
1025
}
1026
}));
1027
}
1028
1029
1030
// --- document drop Edits
1031
1032
private readonly _documentOnDropEditProviders = new Map<number, MainThreadDocumentOnDropEditProvider>();
1033
1034
$registerDocumentOnDropEditProvider(handle: number, selector: IDocumentFilterDto[], metadata: IDocumentDropEditProviderMetadata): void {
1035
const provider = new MainThreadDocumentOnDropEditProvider(handle, this._proxy, metadata, this._uriIdentService);
1036
this._documentOnDropEditProviders.set(handle, provider);
1037
this._registrations.set(handle, combinedDisposable(
1038
this._languageFeaturesService.documentDropEditProvider.register(selector, provider),
1039
toDisposable(() => this._documentOnDropEditProviders.delete(handle)),
1040
));
1041
}
1042
1043
async $resolveDocumentOnDropFileData(handle: number, requestId: number, dataId: string): Promise<VSBuffer> {
1044
const provider = this._documentOnDropEditProviders.get(handle);
1045
if (!provider) {
1046
throw new Error('Could not find provider');
1047
}
1048
return provider.resolveDocumentOnDropFileData(requestId, dataId);
1049
}
1050
}
1051
1052
class MainThreadPasteEditProvider implements languages.DocumentPasteEditProvider {
1053
1054
private readonly dataTransfers = new DataTransferFileCache();
1055
1056
public readonly copyMimeTypes: readonly string[];
1057
public readonly pasteMimeTypes: readonly string[];
1058
public readonly providedPasteEditKinds: readonly HierarchicalKind[];
1059
1060
readonly prepareDocumentPaste?: languages.DocumentPasteEditProvider['prepareDocumentPaste'];
1061
readonly provideDocumentPasteEdits?: languages.DocumentPasteEditProvider['provideDocumentPasteEdits'];
1062
readonly resolveDocumentPasteEdit?: languages.DocumentPasteEditProvider['resolveDocumentPasteEdit'];
1063
1064
constructor(
1065
private readonly _handle: number,
1066
private readonly _proxy: ExtHostLanguageFeaturesShape,
1067
metadata: IPasteEditProviderMetadataDto,
1068
@IUriIdentityService private readonly _uriIdentService: IUriIdentityService
1069
) {
1070
this.copyMimeTypes = metadata.copyMimeTypes ?? [];
1071
this.pasteMimeTypes = metadata.pasteMimeTypes ?? [];
1072
this.providedPasteEditKinds = metadata.providedPasteEditKinds?.map(kind => new HierarchicalKind(kind)) ?? [];
1073
1074
if (metadata.supportsCopy) {
1075
this.prepareDocumentPaste = async (model: ITextModel, selections: readonly IRange[], dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise<IReadonlyVSDataTransfer | undefined> => {
1076
const dataTransferDto = await typeConvert.DataTransfer.fromList(dataTransfer);
1077
if (token.isCancellationRequested) {
1078
return undefined;
1079
}
1080
1081
const newDataTransfer = await this._proxy.$prepareDocumentPaste(_handle, model.uri, selections, dataTransferDto, token);
1082
if (!newDataTransfer) {
1083
return undefined;
1084
}
1085
1086
const dataTransferOut = new VSDataTransfer();
1087
for (const [type, item] of newDataTransfer.items) {
1088
dataTransferOut.replace(type, createStringDataTransferItem(item.asString, item.id));
1089
}
1090
return dataTransferOut;
1091
};
1092
}
1093
1094
if (metadata.supportsPaste) {
1095
this.provideDocumentPasteEdits = async (model: ITextModel, selections: Selection[], dataTransfer: IReadonlyVSDataTransfer, context: languages.DocumentPasteContext, token: CancellationToken) => {
1096
const request = this.dataTransfers.add(dataTransfer);
1097
try {
1098
const dataTransferDto = await typeConvert.DataTransfer.fromList(dataTransfer);
1099
if (token.isCancellationRequested) {
1100
return;
1101
}
1102
1103
const edits = await this._proxy.$providePasteEdits(this._handle, request.id, model.uri, selections, dataTransferDto, {
1104
only: context.only?.value,
1105
triggerKind: context.triggerKind,
1106
}, token);
1107
if (!edits) {
1108
return;
1109
}
1110
1111
return {
1112
edits: edits.map((edit): languages.DocumentPasteEdit => {
1113
return {
1114
...edit,
1115
kind: edit.kind ? new HierarchicalKind(edit.kind.value) : new HierarchicalKind(''),
1116
yieldTo: edit.yieldTo?.map(x => ({ kind: new HierarchicalKind(x) })),
1117
additionalEdit: edit.additionalEdit ? reviveWorkspaceEditDto(edit.additionalEdit, this._uriIdentService, dataId => this.resolveFileData(request.id, dataId)) : undefined,
1118
};
1119
}),
1120
dispose: () => {
1121
this._proxy.$releasePasteEdits(this._handle, request.id);
1122
},
1123
};
1124
} finally {
1125
request.dispose();
1126
}
1127
};
1128
}
1129
if (metadata.supportsResolve) {
1130
this.resolveDocumentPasteEdit = async (edit: languages.DocumentPasteEdit, token: CancellationToken) => {
1131
const resolved = await this._proxy.$resolvePasteEdit(this._handle, (<IPasteEditDto>edit)._cacheId!, token);
1132
if (typeof resolved.insertText !== 'undefined') {
1133
edit.insertText = resolved.insertText;
1134
}
1135
1136
if (resolved.additionalEdit) {
1137
edit.additionalEdit = reviveWorkspaceEditDto(resolved.additionalEdit, this._uriIdentService);
1138
}
1139
return edit;
1140
};
1141
}
1142
}
1143
1144
resolveFileData(requestId: number, dataId: string): Promise<VSBuffer> {
1145
return this.dataTransfers.resolveFileData(requestId, dataId);
1146
}
1147
}
1148
1149
class MainThreadDocumentOnDropEditProvider implements languages.DocumentDropEditProvider {
1150
1151
private readonly dataTransfers = new DataTransferFileCache();
1152
1153
readonly dropMimeTypes?: readonly string[];
1154
1155
readonly providedDropEditKinds: readonly HierarchicalKind[] | undefined;
1156
1157
readonly resolveDocumentDropEdit?: languages.DocumentDropEditProvider['resolveDocumentDropEdit'];
1158
1159
constructor(
1160
private readonly _handle: number,
1161
private readonly _proxy: ExtHostLanguageFeaturesShape,
1162
metadata: IDocumentDropEditProviderMetadata | undefined,
1163
@IUriIdentityService private readonly _uriIdentService: IUriIdentityService
1164
) {
1165
this.dropMimeTypes = metadata?.dropMimeTypes ?? ['*/*'];
1166
this.providedDropEditKinds = metadata?.providedDropKinds?.map(kind => new HierarchicalKind(kind));
1167
1168
if (metadata?.supportsResolve) {
1169
this.resolveDocumentDropEdit = async (edit, token) => {
1170
const resolved = await this._proxy.$resolvePasteEdit(this._handle, (<IDocumentDropEditDto>edit)._cacheId!, token);
1171
if (resolved.additionalEdit) {
1172
edit.additionalEdit = reviveWorkspaceEditDto(resolved.additionalEdit, this._uriIdentService);
1173
}
1174
return edit;
1175
};
1176
}
1177
}
1178
1179
async provideDocumentDropEdits(model: ITextModel, position: IPosition, dataTransfer: IReadonlyVSDataTransfer, token: CancellationToken): Promise<languages.DocumentDropEditsSession | undefined> {
1180
const request = this.dataTransfers.add(dataTransfer);
1181
try {
1182
const dataTransferDto = await typeConvert.DataTransfer.fromList(dataTransfer);
1183
if (token.isCancellationRequested) {
1184
return;
1185
}
1186
1187
const edits = await this._proxy.$provideDocumentOnDropEdits(this._handle, request.id, model.uri, position, dataTransferDto, token);
1188
if (!edits) {
1189
return;
1190
}
1191
1192
return {
1193
edits: edits.map(edit => {
1194
return {
1195
...edit,
1196
yieldTo: edit.yieldTo?.map(x => ({ kind: new HierarchicalKind(x) })),
1197
kind: edit.kind ? new HierarchicalKind(edit.kind) : undefined,
1198
additionalEdit: reviveWorkspaceEditDto(edit.additionalEdit, this._uriIdentService, dataId => this.resolveDocumentOnDropFileData(request.id, dataId)),
1199
};
1200
}),
1201
dispose: () => {
1202
this._proxy.$releaseDocumentOnDropEdits(this._handle, request.id);
1203
},
1204
};
1205
} finally {
1206
request.dispose();
1207
}
1208
}
1209
1210
public resolveDocumentOnDropFileData(requestId: number, dataId: string): Promise<VSBuffer> {
1211
return this.dataTransfers.resolveFileData(requestId, dataId);
1212
}
1213
}
1214
1215
export class MainThreadDocumentSemanticTokensProvider implements languages.DocumentSemanticTokensProvider {
1216
1217
constructor(
1218
private readonly _proxy: ExtHostLanguageFeaturesShape,
1219
private readonly _handle: number,
1220
private readonly _legend: languages.SemanticTokensLegend,
1221
public readonly onDidChange: Event<void> | undefined,
1222
) {
1223
}
1224
1225
public releaseDocumentSemanticTokens(resultId: string | undefined): void {
1226
if (resultId) {
1227
this._proxy.$releaseDocumentSemanticTokens(this._handle, parseInt(resultId, 10));
1228
}
1229
}
1230
1231
public getLegend(): languages.SemanticTokensLegend {
1232
return this._legend;
1233
}
1234
1235
async provideDocumentSemanticTokens(model: ITextModel, lastResultId: string | null, token: CancellationToken): Promise<languages.SemanticTokens | languages.SemanticTokensEdits | null> {
1236
const nLastResultId = lastResultId ? parseInt(lastResultId, 10) : 0;
1237
const encodedDto = await this._proxy.$provideDocumentSemanticTokens(this._handle, model.uri, nLastResultId, token);
1238
if (!encodedDto) {
1239
return null;
1240
}
1241
if (token.isCancellationRequested) {
1242
return null;
1243
}
1244
const dto = decodeSemanticTokensDto(encodedDto);
1245
if (dto.type === 'full') {
1246
return {
1247
resultId: String(dto.id),
1248
data: dto.data
1249
};
1250
}
1251
return {
1252
resultId: String(dto.id),
1253
edits: dto.deltas
1254
};
1255
}
1256
}
1257
1258
export class MainThreadDocumentRangeSemanticTokensProvider implements languages.DocumentRangeSemanticTokensProvider {
1259
1260
constructor(
1261
private readonly _proxy: ExtHostLanguageFeaturesShape,
1262
private readonly _handle: number,
1263
private readonly _legend: languages.SemanticTokensLegend,
1264
public readonly onDidChange: Event<void> | undefined,
1265
) {
1266
}
1267
1268
public getLegend(): languages.SemanticTokensLegend {
1269
return this._legend;
1270
}
1271
1272
async provideDocumentRangeSemanticTokens(model: ITextModel, range: EditorRange, token: CancellationToken): Promise<languages.SemanticTokens | null> {
1273
const encodedDto = await this._proxy.$provideDocumentRangeSemanticTokens(this._handle, model.uri, range, token);
1274
if (!encodedDto) {
1275
return null;
1276
}
1277
if (token.isCancellationRequested) {
1278
return null;
1279
}
1280
const dto = decodeSemanticTokensDto(encodedDto);
1281
if (dto.type === 'full') {
1282
return {
1283
resultId: String(dto.id),
1284
data: dto.data
1285
};
1286
}
1287
throw new Error(`Unexpected`);
1288
}
1289
}
1290
1291
class ExtensionBackedInlineCompletionsProvider extends Disposable implements languages.InlineCompletionsProvider<IdentifiableInlineCompletions> {
1292
public readonly setModelId: ((modelId: string) => Promise<void>) | undefined;
1293
public readonly _onDidChangeEmitter = new Emitter<languages.IInlineCompletionChangeHint | void>();
1294
public readonly onDidChangeInlineCompletions: Event<languages.IInlineCompletionChangeHint | void> | undefined;
1295
1296
public readonly _onDidChangeModelInfoEmitter = new Emitter<void>();
1297
public readonly onDidChangeModelInfo: Event<void> | undefined;
1298
1299
constructor(
1300
public readonly handle: number,
1301
public readonly groupId: string,
1302
public readonly providerId: languages.ProviderId,
1303
public readonly yieldsToGroupIds: string[],
1304
public readonly excludesGroupIds: string[],
1305
public readonly debounceDelayMs: number | undefined,
1306
public readonly displayName: string | undefined,
1307
public modelInfo: languages.IInlineCompletionModelInfo | undefined,
1308
private readonly _supportsHandleEvents: boolean,
1309
private readonly _supportsSetModelId: boolean,
1310
private readonly _supportsOnDidChange: boolean,
1311
private readonly _supportsOnDidChangeModelInfo: boolean,
1312
private readonly _selector: IDocumentFilterDto[],
1313
private readonly _proxy: ExtHostLanguageFeaturesShape,
1314
@ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,
1315
@IAiEditTelemetryService private readonly _aiEditTelemetryService: IAiEditTelemetryService,
1316
@IInstantiationService private readonly _instantiationService: IInstantiationService,
1317
) {
1318
super();
1319
1320
this.setModelId = this._supportsSetModelId ? async (modelId: string) => {
1321
await this._proxy.$handleInlineCompletionSetCurrentModelId(this.handle, modelId);
1322
} : undefined;
1323
1324
this.onDidChangeInlineCompletions = this._supportsOnDidChange ? this._onDidChangeEmitter.event : undefined;
1325
this.onDidChangeModelInfo = this._supportsOnDidChangeModelInfo ? this._onDidChangeModelInfoEmitter.event : undefined;
1326
1327
this._register(this._languageFeaturesService.inlineCompletionsProvider.register(this._selector, this));
1328
}
1329
1330
public _setModelInfo(newModelInfo: languages.IInlineCompletionModelInfo | undefined) {
1331
this.modelInfo = newModelInfo;
1332
if (this._supportsOnDidChangeModelInfo) {
1333
this._onDidChangeModelInfoEmitter.fire();
1334
}
1335
}
1336
1337
public _emitDidChange(changeHint: IInlineCompletionChangeHintDto | undefined) {
1338
if (this._supportsOnDidChange) {
1339
this._onDidChangeEmitter.fire(changeHint);
1340
}
1341
}
1342
1343
public async provideInlineCompletions(model: ITextModel, position: EditorPosition, context: languages.InlineCompletionContext, token: CancellationToken): Promise<IdentifiableInlineCompletions | undefined> {
1344
const result = await this._proxy.$provideInlineCompletions(this.handle, model.uri, position, context, token);
1345
return result;
1346
}
1347
1348
public async handleItemDidShow(completions: IdentifiableInlineCompletions, item: IdentifiableInlineCompletion, updatedInsertText: string, editDeltaInfo: EditDeltaInfo): Promise<void> {
1349
if (item.suggestionId === undefined) {
1350
item.suggestionId = this._aiEditTelemetryService.createSuggestionId({
1351
applyCodeBlockSuggestionId: undefined,
1352
feature: 'inlineSuggestion',
1353
source: this.providerId,
1354
languageId: completions.languageId,
1355
editDeltaInfo: editDeltaInfo,
1356
modeId: undefined,
1357
modelId: undefined,
1358
presentation: item.isInlineEdit ? 'nextEditSuggestion' : 'inlineCompletion',
1359
});
1360
}
1361
1362
if (this._supportsHandleEvents) {
1363
await this._proxy.$handleInlineCompletionDidShow(this.handle, completions.pid, item.idx, updatedInsertText);
1364
}
1365
}
1366
1367
public async handlePartialAccept(completions: IdentifiableInlineCompletions, item: IdentifiableInlineCompletion, acceptedCharacters: number, info: languages.PartialAcceptInfo): Promise<void> {
1368
if (this._supportsHandleEvents) {
1369
await this._proxy.$handleInlineCompletionPartialAccept(this.handle, completions.pid, item.idx, acceptedCharacters, info);
1370
}
1371
}
1372
1373
public async handleEndOfLifetime(completions: IdentifiableInlineCompletions, item: IdentifiableInlineCompletion, reason: languages.InlineCompletionEndOfLifeReason<IdentifiableInlineCompletion>, lifetimeSummary: languages.LifetimeSummary): Promise<void> {
1374
function mapReason<T1, T2>(reason: languages.InlineCompletionEndOfLifeReason<T1>, f: (reason: T1) => T2): languages.InlineCompletionEndOfLifeReason<T2> {
1375
if (reason.kind === languages.InlineCompletionEndOfLifeReasonKind.Ignored) {
1376
return {
1377
...reason,
1378
supersededBy: reason.supersededBy ? f(reason.supersededBy) : undefined,
1379
};
1380
}
1381
return reason;
1382
}
1383
1384
if (this._supportsHandleEvents) {
1385
await this._proxy.$handleInlineCompletionEndOfLifetime(this.handle, completions.pid, item.idx, mapReason(reason, i => ({ pid: completions.pid, idx: i.idx })));
1386
}
1387
1388
if (reason.kind === languages.InlineCompletionEndOfLifeReasonKind.Accepted) {
1389
if (item.suggestionId !== undefined) {
1390
this._aiEditTelemetryService.handleCodeAccepted({
1391
suggestionId: item.suggestionId,
1392
feature: 'inlineSuggestion',
1393
source: this.providerId,
1394
languageId: completions.languageId,
1395
editDeltaInfo: EditDeltaInfo.tryCreate(
1396
lifetimeSummary.lineCountModified,
1397
lifetimeSummary.lineCountOriginal,
1398
lifetimeSummary.characterCountModified,
1399
lifetimeSummary.characterCountOriginal,
1400
),
1401
modeId: undefined,
1402
modelId: undefined,
1403
presentation: item.isInlineEdit ? 'nextEditSuggestion' : 'inlineCompletion',
1404
acceptanceMethod: 'accept',
1405
applyCodeBlockSuggestionId: undefined,
1406
});
1407
}
1408
}
1409
1410
const endOfLifeSummary: InlineCompletionEndOfLifeEvent = {
1411
opportunityId: lifetimeSummary.requestUuid,
1412
correlationId: lifetimeSummary.correlationId,
1413
shown: lifetimeSummary.shown,
1414
shownDuration: lifetimeSummary.shownDuration,
1415
shownDurationUncollapsed: lifetimeSummary.shownDurationUncollapsed,
1416
timeUntilShown: lifetimeSummary.timeUntilShown,
1417
timeUntilProviderRequest: lifetimeSummary.timeUntilProviderRequest,
1418
timeUntilProviderResponse: lifetimeSummary.timeUntilProviderResponse,
1419
editorType: lifetimeSummary.editorType,
1420
viewKind: lifetimeSummary.viewKind,
1421
preceeded: lifetimeSummary.preceeded,
1422
requestReason: lifetimeSummary.requestReason,
1423
typingInterval: lifetimeSummary.typingInterval,
1424
typingIntervalCharacterCount: lifetimeSummary.typingIntervalCharacterCount,
1425
languageId: lifetimeSummary.languageId,
1426
cursorColumnDistance: lifetimeSummary.cursorColumnDistance,
1427
cursorLineDistance: lifetimeSummary.cursorLineDistance,
1428
lineCountOriginal: lifetimeSummary.lineCountOriginal,
1429
lineCountModified: lifetimeSummary.lineCountModified,
1430
characterCountOriginal: lifetimeSummary.characterCountOriginal,
1431
characterCountModified: lifetimeSummary.characterCountModified,
1432
disjointReplacements: lifetimeSummary.disjointReplacements,
1433
sameShapeReplacements: lifetimeSummary.sameShapeReplacements,
1434
selectedSuggestionInfo: lifetimeSummary.selectedSuggestionInfo,
1435
extensionId: this.providerId.extensionId!,
1436
extensionVersion: this.providerId.extensionVersion!,
1437
groupId: extractEngineFromCorrelationId(lifetimeSummary.correlationId) ?? this.groupId,
1438
skuPlan: lifetimeSummary.skuPlan,
1439
skuType: lifetimeSummary.skuType,
1440
performanceMarkers: lifetimeSummary.performanceMarkers,
1441
availableProviders: lifetimeSummary.availableProviders,
1442
partiallyAccepted: lifetimeSummary.partiallyAccepted,
1443
partiallyAcceptedCountSinceOriginal: lifetimeSummary.partiallyAcceptedCountSinceOriginal,
1444
partiallyAcceptedRatioSinceOriginal: lifetimeSummary.partiallyAcceptedRatioSinceOriginal,
1445
partiallyAcceptedCharactersSinceOriginal: lifetimeSummary.partiallyAcceptedCharactersSinceOriginal,
1446
superseded: reason.kind === InlineCompletionEndOfLifeReasonKind.Ignored && !!reason.supersededBy,
1447
reason: reason.kind === InlineCompletionEndOfLifeReasonKind.Accepted ? 'accepted'
1448
: reason.kind === InlineCompletionEndOfLifeReasonKind.Rejected ? 'rejected'
1449
: reason.kind === InlineCompletionEndOfLifeReasonKind.Ignored ? 'ignored' : undefined,
1450
acceptedAlternativeAction: reason.kind === InlineCompletionEndOfLifeReasonKind.Accepted && reason.alternativeAction,
1451
noSuggestionReason: undefined,
1452
notShownReason: lifetimeSummary.notShownReason,
1453
renameCreated: lifetimeSummary.renameCreated,
1454
renameDuration: lifetimeSummary.renameDuration,
1455
renameTimedOut: lifetimeSummary.renameTimedOut,
1456
renameDroppedOtherEdits: lifetimeSummary.renameDroppedOtherEdits,
1457
renameDroppedRenameEdits: lifetimeSummary.renameDroppedRenameEdits,
1458
editKind: lifetimeSummary.editKind,
1459
longDistanceHintVisible: lifetimeSummary.longDistanceHintVisible,
1460
longDistanceHintDistance: lifetimeSummary.longDistanceHintDistance,
1461
...forwardToChannelIf(isCopilotLikeExtension(this.providerId.extensionId!)),
1462
};
1463
1464
const dataChannelForwardingTelemetryService = this._instantiationService.createInstance(DataChannelForwardingTelemetryService);
1465
sendInlineCompletionsEndOfLifeTelemetry(dataChannelForwardingTelemetryService, endOfLifeSummary);
1466
}
1467
1468
public disposeInlineCompletions(completions: IdentifiableInlineCompletions, reason: languages.InlineCompletionsDisposeReason): void {
1469
this._proxy.$freeInlineCompletionsList(this.handle, completions.pid, reason);
1470
}
1471
1472
public async handleRejection(completions: IdentifiableInlineCompletions, item: IdentifiableInlineCompletion): Promise<void> {
1473
if (this._supportsHandleEvents) {
1474
await this._proxy.$handleInlineCompletionRejection(this.handle, completions.pid, item.idx);
1475
}
1476
}
1477
1478
override toString() {
1479
return `InlineCompletionsProvider(${this.providerId.toString()})`;
1480
}
1481
}
1482
1483
function extractEngineFromCorrelationId(correlationId: string | undefined): string | undefined {
1484
if (!correlationId) {
1485
return undefined;
1486
}
1487
try {
1488
const parsed = JSON.parse(correlationId);
1489
if (typeof parsed === 'object' && parsed !== null && typeof parsed.engine === 'string') {
1490
return parsed.engine;
1491
}
1492
return undefined;
1493
} catch {
1494
return undefined;
1495
}
1496
}
1497
1498