Path: blob/main/src/vs/editor/contrib/semanticTokens/common/getSemanticTokens.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { CancellationToken } from '../../../../base/common/cancellation.js';6import { onUnexpectedExternalError } from '../../../../base/common/errors.js';7import { URI } from '../../../../base/common/uri.js';8import { ITextModel } from '../../../common/model.js';9import { DocumentSemanticTokensProvider, SemanticTokens, SemanticTokensEdits, SemanticTokensLegend, DocumentRangeSemanticTokensProvider } from '../../../common/languages.js';10import { IModelService } from '../../../common/services/model.js';11import { CommandsRegistry, ICommandService } from '../../../../platform/commands/common/commands.js';12import { assertType } from '../../../../base/common/types.js';13import { VSBuffer } from '../../../../base/common/buffer.js';14import { encodeSemanticTokensDto } from '../../../common/services/semanticTokensDto.js';15import { Range } from '../../../common/core/range.js';16import { LanguageFeatureRegistry } from '../../../common/languageFeatureRegistry.js';17import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js';1819export function isSemanticTokens(v: SemanticTokens | SemanticTokensEdits): v is SemanticTokens {20return v && !!((<SemanticTokens>v).data);21}2223export function isSemanticTokensEdits(v: SemanticTokens | SemanticTokensEdits): v is SemanticTokensEdits {24return v && Array.isArray((<SemanticTokensEdits>v).edits);25}2627export class DocumentSemanticTokensResult {28constructor(29public readonly provider: DocumentSemanticTokensProvider,30public readonly tokens: SemanticTokens | SemanticTokensEdits | null,31public readonly error: any32) { }33}3435export function hasDocumentSemanticTokensProvider(registry: LanguageFeatureRegistry<DocumentSemanticTokensProvider>, model: ITextModel): boolean {36return registry.has(model);37}3839function getDocumentSemanticTokensProviders(registry: LanguageFeatureRegistry<DocumentSemanticTokensProvider>, model: ITextModel): DocumentSemanticTokensProvider[] {40const groups = registry.orderedGroups(model);41return (groups.length > 0 ? groups[0] : []);42}4344export async function getDocumentSemanticTokens(registry: LanguageFeatureRegistry<DocumentSemanticTokensProvider>, model: ITextModel, lastProvider: DocumentSemanticTokensProvider | null, lastResultId: string | null, token: CancellationToken): Promise<DocumentSemanticTokensResult | null> {45const providers = getDocumentSemanticTokensProviders(registry, model);4647// Get tokens from all providers at the same time.48const results = await Promise.all(providers.map(async (provider) => {49let result: SemanticTokens | SemanticTokensEdits | null | undefined;50let error: unknown = null;51try {52result = await provider.provideDocumentSemanticTokens(model, (provider === lastProvider ? lastResultId : null), token);53} catch (err) {54error = err;55result = null;56}5758if (!result || (!isSemanticTokens(result) && !isSemanticTokensEdits(result))) {59result = null;60}6162return new DocumentSemanticTokensResult(provider, result, error);63}));6465// Try to return the first result with actual tokens or66// the first result which threw an error (!!)67for (const result of results) {68if (result.error) {69throw result.error;70}71if (result.tokens) {72return result;73}74}7576// Return the first result, even if it doesn't have tokens77if (results.length > 0) {78return results[0];79}8081return null;82}8384function _getDocumentSemanticTokensProviderHighestGroup(registry: LanguageFeatureRegistry<DocumentSemanticTokensProvider>, model: ITextModel): DocumentSemanticTokensProvider[] | null {85const result = registry.orderedGroups(model);86return (result.length > 0 ? result[0] : null);87}8889class DocumentRangeSemanticTokensResult {90constructor(91public readonly provider: DocumentRangeSemanticTokensProvider,92public readonly tokens: SemanticTokens | null,93) { }94}9596export function hasDocumentRangeSemanticTokensProvider(providers: LanguageFeatureRegistry<DocumentRangeSemanticTokensProvider>, model: ITextModel): boolean {97return providers.has(model);98}99100function getDocumentRangeSemanticTokensProviders(providers: LanguageFeatureRegistry<DocumentRangeSemanticTokensProvider>, model: ITextModel): DocumentRangeSemanticTokensProvider[] {101const groups = providers.orderedGroups(model);102return (groups.length > 0 ? groups[0] : []);103}104105export async function getDocumentRangeSemanticTokens(registry: LanguageFeatureRegistry<DocumentRangeSemanticTokensProvider>, model: ITextModel, range: Range, token: CancellationToken): Promise<DocumentRangeSemanticTokensResult | null> {106const providers = getDocumentRangeSemanticTokensProviders(registry, model);107108// Get tokens from all providers at the same time.109const results = await Promise.all(providers.map(async (provider) => {110let result: SemanticTokens | null | undefined;111try {112result = await provider.provideDocumentRangeSemanticTokens(model, range, token);113} catch (err) {114onUnexpectedExternalError(err);115result = null;116}117118if (!result || !isSemanticTokens(result)) {119result = null;120}121122return new DocumentRangeSemanticTokensResult(provider, result);123}));124125// Try to return the first result with actual tokens126for (const result of results) {127if (result.tokens) {128return result;129}130}131132// Return the first result, even if it doesn't have tokens133if (results.length > 0) {134return results[0];135}136137return null;138}139140CommandsRegistry.registerCommand('_provideDocumentSemanticTokensLegend', async (accessor, ...args): Promise<SemanticTokensLegend | undefined> => {141const [uri] = args;142assertType(uri instanceof URI);143144const model = accessor.get(IModelService).getModel(uri);145if (!model) {146return undefined;147}148const { documentSemanticTokensProvider } = accessor.get(ILanguageFeaturesService);149150const providers = _getDocumentSemanticTokensProviderHighestGroup(documentSemanticTokensProvider, model);151if (!providers) {152// there is no provider => fall back to a document range semantic tokens provider153return accessor.get(ICommandService).executeCommand('_provideDocumentRangeSemanticTokensLegend', uri);154}155156return providers[0].getLegend();157});158159CommandsRegistry.registerCommand('_provideDocumentSemanticTokens', async (accessor, ...args): Promise<VSBuffer | undefined> => {160const [uri] = args;161assertType(uri instanceof URI);162163const model = accessor.get(IModelService).getModel(uri);164if (!model) {165return undefined;166}167const { documentSemanticTokensProvider } = accessor.get(ILanguageFeaturesService);168if (!hasDocumentSemanticTokensProvider(documentSemanticTokensProvider, model)) {169// there is no provider => fall back to a document range semantic tokens provider170return accessor.get(ICommandService).executeCommand('_provideDocumentRangeSemanticTokens', uri, model.getFullModelRange());171}172173const r = await getDocumentSemanticTokens(documentSemanticTokensProvider, model, null, null, CancellationToken.None);174if (!r) {175return undefined;176}177178const { provider, tokens } = r;179180if (!tokens || !isSemanticTokens(tokens)) {181return undefined;182}183184const buff = encodeSemanticTokensDto({185id: 0,186type: 'full',187data: tokens.data188});189if (tokens.resultId) {190provider.releaseDocumentSemanticTokens(tokens.resultId);191}192return buff;193});194195CommandsRegistry.registerCommand('_provideDocumentRangeSemanticTokensLegend', async (accessor, ...args): Promise<SemanticTokensLegend | undefined> => {196const [uri, range] = args;197assertType(uri instanceof URI);198199const model = accessor.get(IModelService).getModel(uri);200if (!model) {201return undefined;202}203const { documentRangeSemanticTokensProvider } = accessor.get(ILanguageFeaturesService);204const providers = getDocumentRangeSemanticTokensProviders(documentRangeSemanticTokensProvider, model);205if (providers.length === 0) {206// no providers207return undefined;208}209210if (providers.length === 1) {211// straight forward case, just a single provider212return providers[0].getLegend();213}214215if (!range || !Range.isIRange(range)) {216// if no range is provided, we cannot support multiple providers217// as we cannot fall back to the one which would give results218// => return the first legend for backwards compatibility and print a warning219console.warn(`provideDocumentRangeSemanticTokensLegend might be out-of-sync with provideDocumentRangeSemanticTokens unless a range argument is passed in`);220return providers[0].getLegend();221}222223const result = await getDocumentRangeSemanticTokens(documentRangeSemanticTokensProvider, model, Range.lift(range), CancellationToken.None);224if (!result) {225return undefined;226}227228return result.provider.getLegend();229});230231CommandsRegistry.registerCommand('_provideDocumentRangeSemanticTokens', async (accessor, ...args): Promise<VSBuffer | undefined> => {232const [uri, range] = args;233assertType(uri instanceof URI);234assertType(Range.isIRange(range));235236const model = accessor.get(IModelService).getModel(uri);237if (!model) {238return undefined;239}240const { documentRangeSemanticTokensProvider } = accessor.get(ILanguageFeaturesService);241242const result = await getDocumentRangeSemanticTokens(documentRangeSemanticTokensProvider, model, Range.lift(range), CancellationToken.None);243if (!result || !result.tokens) {244// there is no provider or it didn't return tokens245return undefined;246}247248return encodeSemanticTokensDto({249id: 0,250type: 'full',251data: result.tokens.data252});253});254255256