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