languagesRegistry.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  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. import { onUnexpectedError } from '../../../base/common/errors.js';
  6. import { Emitter } from '../../../base/common/event.js';
  7. import { Disposable } from '../../../base/common/lifecycle.js';
  8. import * as mime from '../../../base/common/mime.js';
  9. import * as strings from '../../../base/common/strings.js';
  10. import { ModesRegistry, PLAINTEXT_MODE_ID } from '../modes/modesRegistry.js';
  11. import { NULL_MODE_ID } from '../modes/nullMode.js';
  12. import { Extensions } from '../../../platform/configuration/common/configurationRegistry.js';
  13. import { Registry } from '../../../platform/registry/common/platform.js';
  14. const hasOwnProperty = Object.prototype.hasOwnProperty;
  15. export class LanguageIdCodec {
  16. constructor() {
  17. this._languageIdToLanguage = [];
  18. this._languageToLanguageId = new Map();
  19. this._register(NULL_MODE_ID, 0 /* Null */);
  20. this._register(PLAINTEXT_MODE_ID, 1 /* PlainText */);
  21. this._nextLanguageId = 2;
  22. }
  23. _register(language, languageId) {
  24. this._languageIdToLanguage[languageId] = language;
  25. this._languageToLanguageId.set(language, languageId);
  26. }
  27. register(language) {
  28. if (this._languageToLanguageId.has(language)) {
  29. return;
  30. }
  31. const languageId = this._nextLanguageId++;
  32. this._register(language, languageId);
  33. }
  34. encodeLanguageId(languageId) {
  35. return this._languageToLanguageId.get(languageId) || 0 /* Null */;
  36. }
  37. decodeLanguageId(languageId) {
  38. return this._languageIdToLanguage[languageId] || NULL_MODE_ID;
  39. }
  40. }
  41. export class LanguagesRegistry extends Disposable {
  42. constructor(useModesRegistry = true, warnOnOverwrite = false) {
  43. super();
  44. this._onDidChange = this._register(new Emitter());
  45. this.onDidChange = this._onDidChange.event;
  46. LanguagesRegistry.instanceCount++;
  47. this._warnOnOverwrite = warnOnOverwrite;
  48. this.languageIdCodec = new LanguageIdCodec();
  49. this._languages = {};
  50. this._mimeTypesMap = {};
  51. this._nameMap = {};
  52. this._lowercaseNameMap = {};
  53. if (useModesRegistry) {
  54. this._initializeFromRegistry();
  55. this._register(ModesRegistry.onDidChangeLanguages((m) => {
  56. // console.log(`onDidChangeLanguages - inst count: ${LanguagesRegistry.instanceCount}`);
  57. this._initializeFromRegistry();
  58. }));
  59. }
  60. }
  61. dispose() {
  62. LanguagesRegistry.instanceCount--;
  63. super.dispose();
  64. }
  65. _initializeFromRegistry() {
  66. this._languages = {};
  67. this._mimeTypesMap = {};
  68. this._nameMap = {};
  69. this._lowercaseNameMap = {};
  70. mime.clearTextMimes();
  71. const desc = ModesRegistry.getLanguages();
  72. this._registerLanguages(desc);
  73. }
  74. _registerLanguages(desc) {
  75. for (const d of desc) {
  76. this._registerLanguage(d);
  77. }
  78. // Rebuild fast path maps
  79. this._mimeTypesMap = {};
  80. this._nameMap = {};
  81. this._lowercaseNameMap = {};
  82. Object.keys(this._languages).forEach((langId) => {
  83. let language = this._languages[langId];
  84. if (language.name) {
  85. this._nameMap[language.name] = language.identifier;
  86. }
  87. language.aliases.forEach((alias) => {
  88. this._lowercaseNameMap[alias.toLowerCase()] = language.identifier;
  89. });
  90. language.mimetypes.forEach((mimetype) => {
  91. this._mimeTypesMap[mimetype] = language.identifier;
  92. });
  93. });
  94. Registry.as(Extensions.Configuration).registerOverrideIdentifiers(ModesRegistry.getLanguages().map(language => language.id));
  95. this._onDidChange.fire();
  96. }
  97. _registerLanguage(lang) {
  98. const langId = lang.id;
  99. let resolvedLanguage;
  100. if (hasOwnProperty.call(this._languages, langId)) {
  101. resolvedLanguage = this._languages[langId];
  102. }
  103. else {
  104. this.languageIdCodec.register(langId);
  105. resolvedLanguage = {
  106. identifier: langId,
  107. name: null,
  108. mimetypes: [],
  109. aliases: [],
  110. extensions: [],
  111. filenames: [],
  112. configurationFiles: []
  113. };
  114. this._languages[langId] = resolvedLanguage;
  115. }
  116. this._mergeLanguage(resolvedLanguage, lang);
  117. }
  118. _mergeLanguage(resolvedLanguage, lang) {
  119. const langId = lang.id;
  120. let primaryMime = null;
  121. if (Array.isArray(lang.mimetypes) && lang.mimetypes.length > 0) {
  122. resolvedLanguage.mimetypes.push(...lang.mimetypes);
  123. primaryMime = lang.mimetypes[0];
  124. }
  125. if (!primaryMime) {
  126. primaryMime = `text/x-${langId}`;
  127. resolvedLanguage.mimetypes.push(primaryMime);
  128. }
  129. if (Array.isArray(lang.extensions)) {
  130. if (lang.configuration) {
  131. // insert first as this appears to be the 'primary' language definition
  132. resolvedLanguage.extensions = lang.extensions.concat(resolvedLanguage.extensions);
  133. }
  134. else {
  135. resolvedLanguage.extensions = resolvedLanguage.extensions.concat(lang.extensions);
  136. }
  137. for (let extension of lang.extensions) {
  138. mime.registerTextMime({ id: langId, mime: primaryMime, extension: extension }, this._warnOnOverwrite);
  139. }
  140. }
  141. if (Array.isArray(lang.filenames)) {
  142. for (let filename of lang.filenames) {
  143. mime.registerTextMime({ id: langId, mime: primaryMime, filename: filename }, this._warnOnOverwrite);
  144. resolvedLanguage.filenames.push(filename);
  145. }
  146. }
  147. if (Array.isArray(lang.filenamePatterns)) {
  148. for (let filenamePattern of lang.filenamePatterns) {
  149. mime.registerTextMime({ id: langId, mime: primaryMime, filepattern: filenamePattern }, this._warnOnOverwrite);
  150. }
  151. }
  152. if (typeof lang.firstLine === 'string' && lang.firstLine.length > 0) {
  153. let firstLineRegexStr = lang.firstLine;
  154. if (firstLineRegexStr.charAt(0) !== '^') {
  155. firstLineRegexStr = '^' + firstLineRegexStr;
  156. }
  157. try {
  158. let firstLineRegex = new RegExp(firstLineRegexStr);
  159. if (!strings.regExpLeadsToEndlessLoop(firstLineRegex)) {
  160. mime.registerTextMime({ id: langId, mime: primaryMime, firstline: firstLineRegex }, this._warnOnOverwrite);
  161. }
  162. }
  163. catch (err) {
  164. // Most likely, the regex was bad
  165. onUnexpectedError(err);
  166. }
  167. }
  168. resolvedLanguage.aliases.push(langId);
  169. let langAliases = null;
  170. if (typeof lang.aliases !== 'undefined' && Array.isArray(lang.aliases)) {
  171. if (lang.aliases.length === 0) {
  172. // signal that this language should not get a name
  173. langAliases = [null];
  174. }
  175. else {
  176. langAliases = lang.aliases;
  177. }
  178. }
  179. if (langAliases !== null) {
  180. for (const langAlias of langAliases) {
  181. if (!langAlias || langAlias.length === 0) {
  182. continue;
  183. }
  184. resolvedLanguage.aliases.push(langAlias);
  185. }
  186. }
  187. let containsAliases = (langAliases !== null && langAliases.length > 0);
  188. if (containsAliases && langAliases[0] === null) {
  189. // signal that this language should not get a name
  190. }
  191. else {
  192. let bestName = (containsAliases ? langAliases[0] : null) || langId;
  193. if (containsAliases || !resolvedLanguage.name) {
  194. resolvedLanguage.name = bestName;
  195. }
  196. }
  197. if (lang.configuration) {
  198. resolvedLanguage.configurationFiles.push(lang.configuration);
  199. }
  200. }
  201. isRegisteredMode(mimetypeOrModeId) {
  202. // Is this a known mime type ?
  203. if (hasOwnProperty.call(this._mimeTypesMap, mimetypeOrModeId)) {
  204. return true;
  205. }
  206. // Is this a known mode id ?
  207. return hasOwnProperty.call(this._languages, mimetypeOrModeId);
  208. }
  209. getModeIdForLanguageNameLowercase(languageNameLower) {
  210. if (!hasOwnProperty.call(this._lowercaseNameMap, languageNameLower)) {
  211. return null;
  212. }
  213. return this._lowercaseNameMap[languageNameLower];
  214. }
  215. extractModeIds(commaSeparatedMimetypesOrCommaSeparatedIds) {
  216. if (!commaSeparatedMimetypesOrCommaSeparatedIds) {
  217. return [];
  218. }
  219. return (commaSeparatedMimetypesOrCommaSeparatedIds.
  220. split(',').
  221. map((mimeTypeOrId) => mimeTypeOrId.trim()).
  222. map((mimeTypeOrId) => {
  223. if (hasOwnProperty.call(this._mimeTypesMap, mimeTypeOrId)) {
  224. return this._mimeTypesMap[mimeTypeOrId];
  225. }
  226. return mimeTypeOrId;
  227. }).
  228. filter((languageId) => {
  229. return hasOwnProperty.call(this._languages, languageId);
  230. }));
  231. }
  232. validateLanguageId(languageId) {
  233. if (!languageId || languageId === NULL_MODE_ID) {
  234. return NULL_MODE_ID;
  235. }
  236. if (!hasOwnProperty.call(this._languages, languageId)) {
  237. return null;
  238. }
  239. return languageId;
  240. }
  241. getModeIdsFromFilepathOrFirstLine(resource, firstLine) {
  242. if (!resource && !firstLine) {
  243. return [];
  244. }
  245. let mimeTypes = mime.guessMimeTypes(resource, firstLine);
  246. return this.extractModeIds(mimeTypes.join(','));
  247. }
  248. }
  249. LanguagesRegistry.instanceCount = 0;