bada5b7c3a7feb2118dd64354c17e077d8aa5311f08b549b1c7cabcfbd3d6c2c5646a0845da63c1d76bb53500178a5e4e50a65f0b590599c6142a11587c6f0 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  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 { parse } from '../../../base/common/glob.js';
  6. import { Mimes } from '../../../base/common/mime.js';
  7. import { Schemas } from '../../../base/common/network.js';
  8. import { basename, posix } from '../../../base/common/path.js';
  9. import { DataUri } from '../../../base/common/resources.js';
  10. import { startsWithUTF8BOM } from '../../../base/common/strings.js';
  11. import { PLAINTEXT_LANGUAGE_ID } from '../languages/modesRegistry.js';
  12. let registeredAssociations = [];
  13. let nonUserRegisteredAssociations = [];
  14. let userRegisteredAssociations = [];
  15. /**
  16. * Associate a language to the registry (platform).
  17. * * **NOTE**: This association will lose over associations registered using `registerConfiguredLanguageAssociation`.
  18. * * **NOTE**: Use `clearPlatformLanguageAssociations` to remove all associations registered using this function.
  19. */
  20. export function registerPlatformLanguageAssociation(association, warnOnOverwrite = false) {
  21. _registerLanguageAssociation(association, false, warnOnOverwrite);
  22. }
  23. function _registerLanguageAssociation(association, userConfigured, warnOnOverwrite) {
  24. // Register
  25. const associationItem = toLanguageAssociationItem(association, userConfigured);
  26. registeredAssociations.push(associationItem);
  27. if (!associationItem.userConfigured) {
  28. nonUserRegisteredAssociations.push(associationItem);
  29. }
  30. else {
  31. userRegisteredAssociations.push(associationItem);
  32. }
  33. // Check for conflicts unless this is a user configured association
  34. if (warnOnOverwrite && !associationItem.userConfigured) {
  35. registeredAssociations.forEach(a => {
  36. if (a.mime === associationItem.mime || a.userConfigured) {
  37. return; // same mime or userConfigured is ok
  38. }
  39. if (associationItem.extension && a.extension === associationItem.extension) {
  40. console.warn(`Overwriting extension <<${associationItem.extension}>> to now point to mime <<${associationItem.mime}>>`);
  41. }
  42. if (associationItem.filename && a.filename === associationItem.filename) {
  43. console.warn(`Overwriting filename <<${associationItem.filename}>> to now point to mime <<${associationItem.mime}>>`);
  44. }
  45. if (associationItem.filepattern && a.filepattern === associationItem.filepattern) {
  46. console.warn(`Overwriting filepattern <<${associationItem.filepattern}>> to now point to mime <<${associationItem.mime}>>`);
  47. }
  48. if (associationItem.firstline && a.firstline === associationItem.firstline) {
  49. console.warn(`Overwriting firstline <<${associationItem.firstline}>> to now point to mime <<${associationItem.mime}>>`);
  50. }
  51. });
  52. }
  53. }
  54. function toLanguageAssociationItem(association, userConfigured) {
  55. return {
  56. id: association.id,
  57. mime: association.mime,
  58. filename: association.filename,
  59. extension: association.extension,
  60. filepattern: association.filepattern,
  61. firstline: association.firstline,
  62. userConfigured: userConfigured,
  63. filenameLowercase: association.filename ? association.filename.toLowerCase() : undefined,
  64. extensionLowercase: association.extension ? association.extension.toLowerCase() : undefined,
  65. filepatternLowercase: association.filepattern ? parse(association.filepattern.toLowerCase()) : undefined,
  66. filepatternOnPath: association.filepattern ? association.filepattern.indexOf(posix.sep) >= 0 : false
  67. };
  68. }
  69. /**
  70. * Clear language associations from the registry (platform).
  71. */
  72. export function clearPlatformLanguageAssociations() {
  73. registeredAssociations = registeredAssociations.filter(a => a.userConfigured);
  74. nonUserRegisteredAssociations = [];
  75. }
  76. /**
  77. * @see `getMimeTypes`
  78. */
  79. export function getLanguageIds(resource, firstLine) {
  80. return getAssociations(resource, firstLine).map(item => item.id);
  81. }
  82. function getAssociations(resource, firstLine) {
  83. let path;
  84. if (resource) {
  85. switch (resource.scheme) {
  86. case Schemas.file:
  87. path = resource.fsPath;
  88. break;
  89. case Schemas.data: {
  90. const metadata = DataUri.parseMetaData(resource);
  91. path = metadata.get(DataUri.META_DATA_LABEL);
  92. break;
  93. }
  94. case Schemas.vscodeNotebookCell:
  95. // File path not relevant for language detection of cell
  96. path = undefined;
  97. break;
  98. default:
  99. path = resource.path;
  100. }
  101. }
  102. if (!path) {
  103. return [{ id: 'unknown', mime: Mimes.unknown }];
  104. }
  105. path = path.toLowerCase();
  106. const filename = basename(path);
  107. // 1.) User configured mappings have highest priority
  108. const configuredLanguage = getAssociationByPath(path, filename, userRegisteredAssociations);
  109. if (configuredLanguage) {
  110. return [configuredLanguage, { id: PLAINTEXT_LANGUAGE_ID, mime: Mimes.text }];
  111. }
  112. // 2.) Registered mappings have middle priority
  113. const registeredLanguage = getAssociationByPath(path, filename, nonUserRegisteredAssociations);
  114. if (registeredLanguage) {
  115. return [registeredLanguage, { id: PLAINTEXT_LANGUAGE_ID, mime: Mimes.text }];
  116. }
  117. // 3.) Firstline has lowest priority
  118. if (firstLine) {
  119. const firstlineLanguage = getAssociationByFirstline(firstLine);
  120. if (firstlineLanguage) {
  121. return [firstlineLanguage, { id: PLAINTEXT_LANGUAGE_ID, mime: Mimes.text }];
  122. }
  123. }
  124. return [{ id: 'unknown', mime: Mimes.unknown }];
  125. }
  126. function getAssociationByPath(path, filename, associations) {
  127. var _a;
  128. let filenameMatch = undefined;
  129. let patternMatch = undefined;
  130. let extensionMatch = undefined;
  131. // We want to prioritize associations based on the order they are registered so that the last registered
  132. // association wins over all other. This is for https://github.com/microsoft/vscode/issues/20074
  133. for (let i = associations.length - 1; i >= 0; i--) {
  134. const association = associations[i];
  135. // First exact name match
  136. if (filename === association.filenameLowercase) {
  137. filenameMatch = association;
  138. break; // take it!
  139. }
  140. // Longest pattern match
  141. if (association.filepattern) {
  142. if (!patternMatch || association.filepattern.length > patternMatch.filepattern.length) {
  143. const target = association.filepatternOnPath ? path : filename; // match on full path if pattern contains path separator
  144. if ((_a = association.filepatternLowercase) === null || _a === void 0 ? void 0 : _a.call(association, target)) {
  145. patternMatch = association;
  146. }
  147. }
  148. }
  149. // Longest extension match
  150. if (association.extension) {
  151. if (!extensionMatch || association.extension.length > extensionMatch.extension.length) {
  152. if (filename.endsWith(association.extensionLowercase)) {
  153. extensionMatch = association;
  154. }
  155. }
  156. }
  157. }
  158. // 1.) Exact name match has second highest priority
  159. if (filenameMatch) {
  160. return filenameMatch;
  161. }
  162. // 2.) Match on pattern
  163. if (patternMatch) {
  164. return patternMatch;
  165. }
  166. // 3.) Match on extension comes next
  167. if (extensionMatch) {
  168. return extensionMatch;
  169. }
  170. return undefined;
  171. }
  172. function getAssociationByFirstline(firstLine) {
  173. if (startsWithUTF8BOM(firstLine)) {
  174. firstLine = firstLine.substr(1);
  175. }
  176. if (firstLine.length > 0) {
  177. // We want to prioritize associations based on the order they are registered so that the last registered
  178. // association wins over all other. This is for https://github.com/microsoft/vscode/issues/20074
  179. for (let i = registeredAssociations.length - 1; i >= 0; i--) {
  180. const association = registeredAssociations[i];
  181. if (!association.firstline) {
  182. continue;
  183. }
  184. const matches = firstLine.match(association.firstline);
  185. if (matches && matches.length > 0) {
  186. return association;
  187. }
  188. }
  189. }
  190. return undefined;
  191. }