@21epub/epub-thirdparty
Version:
epub-thirdparty
196 lines (195 loc) • 6.13 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* 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 { doHash } from '../../../base/common/hash.js';
import { toDisposable } from '../../../base/common/lifecycle.js';
import { LRUCache } from '../../../base/common/map.js';
import { MovingAverage } from '../../../base/common/numbers.js';
import { score } from './languageSelector.js';
import { shouldSynchronizeModel } from '../services/modelService.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
}
}
export class LanguageFeatureRegistry {
constructor() {
this._clock = 0;
this._entries = [];
this._onDidChange = new Emitter();
}
get onDidChange() {
return 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) {
let 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 (let 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) {
if (!model) {
return;
}
this._updateScores(model);
for (const entry of this._entries) {
if (entry._score > 0) {
callback(entry);
}
}
}
_updateScores(model) {
let candidate = {
uri: model.uri.toString(),
language: model.getLanguageId()
};
if (this._lastCandidate
&& this._lastCandidate.language === candidate.language
&& this._lastCandidate.uri === candidate.uri) {
// nothing has changed
return;
}
this._lastCandidate = candidate;
for (let entry of this._entries) {
entry._score = score(entry.selector, model.uri, model.getLanguageId(), shouldSynchronizeModel(model));
if (isExclusive(entry.selector) && entry._score > 0) {
// support for one exclusive selector that overwrites
// any other selector
for (let 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;
}
else if (a._time < b._time) {
return 1;
}
else if (a._time > b._time) {
return -1;
}
else {
return 0;
}
}
}
const _hashes = new WeakMap();
let pool = 0;
function weakHash(obj) {
let value = _hashes.get(obj);
if (value === undefined) {
value = ++pool;
_hashes.set(obj, value);
}
return value;
}
/**
* Keeps moving average per model and set of providers so that requests
* can be debounce according to the provider performance
*/
export class LanguageFeatureRequestDelays {
constructor(_registry, min, max = Number.MAX_SAFE_INTEGER) {
this._registry = _registry;
this.min = min;
this.max = max;
this._cache = new LRUCache(50, 0.7);
}
_key(model) {
return model.id + this._registry.all(model).reduce((hashVal, obj) => doHash(weakHash(obj), hashVal), 0);
}
_clamp(value) {
if (value === undefined) {
return this.min;
}
else {
return Math.min(this.max, Math.max(this.min, Math.floor(value * 1.3)));
}
}
get(model) {
const key = this._key(model);
const avg = this._cache.get(key);
return this._clamp(avg === null || avg === void 0 ? void 0 : avg.value);
}
update(model, value) {
const key = this._key(model);
let avg = this._cache.get(key);
if (!avg) {
avg = new MovingAverage();
this._cache.set(key, avg);
}
avg.update(value);
return this.get(model);
}
}