in patched-vscode/src/vs/workbench/api/common/extHostLanguageFeatures.ts [522:1367]
isAI: isProposedApiEnabled(this._extension, 'codeActionAI') ? candidate.isAI : false,
ranges: isProposedApiEnabled(this._extension, 'codeActionRanges') ? coalesce(range.map(typeConvert.Range.from)) : undefined,
disabled: candidate.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 readonly _cache = new Cache<vscode.DocumentPasteEdit>('DocumentPasteEdit');
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;
}
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 entries = Array.from(dataTransfer).filter(([, value]) => !(value instanceof InternalDataTransferItem));
return typeConvert.DataTransfer.from(entries);
}
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 dataTransfer = typeConvert.DataTransfer.toDataTransfer(dataTransferDto, async (id) => {
return (await this._proxy.$resolvePasteFileData(this._handle, requestId, id)).buffer;
});
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._cache.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<{ additionalEdit?: extHostProtocol.IWorkspaceEditDto }> {
const [sessionId, itemId] = id;
const item = this._cache.get(sessionId, itemId);
if (!item || !this._provider.resolveDocumentPasteEdit) {
return {}; // this should not happen...
}
const resolvedItem = (await this._provider.resolveDocumentPasteEdit(item, token)) ?? item;
const additionalEdit = resolvedItem.additionalEdit ? typeConvert.WorkspaceEdit.from(resolvedItem.additionalEdit, undefined) : undefined;
return { additionalEdit };
}
releasePasteEdits(id: number): any {
this._cache.delete(id);
}
}
class DocumentFormattingAdapter {
constructor(
private readonly _documents: ExtHostDocuments,
private readonly _provider: vscode.DocumentFormattingEditProvider
) { }
async provideDocumentFormattingEdits(resource: URI, options: languages.FormattingOptions, token: CancellationToken): Promise<ISingleEditOperation[] | undefined> {
const document = this._documents.getDocument(resource);
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<ISingleEditOperation[] | undefined> {
const document = this._documents.getDocument(resource);
const ran = typeConvert.Range.to(range);
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<ISingleEditOperation[] | 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);
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<ISingleEditOperation[] | undefined> {
const document = this._documents.getDocument(resource);
const pos = typeConvert.Position.to(position);
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 InlineCompletionAdapterBase {
async provideInlineCompletions(resource: URI, position: IPosition, context: languages.InlineCompletionContext, token: CancellationToken): Promise<extHostProtocol.IdentifiableInlineCompletions | undefined> {
return undefined;
}
disposeCompletions(pid: number): void { }
handleDidShowCompletionItem(pid: number, idx: number, updatedInsertText: string): void { }
handlePartialAccept(pid: number, idx: number, acceptedCharacters: number, info: languages.PartialAcceptInfo): void { }
}
class InlineCompletionAdapter extends InlineCompletionAdapterBase {
private readonly _references = new ReferenceMap<{
dispose(): void;
items: readonly vscode.InlineCompletionItem[];
}>();
private readonly _isAdditionsProposedApiEnabled = isProposedApiEnabled(this._extension, 'inlineCompletionsAdditions');
constructor(
private readonly _extension: IExtensionDescription,
private readonly _documents: ExtHostDocuments,
private readonly _provider: vscode.InlineCompletionItemProvider,
private readonly _commands: CommandsConverter,
) {
super();
}
public get supportsHandleEvents(): boolean {
return isProposedApiEnabled(this._extension, 'inlineCompletionsAdditions')
&& (typeof this._provider.handleDidShowCompletionItem === 'function'
|| typeof this._provider.handleDidPartiallyAcceptCompletionItem === 'function');
}
private readonly languageTriggerKindToVSCodeTriggerKind: Record<languages.InlineCompletionTriggerKind, InlineCompletionTriggerKind> = {
[languages.InlineCompletionTriggerKind.Automatic]: InlineCompletionTriggerKind.Automatic,
[languages.InlineCompletionTriggerKind.Explicit]: InlineCompletionTriggerKind.Invoke,
};
override 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]
}, token);
if (!result) {
// 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 normalizedResult = Array.isArray(result) ? result : result.items;
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: normalizedResult
});
return {
pid,
items: normalizedResult.map<extHostProtocol.IdentifiableInlineCompletion>((item, idx) => {
let command: languages.Command | undefined = undefined;
if (item.command) {