/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { Emitter } from '../../base/common/event.js'; import { toDisposable } from '../../base/common/lifecycle.js'; import { shouldSynchronizeModel } from './model.js'; import { score } from './languageSelector.js'; function isExclusive(selector) { if (typeof selector === 'string') { return false; } else if (Array.isArray(selector)) { return selector.every(isExclusive); } else { return !!selector.exclusive; // TODO: microsoft/TypeScript#42768 } } class MatchCandidate { constructor(uri, languageId, notebookUri, notebookType) { this.uri = uri; this.languageId = languageId; this.notebookUri = notebookUri; this.notebookType = notebookType; } equals(other) { var _a, _b; return this.notebookType === other.notebookType && this.languageId === other.languageId && this.uri.toString() === other.uri.toString() && ((_a = this.notebookUri) === null || _a === void 0 ? void 0 : _a.toString()) === ((_b = other.notebookUri) === null || _b === void 0 ? void 0 : _b.toString()); } } export class LanguageFeatureRegistry { constructor(_notebookInfoResolver) { this._notebookInfoResolver = _notebookInfoResolver; this._clock = 0; this._entries = []; this._onDidChange = new Emitter(); this.onDidChange = this._onDidChange.event; } register(selector, provider) { let entry = { selector, provider, _score: -1, _time: this._clock++ }; this._entries.push(entry); this._lastCandidate = undefined; this._onDidChange.fire(this._entries.length); return toDisposable(() => { if (entry) { const idx = this._entries.indexOf(entry); if (idx >= 0) { this._entries.splice(idx, 1); this._lastCandidate = undefined; this._onDidChange.fire(this._entries.length); entry = undefined; } } }); } has(model) { return this.all(model).length > 0; } all(model) { if (!model) { return []; } this._updateScores(model); const result = []; // from registry for (const entry of this._entries) { if (entry._score > 0) { result.push(entry.provider); } } return result; } ordered(model) { const result = []; this._orderedForEach(model, entry => result.push(entry.provider)); return result; } orderedGroups(model) { const result = []; let lastBucket; let lastBucketScore; this._orderedForEach(model, entry => { if (lastBucket && lastBucketScore === entry._score) { lastBucket.push(entry.provider); } else { lastBucketScore = entry._score; lastBucket = [entry.provider]; result.push(lastBucket); } }); return result; } _orderedForEach(model, callback) { this._updateScores(model); for (const entry of this._entries) { if (entry._score > 0) { callback(entry); } } } _updateScores(model) { var _a, _b; const notebookInfo = (_a = this._notebookInfoResolver) === null || _a === void 0 ? void 0 : _a.call(this, model.uri); // use the uri (scheme, pattern) of the notebook info iff we have one // otherwise it's the model's/document's uri const candidate = notebookInfo ? new MatchCandidate(model.uri, model.getLanguageId(), notebookInfo.uri, notebookInfo.type) : new MatchCandidate(model.uri, model.getLanguageId(), undefined, undefined); if ((_b = this._lastCandidate) === null || _b === void 0 ? void 0 : _b.equals(candidate)) { // nothing has changed return; } this._lastCandidate = candidate; for (const entry of this._entries) { entry._score = score(entry.selector, candidate.uri, candidate.languageId, shouldSynchronizeModel(model), candidate.notebookUri, candidate.notebookType); if (isExclusive(entry.selector) && entry._score > 0) { // support for one exclusive selector that overwrites // any other selector for (const entry of this._entries) { entry._score = 0; } entry._score = 1000; break; } } // needs sorting this._entries.sort(LanguageFeatureRegistry._compareByScoreAndTime); } static _compareByScoreAndTime(a, b) { if (a._score < b._score) { return 1; } else if (a._score > b._score) { return -1; } // De-prioritize built-in providers if (isBuiltinSelector(a.selector) && !isBuiltinSelector(b.selector)) { return 1; } else if (!isBuiltinSelector(a.selector) && isBuiltinSelector(b.selector)) { return -1; } if (a._time < b._time) { return 1; } else if (a._time > b._time) { return -1; } else { return 0; } } } function isBuiltinSelector(selector) { if (typeof selector === 'string') { return false; } if (Array.isArray(selector)) { return selector.some(isBuiltinSelector); } return Boolean(selector.isBuiltin); }