/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { onUnexpectedError } from '../../../base/common/errors.js'; import { Emitter } from '../../../base/common/event.js'; import { Disposable } from '../../../base/common/lifecycle.js'; import * as mime from '../../../base/common/mime.js'; import * as strings from '../../../base/common/strings.js'; import { ModesRegistry, PLAINTEXT_MODE_ID } from '../modes/modesRegistry.js'; import { NULL_MODE_ID } from '../modes/nullMode.js'; import { Extensions } from '../../../platform/configuration/common/configurationRegistry.js'; import { Registry } from '../../../platform/registry/common/platform.js'; const hasOwnProperty = Object.prototype.hasOwnProperty; export class LanguageIdCodec { constructor() { this._languageIdToLanguage = []; this._languageToLanguageId = new Map(); this._register(NULL_MODE_ID, 0 /* Null */); this._register(PLAINTEXT_MODE_ID, 1 /* PlainText */); this._nextLanguageId = 2; } _register(language, languageId) { this._languageIdToLanguage[languageId] = language; this._languageToLanguageId.set(language, languageId); } register(language) { if (this._languageToLanguageId.has(language)) { return; } const languageId = this._nextLanguageId++; this._register(language, languageId); } encodeLanguageId(languageId) { return this._languageToLanguageId.get(languageId) || 0 /* Null */; } decodeLanguageId(languageId) { return this._languageIdToLanguage[languageId] || NULL_MODE_ID; } } export class LanguagesRegistry extends Disposable { constructor(useModesRegistry = true, warnOnOverwrite = false) { super(); this._onDidChange = this._register(new Emitter()); this.onDidChange = this._onDidChange.event; LanguagesRegistry.instanceCount++; this._warnOnOverwrite = warnOnOverwrite; this.languageIdCodec = new LanguageIdCodec(); this._languages = {}; this._mimeTypesMap = {}; this._nameMap = {}; this._lowercaseNameMap = {}; if (useModesRegistry) { this._initializeFromRegistry(); this._register(ModesRegistry.onDidChangeLanguages((m) => { // console.log(`onDidChangeLanguages - inst count: ${LanguagesRegistry.instanceCount}`); this._initializeFromRegistry(); })); } } dispose() { LanguagesRegistry.instanceCount--; super.dispose(); } _initializeFromRegistry() { this._languages = {}; this._mimeTypesMap = {}; this._nameMap = {}; this._lowercaseNameMap = {}; mime.clearTextMimes(); const desc = ModesRegistry.getLanguages(); this._registerLanguages(desc); } _registerLanguages(desc) { for (const d of desc) { this._registerLanguage(d); } // Rebuild fast path maps this._mimeTypesMap = {}; this._nameMap = {}; this._lowercaseNameMap = {}; Object.keys(this._languages).forEach((langId) => { let language = this._languages[langId]; if (language.name) { this._nameMap[language.name] = language.identifier; } language.aliases.forEach((alias) => { this._lowercaseNameMap[alias.toLowerCase()] = language.identifier; }); language.mimetypes.forEach((mimetype) => { this._mimeTypesMap[mimetype] = language.identifier; }); }); Registry.as(Extensions.Configuration).registerOverrideIdentifiers(ModesRegistry.getLanguages().map(language => language.id)); this._onDidChange.fire(); } _registerLanguage(lang) { const langId = lang.id; let resolvedLanguage; if (hasOwnProperty.call(this._languages, langId)) { resolvedLanguage = this._languages[langId]; } else { this.languageIdCodec.register(langId); resolvedLanguage = { identifier: langId, name: null, mimetypes: [], aliases: [], extensions: [], filenames: [], configurationFiles: [] }; this._languages[langId] = resolvedLanguage; } this._mergeLanguage(resolvedLanguage, lang); } _mergeLanguage(resolvedLanguage, lang) { const langId = lang.id; let primaryMime = null; if (Array.isArray(lang.mimetypes) && lang.mimetypes.length > 0) { resolvedLanguage.mimetypes.push(...lang.mimetypes); primaryMime = lang.mimetypes[0]; } if (!primaryMime) { primaryMime = `text/x-${langId}`; resolvedLanguage.mimetypes.push(primaryMime); } if (Array.isArray(lang.extensions)) { if (lang.configuration) { // insert first as this appears to be the 'primary' language definition resolvedLanguage.extensions = lang.extensions.concat(resolvedLanguage.extensions); } else { resolvedLanguage.extensions = resolvedLanguage.extensions.concat(lang.extensions); } for (let extension of lang.extensions) { mime.registerTextMime({ id: langId, mime: primaryMime, extension: extension }, this._warnOnOverwrite); } } if (Array.isArray(lang.filenames)) { for (let filename of lang.filenames) { mime.registerTextMime({ id: langId, mime: primaryMime, filename: filename }, this._warnOnOverwrite); resolvedLanguage.filenames.push(filename); } } if (Array.isArray(lang.filenamePatterns)) { for (let filenamePattern of lang.filenamePatterns) { mime.registerTextMime({ id: langId, mime: primaryMime, filepattern: filenamePattern }, this._warnOnOverwrite); } } if (typeof lang.firstLine === 'string' && lang.firstLine.length > 0) { let firstLineRegexStr = lang.firstLine; if (firstLineRegexStr.charAt(0) !== '^') { firstLineRegexStr = '^' + firstLineRegexStr; } try { let firstLineRegex = new RegExp(firstLineRegexStr); if (!strings.regExpLeadsToEndlessLoop(firstLineRegex)) { mime.registerTextMime({ id: langId, mime: primaryMime, firstline: firstLineRegex }, this._warnOnOverwrite); } } catch (err) { // Most likely, the regex was bad onUnexpectedError(err); } } resolvedLanguage.aliases.push(langId); let langAliases = null; if (typeof lang.aliases !== 'undefined' && Array.isArray(lang.aliases)) { if (lang.aliases.length === 0) { // signal that this language should not get a name langAliases = [null]; } else { langAliases = lang.aliases; } } if (langAliases !== null) { for (const langAlias of langAliases) { if (!langAlias || langAlias.length === 0) { continue; } resolvedLanguage.aliases.push(langAlias); } } let containsAliases = (langAliases !== null && langAliases.length > 0); if (containsAliases && langAliases[0] === null) { // signal that this language should not get a name } else { let bestName = (containsAliases ? langAliases[0] : null) || langId; if (containsAliases || !resolvedLanguage.name) { resolvedLanguage.name = bestName; } } if (lang.configuration) { resolvedLanguage.configurationFiles.push(lang.configuration); } } isRegisteredMode(mimetypeOrModeId) { // Is this a known mime type ? if (hasOwnProperty.call(this._mimeTypesMap, mimetypeOrModeId)) { return true; } // Is this a known mode id ? return hasOwnProperty.call(this._languages, mimetypeOrModeId); } getModeIdForLanguageNameLowercase(languageNameLower) { if (!hasOwnProperty.call(this._lowercaseNameMap, languageNameLower)) { return null; } return this._lowercaseNameMap[languageNameLower]; } extractModeIds(commaSeparatedMimetypesOrCommaSeparatedIds) { if (!commaSeparatedMimetypesOrCommaSeparatedIds) { return []; } return (commaSeparatedMimetypesOrCommaSeparatedIds. split(','). map((mimeTypeOrId) => mimeTypeOrId.trim()). map((mimeTypeOrId) => { if (hasOwnProperty.call(this._mimeTypesMap, mimeTypeOrId)) { return this._mimeTypesMap[mimeTypeOrId]; } return mimeTypeOrId; }). filter((languageId) => { return hasOwnProperty.call(this._languages, languageId); })); } validateLanguageId(languageId) { if (!languageId || languageId === NULL_MODE_ID) { return NULL_MODE_ID; } if (!hasOwnProperty.call(this._languages, languageId)) { return null; } return languageId; } getModeIdsFromFilepathOrFirstLine(resource, firstLine) { if (!resource && !firstLine) { return []; } let mimeTypes = mime.guessMimeTypes(resource, firstLine); return this.extractModeIds(mimeTypes.join(',')); } } LanguagesRegistry.instanceCount = 0;