src/vs/editor/common/services/languagesAssociations.ts

268 LOC · 233 covered · 35 uncovered · 51 ranges · 1449 concepts · 16 introducers · 709 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- languagesAssociations.ts ×11
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 { ParsedPattern, parse } from '../../../base/common/glob.js';
7 > import { Mimes } from '../../../base/common/mime.js';
8 > import { Schemas } from '../../../base/common/network.js';
9 > import { basename, posix } from '../../../base/common/path.js';
10 > import { DataUri } from '../../../base/common/resources.js';
11 > import { endsWithIgnoreCase, equals, startsWithUTF8BOM } from '../../../base/common/strings.js';
12 > import { URI } from '../../../base/common/uri.js';
13 > import { PLAINTEXT_LANGUAGE_ID } from '../languages/modesRegistry.js';
14 >
15 > export interface ILanguageAssociation {
16 > readonly id: string;
17 > readonly mime: string;
18 > readonly filename?: string;
19 > readonly extension?: string;
20 > readonly filepattern?: string;
21 > readonly firstline?: RegExp;
22 > }
23 >
24 > interface ILanguageAssociationItem extends ILanguageAssociation {
25 > readonly userConfigured: boolean;
26 > readonly filepatternParsed?: ParsedPattern;
27 > readonly filepatternOnPath?: boolean;
28 > }
29 >
30 > let registeredAssociations: ILanguageAssociationItem[] = [];
31 > let nonUserRegisteredAssociations: ILanguageAssociationItem[] = [];
32 > let userRegisteredAssociations: ILanguageAssociationItem[] = [];
33 >
34 > /**
35 > * Associate a language to the registry (platform).
36 > * * **NOTE**: This association will lose over associations registered using `registerConfiguredLanguageAssociation`.
37 > * * **NOTE**: Use `clearPlatformLanguageAssociations` to remove all associations registered using this function.
38 > */
39 > export function registerPlatformLanguageAssociation(association: ILanguageAssociation, warnOnOverwrite = false): void {
40 > _registerLanguageAssociation(association, false, warnOnOverwrite); languagesAssociations.ts ×5
41 > }
43 > /**
44 > * Associate a language to the registry (configured).
45 > * * **NOTE**: This association will win over associations registered using `registerPlatformLanguageAssociation`.
46 > * * **NOTE**: Use `clearConfiguredLanguageAssociations` to remove all associations registered using this function.
47 > */
48 > export function registerConfiguredLanguageAssociation(association: ILanguageAssociation): void {
49 > _registerLanguageAssociation(association, true, false); languagesAssociations.ts ×3
50 > }
52 > function _registerLanguageAssociation(association: ILanguageAssociation, userConfigured: boolean, warnOnOverwrite: boolean): void { languagesAssociations.ts ×5
53 >
54 > // Register
55 > const associationItem = toLanguageAssociationItem(association, userConfigured);
56 > registeredAssociations.push(associationItem);
57 > if (!associationItem.userConfigured) {
58 > nonUserRegisteredAssociations.push(associationItem);
59 > } else {
60 > userRegisteredAssociations.push(associationItem); languagesAssociations.ts ×3
61 > }
63 > // Check for conflicts unless this is a user configured association
64 > if (warnOnOverwrite && !associationItem.userConfigured) {
65 registeredAssociations.forEach(a => {
66 if (a.mime === associationItem.mime || a.userConfigured) {
67 return; // same mime or userConfigured is ok
68 }
69
70 if (associationItem.extension && a.extension === associationItem.extension) {
71 console.warn(`Overwriting extension <<${associationItem.extension}>> to now point to mime <<${associationItem.mime}>>`);
72 }
73
74 if (associationItem.filename && a.filename === associationItem.filename) {
75 console.warn(`Overwriting filename <<${associationItem.filename}>> to now point to mime <<${associationItem.mime}>>`);
76 }
77
78 if (associationItem.filepattern && a.filepattern === associationItem.filepattern) {
79 console.warn(`Overwriting filepattern <<${associationItem.filepattern}>> to now point to mime <<${associationItem.mime}>>`);
80 }
81
82 if (associationItem.firstline && a.firstline === associationItem.firstline) {
83 console.warn(`Overwriting firstline <<${associationItem.firstline}>> to now point to mime <<${associationItem.mime}>>`);
84 }
85 });
86 }
89 > function toLanguageAssociationItem(association: ILanguageAssociation, userConfigured: boolean): ILanguageAssociationItem { languagesAssociations.ts ×5
90 > return {
91 > id: association.id,
92 > mime: association.mime,
93 > filename: association.filename,
94 > extension: association.extension,
95 > filepattern: association.filepattern,
96 > firstline: association.firstline,
97 > userConfigured: userConfigured,
98 > filepatternParsed: association.filepattern ? parse(association.filepattern, { ignoreCase: true }) : undefined,
99 > filepatternOnPath: association.filepattern ? association.filepattern.indexOf(posix.sep) >= 0 : false
100 > };
101 > }
103 > /**
104 > * Clear language associations from the registry (platform).
105 > */
106 > export function clearPlatformLanguageAssociations(): void {
107 > registeredAssociations = registeredAssociations.filter(a => a.userConfigured); languagesRegistry.ts ×4
108 > nonUserRegisteredAssociations = [];
109 > }
111 > /**
112 > * Clear language associations from the registry (configured).
113 > */
114 > export function clearConfiguredLanguageAssociations(): void {
115 registeredAssociations = registeredAssociations.filter(a => !a.userConfigured);
116 userRegisteredAssociations = [];
117 }
119 > interface IdAndMime {
120 > id: string;
121 > mime: string;
122 > }
123 >
124 > /**
125 > * Given a file, return the best matching mime types for it
126 > * based on the registered language associations.
127 > */
128 > export function getMimeTypes(resource: URI | null, firstLine?: string): string[] {
129 > return getAssociations(resource, firstLine).map(item => item.mime); languagesAssociations.ts ×13
130 > }
132 > /**
133 > * @see `getMimeTypes`
134 > */
135 > export function getLanguageIds(resource: URI | null, firstLine?: string): string[] {
136 return getAssociations(resource, firstLine).map(item => item.id);
137 }
139 > function getAssociations(resource: URI | null, firstLine?: string): IdAndMime[] { languagesAssociations.ts ×13
140 > let path: string | undefined;
141 > if (resource) {
142 > switch (resource.scheme) {
143 > case Schemas.file:
144 > path = resource.fsPath; languagesAssociations.ts ×1
145 > break;
146 > case Schemas.data: { languagesAssociations.ts ×13
147 > const metadata = DataUri.parseMetaData(resource); resources.ts ×2
148 > path = metadata.get(DataUri.META_DATA_LABEL);
149 > break;
150 > }
151 > case Schemas.vscodeNotebookCell: languagesAssociations.ts ×13
152 // File path not relevant for language detection of cell
153 path = undefined;
154 break;
156 path = resource.path;
158 > }
159 >
160 > if (!path) {
161 return [{ id: 'unknown', mime: Mimes.unknown }];
162 }
164 > path = path.toLowerCase();
165 >
166 > const filename = basename(path);
167 >
168 > // 1.) User configured mappings have highest priority
169 > const configuredLanguage = getAssociationByPath(path, filename, userRegisteredAssociations);
170 > if (configuredLanguage) {
171 > return [configuredLanguage, { id: PLAINTEXT_LANGUAGE_ID, mime: Mimes.text }]; languagesAssociations.ts ×3
172 > }
174 > // 2.) Registered mappings have middle priority
175 > const registeredLanguage = getAssociationByPath(path, filename, nonUserRegisteredAssociations);
176 > if (registeredLanguage) {
177 > return [registeredLanguage, { id: PLAINTEXT_LANGUAGE_ID, mime: Mimes.text }]; languagesAssociations.ts ×1
178 > }
180 > // 3.) Firstline has lowest priority
181 > if (firstLine) {
182 > const firstlineLanguage = getAssociationByFirstline(firstLine);
183 > if (firstlineLanguage) {
184 > return [firstlineLanguage, { id: PLAINTEXT_LANGUAGE_ID, mime: Mimes.text }];
185 > }
186 > }
188 > return [{ id: 'unknown', mime: Mimes.unknown }];
189 > }
191 > function getAssociationByPath(path: string, filename: string, associations: ILanguageAssociationItem[]): ILanguageAssociationItem | undefined { languagesAssociations.ts ×13
192 > let filenameMatch: ILanguageAssociationItem | undefined = undefined;
193 > let patternMatch: ILanguageAssociationItem | undefined = undefined;
194 > let extensionMatch: ILanguageAssociationItem | undefined = undefined;
195 >
196 > // We want to prioritize associations based on the order they are registered so that the last registered
197 > // association wins over all other. This is for https://github.com/microsoft/vscode/issues/20074
198 > for (let i = associations.length - 1; i >= 0; i--) {
199 > const association = associations[i];
200 >
201 > // First exact name match
202 > if (equals(filename, association.filename, true)) {
203 > filenameMatch = association; languagesAssociations.ts ×2
204 > break; // take it!
205 > }
207 > // Longest pattern match
208 > if (association.filepattern) {
209 > if (!patternMatch || association.filepattern.length > patternMatch.filepattern!.length) { languagesAssociations.ts ×1
210 > const target = association.filepatternOnPath ? path : filename; // match on full path if pattern contains path separator
211 > if (association.filepatternParsed?.(target)) {
212 > patternMatch = association;
213 > }
214 > }
215 > }
217 > // Longest extension match
218 > if (association.extension) {
219 > if (!extensionMatch || association.extension.length > extensionMatch.extension!.length) { languagesAssociations.ts ×2
220 > if (endsWithIgnoreCase(filename, association.extension)) {
221 > extensionMatch = association;
222 > }
223 > }
224 > }
226 >
227 > // 1.) Exact name match has second highest priority
228 > if (filenameMatch) {
229 > return filenameMatch; languagesAssociations.ts ×2
230 > }
232 > // 2.) Match on pattern
233 > if (patternMatch) {
234 > return patternMatch; languagesAssociations.ts ×1
235 > }
237 > // 3.) Match on extension comes next
238 > if (extensionMatch) {
239 > return extensionMatch; languagesAssociations.ts ×2
240 > }
242 > return undefined;
243 > }
245 > function getAssociationByFirstline(firstLine: string): ILanguageAssociationItem | undefined { languagesAssociations.ts ×4
246 > if (startsWithUTF8BOM(firstLine)) {
247 firstLine = firstLine.substring(1);
248 }
250 > if (firstLine.length > 0) {
251 >
252 > // We want to prioritize associations based on the order they are registered so that the last registered
253 > // association wins over all other. This is for https://github.com/microsoft/vscode/issues/20074
254 > for (let i = registeredAssociations.length - 1; i >= 0; i--) {
255 > const association = registeredAssociations[i];
256 > if (!association.firstline) {
258 > }
260 > const matches = firstLine.match(association.firstline);
261 > if (matches && matches.length > 0) {
262 > return association;
263 > }
264 > }
266 >
267 > return undefined;
268 > }