Path: blob/main/src/vs/editor/contrib/codelens/browser/codelens.ts
4779 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 { illegalArgument, onUnexpectedExternalError } from '../../../../base/common/errors.js';7import { DisposableStore, isDisposable } from '../../../../base/common/lifecycle.js';8import { assertType } from '../../../../base/common/types.js';9import { URI } from '../../../../base/common/uri.js';10import { ITextModel } from '../../../common/model.js';11import { CodeLens, CodeLensList, CodeLensProvider } from '../../../common/languages.js';12import { IModelService } from '../../../common/services/model.js';13import { CommandsRegistry } from '../../../../platform/commands/common/commands.js';14import { LanguageFeatureRegistry } from '../../../common/languageFeatureRegistry.js';15import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js';1617export interface CodeLensItem {18readonly symbol: CodeLens;19readonly provider: CodeLensProvider;20}2122export class CodeLensModel {2324static readonly Empty = new CodeLensModel();2526lenses: CodeLensItem[] = [];2728private _store: DisposableStore | undefined;2930dispose(): void {31this._store?.dispose();32}3334get isDisposed(): boolean {35return this._store?.isDisposed ?? false;36}3738add(list: CodeLensList, provider: CodeLensProvider): void {39if (isDisposable(list)) {40this._store ??= new DisposableStore();41this._store.add(list);42}43for (const symbol of list.lenses) {44this.lenses.push({ symbol, provider });45}46}47}4849export async function getCodeLensModel(registry: LanguageFeatureRegistry<CodeLensProvider>, model: ITextModel, token: CancellationToken): Promise<CodeLensModel> {5051const provider = registry.ordered(model);52const providerRanks = new Map<CodeLensProvider, number>();53const result = new CodeLensModel();5455const promises = provider.map(async (provider, i) => {5657providerRanks.set(provider, i);5859try {60const list = await Promise.resolve(provider.provideCodeLenses(model, token));61if (list) {62result.add(list, provider);63}64} catch (err) {65onUnexpectedExternalError(err);66}67});6869await Promise.all(promises);7071if (token.isCancellationRequested) {72result.dispose();73return CodeLensModel.Empty;74}7576result.lenses = result.lenses.sort((a, b) => {77// sort by lineNumber, provider-rank, and column78if (a.symbol.range.startLineNumber < b.symbol.range.startLineNumber) {79return -1;80} else if (a.symbol.range.startLineNumber > b.symbol.range.startLineNumber) {81return 1;82} else if ((providerRanks.get(a.provider)!) < (providerRanks.get(b.provider)!)) {83return -1;84} else if ((providerRanks.get(a.provider)!) > (providerRanks.get(b.provider)!)) {85return 1;86} else if (a.symbol.range.startColumn < b.symbol.range.startColumn) {87return -1;88} else if (a.symbol.range.startColumn > b.symbol.range.startColumn) {89return 1;90} else {91return 0;92}93});94return result;95}9697CommandsRegistry.registerCommand('_executeCodeLensProvider', function (accessor, ...args: [URI, number | undefined | null]) {98let [uri, itemResolveCount] = args;99assertType(URI.isUri(uri));100assertType(typeof itemResolveCount === 'number' || !itemResolveCount);101102const { codeLensProvider } = accessor.get(ILanguageFeaturesService);103104const model = accessor.get(IModelService).getModel(uri);105if (!model) {106throw illegalArgument();107}108109const result: CodeLens[] = [];110const disposables = new DisposableStore();111return getCodeLensModel(codeLensProvider, model, CancellationToken.None).then(value => {112113disposables.add(value);114const resolve: Promise<unknown>[] = [];115116for (const item of value.lenses) {117if (itemResolveCount === undefined || itemResolveCount === null || Boolean(item.symbol.command)) {118result.push(item.symbol);119} else if (itemResolveCount-- > 0 && item.provider.resolveCodeLens) {120resolve.push(Promise.resolve(item.provider.resolveCodeLens(model, item.symbol, CancellationToken.None)).then(symbol => result.push(symbol || item.symbol)));121}122}123124return Promise.all(resolve);125126}).then(() => {127return result;128}).finally(() => {129// make sure to return results, then (on next tick)130// dispose the results131setTimeout(() => disposables.dispose(), 100);132});133});134135136