Path: blob/main/src/vs/editor/common/languageFeatureRegistry.ts
3292 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 { Emitter } from '../../base/common/event.js';6import { IDisposable, toDisposable } from '../../base/common/lifecycle.js';7import { ITextModel, shouldSynchronizeModel } from './model.js';8import { LanguageFilter, LanguageSelector, score } from './languageSelector.js';9import { URI } from '../../base/common/uri.js';1011interface Entry<T> {12readonly selector: LanguageSelector;13readonly provider: T;14_score: number;15readonly _time: number;16}1718function isExclusive(selector: LanguageSelector): boolean {19if (typeof selector === 'string') {20return false;21} else if (Array.isArray(selector)) {22return selector.every(isExclusive);23} else {24return !!(selector as LanguageFilter).exclusive; // TODO: microsoft/TypeScript#4276825}26}2728export interface NotebookInfo {29readonly uri: URI;30readonly type: string;31}3233export interface NotebookInfoResolver {34(uri: URI): NotebookInfo | undefined;35}3637class MatchCandidate {38constructor(39readonly uri: URI,40readonly languageId: string,41readonly notebookUri: URI | undefined,42readonly notebookType: string | undefined,43readonly recursive: boolean,44) { }4546equals(other: MatchCandidate): boolean {47return this.notebookType === other.notebookType48&& this.languageId === other.languageId49&& this.uri.toString() === other.uri.toString()50&& this.notebookUri?.toString() === other.notebookUri?.toString()51&& this.recursive === other.recursive;52}53}5455export class LanguageFeatureRegistry<T> {5657private _clock: number = 0;58private readonly _entries: Entry<T>[] = [];5960private readonly _onDidChange = new Emitter<number>();61get onDidChange() { return this._onDidChange.event; }6263constructor(private readonly _notebookInfoResolver?: NotebookInfoResolver) { }6465register(selector: LanguageSelector, provider: T): IDisposable {6667let entry: Entry<T> | undefined = {68selector,69provider,70_score: -1,71_time: this._clock++72};7374this._entries.push(entry);75this._lastCandidate = undefined;76this._onDidChange.fire(this._entries.length);7778return toDisposable(() => {79if (entry) {80const idx = this._entries.indexOf(entry);81if (idx >= 0) {82this._entries.splice(idx, 1);83this._lastCandidate = undefined;84this._onDidChange.fire(this._entries.length);85entry = undefined;86}87}88});89}9091has(model: ITextModel): boolean {92return this.all(model).length > 0;93}9495all(model: ITextModel): T[] {96if (!model) {97return [];98}99100this._updateScores(model, false);101const result: T[] = [];102103// from registry104for (const entry of this._entries) {105if (entry._score > 0) {106result.push(entry.provider);107}108}109110return result;111}112113allNoModel(): T[] {114return this._entries.map(entry => entry.provider);115}116117ordered(model: ITextModel, recursive = false): T[] {118const result: T[] = [];119this._orderedForEach(model, recursive, entry => result.push(entry.provider));120return result;121}122123orderedGroups(model: ITextModel): T[][] {124const result: T[][] = [];125let lastBucket: T[];126let lastBucketScore: number;127128this._orderedForEach(model, false, entry => {129if (lastBucket && lastBucketScore === entry._score) {130lastBucket.push(entry.provider);131} else {132lastBucketScore = entry._score;133lastBucket = [entry.provider];134result.push(lastBucket);135}136});137138return result;139}140141private _orderedForEach(model: ITextModel, recursive: boolean, callback: (provider: Entry<T>) => void): void {142143this._updateScores(model, recursive);144145for (const entry of this._entries) {146if (entry._score > 0) {147callback(entry);148}149}150}151152private _lastCandidate: MatchCandidate | undefined;153154private _updateScores(model: ITextModel, recursive: boolean): void {155156const notebookInfo = this._notebookInfoResolver?.(model.uri);157158// use the uri (scheme, pattern) of the notebook info iff we have one159// otherwise it's the model's/document's uri160const candidate = notebookInfo161? new MatchCandidate(model.uri, model.getLanguageId(), notebookInfo.uri, notebookInfo.type, recursive)162: new MatchCandidate(model.uri, model.getLanguageId(), undefined, undefined, recursive);163164if (this._lastCandidate?.equals(candidate)) {165// nothing has changed166return;167}168169this._lastCandidate = candidate;170171for (const entry of this._entries) {172entry._score = score(entry.selector, candidate.uri, candidate.languageId, shouldSynchronizeModel(model), candidate.notebookUri, candidate.notebookType);173174if (isExclusive(entry.selector) && entry._score > 0) {175if (recursive) {176entry._score = 0;177} else {178// support for one exclusive selector that overwrites179// any other selector180for (const entry of this._entries) {181entry._score = 0;182}183entry._score = 1000;184break;185}186}187}188189// needs sorting190this._entries.sort(LanguageFeatureRegistry._compareByScoreAndTime);191}192193private static _compareByScoreAndTime(a: Entry<unknown>, b: Entry<unknown>): number {194if (a._score < b._score) {195return 1;196} else if (a._score > b._score) {197return -1;198}199200// De-prioritize built-in providers201if (isBuiltinSelector(a.selector) && !isBuiltinSelector(b.selector)) {202return 1;203} else if (!isBuiltinSelector(a.selector) && isBuiltinSelector(b.selector)) {204return -1;205}206207if (a._time < b._time) {208return 1;209} else if (a._time > b._time) {210return -1;211} else {212return 0;213}214}215}216217function isBuiltinSelector(selector: LanguageSelector): boolean {218if (typeof selector === 'string') {219return false;220}221222if (Array.isArray(selector)) {223return selector.some(isBuiltinSelector);224}225226return Boolean((selector as LanguageFilter).isBuiltin);227}228229230231