mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-08 17:19:48 +01:00
2974 lines
128 KiB
TypeScript
2974 lines
128 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import type * as vscode from 'vscode';
|
|
import { asArray, coalesce, isFalsyOrEmpty, isNonEmptyArray } from '../../../base/common/arrays.js';
|
|
import { raceCancellationError } from '../../../base/common/async.js';
|
|
import { VSBuffer } from '../../../base/common/buffer.js';
|
|
import { CancellationToken } from '../../../base/common/cancellation.js';
|
|
import { NotImplementedError, isCancellationError } from '../../../base/common/errors.js';
|
|
import { IdGenerator } from '../../../base/common/idGenerator.js';
|
|
import { DisposableStore, Disposable as CoreDisposable } from '../../../base/common/lifecycle.js';
|
|
import { equals, mixin } from '../../../base/common/objects.js';
|
|
import { StopWatch } from '../../../base/common/stopwatch.js';
|
|
import { regExpLeadsToEndlessLoop } from '../../../base/common/strings.js';
|
|
import { assertType, isObject } from '../../../base/common/types.js';
|
|
import { URI, UriComponents } from '../../../base/common/uri.js';
|
|
import { IURITransformer } from '../../../base/common/uriIpc.js';
|
|
import { generateUuid } from '../../../base/common/uuid.js';
|
|
import { IPosition } from '../../../editor/common/core/position.js';
|
|
import { Range as EditorRange, IRange } from '../../../editor/common/core/range.js';
|
|
import { ISelection, Selection } from '../../../editor/common/core/selection.js';
|
|
import * as languages from '../../../editor/common/languages.js';
|
|
import { IAutoClosingPairConditional } from '../../../editor/common/languages/languageConfiguration.js';
|
|
import { encodeSemanticTokensDto } from '../../../editor/common/services/semanticTokensDto.js';
|
|
import { localize } from '../../../nls.js';
|
|
import { ExtensionIdentifier, IExtensionDescription } from '../../../platform/extensions/common/extensions.js';
|
|
import { ILogService } from '../../../platform/log/common/log.js';
|
|
import { isProposedApiEnabled } from '../../services/extensions/common/extensions.js';
|
|
import { Cache } from './cache.js';
|
|
import * as extHostProtocol from './extHost.protocol.js';
|
|
import { IExtHostApiDeprecationService } from './extHostApiDeprecationService.js';
|
|
import { CommandsConverter, ExtHostCommands } from './extHostCommands.js';
|
|
import { ExtHostDiagnostics } from './extHostDiagnostics.js';
|
|
import { ExtHostDocuments } from './extHostDocuments.js';
|
|
import { ExtHostTelemetry, IExtHostTelemetry } from './extHostTelemetry.js';
|
|
import * as typeConvert from './extHostTypeConverters.js';
|
|
import { CodeAction, CodeActionKind, CompletionList, DataTransfer, Disposable, DocumentDropOrPasteEditKind, DocumentSymbol, InlineCompletionsDisposeReasonKind, InlineCompletionTriggerKind, InternalDataTransferItem, Location, NewSymbolNameTriggerKind, Range, SemanticTokens, SemanticTokensEdit, SemanticTokensEdits, SnippetString, SymbolInformation, SyntaxTokenType } from './extHostTypes.js';
|
|
import { Emitter } from '../../../base/common/event.js';
|
|
import { IInlineCompletionsUnificationState } from '../../services/inlineCompletions/common/inlineCompletionsUnification.js';
|
|
|
|
// --- adapter
|
|
|
|
class DocumentSymbolAdapter {
|
|
|
|
constructor(
|
|
private readonly _documents: ExtHostDocuments,
|
|
private readonly _provider: vscode.DocumentSymbolProvider
|
|
) { }
|
|
|
|
async provideDocumentSymbols(resource: URI, token: CancellationToken): Promise<languages.DocumentSymbol[] | undefined> {
|
|
const doc = this._documents.getDocument(resource);
|
|
const value = await this._provider.provideDocumentSymbols(doc, token);
|
|
if (isFalsyOrEmpty(value)) {
|
|
return undefined;
|
|
} else if (value![0] instanceof DocumentSymbol) {
|
|
return (<DocumentSymbol[]>value).map(typeConvert.DocumentSymbol.from);
|
|
} else {
|
|
return DocumentSymbolAdapter._asDocumentSymbolTree(<SymbolInformation[]>value);
|
|
}
|
|
}
|
|
|
|
private static _asDocumentSymbolTree(infos: SymbolInformation[]): languages.DocumentSymbol[] {
|
|
// first sort by start (and end) and then loop over all elements
|
|
// and build a tree based on containment.
|
|
infos = infos.slice(0).sort((a, b) => {
|
|
let res = a.location.range.start.compareTo(b.location.range.start);
|
|
if (res === 0) {
|
|
res = b.location.range.end.compareTo(a.location.range.end);
|
|
}
|
|
return res;
|
|
});
|
|
const res: languages.DocumentSymbol[] = [];
|
|
const parentStack: languages.DocumentSymbol[] = [];
|
|
for (const info of infos) {
|
|
const element: languages.DocumentSymbol = {
|
|
name: info.name || '!!MISSING: name!!',
|
|
kind: typeConvert.SymbolKind.from(info.kind),
|
|
tags: info.tags?.map(typeConvert.SymbolTag.from) || [],
|
|
detail: '',
|
|
containerName: info.containerName,
|
|
range: typeConvert.Range.from(info.location.range),
|
|
selectionRange: typeConvert.Range.from(info.location.range),
|
|
children: []
|
|
};
|
|
|
|
while (true) {
|
|
if (parentStack.length === 0) {
|
|
parentStack.push(element);
|
|
res.push(element);
|
|
break;
|
|
}
|
|
const parent = parentStack[parentStack.length - 1];
|
|
if (EditorRange.containsRange(parent.range, element.range) && !EditorRange.equalsRange(parent.range, element.range)) {
|
|
parent.children?.push(element);
|
|
parentStack.push(element);
|
|
break;
|
|
}
|
|
parentStack.pop();
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
}
|
|
|
|
class CodeLensAdapter {
|
|
|
|
private readonly _cache = new Cache<vscode.CodeLens>('CodeLens');
|
|
private readonly _disposables = new Map<number, DisposableStore>();
|
|
|
|
constructor(
|
|
private readonly _documents: ExtHostDocuments,
|
|
private readonly _commands: CommandsConverter,
|
|
private readonly _provider: vscode.CodeLensProvider,
|
|
private readonly _extension: IExtensionDescription,
|
|
private readonly _extTelemetry: ExtHostTelemetry,
|
|
private readonly _logService: ILogService,
|
|
) { }
|
|
|
|
async provideCodeLenses(resource: URI, token: CancellationToken): Promise<extHostProtocol.ICodeLensListDto | undefined> {
|
|
const doc = this._documents.getDocument(resource);
|
|
|
|
const lenses = await this._provider.provideCodeLenses(doc, token);
|
|
if (!lenses || token.isCancellationRequested) {
|
|
return undefined;
|
|
}
|
|
const cacheId = this._cache.add(lenses);
|
|
const disposables = new DisposableStore();
|
|
this._disposables.set(cacheId, disposables);
|
|
const result: extHostProtocol.ICodeLensListDto = {
|
|
cacheId,
|
|
lenses: [],
|
|
};
|
|
for (let i = 0; i < lenses.length; i++) {
|
|
|
|
if (!Range.isRange(lenses[i].range)) {
|
|
console.warn('INVALID code lens, range is not defined', this._extension.identifier.value);
|
|
continue;
|
|
}
|
|
|
|
result.lenses.push({
|
|
cacheId: [cacheId, i],
|
|
range: typeConvert.Range.from(lenses[i].range),
|
|
command: this._commands.toInternal(lenses[i].command, disposables)
|
|
});
|
|
}
|
|
return result;
|
|
}
|
|
|
|
async resolveCodeLens(symbol: extHostProtocol.ICodeLensDto, token: CancellationToken): Promise<extHostProtocol.ICodeLensDto | undefined> {
|
|
|
|
const lens = symbol.cacheId && this._cache.get(...symbol.cacheId);
|
|
if (!lens) {
|
|
return undefined;
|
|
}
|
|
|
|
let resolvedLens: vscode.CodeLens | undefined | null;
|
|
if (typeof this._provider.resolveCodeLens !== 'function' || lens.isResolved) {
|
|
resolvedLens = lens;
|
|
} else {
|
|
resolvedLens = await this._provider.resolveCodeLens(lens, token);
|
|
}
|
|
if (!resolvedLens) {
|
|
resolvedLens = lens;
|
|
}
|
|
|
|
if (token.isCancellationRequested) {
|
|
return undefined;
|
|
}
|
|
const disposables = symbol.cacheId && this._disposables.get(symbol.cacheId[0]);
|
|
if (!disposables) {
|
|
// disposed in the meantime
|
|
return undefined;
|
|
}
|
|
|
|
if (!resolvedLens.command) {
|
|
const error = new Error('INVALID code lens resolved, lacks command: ' + this._extension.identifier.value);
|
|
this._extTelemetry.onExtensionError(this._extension.identifier, error);
|
|
this._logService.error(error);
|
|
return undefined;
|
|
}
|
|
|
|
symbol.command = this._commands.toInternal(resolvedLens.command, disposables);
|
|
return symbol;
|
|
}
|
|
|
|
releaseCodeLenses(cachedId: number): void {
|
|
this._disposables.get(cachedId)?.dispose();
|
|
this._disposables.delete(cachedId);
|
|
this._cache.delete(cachedId);
|
|
}
|
|
}
|
|
|
|
function convertToLocationLinks(value: vscode.Location | vscode.Location[] | vscode.LocationLink[] | undefined | null): languages.LocationLink[] {
|
|
if (Array.isArray(value)) {
|
|
// eslint-disable-next-line local/code-no-any-casts
|
|
return (<any>value).map(typeConvert.DefinitionLink.from);
|
|
} else if (value) {
|
|
return [typeConvert.DefinitionLink.from(value)];
|
|
}
|
|
return [];
|
|
}
|
|
|
|
class DefinitionAdapter {
|
|
|
|
constructor(
|
|
private readonly _documents: ExtHostDocuments,
|
|
private readonly _provider: vscode.DefinitionProvider
|
|
) { }
|
|
|
|
async provideDefinition(resource: URI, position: IPosition, token: CancellationToken): Promise<languages.LocationLink[]> {
|
|
const doc = this._documents.getDocument(resource);
|
|
const pos = typeConvert.Position.to(position);
|
|
const value = await this._provider.provideDefinition(doc, pos, token);
|
|
return convertToLocationLinks(value);
|
|
}
|
|
}
|
|
|
|
class DeclarationAdapter {
|
|
|
|
constructor(
|
|
private readonly _documents: ExtHostDocuments,
|
|
private readonly _provider: vscode.DeclarationProvider
|
|
) { }
|
|
|
|
async provideDeclaration(resource: URI, position: IPosition, token: CancellationToken): Promise<languages.LocationLink[]> {
|
|
const doc = this._documents.getDocument(resource);
|
|
const pos = typeConvert.Position.to(position);
|
|
const value = await this._provider.provideDeclaration(doc, pos, token);
|
|
return convertToLocationLinks(value);
|
|
}
|
|
}
|
|
|
|
class ImplementationAdapter {
|
|
|
|
constructor(
|
|
private readonly _documents: ExtHostDocuments,
|
|
private readonly _provider: vscode.ImplementationProvider
|
|
) { }
|
|
|
|
async provideImplementation(resource: URI, position: IPosition, token: CancellationToken): Promise<languages.LocationLink[]> {
|
|
const doc = this._documents.getDocument(resource);
|
|
const pos = typeConvert.Position.to(position);
|
|
const value = await this._provider.provideImplementation(doc, pos, token);
|
|
return convertToLocationLinks(value);
|
|
}
|
|
}
|
|
|
|
class TypeDefinitionAdapter {
|
|
|
|
constructor(
|
|
private readonly _documents: ExtHostDocuments,
|
|
private readonly _provider: vscode.TypeDefinitionProvider
|
|
) { }
|
|
|
|
async provideTypeDefinition(resource: URI, position: IPosition, token: CancellationToken): Promise<languages.LocationLink[]> {
|
|
const doc = this._documents.getDocument(resource);
|
|
const pos = typeConvert.Position.to(position);
|
|
const value = await this._provider.provideTypeDefinition(doc, pos, token);
|
|
return convertToLocationLinks(value);
|
|
}
|
|
}
|
|
|
|
class HoverAdapter {
|
|
|
|
private _hoverCounter: number = 0;
|
|
private _hoverMap: Map<number, vscode.Hover> = new Map<number, vscode.Hover>();
|
|
|
|
private static HOVER_MAP_MAX_SIZE = 10;
|
|
|
|
constructor(
|
|
private readonly _documents: ExtHostDocuments,
|
|
private readonly _provider: vscode.HoverProvider,
|
|
) { }
|
|
|
|
async provideHover(resource: URI, position: IPosition, context: languages.HoverContext<{ id: number }> | undefined, token: CancellationToken): Promise<extHostProtocol.HoverWithId | undefined> {
|
|
|
|
const doc = this._documents.getDocument(resource);
|
|
const pos = typeConvert.Position.to(position);
|
|
|
|
let value: vscode.Hover | null | undefined;
|
|
if (context && context.verbosityRequest) {
|
|
const previousHoverId = context.verbosityRequest.previousHover.id;
|
|
const previousHover = this._hoverMap.get(previousHoverId);
|
|
if (!previousHover) {
|
|
throw new Error(`Hover with id ${previousHoverId} not found`);
|
|
}
|
|
const hoverContext: vscode.HoverContext = { verbosityDelta: context.verbosityRequest.verbosityDelta, previousHover };
|
|
value = await this._provider.provideHover(doc, pos, token, hoverContext);
|
|
} else {
|
|
value = await this._provider.provideHover(doc, pos, token);
|
|
}
|
|
if (!value || isFalsyOrEmpty(value.contents)) {
|
|
return undefined;
|
|
}
|
|
if (!value.range) {
|
|
value.range = doc.getWordRangeAtPosition(pos);
|
|
}
|
|
if (!value.range) {
|
|
value.range = new Range(pos, pos);
|
|
}
|
|
const convertedHover: languages.Hover = typeConvert.Hover.from(value);
|
|
const id = this._hoverCounter;
|
|
// Check if hover map has more than 10 elements and if yes, remove oldest from the map
|
|
if (this._hoverMap.size === HoverAdapter.HOVER_MAP_MAX_SIZE) {
|
|
const minimumId = Math.min(...this._hoverMap.keys());
|
|
this._hoverMap.delete(minimumId);
|
|
}
|
|
this._hoverMap.set(id, value);
|
|
this._hoverCounter += 1;
|
|
const hover: extHostProtocol.HoverWithId = {
|
|
...convertedHover,
|
|
id
|
|
};
|
|
return hover;
|
|
}
|
|
|
|
releaseHover(id: number): void {
|
|
this._hoverMap.delete(id);
|
|
}
|
|
}
|
|
|
|
class EvaluatableExpressionAdapter {
|
|
|
|
constructor(
|
|
private readonly _documents: ExtHostDocuments,
|
|
private readonly _provider: vscode.EvaluatableExpressionProvider,
|
|
) { }
|
|
|
|
async provideEvaluatableExpression(resource: URI, position: IPosition, token: CancellationToken): Promise<languages.EvaluatableExpression | undefined> {
|
|
|
|
const doc = this._documents.getDocument(resource);
|
|
const pos = typeConvert.Position.to(position);
|
|
|
|
const value = await this._provider.provideEvaluatableExpression(doc, pos, token);
|
|
if (value) {
|
|
return typeConvert.EvaluatableExpression.from(value);
|
|
}
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
class InlineValuesAdapter {
|
|
|
|
constructor(
|
|
private readonly _documents: ExtHostDocuments,
|
|
private readonly _provider: vscode.InlineValuesProvider,
|
|
) { }
|
|
|
|
async provideInlineValues(resource: URI, viewPort: IRange, context: extHostProtocol.IInlineValueContextDto, token: CancellationToken): Promise<languages.InlineValue[] | undefined> {
|
|
const doc = this._documents.getDocument(resource);
|
|
const value = await this._provider.provideInlineValues(doc, typeConvert.Range.to(viewPort), typeConvert.InlineValueContext.to(context), token);
|
|
if (Array.isArray(value)) {
|
|
return value.map(iv => typeConvert.InlineValue.from(iv));
|
|
}
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
class DocumentHighlightAdapter {
|
|
|
|
constructor(
|
|
private readonly _documents: ExtHostDocuments,
|
|
private readonly _provider: vscode.DocumentHighlightProvider
|
|
) { }
|
|
|
|
async provideDocumentHighlights(resource: URI, position: IPosition, token: CancellationToken): Promise<languages.DocumentHighlight[] | undefined> {
|
|
|
|
const doc = this._documents.getDocument(resource);
|
|
const pos = typeConvert.Position.to(position);
|
|
|
|
const value = await this._provider.provideDocumentHighlights(doc, pos, token);
|
|
if (Array.isArray(value)) {
|
|
return value.map(typeConvert.DocumentHighlight.from);
|
|
}
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
class MultiDocumentHighlightAdapter {
|
|
|
|
constructor(
|
|
private readonly _documents: ExtHostDocuments,
|
|
private readonly _provider: vscode.MultiDocumentHighlightProvider,
|
|
private readonly _logService: ILogService,
|
|
) { }
|
|
|
|
async provideMultiDocumentHighlights(resource: URI, position: IPosition, otherResources: URI[], token: CancellationToken): Promise<languages.MultiDocumentHighlight[] | undefined> {
|
|
const doc = this._documents.getDocument(resource);
|
|
const otherDocuments = otherResources.map(r => {
|
|
try {
|
|
return this._documents.getDocument(r);
|
|
} catch (err) {
|
|
this._logService.error('Error: Unable to retrieve document from URI: ' + r + '. Error message: ' + err);
|
|
return undefined;
|
|
}
|
|
}).filter(doc => doc !== undefined);
|
|
|
|
const pos = typeConvert.Position.to(position);
|
|
|
|
const value = await this._provider.provideMultiDocumentHighlights(doc, pos, otherDocuments, token);
|
|
if (Array.isArray(value)) {
|
|
return value.map(typeConvert.MultiDocumentHighlight.from);
|
|
}
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
class LinkedEditingRangeAdapter {
|
|
constructor(
|
|
private readonly _documents: ExtHostDocuments,
|
|
private readonly _provider: vscode.LinkedEditingRangeProvider
|
|
) { }
|
|
|
|
async provideLinkedEditingRanges(resource: URI, position: IPosition, token: CancellationToken): Promise<languages.LinkedEditingRanges | undefined> {
|
|
|
|
const doc = this._documents.getDocument(resource);
|
|
const pos = typeConvert.Position.to(position);
|
|
|
|
const value = await this._provider.provideLinkedEditingRanges(doc, pos, token);
|
|
if (value && Array.isArray(value.ranges)) {
|
|
return {
|
|
ranges: coalesce(value.ranges.map(typeConvert.Range.from)),
|
|
wordPattern: value.wordPattern
|
|
};
|
|
}
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
class ReferenceAdapter {
|
|
|
|
constructor(
|
|
private readonly _documents: ExtHostDocuments,
|
|
private readonly _provider: vscode.ReferenceProvider
|
|
) { }
|
|
|
|
async provideReferences(resource: URI, position: IPosition, context: languages.ReferenceContext, token: CancellationToken): Promise<languages.Location[] | undefined> {
|
|
const doc = this._documents.getDocument(resource);
|
|
const pos = typeConvert.Position.to(position);
|
|
|
|
const value = await this._provider.provideReferences(doc, pos, context, token);
|
|
if (Array.isArray(value)) {
|
|
return value.map(typeConvert.location.from);
|
|
}
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
export interface CustomCodeAction extends extHostProtocol.ICodeActionDto {
|
|
_isSynthetic?: boolean;
|
|
}
|
|
|
|
class CodeActionAdapter {
|
|
private static readonly _maxCodeActionsPerFile: number = 1000;
|
|
|
|
private readonly _cache = new Cache<vscode.CodeAction | vscode.Command>('CodeAction');
|
|
private readonly _disposables = new Map<number, DisposableStore>();
|
|
|
|
constructor(
|
|
private readonly _documents: ExtHostDocuments,
|
|
private readonly _commands: CommandsConverter,
|
|
private readonly _diagnostics: ExtHostDiagnostics,
|
|
private readonly _provider: vscode.CodeActionProvider,
|
|
private readonly _logService: ILogService,
|
|
private readonly _extension: IExtensionDescription,
|
|
private readonly _apiDeprecation: IExtHostApiDeprecationService,
|
|
) { }
|
|
|
|
async provideCodeActions(resource: URI, rangeOrSelection: IRange | ISelection, context: languages.CodeActionContext, token: CancellationToken): Promise<extHostProtocol.ICodeActionListDto | undefined> {
|
|
|
|
const doc = this._documents.getDocument(resource);
|
|
const ran = Selection.isISelection(rangeOrSelection)
|
|
? <vscode.Selection>typeConvert.Selection.to(rangeOrSelection)
|
|
: <vscode.Range>typeConvert.Range.to(rangeOrSelection);
|
|
const allDiagnostics: vscode.Diagnostic[] = [];
|
|
|
|
for (const diagnostic of this._diagnostics.getDiagnostics(resource)) {
|
|
if (ran.intersection(diagnostic.range)) {
|
|
const newLen = allDiagnostics.push(diagnostic);
|
|
if (newLen > CodeActionAdapter._maxCodeActionsPerFile) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
const codeActionContext: vscode.CodeActionContext = {
|
|
diagnostics: allDiagnostics,
|
|
only: context.only ? new CodeActionKind(context.only) : undefined,
|
|
triggerKind: typeConvert.CodeActionTriggerKind.to(context.trigger),
|
|
};
|
|
|
|
const commandsOrActions = await this._provider.provideCodeActions(doc, ran, codeActionContext, token);
|
|
if (!isNonEmptyArray(commandsOrActions) || token.isCancellationRequested) {
|
|
return undefined;
|
|
}
|
|
|
|
const cacheId = this._cache.add(commandsOrActions);
|
|
const disposables = new DisposableStore();
|
|
this._disposables.set(cacheId, disposables);
|
|
const actions: CustomCodeAction[] = [];
|
|
for (let i = 0; i < commandsOrActions.length; i++) {
|
|
const candidate = commandsOrActions[i];
|
|
if (!candidate) {
|
|
continue;
|
|
}
|
|
|
|
if (CodeActionAdapter._isCommand(candidate) && !(candidate instanceof CodeAction)) {
|
|
// old school: synthetic code action
|
|
this._apiDeprecation.report('CodeActionProvider.provideCodeActions - return commands', this._extension,
|
|
`Return 'CodeAction' instances instead.`);
|
|
|
|
actions.push({
|
|
_isSynthetic: true,
|
|
title: candidate.title,
|
|
command: this._commands.toInternal(candidate, disposables),
|
|
});
|
|
} else {
|
|
const toConvert = candidate as vscode.CodeAction;
|
|
|
|
// new school: convert code action
|
|
if (codeActionContext.only) {
|
|
if (!toConvert.kind) {
|
|
this._logService.warn(`${this._extension.identifier.value} - Code actions of kind '${codeActionContext.only.value}' requested but returned code action does not have a 'kind'. Code action will be dropped. Please set 'CodeAction.kind'.`);
|
|
} else if (!codeActionContext.only.contains(toConvert.kind)) {
|
|
this._logService.warn(`${this._extension.identifier.value} - Code actions of kind '${codeActionContext.only.value}' requested but returned code action is of kind '${toConvert.kind.value}'. Code action will be dropped. Please check 'CodeActionContext.only' to only return requested code actions.`);
|
|
}
|
|
}
|
|
|
|
// Ensures that this is either a Range[] or an empty array so we don't get Array<Range | undefined>
|
|
const range = toConvert.ranges ?? [];
|
|
|
|
actions.push({
|
|
cacheId: [cacheId, i],
|
|
title: toConvert.title,
|
|
command: toConvert.command && this._commands.toInternal(toConvert.command, disposables),
|
|
diagnostics: toConvert.diagnostics && toConvert.diagnostics.map(typeConvert.Diagnostic.from),
|
|
edit: toConvert.edit && typeConvert.WorkspaceEdit.from(toConvert.edit, undefined),
|
|
kind: toConvert.kind && toConvert.kind.value,
|
|
isPreferred: toConvert.isPreferred,
|
|
isAI: isProposedApiEnabled(this._extension, 'codeActionAI') ? toConvert.isAI : false,
|
|
ranges: isProposedApiEnabled(this._extension, 'codeActionRanges') ? coalesce(range.map(typeConvert.Range.from)) : undefined,
|
|
disabled: toConvert.disabled?.reason
|
|
});
|
|
}
|
|
}
|
|
return { cacheId, actions };
|
|
}
|
|
|
|
async resolveCodeAction(id: extHostProtocol.ChainedCacheId, token: CancellationToken): Promise<{ edit?: extHostProtocol.IWorkspaceEditDto; command?: extHostProtocol.ICommandDto }> {
|
|
const [sessionId, itemId] = id;
|
|
const item = this._cache.get(sessionId, itemId);
|
|
if (!item || CodeActionAdapter._isCommand(item)) {
|
|
return {}; // code actions only!
|
|
}
|
|
if (!this._provider.resolveCodeAction) {
|
|
return {}; // this should not happen...
|
|
}
|
|
|
|
|
|
const resolvedItem = (await this._provider.resolveCodeAction(item, token)) ?? item;
|
|
|
|
let resolvedEdit: extHostProtocol.IWorkspaceEditDto | undefined;
|
|
if (resolvedItem.edit) {
|
|
resolvedEdit = typeConvert.WorkspaceEdit.from(resolvedItem.edit, undefined);
|
|
}
|
|
|
|
let resolvedCommand: extHostProtocol.ICommandDto | undefined;
|
|
if (resolvedItem.command) {
|
|
const disposables = this._disposables.get(sessionId);
|
|
if (disposables) {
|
|
resolvedCommand = this._commands.toInternal(resolvedItem.command, disposables);
|
|
}
|
|
}
|
|
|
|
return { edit: resolvedEdit, command: resolvedCommand };
|
|
}
|
|
|
|
releaseCodeActions(cachedId: number): void {
|
|
this._disposables.get(cachedId)?.dispose();
|
|
this._disposables.delete(cachedId);
|
|
this._cache.delete(cachedId);
|
|
}
|
|
|
|
private static _isCommand(thing: any): thing is vscode.Command {
|
|
return typeof (<vscode.Command>thing).command === 'string' && typeof (<vscode.Command>thing).title === 'string';
|
|
}
|
|
}
|
|
|
|
class DocumentPasteEditProvider {
|
|
|
|
private _cachedPrepare?: Map<string, vscode.DataTransferItem>;
|
|
|
|
private readonly _editsCache = new Cache<vscode.DocumentPasteEdit>('DocumentPasteEdit.edits');
|
|
|
|
constructor(
|
|
private readonly _proxy: extHostProtocol.MainThreadLanguageFeaturesShape,
|
|
private readonly _documents: ExtHostDocuments,
|
|
private readonly _provider: vscode.DocumentPasteEditProvider,
|
|
private readonly _handle: number,
|
|
private readonly _extension: IExtensionDescription,
|
|
) { }
|
|
|
|
async prepareDocumentPaste(resource: URI, ranges: IRange[], dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise<extHostProtocol.DataTransferDTO | undefined> {
|
|
if (!this._provider.prepareDocumentPaste) {
|
|
return;
|
|
}
|
|
|
|
this._cachedPrepare = undefined;
|
|
|
|
const doc = this._documents.getDocument(resource);
|
|
const vscodeRanges = ranges.map(range => typeConvert.Range.to(range));
|
|
|
|
const dataTransfer = typeConvert.DataTransfer.toDataTransfer(dataTransferDto, () => {
|
|
throw new NotImplementedError();
|
|
});
|
|
await this._provider.prepareDocumentPaste(doc, vscodeRanges, dataTransfer, token);
|
|
if (token.isCancellationRequested) {
|
|
return;
|
|
}
|
|
|
|
// Only send back values that have been added to the data transfer
|
|
const newEntries = Array.from(dataTransfer).filter(([, value]) => !(value instanceof InternalDataTransferItem));
|
|
|
|
// Store off original data transfer items so we can retrieve them on paste
|
|
const newCache = new Map<string, vscode.DataTransferItem>();
|
|
|
|
const items = await Promise.all(Array.from(newEntries, async ([mime, value]) => {
|
|
const id = generateUuid();
|
|
newCache.set(id, value);
|
|
return [mime, await typeConvert.DataTransferItem.from(mime, value, id)] as const;
|
|
}));
|
|
|
|
this._cachedPrepare = newCache;
|
|
|
|
return { items };
|
|
}
|
|
|
|
async providePasteEdits(requestId: number, resource: URI, ranges: IRange[], dataTransferDto: extHostProtocol.DataTransferDTO, context: extHostProtocol.IDocumentPasteContextDto, token: CancellationToken): Promise<extHostProtocol.IPasteEditDto[]> {
|
|
if (!this._provider.provideDocumentPasteEdits) {
|
|
return [];
|
|
}
|
|
|
|
const doc = this._documents.getDocument(resource);
|
|
const vscodeRanges = ranges.map(range => typeConvert.Range.to(range));
|
|
|
|
const items = dataTransferDto.items.map(([mime, value]): [string, vscode.DataTransferItem] => {
|
|
const cached = this._cachedPrepare?.get(value.id);
|
|
if (cached) {
|
|
return [mime, cached];
|
|
}
|
|
|
|
return [
|
|
mime,
|
|
typeConvert.DataTransferItem.to(mime, value, async id => {
|
|
return (await this._proxy.$resolvePasteFileData(this._handle, requestId, id)).buffer;
|
|
})
|
|
];
|
|
});
|
|
|
|
const dataTransfer = new DataTransfer(items);
|
|
|
|
const edits = await this._provider.provideDocumentPasteEdits(doc, vscodeRanges, dataTransfer, {
|
|
only: context.only ? new DocumentDropOrPasteEditKind(context.only) : undefined,
|
|
triggerKind: context.triggerKind,
|
|
}, token);
|
|
if (!edits || token.isCancellationRequested) {
|
|
return [];
|
|
}
|
|
|
|
const cacheId = this._editsCache.add(edits);
|
|
|
|
return edits.map((edit, i): extHostProtocol.IPasteEditDto => ({
|
|
_cacheId: [cacheId, i],
|
|
title: edit.title ?? localize('defaultPasteLabel', "Paste using '{0}' extension", this._extension.displayName || this._extension.name),
|
|
kind: edit.kind,
|
|
yieldTo: edit.yieldTo?.map(x => x.value),
|
|
insertText: typeof edit.insertText === 'string' ? edit.insertText : { snippet: edit.insertText.value },
|
|
additionalEdit: edit.additionalEdit ? typeConvert.WorkspaceEdit.from(edit.additionalEdit, undefined) : undefined,
|
|
}));
|
|
}
|
|
|
|
async resolvePasteEdit(id: extHostProtocol.ChainedCacheId, token: CancellationToken): Promise<{ insertText?: string | vscode.SnippetString; additionalEdit?: extHostProtocol.IWorkspaceEditDto }> {
|
|
const [sessionId, itemId] = id;
|
|
const item = this._editsCache.get(sessionId, itemId);
|
|
if (!item || !this._provider.resolveDocumentPasteEdit) {
|
|
return {}; // this should not happen...
|
|
}
|
|
|
|
const resolvedItem = (await this._provider.resolveDocumentPasteEdit(item, token)) ?? item;
|
|
return {
|
|
insertText: resolvedItem.insertText,
|
|
additionalEdit: resolvedItem.additionalEdit ? typeConvert.WorkspaceEdit.from(resolvedItem.additionalEdit, undefined) : undefined
|
|
};
|
|
}
|
|
|
|
releasePasteEdits(id: number): any {
|
|
this._editsCache.delete(id);
|
|
}
|
|
}
|
|
|
|
class DocumentFormattingAdapter {
|
|
|
|
constructor(
|
|
private readonly _documents: ExtHostDocuments,
|
|
private readonly _provider: vscode.DocumentFormattingEditProvider
|
|
) { }
|
|
|
|
async provideDocumentFormattingEdits(resource: URI, options: languages.FormattingOptions, token: CancellationToken): Promise<languages.TextEdit[] | undefined> {
|
|
|
|
const document = this._documents.getDocument(resource);
|
|
|
|
// eslint-disable-next-line local/code-no-any-casts
|
|
const value = await this._provider.provideDocumentFormattingEdits(document, <any>options, token);
|
|
if (Array.isArray(value)) {
|
|
return value.map(typeConvert.TextEdit.from);
|
|
}
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
class RangeFormattingAdapter {
|
|
|
|
constructor(
|
|
private readonly _documents: ExtHostDocuments,
|
|
private readonly _provider: vscode.DocumentRangeFormattingEditProvider
|
|
) { }
|
|
|
|
async provideDocumentRangeFormattingEdits(resource: URI, range: IRange, options: languages.FormattingOptions, token: CancellationToken): Promise<languages.TextEdit[] | undefined> {
|
|
|
|
const document = this._documents.getDocument(resource);
|
|
const ran = typeConvert.Range.to(range);
|
|
|
|
// eslint-disable-next-line local/code-no-any-casts
|
|
const value = await this._provider.provideDocumentRangeFormattingEdits(document, ran, <any>options, token);
|
|
if (Array.isArray(value)) {
|
|
return value.map(typeConvert.TextEdit.from);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
async provideDocumentRangesFormattingEdits(resource: URI, ranges: IRange[], options: languages.FormattingOptions, token: CancellationToken): Promise<languages.TextEdit[] | undefined> {
|
|
assertType(typeof this._provider.provideDocumentRangesFormattingEdits === 'function', 'INVALID invocation of `provideDocumentRangesFormattingEdits`');
|
|
|
|
const document = this._documents.getDocument(resource);
|
|
const _ranges = <Range[]>ranges.map(typeConvert.Range.to);
|
|
// eslint-disable-next-line local/code-no-any-casts
|
|
const value = await this._provider.provideDocumentRangesFormattingEdits(document, _ranges, <any>options, token);
|
|
if (Array.isArray(value)) {
|
|
return value.map(typeConvert.TextEdit.from);
|
|
}
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
class OnTypeFormattingAdapter {
|
|
|
|
constructor(
|
|
private readonly _documents: ExtHostDocuments,
|
|
private readonly _provider: vscode.OnTypeFormattingEditProvider
|
|
) { }
|
|
|
|
autoFormatTriggerCharacters: string[] = []; // not here
|
|
|
|
async provideOnTypeFormattingEdits(resource: URI, position: IPosition, ch: string, options: languages.FormattingOptions, token: CancellationToken): Promise<languages.TextEdit[] | undefined> {
|
|
|
|
const document = this._documents.getDocument(resource);
|
|
const pos = typeConvert.Position.to(position);
|
|
|
|
// eslint-disable-next-line local/code-no-any-casts
|
|
const value = await this._provider.provideOnTypeFormattingEdits(document, pos, ch, <any>options, token);
|
|
if (Array.isArray(value)) {
|
|
return value.map(typeConvert.TextEdit.from);
|
|
}
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
class NavigateTypeAdapter {
|
|
|
|
private readonly _cache = new Cache<vscode.SymbolInformation>('WorkspaceSymbols');
|
|
|
|
constructor(
|
|
private readonly _provider: vscode.WorkspaceSymbolProvider,
|
|
private readonly _logService: ILogService
|
|
) { }
|
|
|
|
async provideWorkspaceSymbols(search: string, token: CancellationToken): Promise<extHostProtocol.IWorkspaceSymbolsDto> {
|
|
const value = await this._provider.provideWorkspaceSymbols(search, token);
|
|
|
|
if (!isNonEmptyArray(value)) {
|
|
return { symbols: [] };
|
|
}
|
|
|
|
const sid = this._cache.add(value);
|
|
const result: extHostProtocol.IWorkspaceSymbolsDto = {
|
|
cacheId: sid,
|
|
symbols: []
|
|
};
|
|
|
|
for (let i = 0; i < value.length; i++) {
|
|
const item = value[i];
|
|
if (!item || !item.name) {
|
|
this._logService.warn('INVALID SymbolInformation', item);
|
|
continue;
|
|
}
|
|
result.symbols.push({
|
|
...typeConvert.WorkspaceSymbol.from(item),
|
|
cacheId: [sid, i]
|
|
});
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
async resolveWorkspaceSymbol(symbol: extHostProtocol.IWorkspaceSymbolDto, token: CancellationToken): Promise<extHostProtocol.IWorkspaceSymbolDto | undefined> {
|
|
if (typeof this._provider.resolveWorkspaceSymbol !== 'function') {
|
|
return symbol;
|
|
}
|
|
if (!symbol.cacheId) {
|
|
return symbol;
|
|
}
|
|
const item = this._cache.get(...symbol.cacheId);
|
|
if (item) {
|
|
const value = await this._provider.resolveWorkspaceSymbol(item, token);
|
|
return value && mixin(symbol, typeConvert.WorkspaceSymbol.from(value), true);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
releaseWorkspaceSymbols(id: number): any {
|
|
this._cache.delete(id);
|
|
}
|
|
}
|
|
|
|
class RenameAdapter {
|
|
|
|
static supportsResolving(provider: vscode.RenameProvider): boolean {
|
|
return typeof provider.prepareRename === 'function';
|
|
}
|
|
|
|
constructor(
|
|
private readonly _documents: ExtHostDocuments,
|
|
private readonly _provider: vscode.RenameProvider,
|
|
private readonly _logService: ILogService
|
|
) { }
|
|
|
|
async provideRenameEdits(resource: URI, position: IPosition, newName: string, token: CancellationToken): Promise<extHostProtocol.IWorkspaceEditDto & languages.Rejection | undefined> {
|
|
|
|
const doc = this._documents.getDocument(resource);
|
|
const pos = typeConvert.Position.to(position);
|
|
|
|
try {
|
|
const value = await this._provider.provideRenameEdits(doc, pos, newName, token);
|
|
if (!value) {
|
|
return undefined;
|
|
}
|
|
return typeConvert.WorkspaceEdit.from(value);
|
|
|
|
} catch (err) {
|
|
const rejectReason = RenameAdapter._asMessage(err);
|
|
if (rejectReason) {
|
|
return { rejectReason, edits: undefined! };
|
|
} else {
|
|
// generic error
|
|
return Promise.reject<extHostProtocol.IWorkspaceEditDto>(err);
|
|
}
|
|
}
|
|
}
|
|
|
|
async resolveRenameLocation(resource: URI, position: IPosition, token: CancellationToken): Promise<(languages.RenameLocation & languages.Rejection) | undefined> {
|
|
if (typeof this._provider.prepareRename !== 'function') {
|
|
return Promise.resolve(undefined);
|
|
}
|
|
|
|
const doc = this._documents.getDocument(resource);
|
|
const pos = typeConvert.Position.to(position);
|
|
|
|
try {
|
|
const rangeOrLocation = await this._provider.prepareRename(doc, pos, token);
|
|
|
|
let range: vscode.Range | undefined;
|
|
let text: string | undefined;
|
|
if (Range.isRange(rangeOrLocation)) {
|
|
range = rangeOrLocation;
|
|
text = doc.getText(rangeOrLocation);
|
|
|
|
} else if (isObject(rangeOrLocation)) {
|
|
range = rangeOrLocation.range;
|
|
text = rangeOrLocation.placeholder;
|
|
}
|
|
|
|
if (!range || !text) {
|
|
return undefined;
|
|
}
|
|
if (range.start.line > pos.line || range.end.line < pos.line) {
|
|
this._logService.warn('INVALID rename location: position line must be within range start/end lines');
|
|
return undefined;
|
|
}
|
|
return { range: typeConvert.Range.from(range), text };
|
|
|
|
} catch (err) {
|
|
const rejectReason = RenameAdapter._asMessage(err);
|
|
if (rejectReason) {
|
|
return { rejectReason, range: undefined!, text: undefined! };
|
|
} else {
|
|
return Promise.reject<any>(err);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static _asMessage(err: any): string | undefined {
|
|
if (typeof err === 'string') {
|
|
return err;
|
|
} else if (err instanceof Error && typeof err.message === 'string') {
|
|
return err.message;
|
|
} else {
|
|
return undefined;
|
|
}
|
|
}
|
|
}
|
|
|
|
class NewSymbolNamesAdapter {
|
|
|
|
private static languageTriggerKindToVSCodeTriggerKind: Record<languages.NewSymbolNameTriggerKind, vscode.NewSymbolNameTriggerKind> = {
|
|
[languages.NewSymbolNameTriggerKind.Invoke]: NewSymbolNameTriggerKind.Invoke,
|
|
[languages.NewSymbolNameTriggerKind.Automatic]: NewSymbolNameTriggerKind.Automatic,
|
|
};
|
|
|
|
constructor(
|
|
private readonly _documents: ExtHostDocuments,
|
|
private readonly _provider: vscode.NewSymbolNamesProvider,
|
|
private readonly _logService: ILogService
|
|
) { }
|
|
|
|
async supportsAutomaticNewSymbolNamesTriggerKind() {
|
|
return this._provider.supportsAutomaticTriggerKind;
|
|
}
|
|
|
|
async provideNewSymbolNames(resource: URI, range: IRange, triggerKind: languages.NewSymbolNameTriggerKind, token: CancellationToken): Promise<languages.NewSymbolName[] | undefined> {
|
|
|
|
const doc = this._documents.getDocument(resource);
|
|
const pos = typeConvert.Range.to(range);
|
|
|
|
try {
|
|
const kind = NewSymbolNamesAdapter.languageTriggerKindToVSCodeTriggerKind[triggerKind];
|
|
const value = await this._provider.provideNewSymbolNames(doc, pos, kind, token);
|
|
if (!value) {
|
|
return undefined;
|
|
}
|
|
return value.map(v =>
|
|
typeof v === 'string' /* @ulugbekna: for backward compatibility because `value` used to be just `string[]` */
|
|
? { newSymbolName: v }
|
|
: { newSymbolName: v.newSymbolName, tags: v.tags }
|
|
);
|
|
} catch (err: unknown) {
|
|
this._logService.error(NewSymbolNamesAdapter._asMessage(err) ?? JSON.stringify(err, null, '\t') /* @ulugbekna: assuming `err` doesn't have circular references that could result in an exception when converting to JSON */);
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
// @ulugbekna: this method is also defined in RenameAdapter but seems OK to be duplicated
|
|
private static _asMessage(err: any): string | undefined {
|
|
if (typeof err === 'string') {
|
|
return err;
|
|
} else if (err instanceof Error && typeof err.message === 'string') {
|
|
return err.message;
|
|
} else {
|
|
return undefined;
|
|
}
|
|
}
|
|
}
|
|
|
|
class SemanticTokensPreviousResult {
|
|
constructor(
|
|
readonly resultId: string | undefined,
|
|
readonly tokens?: Uint32Array,
|
|
) { }
|
|
}
|
|
|
|
type RelaxedSemanticTokens = { readonly resultId?: string; readonly data: number[] };
|
|
type RelaxedSemanticTokensEdit = { readonly start: number; readonly deleteCount: number; readonly data?: number[] };
|
|
type RelaxedSemanticTokensEdits = { readonly resultId?: string; readonly edits: RelaxedSemanticTokensEdit[] };
|
|
|
|
type ProvidedSemanticTokens = vscode.SemanticTokens | RelaxedSemanticTokens;
|
|
type ProvidedSemanticTokensEdits = vscode.SemanticTokensEdits | RelaxedSemanticTokensEdits;
|
|
|
|
class DocumentSemanticTokensAdapter {
|
|
|
|
private readonly _previousResults: Map<number, SemanticTokensPreviousResult>;
|
|
private _nextResultId = 1;
|
|
|
|
constructor(
|
|
private readonly _documents: ExtHostDocuments,
|
|
private readonly _provider: vscode.DocumentSemanticTokensProvider,
|
|
) {
|
|
this._previousResults = new Map<number, SemanticTokensPreviousResult>();
|
|
}
|
|
|
|
async provideDocumentSemanticTokens(resource: URI, previousResultId: number, token: CancellationToken): Promise<VSBuffer | null> {
|
|
const doc = this._documents.getDocument(resource);
|
|
const previousResult = (previousResultId !== 0 ? this._previousResults.get(previousResultId) : null);
|
|
let value = typeof previousResult?.resultId === 'string' && typeof this._provider.provideDocumentSemanticTokensEdits === 'function'
|
|
? await this._provider.provideDocumentSemanticTokensEdits(doc, previousResult.resultId, token)
|
|
: await this._provider.provideDocumentSemanticTokens(doc, token);
|
|
|
|
if (previousResult) {
|
|
this._previousResults.delete(previousResultId);
|
|
}
|
|
if (!value) {
|
|
return null;
|
|
}
|
|
value = DocumentSemanticTokensAdapter._fixProvidedSemanticTokens(value);
|
|
return this._send(DocumentSemanticTokensAdapter._convertToEdits(previousResult, value), value);
|
|
}
|
|
|
|
async releaseDocumentSemanticColoring(semanticColoringResultId: number): Promise<void> {
|
|
this._previousResults.delete(semanticColoringResultId);
|
|
}
|
|
|
|
private static _fixProvidedSemanticTokens(v: ProvidedSemanticTokens | ProvidedSemanticTokensEdits): vscode.SemanticTokens | vscode.SemanticTokensEdits {
|
|
if (DocumentSemanticTokensAdapter._isSemanticTokens(v)) {
|
|
if (DocumentSemanticTokensAdapter._isCorrectSemanticTokens(v)) {
|
|
return v;
|
|
}
|
|
return new SemanticTokens(new Uint32Array(v.data), v.resultId);
|
|
} else if (DocumentSemanticTokensAdapter._isSemanticTokensEdits(v)) {
|
|
if (DocumentSemanticTokensAdapter._isCorrectSemanticTokensEdits(v)) {
|
|
return v;
|
|
}
|
|
return new SemanticTokensEdits(v.edits.map(edit => new SemanticTokensEdit(edit.start, edit.deleteCount, edit.data ? new Uint32Array(edit.data) : edit.data)), v.resultId);
|
|
}
|
|
return v;
|
|
}
|
|
|
|
private static _isSemanticTokens(v: ProvidedSemanticTokens | ProvidedSemanticTokensEdits): v is ProvidedSemanticTokens {
|
|
return v && !!((v as ProvidedSemanticTokens).data);
|
|
}
|
|
|
|
private static _isCorrectSemanticTokens(v: ProvidedSemanticTokens): v is vscode.SemanticTokens {
|
|
return (v.data instanceof Uint32Array);
|
|
}
|
|
|
|
private static _isSemanticTokensEdits(v: ProvidedSemanticTokens | ProvidedSemanticTokensEdits): v is ProvidedSemanticTokensEdits {
|
|
return v && Array.isArray((v as ProvidedSemanticTokensEdits).edits);
|
|
}
|
|
|
|
private static _isCorrectSemanticTokensEdits(v: ProvidedSemanticTokensEdits): v is vscode.SemanticTokensEdits {
|
|
for (const edit of v.edits) {
|
|
if (!(edit.data instanceof Uint32Array)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private static _convertToEdits(previousResult: SemanticTokensPreviousResult | null | undefined, newResult: vscode.SemanticTokens | vscode.SemanticTokensEdits): vscode.SemanticTokens | vscode.SemanticTokensEdits {
|
|
if (!DocumentSemanticTokensAdapter._isSemanticTokens(newResult)) {
|
|
return newResult;
|
|
}
|
|
if (!previousResult || !previousResult.tokens) {
|
|
return newResult;
|
|
}
|
|
const oldData = previousResult.tokens;
|
|
const oldLength = oldData.length;
|
|
const newData = newResult.data;
|
|
const newLength = newData.length;
|
|
|
|
let commonPrefixLength = 0;
|
|
const maxCommonPrefixLength = Math.min(oldLength, newLength);
|
|
while (commonPrefixLength < maxCommonPrefixLength && oldData[commonPrefixLength] === newData[commonPrefixLength]) {
|
|
commonPrefixLength++;
|
|
}
|
|
|
|
if (commonPrefixLength === oldLength && commonPrefixLength === newLength) {
|
|
// complete overlap!
|
|
return new SemanticTokensEdits([], newResult.resultId);
|
|
}
|
|
|
|
let commonSuffixLength = 0;
|
|
const maxCommonSuffixLength = maxCommonPrefixLength - commonPrefixLength;
|
|
while (commonSuffixLength < maxCommonSuffixLength && oldData[oldLength - commonSuffixLength - 1] === newData[newLength - commonSuffixLength - 1]) {
|
|
commonSuffixLength++;
|
|
}
|
|
|
|
return new SemanticTokensEdits([{
|
|
start: commonPrefixLength,
|
|
deleteCount: (oldLength - commonPrefixLength - commonSuffixLength),
|
|
data: newData.subarray(commonPrefixLength, newLength - commonSuffixLength)
|
|
}], newResult.resultId);
|
|
}
|
|
|
|
private _send(value: vscode.SemanticTokens | vscode.SemanticTokensEdits, original: vscode.SemanticTokens | vscode.SemanticTokensEdits): VSBuffer | null {
|
|
if (DocumentSemanticTokensAdapter._isSemanticTokens(value)) {
|
|
const myId = this._nextResultId++;
|
|
this._previousResults.set(myId, new SemanticTokensPreviousResult(value.resultId, value.data));
|
|
return encodeSemanticTokensDto({
|
|
id: myId,
|
|
type: 'full',
|
|
data: value.data
|
|
});
|
|
}
|
|
|
|
if (DocumentSemanticTokensAdapter._isSemanticTokensEdits(value)) {
|
|
const myId = this._nextResultId++;
|
|
if (DocumentSemanticTokensAdapter._isSemanticTokens(original)) {
|
|
// store the original
|
|
this._previousResults.set(myId, new SemanticTokensPreviousResult(original.resultId, original.data));
|
|
} else {
|
|
this._previousResults.set(myId, new SemanticTokensPreviousResult(value.resultId));
|
|
}
|
|
return encodeSemanticTokensDto({
|
|
id: myId,
|
|
type: 'delta',
|
|
deltas: (value.edits || []).map(edit => ({ start: edit.start, deleteCount: edit.deleteCount, data: edit.data }))
|
|
});
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
class DocumentRangeSemanticTokensAdapter {
|
|
|
|
constructor(
|
|
private readonly _documents: ExtHostDocuments,
|
|
private readonly _provider: vscode.DocumentRangeSemanticTokensProvider,
|
|
) { }
|
|
|
|
async provideDocumentRangeSemanticTokens(resource: URI, range: IRange, token: CancellationToken): Promise<VSBuffer | null> {
|
|
const doc = this._documents.getDocument(resource);
|
|
const value = await this._provider.provideDocumentRangeSemanticTokens(doc, typeConvert.Range.to(range), token);
|
|
if (!value) {
|
|
return null;
|
|
}
|
|
return this._send(value);
|
|
}
|
|
|
|
private _send(value: vscode.SemanticTokens): VSBuffer {
|
|
return encodeSemanticTokensDto({
|
|
id: 0,
|
|
type: 'full',
|
|
data: value.data
|
|
});
|
|
}
|
|
}
|
|
|
|
class CompletionsAdapter {
|
|
|
|
static supportsResolving(provider: vscode.CompletionItemProvider): boolean {
|
|
return typeof provider.resolveCompletionItem === 'function';
|
|
}
|
|
|
|
private _cache = new Cache<vscode.CompletionItem>('CompletionItem');
|
|
private _disposables = new Map<number, DisposableStore>();
|
|
|
|
constructor(
|
|
private readonly _documents: ExtHostDocuments,
|
|
private readonly _commands: CommandsConverter,
|
|
private readonly _provider: vscode.CompletionItemProvider,
|
|
private readonly _apiDeprecation: IExtHostApiDeprecationService,
|
|
private readonly _extension: IExtensionDescription,
|
|
) { }
|
|
|
|
async provideCompletionItems(resource: URI, position: IPosition, context: languages.CompletionContext, token: CancellationToken): Promise<extHostProtocol.ISuggestResultDto | undefined> {
|
|
|
|
const doc = this._documents.getDocument(resource);
|
|
const pos = typeConvert.Position.to(position);
|
|
|
|
// The default insert/replace ranges. It's important to compute them
|
|
// before asynchronously asking the provider for its results. See
|
|
// https://github.com/microsoft/vscode/issues/83400#issuecomment-546851421
|
|
const replaceRange = doc.getWordRangeAtPosition(pos) || new Range(pos, pos);
|
|
const insertRange = replaceRange.with({ end: pos });
|
|
|
|
const sw = new StopWatch();
|
|
const itemsOrList = await this._provider.provideCompletionItems(doc, pos, token, typeConvert.CompletionContext.to(context));
|
|
|
|
if (!itemsOrList) {
|
|
// undefined and null are valid results
|
|
return undefined;
|
|
}
|
|
|
|
if (token.isCancellationRequested) {
|
|
// cancelled -> return without further ado, esp no caching
|
|
// of results as they will leak
|
|
return undefined;
|
|
}
|
|
|
|
const list = Array.isArray(itemsOrList) ? new CompletionList(itemsOrList) : itemsOrList;
|
|
|
|
// keep result for providers that support resolving
|
|
const pid: number = CompletionsAdapter.supportsResolving(this._provider) ? this._cache.add(list.items) : this._cache.add([]);
|
|
const disposables = new DisposableStore();
|
|
this._disposables.set(pid, disposables);
|
|
|
|
const completions: extHostProtocol.ISuggestDataDto[] = [];
|
|
const result: extHostProtocol.ISuggestResultDto = {
|
|
x: pid,
|
|
[extHostProtocol.ISuggestResultDtoField.completions]: completions,
|
|
[extHostProtocol.ISuggestResultDtoField.defaultRanges]: { replace: typeConvert.Range.from(replaceRange), insert: typeConvert.Range.from(insertRange) },
|
|
[extHostProtocol.ISuggestResultDtoField.isIncomplete]: list.isIncomplete || undefined,
|
|
[extHostProtocol.ISuggestResultDtoField.duration]: sw.elapsed()
|
|
};
|
|
|
|
for (let i = 0; i < list.items.length; i++) {
|
|
const item = list.items[i];
|
|
// check for bad completion item first
|
|
const dto = this._convertCompletionItem(item, [pid, i], insertRange, replaceRange);
|
|
completions.push(dto);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
async resolveCompletionItem(id: extHostProtocol.ChainedCacheId, token: CancellationToken): Promise<extHostProtocol.ISuggestDataDto | undefined> {
|
|
|
|
if (typeof this._provider.resolveCompletionItem !== 'function') {
|
|
return undefined;
|
|
}
|
|
|
|
const item = this._cache.get(...id);
|
|
if (!item) {
|
|
return undefined;
|
|
}
|
|
|
|
const dto1 = this._convertCompletionItem(item, id);
|
|
|
|
const resolvedItem = await this._provider.resolveCompletionItem(item, token);
|
|
|
|
if (!resolvedItem) {
|
|
return undefined;
|
|
}
|
|
|
|
const dto2 = this._convertCompletionItem(resolvedItem, id);
|
|
|
|
if (dto1[extHostProtocol.ISuggestDataDtoField.insertText] !== dto2[extHostProtocol.ISuggestDataDtoField.insertText]
|
|
|| dto1[extHostProtocol.ISuggestDataDtoField.insertTextRules] !== dto2[extHostProtocol.ISuggestDataDtoField.insertTextRules]
|
|
) {
|
|
this._apiDeprecation.report('CompletionItem.insertText', this._extension, 'extension MAY NOT change \'insertText\' of a CompletionItem during resolve');
|
|
}
|
|
|
|
if (dto1[extHostProtocol.ISuggestDataDtoField.commandIdent] !== dto2[extHostProtocol.ISuggestDataDtoField.commandIdent]
|
|
|| dto1[extHostProtocol.ISuggestDataDtoField.commandId] !== dto2[extHostProtocol.ISuggestDataDtoField.commandId]
|
|
|| !equals(dto1[extHostProtocol.ISuggestDataDtoField.commandArguments], dto2[extHostProtocol.ISuggestDataDtoField.commandArguments])
|
|
) {
|
|
this._apiDeprecation.report('CompletionItem.command', this._extension, 'extension MAY NOT change \'command\' of a CompletionItem during resolve');
|
|
}
|
|
|
|
return {
|
|
...dto1,
|
|
[extHostProtocol.ISuggestDataDtoField.documentation]: dto2[extHostProtocol.ISuggestDataDtoField.documentation],
|
|
[extHostProtocol.ISuggestDataDtoField.detail]: dto2[extHostProtocol.ISuggestDataDtoField.detail],
|
|
[extHostProtocol.ISuggestDataDtoField.additionalTextEdits]: dto2[extHostProtocol.ISuggestDataDtoField.additionalTextEdits],
|
|
|
|
// (fishy) async insertText
|
|
[extHostProtocol.ISuggestDataDtoField.insertText]: dto2[extHostProtocol.ISuggestDataDtoField.insertText],
|
|
[extHostProtocol.ISuggestDataDtoField.insertTextRules]: dto2[extHostProtocol.ISuggestDataDtoField.insertTextRules],
|
|
|
|
// (fishy) async command
|
|
[extHostProtocol.ISuggestDataDtoField.commandIdent]: dto2[extHostProtocol.ISuggestDataDtoField.commandIdent],
|
|
[extHostProtocol.ISuggestDataDtoField.commandId]: dto2[extHostProtocol.ISuggestDataDtoField.commandId],
|
|
[extHostProtocol.ISuggestDataDtoField.commandArguments]: dto2[extHostProtocol.ISuggestDataDtoField.commandArguments],
|
|
};
|
|
}
|
|
|
|
releaseCompletionItems(id: number): any {
|
|
this._disposables.get(id)?.dispose();
|
|
this._disposables.delete(id);
|
|
this._cache.delete(id);
|
|
}
|
|
|
|
private _convertCompletionItem(item: vscode.CompletionItem, id: extHostProtocol.ChainedCacheId, defaultInsertRange?: vscode.Range, defaultReplaceRange?: vscode.Range): extHostProtocol.ISuggestDataDto {
|
|
|
|
const disposables = this._disposables.get(id[0]);
|
|
if (!disposables) {
|
|
throw Error('DisposableStore is missing...');
|
|
}
|
|
|
|
const command = this._commands.toInternal(item.command, disposables);
|
|
const result: extHostProtocol.ISuggestDataDto = {
|
|
//
|
|
x: id,
|
|
//
|
|
[extHostProtocol.ISuggestDataDtoField.label]: item.label,
|
|
[extHostProtocol.ISuggestDataDtoField.kind]: item.kind !== undefined ? typeConvert.CompletionItemKind.from(item.kind) : undefined,
|
|
[extHostProtocol.ISuggestDataDtoField.kindModifier]: item.tags && item.tags.map(typeConvert.CompletionItemTag.from),
|
|
[extHostProtocol.ISuggestDataDtoField.detail]: item.detail,
|
|
[extHostProtocol.ISuggestDataDtoField.documentation]: typeof item.documentation === 'undefined' ? undefined : typeConvert.MarkdownString.fromStrict(item.documentation),
|
|
[extHostProtocol.ISuggestDataDtoField.sortText]: item.sortText !== item.label ? item.sortText : undefined,
|
|
[extHostProtocol.ISuggestDataDtoField.filterText]: item.filterText !== item.label ? item.filterText : undefined,
|
|
[extHostProtocol.ISuggestDataDtoField.preselect]: item.preselect || undefined,
|
|
[extHostProtocol.ISuggestDataDtoField.insertTextRules]: item.keepWhitespace ? languages.CompletionItemInsertTextRule.KeepWhitespace : languages.CompletionItemInsertTextRule.None,
|
|
[extHostProtocol.ISuggestDataDtoField.commitCharacters]: item.commitCharacters?.join(''),
|
|
[extHostProtocol.ISuggestDataDtoField.additionalTextEdits]: item.additionalTextEdits && item.additionalTextEdits.map(typeConvert.TextEdit.from),
|
|
[extHostProtocol.ISuggestDataDtoField.commandIdent]: command?.$ident,
|
|
[extHostProtocol.ISuggestDataDtoField.commandId]: command?.id,
|
|
[extHostProtocol.ISuggestDataDtoField.commandArguments]: command?.$ident ? undefined : command?.arguments, // filled in on main side from $ident
|
|
};
|
|
|
|
// 'insertText'-logic
|
|
if (item.textEdit) {
|
|
this._apiDeprecation.report('CompletionItem.textEdit', this._extension, `Use 'CompletionItem.insertText' and 'CompletionItem.range' instead.`);
|
|
result[extHostProtocol.ISuggestDataDtoField.insertText] = item.textEdit.newText;
|
|
|
|
} else if (typeof item.insertText === 'string') {
|
|
result[extHostProtocol.ISuggestDataDtoField.insertText] = item.insertText;
|
|
|
|
} else if (item.insertText instanceof SnippetString) {
|
|
result[extHostProtocol.ISuggestDataDtoField.insertText] = item.insertText.value;
|
|
result[extHostProtocol.ISuggestDataDtoField.insertTextRules]! |= languages.CompletionItemInsertTextRule.InsertAsSnippet;
|
|
}
|
|
|
|
// 'overwrite[Before|After]'-logic
|
|
let range: vscode.Range | { inserting: vscode.Range; replacing: vscode.Range } | undefined;
|
|
if (item.textEdit) {
|
|
range = item.textEdit.range;
|
|
} else if (item.range) {
|
|
range = item.range;
|
|
}
|
|
|
|
if (Range.isRange(range)) {
|
|
// "old" range
|
|
result[extHostProtocol.ISuggestDataDtoField.range] = typeConvert.Range.from(range);
|
|
|
|
} else if (range && (!defaultInsertRange?.isEqual(range.inserting) || !defaultReplaceRange?.isEqual(range.replacing))) {
|
|
// ONLY send range when it's different from the default ranges (safe bandwidth)
|
|
result[extHostProtocol.ISuggestDataDtoField.range] = {
|
|
insert: typeConvert.Range.from(range.inserting),
|
|
replace: typeConvert.Range.from(range.replacing)
|
|
};
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
class InlineCompletionAdapter {
|
|
private readonly _references = new ReferenceMap<{
|
|
dispose(): void;
|
|
items: readonly vscode.InlineCompletionItem[];
|
|
list: vscode.InlineCompletionList | undefined;
|
|
}>();
|
|
|
|
private readonly _isAdditionsProposedApiEnabled: boolean;
|
|
|
|
constructor(
|
|
private readonly _extension: IExtensionDescription,
|
|
private readonly _documents: ExtHostDocuments,
|
|
private readonly _provider: vscode.InlineCompletionItemProvider,
|
|
private readonly _commands: CommandsConverter,
|
|
) {
|
|
this._isAdditionsProposedApiEnabled = isProposedApiEnabled(this._extension, 'inlineCompletionsAdditions');
|
|
}
|
|
|
|
public get supportsHandleEvents(): boolean {
|
|
return isProposedApiEnabled(this._extension, 'inlineCompletionsAdditions')
|
|
&& (typeof this._provider.handleDidShowCompletionItem === 'function'
|
|
|| typeof this._provider.handleDidPartiallyAcceptCompletionItem === 'function'
|
|
|| typeof this._provider.handleDidRejectCompletionItem === 'function'
|
|
|| typeof this._provider.handleEndOfLifetime === 'function'
|
|
);
|
|
}
|
|
|
|
private readonly languageTriggerKindToVSCodeTriggerKind: Record<languages.InlineCompletionTriggerKind, InlineCompletionTriggerKind> = {
|
|
[languages.InlineCompletionTriggerKind.Automatic]: InlineCompletionTriggerKind.Automatic,
|
|
[languages.InlineCompletionTriggerKind.Explicit]: InlineCompletionTriggerKind.Invoke,
|
|
};
|
|
|
|
async provideInlineCompletions(resource: URI, position: IPosition, context: languages.InlineCompletionContext, token: CancellationToken): Promise<extHostProtocol.IdentifiableInlineCompletions | undefined> {
|
|
const doc = this._documents.getDocument(resource);
|
|
const pos = typeConvert.Position.to(position);
|
|
|
|
const result = await this._provider.provideInlineCompletionItems(doc, pos, {
|
|
selectedCompletionInfo:
|
|
context.selectedSuggestionInfo
|
|
? {
|
|
range: typeConvert.Range.to(context.selectedSuggestionInfo.range),
|
|
text: context.selectedSuggestionInfo.text
|
|
}
|
|
: undefined,
|
|
triggerKind: this.languageTriggerKindToVSCodeTriggerKind[context.triggerKind],
|
|
requestUuid: context.requestUuid,
|
|
requestIssuedDateTime: context.requestIssuedDateTime,
|
|
earliestShownDateTime: context.earliestShownDateTime,
|
|
}, token);
|
|
|
|
if (!result) {
|
|
// undefined and null are valid results
|
|
return undefined;
|
|
}
|
|
|
|
const { resultItems, list } = Array.isArray(result) ? { resultItems: result, list: undefined } : { resultItems: result.items, list: result };
|
|
const commands = this._isAdditionsProposedApiEnabled ? Array.isArray(result) ? [] : result.commands || [] : [];
|
|
const enableForwardStability = this._isAdditionsProposedApiEnabled && !Array.isArray(result) ? result.enableForwardStability : undefined;
|
|
|
|
let disposableStore: DisposableStore | undefined = undefined;
|
|
const pid = this._references.createReferenceId({
|
|
dispose() {
|
|
disposableStore?.dispose();
|
|
},
|
|
items: resultItems,
|
|
list,
|
|
});
|
|
|
|
return {
|
|
pid,
|
|
languageId: doc.languageId,
|
|
items: resultItems.map<extHostProtocol.IdentifiableInlineCompletion>((item, idx) => {
|
|
let command: languages.Command | undefined = undefined;
|
|
if (item.command) {
|
|
if (!disposableStore) {
|
|
disposableStore = new DisposableStore();
|
|
}
|
|
command = this._commands.toInternal(item.command, disposableStore);
|
|
}
|
|
|
|
let action: languages.Command | undefined = undefined;
|
|
if (item.action) {
|
|
if (!disposableStore) {
|
|
disposableStore = new DisposableStore();
|
|
}
|
|
action = this._commands.toInternal(item.action, disposableStore);
|
|
}
|
|
|
|
const insertText = item.insertText;
|
|
return ({
|
|
insertText: insertText === undefined ? undefined : (typeof insertText === 'string' ? insertText : { snippet: insertText.value }),
|
|
range: item.range ? typeConvert.Range.from(item.range) : undefined,
|
|
showRange: (this._isAdditionsProposedApiEnabled && item.showRange) ? typeConvert.Range.from(item.showRange) : undefined,
|
|
command,
|
|
gutterMenuLinkAction: action,
|
|
idx: idx,
|
|
completeBracketPairs: this._isAdditionsProposedApiEnabled ? item.completeBracketPairs : false,
|
|
isInlineEdit: this._isAdditionsProposedApiEnabled ? item.isInlineEdit : false,
|
|
showInlineEditMenu: this._isAdditionsProposedApiEnabled ? item.showInlineEditMenu : false,
|
|
hint: (item.displayLocation && this._isAdditionsProposedApiEnabled) ? {
|
|
range: typeConvert.Range.from(item.displayLocation.range),
|
|
content: item.displayLocation.label,
|
|
style: item.displayLocation.kind ? typeConvert.InlineCompletionHintStyle.from(item.displayLocation.kind) : languages.InlineCompletionHintStyle.Code,
|
|
} : undefined,
|
|
warning: (item.warning && this._isAdditionsProposedApiEnabled) ? {
|
|
message: typeConvert.MarkdownString.from(item.warning.message),
|
|
icon: item.warning.icon ? typeConvert.IconPath.fromThemeIcon(item.warning.icon) : undefined,
|
|
} : undefined,
|
|
correlationId: this._isAdditionsProposedApiEnabled ? item.correlationId : undefined,
|
|
suggestionId: undefined,
|
|
uri: (this._isAdditionsProposedApiEnabled && item.uri) ? item.uri : undefined,
|
|
supportsRename: this._isAdditionsProposedApiEnabled ? item.supportsRename : false,
|
|
});
|
|
}),
|
|
commands: commands.map(c => {
|
|
if (!disposableStore) {
|
|
disposableStore = new DisposableStore();
|
|
}
|
|
return typeConvert.CompletionCommand.from(c, this._commands, disposableStore);
|
|
}),
|
|
suppressSuggestions: false,
|
|
enableForwardStability,
|
|
};
|
|
}
|
|
|
|
disposeCompletions(pid: number, reason: languages.InlineCompletionsDisposeReason) {
|
|
const completionList = this._references.get(pid);
|
|
if (this._provider.handleListEndOfLifetime && this._isAdditionsProposedApiEnabled && completionList?.list) {
|
|
function translateReason(reason: languages.InlineCompletionsDisposeReason): vscode.InlineCompletionsDisposeReason {
|
|
switch (reason.kind) {
|
|
case 'lostRace':
|
|
return { kind: InlineCompletionsDisposeReasonKind.LostRace };
|
|
case 'tokenCancellation':
|
|
return { kind: InlineCompletionsDisposeReasonKind.TokenCancellation };
|
|
case 'other':
|
|
return { kind: InlineCompletionsDisposeReasonKind.Other };
|
|
case 'empty':
|
|
return { kind: InlineCompletionsDisposeReasonKind.Empty };
|
|
case 'notTaken':
|
|
return { kind: InlineCompletionsDisposeReasonKind.NotTaken };
|
|
default:
|
|
return { kind: InlineCompletionsDisposeReasonKind.Other };
|
|
}
|
|
}
|
|
|
|
this._provider.handleListEndOfLifetime(completionList.list, translateReason(reason));
|
|
}
|
|
|
|
const data = this._references.disposeReferenceId(pid);
|
|
data?.dispose();
|
|
}
|
|
|
|
handleDidShowCompletionItem(pid: number, idx: number, updatedInsertText: string): void {
|
|
const completionItem = this._references.get(pid)?.items[idx];
|
|
if (completionItem) {
|
|
if (this._provider.handleDidShowCompletionItem && this._isAdditionsProposedApiEnabled) {
|
|
this._provider.handleDidShowCompletionItem(completionItem, updatedInsertText);
|
|
}
|
|
}
|
|
}
|
|
|
|
handlePartialAccept(pid: number, idx: number, acceptedCharacters: number, info: languages.PartialAcceptInfo): void {
|
|
const completionItem = this._references.get(pid)?.items[idx];
|
|
if (completionItem) {
|
|
if (this._provider.handleDidPartiallyAcceptCompletionItem && this._isAdditionsProposedApiEnabled) {
|
|
this._provider.handleDidPartiallyAcceptCompletionItem(completionItem, acceptedCharacters);
|
|
this._provider.handleDidPartiallyAcceptCompletionItem(completionItem, typeConvert.PartialAcceptInfo.to(info));
|
|
}
|
|
}
|
|
}
|
|
|
|
handleEndOfLifetime(pid: number, idx: number, reason: languages.InlineCompletionEndOfLifeReason<{ pid: number; idx: number }>): void {
|
|
const completionItem = this._references.get(pid)?.items[idx];
|
|
if (completionItem) {
|
|
if (this._provider.handleEndOfLifetime && this._isAdditionsProposedApiEnabled) {
|
|
const r = typeConvert.InlineCompletionEndOfLifeReason.to(reason, ref => this._references.get(ref.pid)?.items[ref.idx]);
|
|
this._provider.handleEndOfLifetime(completionItem, r);
|
|
}
|
|
}
|
|
}
|
|
|
|
handleRejection(pid: number, idx: number): void {
|
|
const completionItem = this._references.get(pid)?.items[idx];
|
|
if (completionItem) {
|
|
if (this._provider.handleDidRejectCompletionItem && this._isAdditionsProposedApiEnabled) {
|
|
this._provider.handleDidRejectCompletionItem(completionItem);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class ReferenceMap<T> {
|
|
private readonly _references = new Map<number, T>();
|
|
private _idPool = 1;
|
|
|
|
createReferenceId(value: T): number {
|
|
const id = this._idPool++;
|
|
this._references.set(id, value);
|
|
return id;
|
|
}
|
|
|
|
disposeReferenceId(referenceId: number): T | undefined {
|
|
const value = this._references.get(referenceId);
|
|
this._references.delete(referenceId);
|
|
return value;
|
|
}
|
|
|
|
get(referenceId: number): T | undefined {
|
|
return this._references.get(referenceId);
|
|
}
|
|
}
|
|
|
|
class SignatureHelpAdapter {
|
|
|
|
private readonly _cache = new Cache<vscode.SignatureHelp>('SignatureHelp');
|
|
|
|
constructor(
|
|
private readonly _documents: ExtHostDocuments,
|
|
private readonly _provider: vscode.SignatureHelpProvider,
|
|
) { }
|
|
|
|
async provideSignatureHelp(resource: URI, position: IPosition, context: extHostProtocol.ISignatureHelpContextDto, token: CancellationToken): Promise<extHostProtocol.ISignatureHelpDto | undefined> {
|
|
const doc = this._documents.getDocument(resource);
|
|
const pos = typeConvert.Position.to(position);
|
|
const vscodeContext = this.reviveContext(context);
|
|
|
|
const value = await this._provider.provideSignatureHelp(doc, pos, token, vscodeContext);
|
|
if (value) {
|
|
const id = this._cache.add([value]);
|
|
return { ...typeConvert.SignatureHelp.from(value), id };
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
private reviveContext(context: extHostProtocol.ISignatureHelpContextDto): vscode.SignatureHelpContext {
|
|
let activeSignatureHelp: vscode.SignatureHelp | undefined = undefined;
|
|
if (context.activeSignatureHelp) {
|
|
const revivedSignatureHelp = typeConvert.SignatureHelp.to(context.activeSignatureHelp);
|
|
const saved = this._cache.get(context.activeSignatureHelp.id, 0);
|
|
if (saved) {
|
|
activeSignatureHelp = saved;
|
|
activeSignatureHelp.activeSignature = revivedSignatureHelp.activeSignature;
|
|
activeSignatureHelp.activeParameter = revivedSignatureHelp.activeParameter;
|
|
} else {
|
|
activeSignatureHelp = revivedSignatureHelp;
|
|
}
|
|
}
|
|
return { ...context, activeSignatureHelp };
|
|
}
|
|
|
|
releaseSignatureHelp(id: number): any {
|
|
this._cache.delete(id);
|
|
}
|
|
}
|
|
|
|
class InlayHintsAdapter {
|
|
|
|
private _cache = new Cache<vscode.InlayHint>('InlayHints');
|
|
private readonly _disposables = new Map<number, DisposableStore>();
|
|
|
|
constructor(
|
|
private readonly _documents: ExtHostDocuments,
|
|
private readonly _commands: CommandsConverter,
|
|
private readonly _provider: vscode.InlayHintsProvider,
|
|
private readonly _logService: ILogService,
|
|
private readonly _extension: IExtensionDescription
|
|
) { }
|
|
|
|
async provideInlayHints(resource: URI, ran: IRange, token: CancellationToken): Promise<extHostProtocol.IInlayHintsDto | undefined> {
|
|
const doc = this._documents.getDocument(resource);
|
|
const range = typeConvert.Range.to(ran);
|
|
|
|
const hints = await this._provider.provideInlayHints(doc, range, token);
|
|
if (!Array.isArray(hints) || hints.length === 0) {
|
|
// bad result
|
|
this._logService.trace(`[InlayHints] NO inlay hints from '${this._extension.identifier.value}' for range ${JSON.stringify(ran)}`);
|
|
return undefined;
|
|
}
|
|
if (token.isCancellationRequested) {
|
|
// cancelled -> return without further ado, esp no caching
|
|
// of results as they will leak
|
|
return undefined;
|
|
}
|
|
const pid = this._cache.add(hints);
|
|
this._disposables.set(pid, new DisposableStore());
|
|
const result: extHostProtocol.IInlayHintsDto = { hints: [], cacheId: pid };
|
|
for (let i = 0; i < hints.length; i++) {
|
|
if (this._isValidInlayHint(hints[i], range)) {
|
|
result.hints.push(this._convertInlayHint(hints[i], [pid, i]));
|
|
}
|
|
}
|
|
this._logService.trace(`[InlayHints] ${result.hints.length} inlay hints from '${this._extension.identifier.value}' for range ${JSON.stringify(ran)}`);
|
|
return result;
|
|
}
|
|
|
|
async resolveInlayHint(id: extHostProtocol.ChainedCacheId, token: CancellationToken) {
|
|
if (typeof this._provider.resolveInlayHint !== 'function') {
|
|
return undefined;
|
|
}
|
|
const item = this._cache.get(...id);
|
|
if (!item) {
|
|
return undefined;
|
|
}
|
|
const hint = await this._provider.resolveInlayHint(item, token);
|
|
if (!hint) {
|
|
return undefined;
|
|
}
|
|
if (!this._isValidInlayHint(hint)) {
|
|
return undefined;
|
|
}
|
|
return this._convertInlayHint(hint, id);
|
|
}
|
|
|
|
releaseHints(id: number): any {
|
|
this._disposables.get(id)?.dispose();
|
|
this._disposables.delete(id);
|
|
this._cache.delete(id);
|
|
}
|
|
|
|
private _isValidInlayHint(hint: vscode.InlayHint, range?: vscode.Range): boolean {
|
|
if (hint.label.length === 0 || Array.isArray(hint.label) && hint.label.every(part => part.value.length === 0)) {
|
|
console.log('INVALID inlay hint, empty label', hint);
|
|
return false;
|
|
}
|
|
if (range && !range.contains(hint.position)) {
|
|
// console.log('INVALID inlay hint, position outside range', range, hint);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private _convertInlayHint(hint: vscode.InlayHint, id: extHostProtocol.ChainedCacheId): extHostProtocol.IInlayHintDto {
|
|
|
|
const disposables = this._disposables.get(id[0]);
|
|
if (!disposables) {
|
|
throw Error('DisposableStore is missing...');
|
|
}
|
|
|
|
const result: extHostProtocol.IInlayHintDto = {
|
|
label: '', // fill-in below
|
|
cacheId: id,
|
|
tooltip: typeConvert.MarkdownString.fromStrict(hint.tooltip),
|
|
position: typeConvert.Position.from(hint.position),
|
|
textEdits: hint.textEdits && hint.textEdits.map(typeConvert.TextEdit.from),
|
|
kind: hint.kind && typeConvert.InlayHintKind.from(hint.kind),
|
|
paddingLeft: hint.paddingLeft,
|
|
paddingRight: hint.paddingRight,
|
|
};
|
|
|
|
if (typeof hint.label === 'string') {
|
|
result.label = hint.label;
|
|
} else {
|
|
const parts: languages.InlayHintLabelPart[] = [];
|
|
result.label = parts;
|
|
|
|
for (const part of hint.label) {
|
|
if (!part.value) {
|
|
console.warn('INVALID inlay hint, empty label part', this._extension.identifier.value);
|
|
continue;
|
|
}
|
|
const part2: languages.InlayHintLabelPart = {
|
|
label: part.value,
|
|
tooltip: typeConvert.MarkdownString.fromStrict(part.tooltip)
|
|
};
|
|
if (Location.isLocation(part.location)) {
|
|
part2.location = typeConvert.location.from(part.location);
|
|
}
|
|
if (part.command) {
|
|
part2.command = this._commands.toInternal(part.command, disposables);
|
|
}
|
|
parts.push(part2);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
class LinkProviderAdapter {
|
|
|
|
private _cache = new Cache<vscode.DocumentLink>('DocumentLink');
|
|
|
|
constructor(
|
|
private readonly _documents: ExtHostDocuments,
|
|
private readonly _provider: vscode.DocumentLinkProvider
|
|
) { }
|
|
|
|
async provideLinks(resource: URI, token: CancellationToken): Promise<extHostProtocol.ILinksListDto | undefined> {
|
|
const doc = this._documents.getDocument(resource);
|
|
|
|
const links = await this._provider.provideDocumentLinks(doc, token);
|
|
if (!Array.isArray(links) || links.length === 0) {
|
|
// bad result
|
|
return undefined;
|
|
}
|
|
if (token.isCancellationRequested) {
|
|
// cancelled -> return without further ado, esp no caching
|
|
// of results as they will leak
|
|
return undefined;
|
|
}
|
|
if (typeof this._provider.resolveDocumentLink !== 'function') {
|
|
// no resolve -> no caching
|
|
return { links: links.filter(LinkProviderAdapter._validateLink).map(typeConvert.DocumentLink.from) };
|
|
|
|
} else {
|
|
// cache links for future resolving
|
|
const pid = this._cache.add(links);
|
|
const result: extHostProtocol.ILinksListDto = { links: [], cacheId: pid };
|
|
for (let i = 0; i < links.length; i++) {
|
|
|
|
if (!LinkProviderAdapter._validateLink(links[i])) {
|
|
continue;
|
|
}
|
|
|
|
const dto: extHostProtocol.ILinkDto = typeConvert.DocumentLink.from(links[i]);
|
|
dto.cacheId = [pid, i];
|
|
result.links.push(dto);
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
private static _validateLink(link: vscode.DocumentLink): boolean {
|
|
if (link.target && link.target.path.length > 50_000) {
|
|
console.warn('DROPPING link because it is too long');
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
async resolveLink(id: extHostProtocol.ChainedCacheId, token: CancellationToken): Promise<extHostProtocol.ILinkDto | undefined> {
|
|
if (typeof this._provider.resolveDocumentLink !== 'function') {
|
|
return undefined;
|
|
}
|
|
const item = this._cache.get(...id);
|
|
if (!item) {
|
|
return undefined;
|
|
}
|
|
const link = await this._provider.resolveDocumentLink(item, token);
|
|
if (!link || !LinkProviderAdapter._validateLink(link)) {
|
|
return undefined;
|
|
}
|
|
return typeConvert.DocumentLink.from(link);
|
|
}
|
|
|
|
releaseLinks(id: number): any {
|
|
this._cache.delete(id);
|
|
}
|
|
}
|
|
|
|
class ColorProviderAdapter {
|
|
|
|
constructor(
|
|
private _documents: ExtHostDocuments,
|
|
private _provider: vscode.DocumentColorProvider
|
|
) { }
|
|
|
|
async provideColors(resource: URI, token: CancellationToken): Promise<extHostProtocol.IRawColorInfo[]> {
|
|
const doc = this._documents.getDocument(resource);
|
|
const colors = await this._provider.provideDocumentColors(doc, token);
|
|
if (!Array.isArray(colors)) {
|
|
return [];
|
|
}
|
|
const colorInfos: extHostProtocol.IRawColorInfo[] = colors.map(ci => {
|
|
return {
|
|
color: typeConvert.Color.from(ci.color),
|
|
range: typeConvert.Range.from(ci.range)
|
|
};
|
|
});
|
|
return colorInfos;
|
|
}
|
|
|
|
async provideColorPresentations(resource: URI, raw: extHostProtocol.IRawColorInfo, token: CancellationToken): Promise<languages.IColorPresentation[] | undefined> {
|
|
const document = this._documents.getDocument(resource);
|
|
const range = typeConvert.Range.to(raw.range);
|
|
const color = typeConvert.Color.to(raw.color);
|
|
const value = await this._provider.provideColorPresentations(color, { document, range }, token);
|
|
if (!Array.isArray(value)) {
|
|
return undefined;
|
|
}
|
|
return value.map(typeConvert.ColorPresentation.from);
|
|
}
|
|
}
|
|
|
|
class FoldingProviderAdapter {
|
|
|
|
constructor(
|
|
private _documents: ExtHostDocuments,
|
|
private _provider: vscode.FoldingRangeProvider
|
|
) { }
|
|
|
|
async provideFoldingRanges(resource: URI, context: languages.FoldingContext, token: CancellationToken): Promise<languages.FoldingRange[] | undefined> {
|
|
const doc = this._documents.getDocument(resource);
|
|
const ranges = await this._provider.provideFoldingRanges(doc, context, token);
|
|
if (!Array.isArray(ranges)) {
|
|
return undefined;
|
|
}
|
|
return ranges.map(typeConvert.FoldingRange.from);
|
|
}
|
|
}
|
|
|
|
class SelectionRangeAdapter {
|
|
|
|
constructor(
|
|
private readonly _documents: ExtHostDocuments,
|
|
private readonly _provider: vscode.SelectionRangeProvider,
|
|
private readonly _logService: ILogService
|
|
) { }
|
|
|
|
async provideSelectionRanges(resource: URI, pos: IPosition[], token: CancellationToken): Promise<languages.SelectionRange[][]> {
|
|
const document = this._documents.getDocument(resource);
|
|
const positions = pos.map(typeConvert.Position.to);
|
|
|
|
const allProviderRanges = await this._provider.provideSelectionRanges(document, positions, token);
|
|
if (!isNonEmptyArray(allProviderRanges)) {
|
|
return [];
|
|
}
|
|
if (allProviderRanges.length !== positions.length) {
|
|
this._logService.warn('BAD selection ranges, provider must return ranges for each position');
|
|
return [];
|
|
}
|
|
const allResults: languages.SelectionRange[][] = [];
|
|
for (let i = 0; i < positions.length; i++) {
|
|
const oneResult: languages.SelectionRange[] = [];
|
|
allResults.push(oneResult);
|
|
|
|
let last: vscode.Position | vscode.Range = positions[i];
|
|
let selectionRange = allProviderRanges[i];
|
|
|
|
while (true) {
|
|
if (!selectionRange.range.contains(last)) {
|
|
throw new Error('INVALID selection range, must contain the previous range');
|
|
}
|
|
oneResult.push(typeConvert.SelectionRange.from(selectionRange));
|
|
if (!selectionRange.parent) {
|
|
break;
|
|
}
|
|
last = selectionRange.range;
|
|
selectionRange = selectionRange.parent;
|
|
}
|
|
}
|
|
return allResults;
|
|
}
|
|
}
|
|
|
|
class CallHierarchyAdapter {
|
|
|
|
private readonly _idPool = new IdGenerator('');
|
|
private readonly _cache = new Map<string, Map<string, vscode.CallHierarchyItem>>();
|
|
|
|
constructor(
|
|
private readonly _documents: ExtHostDocuments,
|
|
private readonly _provider: vscode.CallHierarchyProvider
|
|
) { }
|
|
|
|
async prepareSession(uri: URI, position: IPosition, token: CancellationToken): Promise<extHostProtocol.ICallHierarchyItemDto[] | undefined> {
|
|
const doc = this._documents.getDocument(uri);
|
|
const pos = typeConvert.Position.to(position);
|
|
|
|
const items = await this._provider.prepareCallHierarchy(doc, pos, token);
|
|
if (!items) {
|
|
return undefined;
|
|
}
|
|
|
|
const sessionId = this._idPool.nextId();
|
|
this._cache.set(sessionId, new Map());
|
|
|
|
if (Array.isArray(items)) {
|
|
return items.map(item => this._cacheAndConvertItem(sessionId, item));
|
|
} else {
|
|
return [this._cacheAndConvertItem(sessionId, items)];
|
|
}
|
|
}
|
|
|
|
async provideCallsTo(sessionId: string, itemId: string, token: CancellationToken): Promise<extHostProtocol.IIncomingCallDto[] | undefined> {
|
|
const item = this._itemFromCache(sessionId, itemId);
|
|
if (!item) {
|
|
throw new Error('missing call hierarchy item');
|
|
}
|
|
const calls = await this._provider.provideCallHierarchyIncomingCalls(item, token);
|
|
if (!calls) {
|
|
return undefined;
|
|
}
|
|
return calls.map(call => {
|
|
return {
|
|
from: this._cacheAndConvertItem(sessionId, call.from),
|
|
fromRanges: call.fromRanges.map(r => typeConvert.Range.from(r))
|
|
};
|
|
});
|
|
}
|
|
|
|
async provideCallsFrom(sessionId: string, itemId: string, token: CancellationToken): Promise<extHostProtocol.IOutgoingCallDto[] | undefined> {
|
|
const item = this._itemFromCache(sessionId, itemId);
|
|
if (!item) {
|
|
throw new Error('missing call hierarchy item');
|
|
}
|
|
const calls = await this._provider.provideCallHierarchyOutgoingCalls(item, token);
|
|
if (!calls) {
|
|
return undefined;
|
|
}
|
|
return calls.map(call => {
|
|
return {
|
|
to: this._cacheAndConvertItem(sessionId, call.to),
|
|
fromRanges: call.fromRanges.map(r => typeConvert.Range.from(r))
|
|
};
|
|
});
|
|
}
|
|
|
|
releaseSession(sessionId: string): void {
|
|
this._cache.delete(sessionId);
|
|
}
|
|
|
|
private _cacheAndConvertItem(sessionId: string, item: vscode.CallHierarchyItem): extHostProtocol.ICallHierarchyItemDto {
|
|
const map = this._cache.get(sessionId)!;
|
|
const dto = typeConvert.CallHierarchyItem.from(item, sessionId, map.size.toString(36));
|
|
map.set(dto._itemId, item);
|
|
return dto;
|
|
}
|
|
|
|
private _itemFromCache(sessionId: string, itemId: string): vscode.CallHierarchyItem | undefined {
|
|
const map = this._cache.get(sessionId);
|
|
return map?.get(itemId);
|
|
}
|
|
}
|
|
|
|
class TypeHierarchyAdapter {
|
|
|
|
private readonly _idPool = new IdGenerator('');
|
|
private readonly _cache = new Map<string, Map<string, vscode.TypeHierarchyItem>>();
|
|
|
|
constructor(
|
|
private readonly _documents: ExtHostDocuments,
|
|
private readonly _provider: vscode.TypeHierarchyProvider
|
|
) { }
|
|
|
|
async prepareSession(uri: URI, position: IPosition, token: CancellationToken): Promise<extHostProtocol.ITypeHierarchyItemDto[] | undefined> {
|
|
const doc = this._documents.getDocument(uri);
|
|
const pos = typeConvert.Position.to(position);
|
|
|
|
const items = await this._provider.prepareTypeHierarchy(doc, pos, token);
|
|
if (!items) {
|
|
return undefined;
|
|
}
|
|
|
|
const sessionId = this._idPool.nextId();
|
|
this._cache.set(sessionId, new Map());
|
|
|
|
if (Array.isArray(items)) {
|
|
return items.map(item => this._cacheAndConvertItem(sessionId, item));
|
|
} else {
|
|
return [this._cacheAndConvertItem(sessionId, items)];
|
|
}
|
|
}
|
|
|
|
async provideSupertypes(sessionId: string, itemId: string, token: CancellationToken): Promise<extHostProtocol.ITypeHierarchyItemDto[] | undefined> {
|
|
const item = this._itemFromCache(sessionId, itemId);
|
|
if (!item) {
|
|
throw new Error('missing type hierarchy item');
|
|
}
|
|
const supertypes = await this._provider.provideTypeHierarchySupertypes(item, token);
|
|
if (!supertypes) {
|
|
return undefined;
|
|
}
|
|
return supertypes.map(supertype => {
|
|
return this._cacheAndConvertItem(sessionId, supertype);
|
|
});
|
|
}
|
|
|
|
async provideSubtypes(sessionId: string, itemId: string, token: CancellationToken): Promise<extHostProtocol.ITypeHierarchyItemDto[] | undefined> {
|
|
const item = this._itemFromCache(sessionId, itemId);
|
|
if (!item) {
|
|
throw new Error('missing type hierarchy item');
|
|
}
|
|
const subtypes = await this._provider.provideTypeHierarchySubtypes(item, token);
|
|
if (!subtypes) {
|
|
return undefined;
|
|
}
|
|
return subtypes.map(subtype => {
|
|
return this._cacheAndConvertItem(sessionId, subtype);
|
|
});
|
|
}
|
|
|
|
releaseSession(sessionId: string): void {
|
|
this._cache.delete(sessionId);
|
|
}
|
|
|
|
private _cacheAndConvertItem(sessionId: string, item: vscode.TypeHierarchyItem): extHostProtocol.ITypeHierarchyItemDto {
|
|
const map = this._cache.get(sessionId)!;
|
|
const dto = typeConvert.TypeHierarchyItem.from(item, sessionId, map.size.toString(36));
|
|
map.set(dto._itemId, item);
|
|
return dto;
|
|
}
|
|
|
|
private _itemFromCache(sessionId: string, itemId: string): vscode.TypeHierarchyItem | undefined {
|
|
const map = this._cache.get(sessionId);
|
|
return map?.get(itemId);
|
|
}
|
|
}
|
|
|
|
class DocumentDropEditAdapter {
|
|
|
|
private readonly _cache = new Cache<vscode.DocumentDropEdit>('DocumentDropEdit');
|
|
|
|
constructor(
|
|
private readonly _proxy: extHostProtocol.MainThreadLanguageFeaturesShape,
|
|
private readonly _documents: ExtHostDocuments,
|
|
private readonly _provider: vscode.DocumentDropEditProvider,
|
|
private readonly _handle: number,
|
|
private readonly _extension: IExtensionDescription,
|
|
) { }
|
|
|
|
async provideDocumentOnDropEdits(requestId: number, uri: URI, position: IPosition, dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise<extHostProtocol.IDocumentDropEditDto[] | undefined> {
|
|
const doc = this._documents.getDocument(uri);
|
|
const pos = typeConvert.Position.to(position);
|
|
const dataTransfer = typeConvert.DataTransfer.toDataTransfer(dataTransferDto, async (id) => {
|
|
return (await this._proxy.$resolveDocumentOnDropFileData(this._handle, requestId, id)).buffer;
|
|
});
|
|
|
|
const edits = await this._provider.provideDocumentDropEdits(doc, pos, dataTransfer, token);
|
|
if (!edits) {
|
|
return undefined;
|
|
}
|
|
|
|
const editsArray = asArray(edits);
|
|
const cacheId = this._cache.add(editsArray);
|
|
|
|
return editsArray.map((edit, i): extHostProtocol.IDocumentDropEditDto => ({
|
|
_cacheId: [cacheId, i],
|
|
title: edit.title ?? localize('defaultDropLabel', "Drop using '{0}' extension", this._extension.displayName || this._extension.name),
|
|
kind: edit.kind?.value,
|
|
yieldTo: edit.yieldTo?.map(x => x.value),
|
|
insertText: typeof edit.insertText === 'string' ? edit.insertText : { snippet: edit.insertText.value },
|
|
additionalEdit: edit.additionalEdit ? typeConvert.WorkspaceEdit.from(edit.additionalEdit, undefined) : undefined,
|
|
}));
|
|
}
|
|
|
|
async resolveDropEdit(id: extHostProtocol.ChainedCacheId, token: CancellationToken): Promise<{ additionalEdit?: extHostProtocol.IWorkspaceEditDto }> {
|
|
const [sessionId, itemId] = id;
|
|
const item = this._cache.get(sessionId, itemId);
|
|
if (!item || !this._provider.resolveDocumentDropEdit) {
|
|
return {}; // this should not happen...
|
|
}
|
|
|
|
const resolvedItem = (await this._provider.resolveDocumentDropEdit(item, token)) ?? item;
|
|
const additionalEdit = resolvedItem.additionalEdit ? typeConvert.WorkspaceEdit.from(resolvedItem.additionalEdit, undefined) : undefined;
|
|
return { additionalEdit };
|
|
}
|
|
|
|
releaseDropEdits(id: number): any {
|
|
this._cache.delete(id);
|
|
}
|
|
}
|
|
|
|
type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | HoverAdapter
|
|
| DocumentHighlightAdapter | MultiDocumentHighlightAdapter | ReferenceAdapter | CodeActionAdapter
|
|
| DocumentPasteEditProvider | DocumentFormattingAdapter | RangeFormattingAdapter
|
|
| OnTypeFormattingAdapter | NavigateTypeAdapter | RenameAdapter
|
|
| CompletionsAdapter | SignatureHelpAdapter | LinkProviderAdapter | ImplementationAdapter
|
|
| TypeDefinitionAdapter | ColorProviderAdapter | FoldingProviderAdapter | DeclarationAdapter
|
|
| SelectionRangeAdapter | CallHierarchyAdapter | TypeHierarchyAdapter
|
|
| DocumentSemanticTokensAdapter | DocumentRangeSemanticTokensAdapter
|
|
| EvaluatableExpressionAdapter | InlineValuesAdapter
|
|
| LinkedEditingRangeAdapter | InlayHintsAdapter | InlineCompletionAdapter
|
|
| DocumentDropEditAdapter | NewSymbolNamesAdapter;
|
|
|
|
class AdapterData {
|
|
constructor(
|
|
readonly adapter: Adapter,
|
|
readonly extension: IExtensionDescription
|
|
) { }
|
|
}
|
|
|
|
export class ExtHostLanguageFeatures extends CoreDisposable implements extHostProtocol.ExtHostLanguageFeaturesShape {
|
|
|
|
private static _handlePool: number = 0;
|
|
|
|
private readonly _proxy: extHostProtocol.MainThreadLanguageFeaturesShape;
|
|
private readonly _adapter = new Map<number, AdapterData>();
|
|
|
|
private _inlineCompletionsUnificationState: vscode.InlineCompletionsUnificationState;
|
|
public get inlineCompletionsUnificationState(): vscode.InlineCompletionsUnificationState {
|
|
return this._inlineCompletionsUnificationState;
|
|
}
|
|
|
|
private readonly _onDidChangeInlineCompletionsUnificationState = this._register(new Emitter<void>());
|
|
readonly onDidChangeInlineCompletionsUnificationState = this._onDidChangeInlineCompletionsUnificationState.event;
|
|
|
|
constructor(
|
|
mainContext: extHostProtocol.IMainContext,
|
|
private readonly _uriTransformer: IURITransformer,
|
|
private readonly _documents: ExtHostDocuments,
|
|
private readonly _commands: ExtHostCommands,
|
|
private readonly _diagnostics: ExtHostDiagnostics,
|
|
private readonly _logService: ILogService,
|
|
private readonly _apiDeprecation: IExtHostApiDeprecationService,
|
|
private readonly _extensionTelemetry: IExtHostTelemetry
|
|
) {
|
|
super();
|
|
this._proxy = mainContext.getProxy(extHostProtocol.MainContext.MainThreadLanguageFeatures);
|
|
this._inlineCompletionsUnificationState = {
|
|
codeUnification: false,
|
|
modelUnification: false,
|
|
extensionUnification: false,
|
|
expAssignments: []
|
|
};
|
|
}
|
|
|
|
private _transformDocumentSelector(selector: vscode.DocumentSelector, extension: IExtensionDescription): Array<extHostProtocol.IDocumentFilterDto> {
|
|
return typeConvert.DocumentSelector.from(selector, this._uriTransformer, extension);
|
|
}
|
|
|
|
private _createDisposable(handle: number): Disposable {
|
|
return new Disposable(() => {
|
|
this._adapter.delete(handle);
|
|
this._proxy.$unregister(handle);
|
|
});
|
|
}
|
|
|
|
private _nextHandle(): number {
|
|
return ExtHostLanguageFeatures._handlePool++;
|
|
}
|
|
|
|
private async _withAdapter<A, R>(
|
|
handle: number,
|
|
ctor: { new(...args: any[]): A },
|
|
callback: (adapter: A, extension: IExtensionDescription) => Promise<R>,
|
|
fallbackValue: R,
|
|
tokenToRaceAgainst: CancellationToken | undefined,
|
|
doNotLog: boolean = false
|
|
): Promise<R> {
|
|
const data = this._adapter.get(handle);
|
|
if (!data || !(data.adapter instanceof ctor)) {
|
|
return fallbackValue;
|
|
}
|
|
|
|
const t1: number = Date.now();
|
|
if (!doNotLog) {
|
|
this._logService.trace(`[${data.extension.identifier.value}] INVOKE provider '${callback.toString().replace(/[\r\n]/g, '')}'`);
|
|
}
|
|
|
|
const result = callback(data.adapter, data.extension);
|
|
|
|
// logging,tracing
|
|
Promise.resolve(result).catch(err => {
|
|
if (!isCancellationError(err)) {
|
|
this._logService.error(`[${data.extension.identifier.value}] provider FAILED`);
|
|
this._logService.error(err);
|
|
|
|
this._extensionTelemetry.onExtensionError(data.extension.identifier, err);
|
|
}
|
|
}).finally(() => {
|
|
if (!doNotLog) {
|
|
this._logService.trace(`[${data.extension.identifier.value}] provider DONE after ${Date.now() - t1}ms`);
|
|
}
|
|
});
|
|
|
|
if (CancellationToken.isCancellationToken(tokenToRaceAgainst)) {
|
|
return raceCancellationError(result, tokenToRaceAgainst);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private _addNewAdapter(adapter: Adapter, extension: IExtensionDescription): number {
|
|
const handle = this._nextHandle();
|
|
this._adapter.set(handle, new AdapterData(adapter, extension));
|
|
return handle;
|
|
}
|
|
|
|
private static _extLabel(ext: IExtensionDescription): string {
|
|
return ext.displayName || ext.name;
|
|
}
|
|
|
|
private static _extId(ext: IExtensionDescription): string {
|
|
return ext.identifier.value;
|
|
}
|
|
|
|
// --- outline
|
|
|
|
registerDocumentSymbolProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentSymbolProvider, metadata?: vscode.DocumentSymbolProviderMetadata): vscode.Disposable {
|
|
const handle = this._addNewAdapter(new DocumentSymbolAdapter(this._documents, provider), extension);
|
|
const displayName = (metadata && metadata.label) || ExtHostLanguageFeatures._extLabel(extension);
|
|
this._proxy.$registerDocumentSymbolProvider(handle, this._transformDocumentSelector(selector, extension), displayName);
|
|
return this._createDisposable(handle);
|
|
}
|
|
|
|
$provideDocumentSymbols(handle: number, resource: UriComponents, token: CancellationToken): Promise<languages.DocumentSymbol[] | undefined> {
|
|
return this._withAdapter(handle, DocumentSymbolAdapter, adapter => adapter.provideDocumentSymbols(URI.revive(resource), token), undefined, token);
|
|
}
|
|
|
|
// --- code lens
|
|
|
|
registerCodeLensProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.CodeLensProvider): vscode.Disposable {
|
|
const handle = this._nextHandle();
|
|
const eventHandle = typeof provider.onDidChangeCodeLenses === 'function' ? this._nextHandle() : undefined;
|
|
|
|
this._adapter.set(handle, new AdapterData(new CodeLensAdapter(this._documents, this._commands.converter, provider, extension, this._extensionTelemetry, this._logService), extension));
|
|
this._proxy.$registerCodeLensSupport(handle, this._transformDocumentSelector(selector, extension), eventHandle);
|
|
let result = this._createDisposable(handle);
|
|
|
|
if (eventHandle !== undefined) {
|
|
const subscription = provider.onDidChangeCodeLenses!(_ => this._proxy.$emitCodeLensEvent(eventHandle));
|
|
result = Disposable.from(result, subscription);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
$provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise<extHostProtocol.ICodeLensListDto | undefined> {
|
|
return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.provideCodeLenses(URI.revive(resource), token), undefined, token, resource.scheme === 'output');
|
|
}
|
|
|
|
$resolveCodeLens(handle: number, symbol: extHostProtocol.ICodeLensDto, token: CancellationToken): Promise<extHostProtocol.ICodeLensDto | undefined> {
|
|
return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.resolveCodeLens(symbol, token), undefined, undefined, true);
|
|
}
|
|
|
|
$releaseCodeLenses(handle: number, cacheId: number): void {
|
|
this._withAdapter(handle, CodeLensAdapter, adapter => Promise.resolve(adapter.releaseCodeLenses(cacheId)), undefined, undefined, true);
|
|
}
|
|
|
|
// --- declaration
|
|
|
|
registerDefinitionProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DefinitionProvider): vscode.Disposable {
|
|
const handle = this._addNewAdapter(new DefinitionAdapter(this._documents, provider), extension);
|
|
this._proxy.$registerDefinitionSupport(handle, this._transformDocumentSelector(selector, extension));
|
|
return this._createDisposable(handle);
|
|
}
|
|
|
|
$provideDefinition(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<languages.LocationLink[]> {
|
|
return this._withAdapter(handle, DefinitionAdapter, adapter => adapter.provideDefinition(URI.revive(resource), position, token), [], token);
|
|
}
|
|
|
|
registerDeclarationProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DeclarationProvider): vscode.Disposable {
|
|
const handle = this._addNewAdapter(new DeclarationAdapter(this._documents, provider), extension);
|
|
this._proxy.$registerDeclarationSupport(handle, this._transformDocumentSelector(selector, extension));
|
|
return this._createDisposable(handle);
|
|
}
|
|
|
|
$provideDeclaration(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<languages.LocationLink[]> {
|
|
return this._withAdapter(handle, DeclarationAdapter, adapter => adapter.provideDeclaration(URI.revive(resource), position, token), [], token);
|
|
}
|
|
|
|
registerImplementationProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.ImplementationProvider): vscode.Disposable {
|
|
const handle = this._addNewAdapter(new ImplementationAdapter(this._documents, provider), extension);
|
|
this._proxy.$registerImplementationSupport(handle, this._transformDocumentSelector(selector, extension));
|
|
return this._createDisposable(handle);
|
|
}
|
|
|
|
$provideImplementation(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<languages.LocationLink[]> {
|
|
return this._withAdapter(handle, ImplementationAdapter, adapter => adapter.provideImplementation(URI.revive(resource), position, token), [], token);
|
|
}
|
|
|
|
registerTypeDefinitionProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.TypeDefinitionProvider): vscode.Disposable {
|
|
const handle = this._addNewAdapter(new TypeDefinitionAdapter(this._documents, provider), extension);
|
|
this._proxy.$registerTypeDefinitionSupport(handle, this._transformDocumentSelector(selector, extension));
|
|
return this._createDisposable(handle);
|
|
}
|
|
|
|
$provideTypeDefinition(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<languages.LocationLink[]> {
|
|
return this._withAdapter(handle, TypeDefinitionAdapter, adapter => adapter.provideTypeDefinition(URI.revive(resource), position, token), [], token);
|
|
}
|
|
|
|
// --- extra info
|
|
|
|
registerHoverProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.HoverProvider, extensionId?: ExtensionIdentifier): vscode.Disposable {
|
|
const handle = this._addNewAdapter(new HoverAdapter(this._documents, provider), extension);
|
|
this._proxy.$registerHoverProvider(handle, this._transformDocumentSelector(selector, extension));
|
|
return this._createDisposable(handle);
|
|
}
|
|
|
|
$provideHover(handle: number, resource: UriComponents, position: IPosition, context: languages.HoverContext<{ id: number }> | undefined, token: CancellationToken,): Promise<extHostProtocol.HoverWithId | undefined> {
|
|
return this._withAdapter(handle, HoverAdapter, adapter => adapter.provideHover(URI.revive(resource), position, context, token), undefined, token);
|
|
}
|
|
|
|
$releaseHover(handle: number, id: number): void {
|
|
this._withAdapter(handle, HoverAdapter, adapter => Promise.resolve(adapter.releaseHover(id)), undefined, undefined);
|
|
}
|
|
|
|
// --- debug hover
|
|
|
|
registerEvaluatableExpressionProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.EvaluatableExpressionProvider, extensionId?: ExtensionIdentifier): vscode.Disposable {
|
|
const handle = this._addNewAdapter(new EvaluatableExpressionAdapter(this._documents, provider), extension);
|
|
this._proxy.$registerEvaluatableExpressionProvider(handle, this._transformDocumentSelector(selector, extension));
|
|
return this._createDisposable(handle);
|
|
}
|
|
|
|
$provideEvaluatableExpression(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<languages.EvaluatableExpression | undefined> {
|
|
return this._withAdapter(handle, EvaluatableExpressionAdapter, adapter => adapter.provideEvaluatableExpression(URI.revive(resource), position, token), undefined, token);
|
|
}
|
|
|
|
// --- debug inline values
|
|
|
|
registerInlineValuesProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.InlineValuesProvider, extensionId?: ExtensionIdentifier): vscode.Disposable {
|
|
|
|
const eventHandle = typeof provider.onDidChangeInlineValues === 'function' ? this._nextHandle() : undefined;
|
|
const handle = this._addNewAdapter(new InlineValuesAdapter(this._documents, provider), extension);
|
|
|
|
this._proxy.$registerInlineValuesProvider(handle, this._transformDocumentSelector(selector, extension), eventHandle);
|
|
let result = this._createDisposable(handle);
|
|
|
|
if (eventHandle !== undefined) {
|
|
const subscription = provider.onDidChangeInlineValues!(_ => this._proxy.$emitInlineValuesEvent(eventHandle));
|
|
result = Disposable.from(result, subscription);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
$provideInlineValues(handle: number, resource: UriComponents, range: IRange, context: extHostProtocol.IInlineValueContextDto, token: CancellationToken): Promise<languages.InlineValue[] | undefined> {
|
|
return this._withAdapter(handle, InlineValuesAdapter, adapter => adapter.provideInlineValues(URI.revive(resource), range, context, token), undefined, token);
|
|
}
|
|
|
|
// --- occurrences
|
|
|
|
registerDocumentHighlightProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentHighlightProvider): vscode.Disposable {
|
|
const handle = this._addNewAdapter(new DocumentHighlightAdapter(this._documents, provider), extension);
|
|
this._proxy.$registerDocumentHighlightProvider(handle, this._transformDocumentSelector(selector, extension));
|
|
return this._createDisposable(handle);
|
|
}
|
|
|
|
registerMultiDocumentHighlightProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.MultiDocumentHighlightProvider): vscode.Disposable {
|
|
const handle = this._addNewAdapter(new MultiDocumentHighlightAdapter(this._documents, provider, this._logService), extension);
|
|
this._proxy.$registerMultiDocumentHighlightProvider(handle, this._transformDocumentSelector(selector, extension));
|
|
return this._createDisposable(handle);
|
|
}
|
|
|
|
$provideDocumentHighlights(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<languages.DocumentHighlight[] | undefined> {
|
|
return this._withAdapter(handle, DocumentHighlightAdapter, adapter => adapter.provideDocumentHighlights(URI.revive(resource), position, token), undefined, token);
|
|
}
|
|
|
|
$provideMultiDocumentHighlights(handle: number, resource: UriComponents, position: IPosition, otherModels: UriComponents[], token: CancellationToken): Promise<languages.MultiDocumentHighlight[] | undefined> {
|
|
return this._withAdapter(handle, MultiDocumentHighlightAdapter, adapter => adapter.provideMultiDocumentHighlights(URI.revive(resource), position, otherModels.map(model => URI.revive(model)), token), undefined, token);
|
|
}
|
|
|
|
// --- linked editing
|
|
|
|
registerLinkedEditingRangeProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.LinkedEditingRangeProvider): vscode.Disposable {
|
|
const handle = this._addNewAdapter(new LinkedEditingRangeAdapter(this._documents, provider), extension);
|
|
this._proxy.$registerLinkedEditingRangeProvider(handle, this._transformDocumentSelector(selector, extension));
|
|
return this._createDisposable(handle);
|
|
}
|
|
|
|
$provideLinkedEditingRanges(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<extHostProtocol.ILinkedEditingRangesDto | undefined> {
|
|
return this._withAdapter(handle, LinkedEditingRangeAdapter, async adapter => {
|
|
const res = await adapter.provideLinkedEditingRanges(URI.revive(resource), position, token);
|
|
if (res) {
|
|
return {
|
|
ranges: res.ranges,
|
|
wordPattern: res.wordPattern ? ExtHostLanguageFeatures._serializeRegExp(res.wordPattern) : undefined
|
|
};
|
|
}
|
|
return undefined;
|
|
}, undefined, token);
|
|
}
|
|
|
|
// --- references
|
|
|
|
registerReferenceProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.ReferenceProvider): vscode.Disposable {
|
|
const handle = this._addNewAdapter(new ReferenceAdapter(this._documents, provider), extension);
|
|
this._proxy.$registerReferenceSupport(handle, this._transformDocumentSelector(selector, extension));
|
|
return this._createDisposable(handle);
|
|
}
|
|
|
|
$provideReferences(handle: number, resource: UriComponents, position: IPosition, context: languages.ReferenceContext, token: CancellationToken): Promise<languages.Location[] | undefined> {
|
|
return this._withAdapter(handle, ReferenceAdapter, adapter => adapter.provideReferences(URI.revive(resource), position, context, token), undefined, token);
|
|
}
|
|
|
|
// --- code actions
|
|
|
|
registerCodeActionProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.CodeActionProvider, metadata?: vscode.CodeActionProviderMetadata): vscode.Disposable {
|
|
const store = new DisposableStore();
|
|
const handle = this._addNewAdapter(new CodeActionAdapter(this._documents, this._commands.converter, this._diagnostics, provider, this._logService, extension, this._apiDeprecation), extension);
|
|
this._proxy.$registerCodeActionSupport(handle, this._transformDocumentSelector(selector, extension), {
|
|
providedKinds: metadata?.providedCodeActionKinds?.map(kind => kind.value),
|
|
documentation: metadata?.documentation?.map(x => ({
|
|
kind: x.kind.value,
|
|
command: this._commands.converter.toInternal(x.command, store),
|
|
}))
|
|
}, ExtHostLanguageFeatures._extLabel(extension), ExtHostLanguageFeatures._extId(extension), Boolean(provider.resolveCodeAction));
|
|
store.add(this._createDisposable(handle));
|
|
return store;
|
|
}
|
|
|
|
|
|
$provideCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: languages.CodeActionContext, token: CancellationToken): Promise<extHostProtocol.ICodeActionListDto | undefined> {
|
|
return this._withAdapter(handle, CodeActionAdapter, adapter => adapter.provideCodeActions(URI.revive(resource), rangeOrSelection, context, token), undefined, token);
|
|
}
|
|
|
|
$resolveCodeAction(handle: number, id: extHostProtocol.ChainedCacheId, token: CancellationToken): Promise<{ edit?: extHostProtocol.IWorkspaceEditDto; command?: extHostProtocol.ICommandDto }> {
|
|
return this._withAdapter(handle, CodeActionAdapter, adapter => adapter.resolveCodeAction(id, token), {}, undefined);
|
|
}
|
|
|
|
$releaseCodeActions(handle: number, cacheId: number): void {
|
|
this._withAdapter(handle, CodeActionAdapter, adapter => Promise.resolve(adapter.releaseCodeActions(cacheId)), undefined, undefined);
|
|
}
|
|
|
|
// --- formatting
|
|
|
|
registerDocumentFormattingEditProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentFormattingEditProvider): vscode.Disposable {
|
|
const handle = this._addNewAdapter(new DocumentFormattingAdapter(this._documents, provider), extension);
|
|
this._proxy.$registerDocumentFormattingSupport(handle, this._transformDocumentSelector(selector, extension), extension.identifier, extension.displayName || extension.name);
|
|
return this._createDisposable(handle);
|
|
}
|
|
|
|
$provideDocumentFormattingEdits(handle: number, resource: UriComponents, options: languages.FormattingOptions, token: CancellationToken): Promise<languages.TextEdit[] | undefined> {
|
|
return this._withAdapter(handle, DocumentFormattingAdapter, adapter => adapter.provideDocumentFormattingEdits(URI.revive(resource), options, token), undefined, token);
|
|
}
|
|
|
|
registerDocumentRangeFormattingEditProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentRangeFormattingEditProvider): vscode.Disposable {
|
|
const canFormatMultipleRanges = typeof provider.provideDocumentRangesFormattingEdits === 'function';
|
|
const handle = this._addNewAdapter(new RangeFormattingAdapter(this._documents, provider), extension);
|
|
this._proxy.$registerRangeFormattingSupport(handle, this._transformDocumentSelector(selector, extension), extension.identifier, extension.displayName || extension.name, canFormatMultipleRanges);
|
|
return this._createDisposable(handle);
|
|
}
|
|
|
|
$provideDocumentRangeFormattingEdits(handle: number, resource: UriComponents, range: IRange, options: languages.FormattingOptions, token: CancellationToken): Promise<languages.TextEdit[] | undefined> {
|
|
return this._withAdapter(handle, RangeFormattingAdapter, adapter => adapter.provideDocumentRangeFormattingEdits(URI.revive(resource), range, options, token), undefined, token);
|
|
}
|
|
|
|
$provideDocumentRangesFormattingEdits(handle: number, resource: UriComponents, ranges: IRange[], options: languages.FormattingOptions, token: CancellationToken): Promise<languages.TextEdit[] | undefined> {
|
|
return this._withAdapter(handle, RangeFormattingAdapter, adapter => adapter.provideDocumentRangesFormattingEdits(URI.revive(resource), ranges, options, token), undefined, token);
|
|
}
|
|
|
|
registerOnTypeFormattingEditProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.OnTypeFormattingEditProvider, triggerCharacters: string[]): vscode.Disposable {
|
|
const handle = this._addNewAdapter(new OnTypeFormattingAdapter(this._documents, provider), extension);
|
|
this._proxy.$registerOnTypeFormattingSupport(handle, this._transformDocumentSelector(selector, extension), triggerCharacters, extension.identifier);
|
|
return this._createDisposable(handle);
|
|
}
|
|
|
|
$provideOnTypeFormattingEdits(handle: number, resource: UriComponents, position: IPosition, ch: string, options: languages.FormattingOptions, token: CancellationToken): Promise<languages.TextEdit[] | undefined> {
|
|
return this._withAdapter(handle, OnTypeFormattingAdapter, adapter => adapter.provideOnTypeFormattingEdits(URI.revive(resource), position, ch, options, token), undefined, token);
|
|
}
|
|
|
|
// --- navigate types
|
|
|
|
registerWorkspaceSymbolProvider(extension: IExtensionDescription, provider: vscode.WorkspaceSymbolProvider): vscode.Disposable {
|
|
const handle = this._addNewAdapter(new NavigateTypeAdapter(provider, this._logService), extension);
|
|
this._proxy.$registerNavigateTypeSupport(handle, typeof provider.resolveWorkspaceSymbol === 'function');
|
|
return this._createDisposable(handle);
|
|
}
|
|
|
|
$provideWorkspaceSymbols(handle: number, search: string, token: CancellationToken): Promise<extHostProtocol.IWorkspaceSymbolsDto> {
|
|
return this._withAdapter(handle, NavigateTypeAdapter, adapter => adapter.provideWorkspaceSymbols(search, token), { symbols: [] }, token);
|
|
}
|
|
|
|
$resolveWorkspaceSymbol(handle: number, symbol: extHostProtocol.IWorkspaceSymbolDto, token: CancellationToken): Promise<extHostProtocol.IWorkspaceSymbolDto | undefined> {
|
|
return this._withAdapter(handle, NavigateTypeAdapter, adapter => adapter.resolveWorkspaceSymbol(symbol, token), undefined, undefined);
|
|
}
|
|
|
|
$releaseWorkspaceSymbols(handle: number, id: number): void {
|
|
this._withAdapter(handle, NavigateTypeAdapter, adapter => adapter.releaseWorkspaceSymbols(id), undefined, undefined);
|
|
}
|
|
|
|
// --- rename
|
|
|
|
registerRenameProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.RenameProvider): vscode.Disposable {
|
|
const handle = this._addNewAdapter(new RenameAdapter(this._documents, provider, this._logService), extension);
|
|
this._proxy.$registerRenameSupport(handle, this._transformDocumentSelector(selector, extension), RenameAdapter.supportsResolving(provider));
|
|
return this._createDisposable(handle);
|
|
}
|
|
|
|
$provideRenameEdits(handle: number, resource: UriComponents, position: IPosition, newName: string, token: CancellationToken): Promise<extHostProtocol.IWorkspaceEditDto | undefined> {
|
|
return this._withAdapter(handle, RenameAdapter, adapter => adapter.provideRenameEdits(URI.revive(resource), position, newName, token), undefined, token);
|
|
}
|
|
|
|
$resolveRenameLocation(handle: number, resource: URI, position: IPosition, token: CancellationToken): Promise<languages.RenameLocation | undefined> {
|
|
return this._withAdapter(handle, RenameAdapter, adapter => adapter.resolveRenameLocation(URI.revive(resource), position, token), undefined, token);
|
|
}
|
|
|
|
registerNewSymbolNamesProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.NewSymbolNamesProvider): vscode.Disposable {
|
|
const handle = this._addNewAdapter(new NewSymbolNamesAdapter(this._documents, provider, this._logService), extension);
|
|
this._proxy.$registerNewSymbolNamesProvider(handle, this._transformDocumentSelector(selector, extension));
|
|
return this._createDisposable(handle);
|
|
}
|
|
|
|
$supportsAutomaticNewSymbolNamesTriggerKind(handle: number): Promise<boolean | undefined> {
|
|
return this._withAdapter(
|
|
handle,
|
|
NewSymbolNamesAdapter,
|
|
adapter => adapter.supportsAutomaticNewSymbolNamesTriggerKind(),
|
|
false,
|
|
undefined
|
|
);
|
|
}
|
|
|
|
$provideNewSymbolNames(handle: number, resource: UriComponents, range: IRange, triggerKind: languages.NewSymbolNameTriggerKind, token: CancellationToken): Promise<languages.NewSymbolName[] | undefined> {
|
|
return this._withAdapter(handle, NewSymbolNamesAdapter, adapter => adapter.provideNewSymbolNames(URI.revive(resource), range, triggerKind, token), undefined, token);
|
|
}
|
|
|
|
//#region semantic coloring
|
|
|
|
registerDocumentSemanticTokensProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentSemanticTokensProvider, legend: vscode.SemanticTokensLegend): vscode.Disposable {
|
|
const handle = this._addNewAdapter(new DocumentSemanticTokensAdapter(this._documents, provider), extension);
|
|
const eventHandle = (typeof provider.onDidChangeSemanticTokens === 'function' ? this._nextHandle() : undefined);
|
|
this._proxy.$registerDocumentSemanticTokensProvider(handle, this._transformDocumentSelector(selector, extension), legend, eventHandle);
|
|
let result = this._createDisposable(handle);
|
|
|
|
if (eventHandle) {
|
|
const subscription = provider.onDidChangeSemanticTokens!(_ => this._proxy.$emitDocumentSemanticTokensEvent(eventHandle));
|
|
result = Disposable.from(result, subscription);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
$provideDocumentSemanticTokens(handle: number, resource: UriComponents, previousResultId: number, token: CancellationToken): Promise<VSBuffer | null> {
|
|
return this._withAdapter(handle, DocumentSemanticTokensAdapter, adapter => adapter.provideDocumentSemanticTokens(URI.revive(resource), previousResultId, token), null, token);
|
|
}
|
|
|
|
$releaseDocumentSemanticTokens(handle: number, semanticColoringResultId: number): void {
|
|
this._withAdapter(handle, DocumentSemanticTokensAdapter, adapter => adapter.releaseDocumentSemanticColoring(semanticColoringResultId), undefined, undefined);
|
|
}
|
|
|
|
registerDocumentRangeSemanticTokensProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentRangeSemanticTokensProvider, legend: vscode.SemanticTokensLegend): vscode.Disposable {
|
|
const handle = this._addNewAdapter(new DocumentRangeSemanticTokensAdapter(this._documents, provider), extension);
|
|
const eventHandle = (typeof provider.onDidChangeSemanticTokens === 'function' ? this._nextHandle() : undefined);
|
|
this._proxy.$registerDocumentRangeSemanticTokensProvider(handle, this._transformDocumentSelector(selector, extension), legend, eventHandle);
|
|
let result = this._createDisposable(handle);
|
|
|
|
if (eventHandle) {
|
|
const subscription = provider.onDidChangeSemanticTokens!(_ => this._proxy.$emitDocumentRangeSemanticTokensEvent(eventHandle));
|
|
result = Disposable.from(result, subscription);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
$provideDocumentRangeSemanticTokens(handle: number, resource: UriComponents, range: IRange, token: CancellationToken): Promise<VSBuffer | null> {
|
|
return this._withAdapter(handle, DocumentRangeSemanticTokensAdapter, adapter => adapter.provideDocumentRangeSemanticTokens(URI.revive(resource), range, token), null, token);
|
|
}
|
|
|
|
//#endregion
|
|
|
|
// --- suggestion
|
|
|
|
registerCompletionItemProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.CompletionItemProvider, triggerCharacters: string[]): vscode.Disposable {
|
|
const handle = this._addNewAdapter(new CompletionsAdapter(this._documents, this._commands.converter, provider, this._apiDeprecation, extension), extension);
|
|
this._proxy.$registerCompletionsProvider(handle, this._transformDocumentSelector(selector, extension), triggerCharacters, CompletionsAdapter.supportsResolving(provider), extension.identifier);
|
|
return this._createDisposable(handle);
|
|
}
|
|
|
|
$provideCompletionItems(handle: number, resource: UriComponents, position: IPosition, context: languages.CompletionContext, token: CancellationToken): Promise<extHostProtocol.ISuggestResultDto | undefined> {
|
|
return this._withAdapter(handle, CompletionsAdapter, adapter => adapter.provideCompletionItems(URI.revive(resource), position, context, token), undefined, token);
|
|
}
|
|
|
|
$resolveCompletionItem(handle: number, id: extHostProtocol.ChainedCacheId, token: CancellationToken): Promise<extHostProtocol.ISuggestDataDto | undefined> {
|
|
return this._withAdapter(handle, CompletionsAdapter, adapter => adapter.resolveCompletionItem(id, token), undefined, token);
|
|
}
|
|
|
|
$releaseCompletionItems(handle: number, id: number): void {
|
|
this._withAdapter(handle, CompletionsAdapter, adapter => adapter.releaseCompletionItems(id), undefined, undefined);
|
|
}
|
|
|
|
// --- ghost text
|
|
|
|
registerInlineCompletionsProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.InlineCompletionItemProvider, metadata: vscode.InlineCompletionItemProviderMetadata | undefined): vscode.Disposable {
|
|
const eventHandle = typeof provider.onDidChange === 'function' && isProposedApiEnabled(extension, 'inlineCompletionsAdditions') ? this._nextHandle() : undefined;
|
|
const adapter = new InlineCompletionAdapter(extension, this._documents, provider, this._commands.converter);
|
|
const handle = this._addNewAdapter(adapter, extension);
|
|
let result = this._createDisposable(handle);
|
|
|
|
if (eventHandle !== undefined) {
|
|
const subscription = provider.onDidChange!(_ => this._proxy.$emitInlineCompletionsChange(eventHandle));
|
|
result = Disposable.from(result, subscription);
|
|
}
|
|
|
|
this._proxy.$registerInlineCompletionsSupport(
|
|
handle,
|
|
this._transformDocumentSelector(selector, extension),
|
|
adapter.supportsHandleEvents,
|
|
ExtensionIdentifier.toKey(extension.identifier.value),
|
|
extension.version,
|
|
metadata?.groupId ? ExtensionIdentifier.toKey(metadata.groupId) : undefined,
|
|
metadata?.yieldTo?.map(extId => ExtensionIdentifier.toKey(extId)) || [],
|
|
metadata?.displayName,
|
|
metadata?.debounceDelayMs,
|
|
metadata?.excludes?.map(extId => ExtensionIdentifier.toKey(extId)) || [],
|
|
eventHandle,
|
|
);
|
|
return result;
|
|
}
|
|
|
|
$provideInlineCompletions(handle: number, resource: UriComponents, position: IPosition, context: languages.InlineCompletionContext, token: CancellationToken): Promise<extHostProtocol.IdentifiableInlineCompletions | undefined> {
|
|
return this._withAdapter(handle, InlineCompletionAdapter, adapter => adapter.provideInlineCompletions(URI.revive(resource), position, context, token), undefined, undefined);
|
|
}
|
|
|
|
$handleInlineCompletionDidShow(handle: number, pid: number, idx: number, updatedInsertText: string): void {
|
|
this._withAdapter(handle, InlineCompletionAdapter, async adapter => {
|
|
adapter.handleDidShowCompletionItem(pid, idx, updatedInsertText);
|
|
}, undefined, undefined);
|
|
}
|
|
|
|
$handleInlineCompletionPartialAccept(handle: number, pid: number, idx: number, acceptedCharacters: number, info: languages.PartialAcceptInfo): void {
|
|
this._withAdapter(handle, InlineCompletionAdapter, async adapter => {
|
|
adapter.handlePartialAccept(pid, idx, acceptedCharacters, info);
|
|
}, undefined, undefined);
|
|
}
|
|
|
|
$handleInlineCompletionEndOfLifetime(handle: number, pid: number, idx: number, reason: languages.InlineCompletionEndOfLifeReason<{ pid: number; idx: number }>): void {
|
|
this._withAdapter(handle, InlineCompletionAdapter, async adapter => {
|
|
adapter.handleEndOfLifetime(pid, idx, reason);
|
|
}, undefined, undefined);
|
|
}
|
|
|
|
$handleInlineCompletionRejection(handle: number, pid: number, idx: number): void {
|
|
this._withAdapter(handle, InlineCompletionAdapter, async adapter => {
|
|
adapter.handleRejection(pid, idx);
|
|
}, undefined, undefined);
|
|
}
|
|
|
|
$freeInlineCompletionsList(handle: number, pid: number, reason: languages.InlineCompletionsDisposeReason): void {
|
|
this._withAdapter(handle, InlineCompletionAdapter, async adapter => { adapter.disposeCompletions(pid, reason); }, undefined, undefined);
|
|
}
|
|
|
|
$acceptInlineCompletionsUnificationState(state: IInlineCompletionsUnificationState): void {
|
|
this._inlineCompletionsUnificationState = state;
|
|
this._onDidChangeInlineCompletionsUnificationState.fire();
|
|
}
|
|
|
|
// --- parameter hints
|
|
|
|
registerSignatureHelpProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.SignatureHelpProvider, metadataOrTriggerChars: string[] | vscode.SignatureHelpProviderMetadata): vscode.Disposable {
|
|
const metadata: extHostProtocol.ISignatureHelpProviderMetadataDto | undefined = Array.isArray(metadataOrTriggerChars)
|
|
? { triggerCharacters: metadataOrTriggerChars, retriggerCharacters: [] }
|
|
: metadataOrTriggerChars;
|
|
|
|
const handle = this._addNewAdapter(new SignatureHelpAdapter(this._documents, provider), extension);
|
|
this._proxy.$registerSignatureHelpProvider(handle, this._transformDocumentSelector(selector, extension), metadata);
|
|
return this._createDisposable(handle);
|
|
}
|
|
|
|
$provideSignatureHelp(handle: number, resource: UriComponents, position: IPosition, context: extHostProtocol.ISignatureHelpContextDto, token: CancellationToken): Promise<extHostProtocol.ISignatureHelpDto | undefined> {
|
|
return this._withAdapter(handle, SignatureHelpAdapter, adapter => adapter.provideSignatureHelp(URI.revive(resource), position, context, token), undefined, token);
|
|
}
|
|
|
|
$releaseSignatureHelp(handle: number, id: number): void {
|
|
this._withAdapter(handle, SignatureHelpAdapter, adapter => adapter.releaseSignatureHelp(id), undefined, undefined);
|
|
}
|
|
|
|
// --- inline hints
|
|
|
|
registerInlayHintsProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.InlayHintsProvider): vscode.Disposable {
|
|
|
|
const eventHandle = typeof provider.onDidChangeInlayHints === 'function' ? this._nextHandle() : undefined;
|
|
const handle = this._addNewAdapter(new InlayHintsAdapter(this._documents, this._commands.converter, provider, this._logService, extension), extension);
|
|
|
|
this._proxy.$registerInlayHintsProvider(handle, this._transformDocumentSelector(selector, extension), typeof provider.resolveInlayHint === 'function', eventHandle, ExtHostLanguageFeatures._extLabel(extension));
|
|
let result = this._createDisposable(handle);
|
|
|
|
if (eventHandle !== undefined) {
|
|
const subscription = provider.onDidChangeInlayHints!(uri => this._proxy.$emitInlayHintsEvent(eventHandle));
|
|
result = Disposable.from(result, subscription);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
$provideInlayHints(handle: number, resource: UriComponents, range: IRange, token: CancellationToken): Promise<extHostProtocol.IInlayHintsDto | undefined> {
|
|
return this._withAdapter(handle, InlayHintsAdapter, adapter => adapter.provideInlayHints(URI.revive(resource), range, token), undefined, token);
|
|
}
|
|
|
|
$resolveInlayHint(handle: number, id: extHostProtocol.ChainedCacheId, token: CancellationToken): Promise<extHostProtocol.IInlayHintDto | undefined> {
|
|
return this._withAdapter(handle, InlayHintsAdapter, adapter => adapter.resolveInlayHint(id, token), undefined, token);
|
|
}
|
|
|
|
$releaseInlayHints(handle: number, id: number): void {
|
|
this._withAdapter(handle, InlayHintsAdapter, adapter => adapter.releaseHints(id), undefined, undefined);
|
|
}
|
|
|
|
// --- links
|
|
|
|
registerDocumentLinkProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentLinkProvider): vscode.Disposable {
|
|
const handle = this._addNewAdapter(new LinkProviderAdapter(this._documents, provider), extension);
|
|
this._proxy.$registerDocumentLinkProvider(handle, this._transformDocumentSelector(selector, extension), typeof provider.resolveDocumentLink === 'function');
|
|
return this._createDisposable(handle);
|
|
}
|
|
|
|
$provideDocumentLinks(handle: number, resource: UriComponents, token: CancellationToken): Promise<extHostProtocol.ILinksListDto | undefined> {
|
|
return this._withAdapter(handle, LinkProviderAdapter, adapter => adapter.provideLinks(URI.revive(resource), token), undefined, token, resource.scheme === 'output');
|
|
}
|
|
|
|
$resolveDocumentLink(handle: number, id: extHostProtocol.ChainedCacheId, token: CancellationToken): Promise<extHostProtocol.ILinkDto | undefined> {
|
|
return this._withAdapter(handle, LinkProviderAdapter, adapter => adapter.resolveLink(id, token), undefined, undefined, true);
|
|
}
|
|
|
|
$releaseDocumentLinks(handle: number, id: number): void {
|
|
this._withAdapter(handle, LinkProviderAdapter, adapter => adapter.releaseLinks(id), undefined, undefined, true);
|
|
}
|
|
|
|
registerColorProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentColorProvider): vscode.Disposable {
|
|
const handle = this._addNewAdapter(new ColorProviderAdapter(this._documents, provider), extension);
|
|
this._proxy.$registerDocumentColorProvider(handle, this._transformDocumentSelector(selector, extension));
|
|
return this._createDisposable(handle);
|
|
}
|
|
|
|
$provideDocumentColors(handle: number, resource: UriComponents, token: CancellationToken): Promise<extHostProtocol.IRawColorInfo[]> {
|
|
return this._withAdapter(handle, ColorProviderAdapter, adapter => adapter.provideColors(URI.revive(resource), token), [], token);
|
|
}
|
|
|
|
$provideColorPresentations(handle: number, resource: UriComponents, colorInfo: extHostProtocol.IRawColorInfo, token: CancellationToken): Promise<languages.IColorPresentation[] | undefined> {
|
|
return this._withAdapter(handle, ColorProviderAdapter, adapter => adapter.provideColorPresentations(URI.revive(resource), colorInfo, token), undefined, token);
|
|
}
|
|
|
|
registerFoldingRangeProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.FoldingRangeProvider): vscode.Disposable {
|
|
const handle = this._nextHandle();
|
|
const eventHandle = typeof provider.onDidChangeFoldingRanges === 'function' ? this._nextHandle() : undefined;
|
|
|
|
this._adapter.set(handle, new AdapterData(new FoldingProviderAdapter(this._documents, provider), extension));
|
|
this._proxy.$registerFoldingRangeProvider(handle, this._transformDocumentSelector(selector, extension), extension.identifier, eventHandle);
|
|
let result = this._createDisposable(handle);
|
|
|
|
if (eventHandle !== undefined) {
|
|
const subscription = provider.onDidChangeFoldingRanges!(() => this._proxy.$emitFoldingRangeEvent(eventHandle));
|
|
result = Disposable.from(result, subscription);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
$provideFoldingRanges(handle: number, resource: UriComponents, context: vscode.FoldingContext, token: CancellationToken): Promise<languages.FoldingRange[] | undefined> {
|
|
return this._withAdapter(
|
|
handle,
|
|
FoldingProviderAdapter,
|
|
(adapter) =>
|
|
adapter.provideFoldingRanges(URI.revive(resource), context, token),
|
|
undefined,
|
|
token
|
|
);
|
|
}
|
|
|
|
// --- smart select
|
|
|
|
registerSelectionRangeProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.SelectionRangeProvider): vscode.Disposable {
|
|
const handle = this._addNewAdapter(new SelectionRangeAdapter(this._documents, provider, this._logService), extension);
|
|
this._proxy.$registerSelectionRangeProvider(handle, this._transformDocumentSelector(selector, extension));
|
|
return this._createDisposable(handle);
|
|
}
|
|
|
|
$provideSelectionRanges(handle: number, resource: UriComponents, positions: IPosition[], token: CancellationToken): Promise<languages.SelectionRange[][]> {
|
|
return this._withAdapter(handle, SelectionRangeAdapter, adapter => adapter.provideSelectionRanges(URI.revive(resource), positions, token), [], token);
|
|
}
|
|
|
|
// --- call hierarchy
|
|
|
|
registerCallHierarchyProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.CallHierarchyProvider): vscode.Disposable {
|
|
const handle = this._addNewAdapter(new CallHierarchyAdapter(this._documents, provider), extension);
|
|
this._proxy.$registerCallHierarchyProvider(handle, this._transformDocumentSelector(selector, extension));
|
|
return this._createDisposable(handle);
|
|
}
|
|
|
|
$prepareCallHierarchy(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<extHostProtocol.ICallHierarchyItemDto[] | undefined> {
|
|
return this._withAdapter(handle, CallHierarchyAdapter, adapter => Promise.resolve(adapter.prepareSession(URI.revive(resource), position, token)), undefined, token);
|
|
}
|
|
|
|
$provideCallHierarchyIncomingCalls(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise<extHostProtocol.IIncomingCallDto[] | undefined> {
|
|
return this._withAdapter(handle, CallHierarchyAdapter, adapter => adapter.provideCallsTo(sessionId, itemId, token), undefined, token);
|
|
}
|
|
|
|
$provideCallHierarchyOutgoingCalls(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise<extHostProtocol.IOutgoingCallDto[] | undefined> {
|
|
return this._withAdapter(handle, CallHierarchyAdapter, adapter => adapter.provideCallsFrom(sessionId, itemId, token), undefined, token);
|
|
}
|
|
|
|
$releaseCallHierarchy(handle: number, sessionId: string): void {
|
|
this._withAdapter(handle, CallHierarchyAdapter, adapter => Promise.resolve(adapter.releaseSession(sessionId)), undefined, undefined);
|
|
}
|
|
|
|
// --- type hierarchy
|
|
registerTypeHierarchyProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.TypeHierarchyProvider): vscode.Disposable {
|
|
const handle = this._addNewAdapter(new TypeHierarchyAdapter(this._documents, provider), extension);
|
|
this._proxy.$registerTypeHierarchyProvider(handle, this._transformDocumentSelector(selector, extension));
|
|
return this._createDisposable(handle);
|
|
}
|
|
|
|
$prepareTypeHierarchy(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<extHostProtocol.ITypeHierarchyItemDto[] | undefined> {
|
|
return this._withAdapter(handle, TypeHierarchyAdapter, adapter => Promise.resolve(adapter.prepareSession(URI.revive(resource), position, token)), undefined, token);
|
|
}
|
|
|
|
$provideTypeHierarchySupertypes(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise<extHostProtocol.ITypeHierarchyItemDto[] | undefined> {
|
|
return this._withAdapter(handle, TypeHierarchyAdapter, adapter => adapter.provideSupertypes(sessionId, itemId, token), undefined, token);
|
|
}
|
|
|
|
$provideTypeHierarchySubtypes(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise<extHostProtocol.ITypeHierarchyItemDto[] | undefined> {
|
|
return this._withAdapter(handle, TypeHierarchyAdapter, adapter => adapter.provideSubtypes(sessionId, itemId, token), undefined, token);
|
|
}
|
|
|
|
$releaseTypeHierarchy(handle: number, sessionId: string): void {
|
|
this._withAdapter(handle, TypeHierarchyAdapter, adapter => Promise.resolve(adapter.releaseSession(sessionId)), undefined, undefined);
|
|
}
|
|
|
|
// --- Document on drop
|
|
|
|
registerDocumentOnDropEditProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentDropEditProvider, metadata?: vscode.DocumentDropEditProviderMetadata) {
|
|
const handle = this._nextHandle();
|
|
this._adapter.set(handle, new AdapterData(new DocumentDropEditAdapter(this._proxy, this._documents, provider, handle, extension), extension));
|
|
|
|
this._proxy.$registerDocumentOnDropEditProvider(handle, this._transformDocumentSelector(selector, extension), metadata ? {
|
|
supportsResolve: !!provider.resolveDocumentDropEdit,
|
|
dropMimeTypes: metadata.dropMimeTypes,
|
|
providedDropKinds: metadata.providedDropEditKinds?.map(x => x.value),
|
|
} : undefined);
|
|
|
|
return this._createDisposable(handle);
|
|
}
|
|
|
|
$provideDocumentOnDropEdits(handle: number, requestId: number, resource: UriComponents, position: IPosition, dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise<extHostProtocol.IDocumentDropEditDto[] | undefined> {
|
|
return this._withAdapter(handle, DocumentDropEditAdapter, adapter =>
|
|
Promise.resolve(adapter.provideDocumentOnDropEdits(requestId, URI.revive(resource), position, dataTransferDto, token)), undefined, undefined);
|
|
}
|
|
|
|
$resolveDropEdit(handle: number, id: extHostProtocol.ChainedCacheId, token: CancellationToken): Promise<{ additionalEdit?: extHostProtocol.IWorkspaceEditDto }> {
|
|
return this._withAdapter(handle, DocumentDropEditAdapter, adapter => adapter.resolveDropEdit(id, token), {}, undefined);
|
|
}
|
|
|
|
$releaseDocumentOnDropEdits(handle: number, cacheId: number): void {
|
|
this._withAdapter(handle, DocumentDropEditAdapter, adapter => Promise.resolve(adapter.releaseDropEdits(cacheId)), undefined, undefined);
|
|
}
|
|
|
|
// --- copy/paste actions
|
|
|
|
registerDocumentPasteEditProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentPasteEditProvider, metadata: vscode.DocumentPasteProviderMetadata): vscode.Disposable {
|
|
const handle = this._nextHandle();
|
|
this._adapter.set(handle, new AdapterData(new DocumentPasteEditProvider(this._proxy, this._documents, provider, handle, extension), extension));
|
|
this._proxy.$registerPasteEditProvider(handle, this._transformDocumentSelector(selector, extension), {
|
|
supportsCopy: !!provider.prepareDocumentPaste,
|
|
supportsPaste: !!provider.provideDocumentPasteEdits,
|
|
supportsResolve: !!provider.resolveDocumentPasteEdit,
|
|
providedPasteEditKinds: metadata.providedPasteEditKinds?.map(x => x.value),
|
|
copyMimeTypes: metadata.copyMimeTypes,
|
|
pasteMimeTypes: metadata.pasteMimeTypes,
|
|
});
|
|
return this._createDisposable(handle);
|
|
}
|
|
|
|
$prepareDocumentPaste(handle: number, resource: UriComponents, ranges: IRange[], dataTransfer: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise<extHostProtocol.DataTransferDTO | undefined> {
|
|
return this._withAdapter(handle, DocumentPasteEditProvider, adapter => adapter.prepareDocumentPaste(URI.revive(resource), ranges, dataTransfer, token), undefined, token);
|
|
}
|
|
|
|
$providePasteEdits(handle: number, requestId: number, resource: UriComponents, ranges: IRange[], dataTransferDto: extHostProtocol.DataTransferDTO, context: extHostProtocol.IDocumentPasteContextDto, token: CancellationToken): Promise<extHostProtocol.IPasteEditDto[] | undefined> {
|
|
return this._withAdapter(handle, DocumentPasteEditProvider, adapter => adapter.providePasteEdits(requestId, URI.revive(resource), ranges, dataTransferDto, context, token), undefined, token);
|
|
}
|
|
|
|
$resolvePasteEdit(handle: number, id: extHostProtocol.ChainedCacheId, token: CancellationToken): Promise<{ additionalEdit?: extHostProtocol.IWorkspaceEditDto }> {
|
|
return this._withAdapter(handle, DocumentPasteEditProvider, adapter => adapter.resolvePasteEdit(id, token), {}, undefined);
|
|
}
|
|
|
|
$releasePasteEdits(handle: number, cacheId: number): void {
|
|
this._withAdapter(handle, DocumentPasteEditProvider, adapter => Promise.resolve(adapter.releasePasteEdits(cacheId)), undefined, undefined);
|
|
}
|
|
|
|
// --- configuration
|
|
|
|
private static _serializeRegExp(regExp: RegExp): extHostProtocol.IRegExpDto {
|
|
return {
|
|
pattern: regExp.source,
|
|
flags: regExp.flags,
|
|
};
|
|
}
|
|
|
|
private static _serializeIndentationRule(indentationRule: vscode.IndentationRule): extHostProtocol.IIndentationRuleDto {
|
|
return {
|
|
decreaseIndentPattern: ExtHostLanguageFeatures._serializeRegExp(indentationRule.decreaseIndentPattern),
|
|
increaseIndentPattern: ExtHostLanguageFeatures._serializeRegExp(indentationRule.increaseIndentPattern),
|
|
indentNextLinePattern: indentationRule.indentNextLinePattern ? ExtHostLanguageFeatures._serializeRegExp(indentationRule.indentNextLinePattern) : undefined,
|
|
unIndentedLinePattern: indentationRule.unIndentedLinePattern ? ExtHostLanguageFeatures._serializeRegExp(indentationRule.unIndentedLinePattern) : undefined,
|
|
};
|
|
}
|
|
|
|
private static _serializeOnEnterRule(onEnterRule: vscode.OnEnterRule): extHostProtocol.IOnEnterRuleDto {
|
|
return {
|
|
beforeText: ExtHostLanguageFeatures._serializeRegExp(onEnterRule.beforeText),
|
|
afterText: onEnterRule.afterText ? ExtHostLanguageFeatures._serializeRegExp(onEnterRule.afterText) : undefined,
|
|
previousLineText: onEnterRule.previousLineText ? ExtHostLanguageFeatures._serializeRegExp(onEnterRule.previousLineText) : undefined,
|
|
action: onEnterRule.action
|
|
};
|
|
}
|
|
|
|
private static _serializeOnEnterRules(onEnterRules: vscode.OnEnterRule[]): extHostProtocol.IOnEnterRuleDto[] {
|
|
return onEnterRules.map(ExtHostLanguageFeatures._serializeOnEnterRule);
|
|
}
|
|
|
|
private static _serializeAutoClosingPair(autoClosingPair: vscode.AutoClosingPair): IAutoClosingPairConditional {
|
|
return {
|
|
open: autoClosingPair.open,
|
|
close: autoClosingPair.close,
|
|
notIn: autoClosingPair.notIn ? autoClosingPair.notIn.map(v => SyntaxTokenType.toString(v)) : undefined,
|
|
};
|
|
}
|
|
|
|
private static _serializeAutoClosingPairs(autoClosingPairs: vscode.AutoClosingPair[]): IAutoClosingPairConditional[] {
|
|
return autoClosingPairs.map(ExtHostLanguageFeatures._serializeAutoClosingPair);
|
|
}
|
|
|
|
setLanguageConfiguration(extension: IExtensionDescription, languageId: string, configuration: vscode.LanguageConfiguration): vscode.Disposable {
|
|
const { wordPattern } = configuration;
|
|
|
|
// check for a valid word pattern
|
|
if (wordPattern && regExpLeadsToEndlessLoop(wordPattern)) {
|
|
throw new Error(`Invalid language configuration: wordPattern '${wordPattern}' is not allowed to match the empty string.`);
|
|
}
|
|
|
|
// word definition
|
|
if (wordPattern) {
|
|
this._documents.setWordDefinitionFor(languageId, wordPattern);
|
|
} else {
|
|
this._documents.setWordDefinitionFor(languageId, undefined);
|
|
}
|
|
|
|
if (configuration.__electricCharacterSupport) {
|
|
this._apiDeprecation.report('LanguageConfiguration.__electricCharacterSupport', extension,
|
|
`Do not use.`);
|
|
}
|
|
|
|
if (configuration.__characterPairSupport) {
|
|
this._apiDeprecation.report('LanguageConfiguration.__characterPairSupport', extension,
|
|
`Do not use.`);
|
|
}
|
|
|
|
const handle = this._nextHandle();
|
|
const serializedConfiguration: extHostProtocol.ILanguageConfigurationDto = {
|
|
comments: configuration.comments,
|
|
brackets: configuration.brackets,
|
|
wordPattern: configuration.wordPattern ? ExtHostLanguageFeatures._serializeRegExp(configuration.wordPattern) : undefined,
|
|
indentationRules: configuration.indentationRules ? ExtHostLanguageFeatures._serializeIndentationRule(configuration.indentationRules) : undefined,
|
|
onEnterRules: configuration.onEnterRules ? ExtHostLanguageFeatures._serializeOnEnterRules(configuration.onEnterRules) : undefined,
|
|
__electricCharacterSupport: configuration.__electricCharacterSupport,
|
|
__characterPairSupport: configuration.__characterPairSupport,
|
|
autoClosingPairs: configuration.autoClosingPairs ? ExtHostLanguageFeatures._serializeAutoClosingPairs(configuration.autoClosingPairs) : undefined,
|
|
};
|
|
|
|
this._proxy.$setLanguageConfiguration(handle, languageId, serializedConfiguration);
|
|
return this._createDisposable(handle);
|
|
}
|
|
|
|
$setWordDefinitions(wordDefinitions: extHostProtocol.ILanguageWordDefinitionDto[]): void {
|
|
for (const wordDefinition of wordDefinitions) {
|
|
this._documents.setWordDefinitionFor(wordDefinition.languageId, new RegExp(wordDefinition.regexSource, wordDefinition.regexFlags));
|
|
}
|
|
}
|
|
}
|