Path: blob/main/src/vs/editor/contrib/links/browser/getLinks.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 { coalesce } from '../../../../base/common/arrays.js';6import { CancellationToken } from '../../../../base/common/cancellation.js';7import { onUnexpectedExternalError } from '../../../../base/common/errors.js';8import { DisposableStore, isDisposable } from '../../../../base/common/lifecycle.js';9import { assertType } from '../../../../base/common/types.js';10import { URI } from '../../../../base/common/uri.js';11import { IRange, Range } from '../../../common/core/range.js';12import { ITextModel } from '../../../common/model.js';13import { ILink, ILinksList, LinkProvider } from '../../../common/languages.js';14import { IModelService } from '../../../common/services/model.js';15import { CommandsRegistry } from '../../../../platform/commands/common/commands.js';16import { LanguageFeatureRegistry } from '../../../common/languageFeatureRegistry.js';17import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js';1819export class Link implements ILink {2021private _link: ILink;22private readonly _provider: LinkProvider;2324constructor(link: ILink, provider: LinkProvider) {25this._link = link;26this._provider = provider;27}2829toJSON(): ILink {30return {31range: this.range,32url: this.url,33tooltip: this.tooltip34};35}3637get range(): IRange {38return this._link.range;39}4041get url(): URI | string | undefined {42return this._link.url;43}4445get tooltip(): string | undefined {46return this._link.tooltip;47}4849async resolve(token: CancellationToken): Promise<URI | string> {50if (this._link.url) {51return this._link.url;52}5354if (typeof this._provider.resolveLink === 'function') {55return Promise.resolve(this._provider.resolveLink(this._link, token)).then(value => {56this._link = value || this._link;57if (this._link.url) {58// recurse59return this.resolve(token);60}6162return Promise.reject(new Error('missing'));63});64}6566return Promise.reject(new Error('missing'));67}68}6970export class LinksList {7172static readonly Empty = new LinksList([]);7374readonly links: Link[];7576private readonly _disposables: DisposableStore | undefined = new DisposableStore();7778constructor(tuples: [ILinksList, LinkProvider][]) {7980let links: Link[] = [];81for (const [list, provider] of tuples) {82// merge all links83const newLinks = list.links.map(link => new Link(link, provider));84links = LinksList._union(links, newLinks);85// register disposables86if (isDisposable(list)) {87this._disposables ??= new DisposableStore();88this._disposables.add(list);89}90}91this.links = links;92}9394dispose(): void {95this._disposables?.dispose();96this.links.length = 0;97}9899private static _union(oldLinks: Link[], newLinks: Link[]): Link[] {100// reunite oldLinks with newLinks and remove duplicates101const result: Link[] = [];102let oldIndex: number;103let oldLen: number;104let newIndex: number;105let newLen: number;106107for (oldIndex = 0, newIndex = 0, oldLen = oldLinks.length, newLen = newLinks.length; oldIndex < oldLen && newIndex < newLen;) {108const oldLink = oldLinks[oldIndex];109const newLink = newLinks[newIndex];110111if (Range.areIntersectingOrTouching(oldLink.range, newLink.range)) {112// Remove the oldLink113oldIndex++;114continue;115}116117const comparisonResult = Range.compareRangesUsingStarts(oldLink.range, newLink.range);118119if (comparisonResult < 0) {120// oldLink is before121result.push(oldLink);122oldIndex++;123} else {124// newLink is before125result.push(newLink);126newIndex++;127}128}129130for (; oldIndex < oldLen; oldIndex++) {131result.push(oldLinks[oldIndex]);132}133for (; newIndex < newLen; newIndex++) {134result.push(newLinks[newIndex]);135}136137return result;138}139140}141142export async function getLinks(providers: LanguageFeatureRegistry<LinkProvider>, model: ITextModel, token: CancellationToken): Promise<LinksList> {143const lists: [ILinksList, LinkProvider][] = [];144145// ask all providers for links in parallel146const promises = providers.ordered(model).reverse().map(async (provider, i) => {147try {148const result = await provider.provideLinks(model, token);149if (result) {150lists[i] = [result, provider];151}152} catch (err) {153onUnexpectedExternalError(err);154}155});156157await Promise.all(promises);158159let res = new LinksList(coalesce(lists));160161if (token.isCancellationRequested) {162res.dispose();163res = LinksList.Empty;164}165166return res;167}168169170CommandsRegistry.registerCommand('_executeLinkProvider', async (accessor, ...args): Promise<ILink[]> => {171let [uri, resolveCount] = args;172assertType(uri instanceof URI);173174if (typeof resolveCount !== 'number') {175resolveCount = 0;176}177178const { linkProvider } = accessor.get(ILanguageFeaturesService);179const model = accessor.get(IModelService).getModel(uri);180if (!model) {181return [];182}183const list = await getLinks(linkProvider, model, CancellationToken.None);184if (!list) {185return [];186}187188// resolve links189for (let i = 0; i < Math.min(resolveCount, list.links.length); i++) {190await list.links[i].resolve(CancellationToken.None);191}192193const result = list.links.slice(0);194list.dispose();195return result;196});197198199