Path: blob/main/src/vs/editor/common/services/languagesRegistry.ts
3295 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, Event } from '../../../base/common/event.js';6import { Disposable, IDisposable } from '../../../base/common/lifecycle.js';7import { compareIgnoreCase, regExpLeadsToEndlessLoop } from '../../../base/common/strings.js';8import { clearPlatformLanguageAssociations, getLanguageIds, registerPlatformLanguageAssociation } from './languagesAssociations.js';9import { URI } from '../../../base/common/uri.js';10import { ILanguageIdCodec } from '../languages.js';11import { LanguageId } from '../encodedTokenAttributes.js';12import { ModesRegistry, PLAINTEXT_LANGUAGE_ID } from '../languages/modesRegistry.js';13import { ILanguageExtensionPoint, ILanguageNameIdPair, ILanguageIcon } from '../languages/language.js';14import { Extensions, IConfigurationRegistry } from '../../../platform/configuration/common/configurationRegistry.js';15import { Registry } from '../../../platform/registry/common/platform.js';1617const hasOwnProperty = Object.prototype.hasOwnProperty;18const NULL_LANGUAGE_ID = 'vs.editor.nullLanguage';1920interface IResolvedLanguage {21identifier: string;22name: string | null;23mimetypes: string[];24aliases: string[];25extensions: string[];26filenames: string[];27configurationFiles: URI[];28icons: ILanguageIcon[];29}3031export class LanguageIdCodec implements ILanguageIdCodec {3233private _nextLanguageId: number;34private readonly _languageIdToLanguage: string[] = [];35private readonly _languageToLanguageId = new Map<string, number>();3637constructor() {38this._register(NULL_LANGUAGE_ID, LanguageId.Null);39this._register(PLAINTEXT_LANGUAGE_ID, LanguageId.PlainText);40this._nextLanguageId = 2;41}4243private _register(language: string, languageId: LanguageId): void {44this._languageIdToLanguage[languageId] = language;45this._languageToLanguageId.set(language, languageId);46}4748public register(language: string): void {49if (this._languageToLanguageId.has(language)) {50return;51}52const languageId = this._nextLanguageId++;53this._register(language, languageId);54}5556public encodeLanguageId(languageId: string): LanguageId {57return this._languageToLanguageId.get(languageId) || LanguageId.Null;58}5960public decodeLanguageId(languageId: LanguageId): string {61return this._languageIdToLanguage[languageId] || NULL_LANGUAGE_ID;62}63}6465export class LanguagesRegistry extends Disposable {6667static instanceCount = 0;6869private readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());70public readonly onDidChange: Event<void> = this._onDidChange.event;7172private readonly _warnOnOverwrite: boolean;73public readonly languageIdCodec: LanguageIdCodec;74private _dynamicLanguages: ILanguageExtensionPoint[];75private _languages: { [id: string]: IResolvedLanguage };76private _mimeTypesMap: { [mimeType: string]: string };77private _nameMap: { [name: string]: string };78private _lowercaseNameMap: { [name: string]: string };7980constructor(useModesRegistry = true, warnOnOverwrite = false) {81super();82LanguagesRegistry.instanceCount++;8384this._warnOnOverwrite = warnOnOverwrite;85this.languageIdCodec = new LanguageIdCodec();86this._dynamicLanguages = [];87this._languages = {};88this._mimeTypesMap = {};89this._nameMap = {};90this._lowercaseNameMap = {};9192if (useModesRegistry) {93this._initializeFromRegistry();94this._register(ModesRegistry.onDidChangeLanguages((m) => {95this._initializeFromRegistry();96}));97}98}99100override dispose() {101LanguagesRegistry.instanceCount--;102super.dispose();103}104105public setDynamicLanguages(def: ILanguageExtensionPoint[]): void {106this._dynamicLanguages = def;107this._initializeFromRegistry();108}109110private _initializeFromRegistry(): void {111this._languages = {};112this._mimeTypesMap = {};113this._nameMap = {};114this._lowercaseNameMap = {};115116clearPlatformLanguageAssociations();117const desc = (<ILanguageExtensionPoint[]>[]).concat(ModesRegistry.getLanguages()).concat(this._dynamicLanguages);118this._registerLanguages(desc);119}120121registerLanguage(desc: ILanguageExtensionPoint): IDisposable {122return ModesRegistry.registerLanguage(desc);123}124125_registerLanguages(desc: ILanguageExtensionPoint[]): void {126127for (const d of desc) {128this._registerLanguage(d);129}130131// Rebuild fast path maps132this._mimeTypesMap = {};133this._nameMap = {};134this._lowercaseNameMap = {};135Object.keys(this._languages).forEach((langId) => {136const language = this._languages[langId];137if (language.name) {138this._nameMap[language.name] = language.identifier;139}140language.aliases.forEach((alias) => {141this._lowercaseNameMap[alias.toLowerCase()] = language.identifier;142});143language.mimetypes.forEach((mimetype) => {144this._mimeTypesMap[mimetype] = language.identifier;145});146});147148Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerOverrideIdentifiers(this.getRegisteredLanguageIds());149150this._onDidChange.fire();151}152153private _registerLanguage(lang: ILanguageExtensionPoint): void {154const langId = lang.id;155156let resolvedLanguage: IResolvedLanguage;157if (hasOwnProperty.call(this._languages, langId)) {158resolvedLanguage = this._languages[langId];159} else {160this.languageIdCodec.register(langId);161resolvedLanguage = {162identifier: langId,163name: null,164mimetypes: [],165aliases: [],166extensions: [],167filenames: [],168configurationFiles: [],169icons: []170};171this._languages[langId] = resolvedLanguage;172}173174this._mergeLanguage(resolvedLanguage, lang);175}176177private _mergeLanguage(resolvedLanguage: IResolvedLanguage, lang: ILanguageExtensionPoint): void {178const langId = lang.id;179180let primaryMime: string | null = null;181182if (Array.isArray(lang.mimetypes) && lang.mimetypes.length > 0) {183resolvedLanguage.mimetypes.push(...lang.mimetypes);184primaryMime = lang.mimetypes[0];185}186187if (!primaryMime) {188primaryMime = `text/x-${langId}`;189resolvedLanguage.mimetypes.push(primaryMime);190}191192if (Array.isArray(lang.extensions)) {193if (lang.configuration) {194// insert first as this appears to be the 'primary' language definition195resolvedLanguage.extensions = lang.extensions.concat(resolvedLanguage.extensions);196} else {197resolvedLanguage.extensions = resolvedLanguage.extensions.concat(lang.extensions);198}199for (const extension of lang.extensions) {200registerPlatformLanguageAssociation({ id: langId, mime: primaryMime, extension: extension }, this._warnOnOverwrite);201}202}203204if (Array.isArray(lang.filenames)) {205for (const filename of lang.filenames) {206registerPlatformLanguageAssociation({ id: langId, mime: primaryMime, filename: filename }, this._warnOnOverwrite);207resolvedLanguage.filenames.push(filename);208}209}210211if (Array.isArray(lang.filenamePatterns)) {212for (const filenamePattern of lang.filenamePatterns) {213registerPlatformLanguageAssociation({ id: langId, mime: primaryMime, filepattern: filenamePattern }, this._warnOnOverwrite);214}215}216217if (typeof lang.firstLine === 'string' && lang.firstLine.length > 0) {218let firstLineRegexStr = lang.firstLine;219if (firstLineRegexStr.charAt(0) !== '^') {220firstLineRegexStr = '^' + firstLineRegexStr;221}222try {223const firstLineRegex = new RegExp(firstLineRegexStr);224if (!regExpLeadsToEndlessLoop(firstLineRegex)) {225registerPlatformLanguageAssociation({ id: langId, mime: primaryMime, firstline: firstLineRegex }, this._warnOnOverwrite);226}227} catch (err) {228// Most likely, the regex was bad229console.warn(`[${lang.id}]: Invalid regular expression \`${firstLineRegexStr}\`: `, err);230}231}232233resolvedLanguage.aliases.push(langId);234235let langAliases: Array<string | null> | null = null;236if (typeof lang.aliases !== 'undefined' && Array.isArray(lang.aliases)) {237if (lang.aliases.length === 0) {238// signal that this language should not get a name239langAliases = [null];240} else {241langAliases = lang.aliases;242}243}244245if (langAliases !== null) {246for (const langAlias of langAliases) {247if (!langAlias || langAlias.length === 0) {248continue;249}250resolvedLanguage.aliases.push(langAlias);251}252}253254const containsAliases = (langAliases !== null && langAliases.length > 0);255if (containsAliases && langAliases![0] === null) {256// signal that this language should not get a name257} else {258const bestName = (containsAliases ? langAliases![0] : null) || langId;259if (containsAliases || !resolvedLanguage.name) {260resolvedLanguage.name = bestName;261}262}263264if (lang.configuration) {265resolvedLanguage.configurationFiles.push(lang.configuration);266}267268if (lang.icon) {269resolvedLanguage.icons.push(lang.icon);270}271}272273public isRegisteredLanguageId(languageId: string | null | undefined): boolean {274if (!languageId) {275return false;276}277return hasOwnProperty.call(this._languages, languageId);278}279280public getRegisteredLanguageIds(): string[] {281return Object.keys(this._languages);282}283284public getSortedRegisteredLanguageNames(): ILanguageNameIdPair[] {285const result: ILanguageNameIdPair[] = [];286for (const languageName in this._nameMap) {287if (hasOwnProperty.call(this._nameMap, languageName)) {288result.push({289languageName: languageName,290languageId: this._nameMap[languageName]291});292}293}294result.sort((a, b) => compareIgnoreCase(a.languageName, b.languageName));295return result;296}297298public getLanguageName(languageId: string): string | null {299if (!hasOwnProperty.call(this._languages, languageId)) {300return null;301}302return this._languages[languageId].name;303}304305public getMimeType(languageId: string): string | null {306if (!hasOwnProperty.call(this._languages, languageId)) {307return null;308}309const language = this._languages[languageId];310return (language.mimetypes[0] || null);311}312313public getExtensions(languageId: string): ReadonlyArray<string> {314if (!hasOwnProperty.call(this._languages, languageId)) {315return [];316}317return this._languages[languageId].extensions;318}319320public getFilenames(languageId: string): ReadonlyArray<string> {321if (!hasOwnProperty.call(this._languages, languageId)) {322return [];323}324return this._languages[languageId].filenames;325}326327public getIcon(languageId: string): ILanguageIcon | null {328if (!hasOwnProperty.call(this._languages, languageId)) {329return null;330}331const language = this._languages[languageId];332return (language.icons[0] || null);333}334335public getConfigurationFiles(languageId: string): ReadonlyArray<URI> {336if (!hasOwnProperty.call(this._languages, languageId)) {337return [];338}339return this._languages[languageId].configurationFiles || [];340}341342public getLanguageIdByLanguageName(languageName: string): string | null {343const languageNameLower = languageName.toLowerCase();344if (!hasOwnProperty.call(this._lowercaseNameMap, languageNameLower)) {345return null;346}347return this._lowercaseNameMap[languageNameLower];348}349350public getLanguageIdByMimeType(mimeType: string | null | undefined): string | null {351if (!mimeType) {352return null;353}354if (hasOwnProperty.call(this._mimeTypesMap, mimeType)) {355return this._mimeTypesMap[mimeType];356}357return null;358}359360public guessLanguageIdByFilepathOrFirstLine(resource: URI | null, firstLine?: string): string[] {361if (!resource && !firstLine) {362return [];363}364return getLanguageIds(resource, firstLine);365}366}367368369