Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/common/languageFeatureRegistry.ts
3292 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { Emitter } from '../../base/common/event.js';
7
import { IDisposable, toDisposable } from '../../base/common/lifecycle.js';
8
import { ITextModel, shouldSynchronizeModel } from './model.js';
9
import { LanguageFilter, LanguageSelector, score } from './languageSelector.js';
10
import { URI } from '../../base/common/uri.js';
11
12
interface Entry<T> {
13
readonly selector: LanguageSelector;
14
readonly provider: T;
15
_score: number;
16
readonly _time: number;
17
}
18
19
function isExclusive(selector: LanguageSelector): boolean {
20
if (typeof selector === 'string') {
21
return false;
22
} else if (Array.isArray(selector)) {
23
return selector.every(isExclusive);
24
} else {
25
return !!(selector as LanguageFilter).exclusive; // TODO: microsoft/TypeScript#42768
26
}
27
}
28
29
export interface NotebookInfo {
30
readonly uri: URI;
31
readonly type: string;
32
}
33
34
export interface NotebookInfoResolver {
35
(uri: URI): NotebookInfo | undefined;
36
}
37
38
class MatchCandidate {
39
constructor(
40
readonly uri: URI,
41
readonly languageId: string,
42
readonly notebookUri: URI | undefined,
43
readonly notebookType: string | undefined,
44
readonly recursive: boolean,
45
) { }
46
47
equals(other: MatchCandidate): boolean {
48
return this.notebookType === other.notebookType
49
&& this.languageId === other.languageId
50
&& this.uri.toString() === other.uri.toString()
51
&& this.notebookUri?.toString() === other.notebookUri?.toString()
52
&& this.recursive === other.recursive;
53
}
54
}
55
56
export class LanguageFeatureRegistry<T> {
57
58
private _clock: number = 0;
59
private readonly _entries: Entry<T>[] = [];
60
61
private readonly _onDidChange = new Emitter<number>();
62
get onDidChange() { return this._onDidChange.event; }
63
64
constructor(private readonly _notebookInfoResolver?: NotebookInfoResolver) { }
65
66
register(selector: LanguageSelector, provider: T): IDisposable {
67
68
let entry: Entry<T> | undefined = {
69
selector,
70
provider,
71
_score: -1,
72
_time: this._clock++
73
};
74
75
this._entries.push(entry);
76
this._lastCandidate = undefined;
77
this._onDidChange.fire(this._entries.length);
78
79
return toDisposable(() => {
80
if (entry) {
81
const idx = this._entries.indexOf(entry);
82
if (idx >= 0) {
83
this._entries.splice(idx, 1);
84
this._lastCandidate = undefined;
85
this._onDidChange.fire(this._entries.length);
86
entry = undefined;
87
}
88
}
89
});
90
}
91
92
has(model: ITextModel): boolean {
93
return this.all(model).length > 0;
94
}
95
96
all(model: ITextModel): T[] {
97
if (!model) {
98
return [];
99
}
100
101
this._updateScores(model, false);
102
const result: T[] = [];
103
104
// from registry
105
for (const entry of this._entries) {
106
if (entry._score > 0) {
107
result.push(entry.provider);
108
}
109
}
110
111
return result;
112
}
113
114
allNoModel(): T[] {
115
return this._entries.map(entry => entry.provider);
116
}
117
118
ordered(model: ITextModel, recursive = false): T[] {
119
const result: T[] = [];
120
this._orderedForEach(model, recursive, entry => result.push(entry.provider));
121
return result;
122
}
123
124
orderedGroups(model: ITextModel): T[][] {
125
const result: T[][] = [];
126
let lastBucket: T[];
127
let lastBucketScore: number;
128
129
this._orderedForEach(model, false, entry => {
130
if (lastBucket && lastBucketScore === entry._score) {
131
lastBucket.push(entry.provider);
132
} else {
133
lastBucketScore = entry._score;
134
lastBucket = [entry.provider];
135
result.push(lastBucket);
136
}
137
});
138
139
return result;
140
}
141
142
private _orderedForEach(model: ITextModel, recursive: boolean, callback: (provider: Entry<T>) => void): void {
143
144
this._updateScores(model, recursive);
145
146
for (const entry of this._entries) {
147
if (entry._score > 0) {
148
callback(entry);
149
}
150
}
151
}
152
153
private _lastCandidate: MatchCandidate | undefined;
154
155
private _updateScores(model: ITextModel, recursive: boolean): void {
156
157
const notebookInfo = this._notebookInfoResolver?.(model.uri);
158
159
// use the uri (scheme, pattern) of the notebook info iff we have one
160
// otherwise it's the model's/document's uri
161
const candidate = notebookInfo
162
? new MatchCandidate(model.uri, model.getLanguageId(), notebookInfo.uri, notebookInfo.type, recursive)
163
: new MatchCandidate(model.uri, model.getLanguageId(), undefined, undefined, recursive);
164
165
if (this._lastCandidate?.equals(candidate)) {
166
// nothing has changed
167
return;
168
}
169
170
this._lastCandidate = candidate;
171
172
for (const entry of this._entries) {
173
entry._score = score(entry.selector, candidate.uri, candidate.languageId, shouldSynchronizeModel(model), candidate.notebookUri, candidate.notebookType);
174
175
if (isExclusive(entry.selector) && entry._score > 0) {
176
if (recursive) {
177
entry._score = 0;
178
} else {
179
// support for one exclusive selector that overwrites
180
// any other selector
181
for (const entry of this._entries) {
182
entry._score = 0;
183
}
184
entry._score = 1000;
185
break;
186
}
187
}
188
}
189
190
// needs sorting
191
this._entries.sort(LanguageFeatureRegistry._compareByScoreAndTime);
192
}
193
194
private static _compareByScoreAndTime(a: Entry<unknown>, b: Entry<unknown>): number {
195
if (a._score < b._score) {
196
return 1;
197
} else if (a._score > b._score) {
198
return -1;
199
}
200
201
// De-prioritize built-in providers
202
if (isBuiltinSelector(a.selector) && !isBuiltinSelector(b.selector)) {
203
return 1;
204
} else if (!isBuiltinSelector(a.selector) && isBuiltinSelector(b.selector)) {
205
return -1;
206
}
207
208
if (a._time < b._time) {
209
return 1;
210
} else if (a._time > b._time) {
211
return -1;
212
} else {
213
return 0;
214
}
215
}
216
}
217
218
function isBuiltinSelector(selector: LanguageSelector): boolean {
219
if (typeof selector === 'string') {
220
return false;
221
}
222
223
if (Array.isArray(selector)) {
224
return selector.some(isBuiltinSelector);
225
}
226
227
return Boolean((selector as LanguageFilter).isBuiltin);
228
}
229
230
231