Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/common/services/languagesRegistry.ts
3295 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, Event } from '../../../base/common/event.js';
7
import { Disposable, IDisposable } from '../../../base/common/lifecycle.js';
8
import { compareIgnoreCase, regExpLeadsToEndlessLoop } from '../../../base/common/strings.js';
9
import { clearPlatformLanguageAssociations, getLanguageIds, registerPlatformLanguageAssociation } from './languagesAssociations.js';
10
import { URI } from '../../../base/common/uri.js';
11
import { ILanguageIdCodec } from '../languages.js';
12
import { LanguageId } from '../encodedTokenAttributes.js';
13
import { ModesRegistry, PLAINTEXT_LANGUAGE_ID } from '../languages/modesRegistry.js';
14
import { ILanguageExtensionPoint, ILanguageNameIdPair, ILanguageIcon } from '../languages/language.js';
15
import { Extensions, IConfigurationRegistry } from '../../../platform/configuration/common/configurationRegistry.js';
16
import { Registry } from '../../../platform/registry/common/platform.js';
17
18
const hasOwnProperty = Object.prototype.hasOwnProperty;
19
const NULL_LANGUAGE_ID = 'vs.editor.nullLanguage';
20
21
interface IResolvedLanguage {
22
identifier: string;
23
name: string | null;
24
mimetypes: string[];
25
aliases: string[];
26
extensions: string[];
27
filenames: string[];
28
configurationFiles: URI[];
29
icons: ILanguageIcon[];
30
}
31
32
export class LanguageIdCodec implements ILanguageIdCodec {
33
34
private _nextLanguageId: number;
35
private readonly _languageIdToLanguage: string[] = [];
36
private readonly _languageToLanguageId = new Map<string, number>();
37
38
constructor() {
39
this._register(NULL_LANGUAGE_ID, LanguageId.Null);
40
this._register(PLAINTEXT_LANGUAGE_ID, LanguageId.PlainText);
41
this._nextLanguageId = 2;
42
}
43
44
private _register(language: string, languageId: LanguageId): void {
45
this._languageIdToLanguage[languageId] = language;
46
this._languageToLanguageId.set(language, languageId);
47
}
48
49
public register(language: string): void {
50
if (this._languageToLanguageId.has(language)) {
51
return;
52
}
53
const languageId = this._nextLanguageId++;
54
this._register(language, languageId);
55
}
56
57
public encodeLanguageId(languageId: string): LanguageId {
58
return this._languageToLanguageId.get(languageId) || LanguageId.Null;
59
}
60
61
public decodeLanguageId(languageId: LanguageId): string {
62
return this._languageIdToLanguage[languageId] || NULL_LANGUAGE_ID;
63
}
64
}
65
66
export class LanguagesRegistry extends Disposable {
67
68
static instanceCount = 0;
69
70
private readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());
71
public readonly onDidChange: Event<void> = this._onDidChange.event;
72
73
private readonly _warnOnOverwrite: boolean;
74
public readonly languageIdCodec: LanguageIdCodec;
75
private _dynamicLanguages: ILanguageExtensionPoint[];
76
private _languages: { [id: string]: IResolvedLanguage };
77
private _mimeTypesMap: { [mimeType: string]: string };
78
private _nameMap: { [name: string]: string };
79
private _lowercaseNameMap: { [name: string]: string };
80
81
constructor(useModesRegistry = true, warnOnOverwrite = false) {
82
super();
83
LanguagesRegistry.instanceCount++;
84
85
this._warnOnOverwrite = warnOnOverwrite;
86
this.languageIdCodec = new LanguageIdCodec();
87
this._dynamicLanguages = [];
88
this._languages = {};
89
this._mimeTypesMap = {};
90
this._nameMap = {};
91
this._lowercaseNameMap = {};
92
93
if (useModesRegistry) {
94
this._initializeFromRegistry();
95
this._register(ModesRegistry.onDidChangeLanguages((m) => {
96
this._initializeFromRegistry();
97
}));
98
}
99
}
100
101
override dispose() {
102
LanguagesRegistry.instanceCount--;
103
super.dispose();
104
}
105
106
public setDynamicLanguages(def: ILanguageExtensionPoint[]): void {
107
this._dynamicLanguages = def;
108
this._initializeFromRegistry();
109
}
110
111
private _initializeFromRegistry(): void {
112
this._languages = {};
113
this._mimeTypesMap = {};
114
this._nameMap = {};
115
this._lowercaseNameMap = {};
116
117
clearPlatformLanguageAssociations();
118
const desc = (<ILanguageExtensionPoint[]>[]).concat(ModesRegistry.getLanguages()).concat(this._dynamicLanguages);
119
this._registerLanguages(desc);
120
}
121
122
registerLanguage(desc: ILanguageExtensionPoint): IDisposable {
123
return ModesRegistry.registerLanguage(desc);
124
}
125
126
_registerLanguages(desc: ILanguageExtensionPoint[]): void {
127
128
for (const d of desc) {
129
this._registerLanguage(d);
130
}
131
132
// Rebuild fast path maps
133
this._mimeTypesMap = {};
134
this._nameMap = {};
135
this._lowercaseNameMap = {};
136
Object.keys(this._languages).forEach((langId) => {
137
const language = this._languages[langId];
138
if (language.name) {
139
this._nameMap[language.name] = language.identifier;
140
}
141
language.aliases.forEach((alias) => {
142
this._lowercaseNameMap[alias.toLowerCase()] = language.identifier;
143
});
144
language.mimetypes.forEach((mimetype) => {
145
this._mimeTypesMap[mimetype] = language.identifier;
146
});
147
});
148
149
Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerOverrideIdentifiers(this.getRegisteredLanguageIds());
150
151
this._onDidChange.fire();
152
}
153
154
private _registerLanguage(lang: ILanguageExtensionPoint): void {
155
const langId = lang.id;
156
157
let resolvedLanguage: IResolvedLanguage;
158
if (hasOwnProperty.call(this._languages, langId)) {
159
resolvedLanguage = this._languages[langId];
160
} else {
161
this.languageIdCodec.register(langId);
162
resolvedLanguage = {
163
identifier: langId,
164
name: null,
165
mimetypes: [],
166
aliases: [],
167
extensions: [],
168
filenames: [],
169
configurationFiles: [],
170
icons: []
171
};
172
this._languages[langId] = resolvedLanguage;
173
}
174
175
this._mergeLanguage(resolvedLanguage, lang);
176
}
177
178
private _mergeLanguage(resolvedLanguage: IResolvedLanguage, lang: ILanguageExtensionPoint): void {
179
const langId = lang.id;
180
181
let primaryMime: string | null = null;
182
183
if (Array.isArray(lang.mimetypes) && lang.mimetypes.length > 0) {
184
resolvedLanguage.mimetypes.push(...lang.mimetypes);
185
primaryMime = lang.mimetypes[0];
186
}
187
188
if (!primaryMime) {
189
primaryMime = `text/x-${langId}`;
190
resolvedLanguage.mimetypes.push(primaryMime);
191
}
192
193
if (Array.isArray(lang.extensions)) {
194
if (lang.configuration) {
195
// insert first as this appears to be the 'primary' language definition
196
resolvedLanguage.extensions = lang.extensions.concat(resolvedLanguage.extensions);
197
} else {
198
resolvedLanguage.extensions = resolvedLanguage.extensions.concat(lang.extensions);
199
}
200
for (const extension of lang.extensions) {
201
registerPlatformLanguageAssociation({ id: langId, mime: primaryMime, extension: extension }, this._warnOnOverwrite);
202
}
203
}
204
205
if (Array.isArray(lang.filenames)) {
206
for (const filename of lang.filenames) {
207
registerPlatformLanguageAssociation({ id: langId, mime: primaryMime, filename: filename }, this._warnOnOverwrite);
208
resolvedLanguage.filenames.push(filename);
209
}
210
}
211
212
if (Array.isArray(lang.filenamePatterns)) {
213
for (const filenamePattern of lang.filenamePatterns) {
214
registerPlatformLanguageAssociation({ id: langId, mime: primaryMime, filepattern: filenamePattern }, this._warnOnOverwrite);
215
}
216
}
217
218
if (typeof lang.firstLine === 'string' && lang.firstLine.length > 0) {
219
let firstLineRegexStr = lang.firstLine;
220
if (firstLineRegexStr.charAt(0) !== '^') {
221
firstLineRegexStr = '^' + firstLineRegexStr;
222
}
223
try {
224
const firstLineRegex = new RegExp(firstLineRegexStr);
225
if (!regExpLeadsToEndlessLoop(firstLineRegex)) {
226
registerPlatformLanguageAssociation({ id: langId, mime: primaryMime, firstline: firstLineRegex }, this._warnOnOverwrite);
227
}
228
} catch (err) {
229
// Most likely, the regex was bad
230
console.warn(`[${lang.id}]: Invalid regular expression \`${firstLineRegexStr}\`: `, err);
231
}
232
}
233
234
resolvedLanguage.aliases.push(langId);
235
236
let langAliases: Array<string | null> | null = null;
237
if (typeof lang.aliases !== 'undefined' && Array.isArray(lang.aliases)) {
238
if (lang.aliases.length === 0) {
239
// signal that this language should not get a name
240
langAliases = [null];
241
} else {
242
langAliases = lang.aliases;
243
}
244
}
245
246
if (langAliases !== null) {
247
for (const langAlias of langAliases) {
248
if (!langAlias || langAlias.length === 0) {
249
continue;
250
}
251
resolvedLanguage.aliases.push(langAlias);
252
}
253
}
254
255
const containsAliases = (langAliases !== null && langAliases.length > 0);
256
if (containsAliases && langAliases![0] === null) {
257
// signal that this language should not get a name
258
} else {
259
const bestName = (containsAliases ? langAliases![0] : null) || langId;
260
if (containsAliases || !resolvedLanguage.name) {
261
resolvedLanguage.name = bestName;
262
}
263
}
264
265
if (lang.configuration) {
266
resolvedLanguage.configurationFiles.push(lang.configuration);
267
}
268
269
if (lang.icon) {
270
resolvedLanguage.icons.push(lang.icon);
271
}
272
}
273
274
public isRegisteredLanguageId(languageId: string | null | undefined): boolean {
275
if (!languageId) {
276
return false;
277
}
278
return hasOwnProperty.call(this._languages, languageId);
279
}
280
281
public getRegisteredLanguageIds(): string[] {
282
return Object.keys(this._languages);
283
}
284
285
public getSortedRegisteredLanguageNames(): ILanguageNameIdPair[] {
286
const result: ILanguageNameIdPair[] = [];
287
for (const languageName in this._nameMap) {
288
if (hasOwnProperty.call(this._nameMap, languageName)) {
289
result.push({
290
languageName: languageName,
291
languageId: this._nameMap[languageName]
292
});
293
}
294
}
295
result.sort((a, b) => compareIgnoreCase(a.languageName, b.languageName));
296
return result;
297
}
298
299
public getLanguageName(languageId: string): string | null {
300
if (!hasOwnProperty.call(this._languages, languageId)) {
301
return null;
302
}
303
return this._languages[languageId].name;
304
}
305
306
public getMimeType(languageId: string): string | null {
307
if (!hasOwnProperty.call(this._languages, languageId)) {
308
return null;
309
}
310
const language = this._languages[languageId];
311
return (language.mimetypes[0] || null);
312
}
313
314
public getExtensions(languageId: string): ReadonlyArray<string> {
315
if (!hasOwnProperty.call(this._languages, languageId)) {
316
return [];
317
}
318
return this._languages[languageId].extensions;
319
}
320
321
public getFilenames(languageId: string): ReadonlyArray<string> {
322
if (!hasOwnProperty.call(this._languages, languageId)) {
323
return [];
324
}
325
return this._languages[languageId].filenames;
326
}
327
328
public getIcon(languageId: string): ILanguageIcon | null {
329
if (!hasOwnProperty.call(this._languages, languageId)) {
330
return null;
331
}
332
const language = this._languages[languageId];
333
return (language.icons[0] || null);
334
}
335
336
public getConfigurationFiles(languageId: string): ReadonlyArray<URI> {
337
if (!hasOwnProperty.call(this._languages, languageId)) {
338
return [];
339
}
340
return this._languages[languageId].configurationFiles || [];
341
}
342
343
public getLanguageIdByLanguageName(languageName: string): string | null {
344
const languageNameLower = languageName.toLowerCase();
345
if (!hasOwnProperty.call(this._lowercaseNameMap, languageNameLower)) {
346
return null;
347
}
348
return this._lowercaseNameMap[languageNameLower];
349
}
350
351
public getLanguageIdByMimeType(mimeType: string | null | undefined): string | null {
352
if (!mimeType) {
353
return null;
354
}
355
if (hasOwnProperty.call(this._mimeTypesMap, mimeType)) {
356
return this._mimeTypesMap[mimeType];
357
}
358
return null;
359
}
360
361
public guessLanguageIdByFilepathOrFirstLine(resource: URI | null, firstLine?: string): string[] {
362
if (!resource && !firstLine) {
363
return [];
364
}
365
return getLanguageIds(resource, firstLine);
366
}
367
}
368
369