src/vs/editor/common/languages/languageConfigurationRegistry.ts

477 LOC · 389 covered · 88 uncovered · 67 ranges · 1740 concepts · 13 introducers · 873 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 > /*--------------------------------------------------------------------------------------------- languageConfigurationRegistry.ts ×28
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 { Emitter, Event } from '../../../base/common/event.js';
7 > import { Disposable, IDisposable, markAsSingleton, toDisposable } from '../../../base/common/lifecycle.js';
8 > import * as strings from '../../../base/common/strings.js';
9 > import { ITextModel } from '../model.js';
10 > import { DEFAULT_WORD_REGEXP, ensureValidWordDefinition } from '../core/wordHelper.js';
11 > import { EnterAction, FoldingRules, IAutoClosingPair, IndentationRule, LanguageConfiguration, AutoClosingPairs, CharacterPair, ExplicitLanguageConfiguration } from './languageConfiguration.js';
12 > import { CharacterPairSupport } from './supports/characterPair.js';
13 > import { BracketElectricCharacterSupport } from './supports/electricCharacter.js';
14 > import { IndentRulesSupport } from './supports/indentRules.js';
15 > import { OnEnterSupport } from './supports/onEnter.js';
16 > import { RichEditBrackets } from './supports/richEditBrackets.js';
17 > import { EditorAutoIndentStrategy } from '../config/editorOptions.js';
18 > import { createDecorator } from '../../../platform/instantiation/common/instantiation.js';
19 > import { IConfigurationService } from '../../../platform/configuration/common/configuration.js';
20 > import { ILanguageService } from './language.js';
21 > import { InstantiationType, registerSingleton } from '../../../platform/instantiation/common/extensions.js';
22 > import { PLAINTEXT_LANGUAGE_ID } from './modesRegistry.js';
23 > import { LanguageBracketsConfiguration } from './supports/languageBracketsConfiguration.js';
24 >
25 > /**
26 > * Interface used to support insertion of mode specific comments.
27 > */
28 > export interface ICommentsConfiguration {
29 > lineCommentToken?: string;
30 > lineCommentNoIndent?: boolean;
31 > blockCommentStartToken?: string;
32 > blockCommentEndToken?: string;
33 > }
34 >
35 > export interface ILanguageConfigurationService {
36 > readonly _serviceBrand: undefined;
37 >
38 > readonly onDidChange: Event<LanguageConfigurationServiceChangeEvent>;
39 >
40 > /**
41 > * @param priority Use a higher number for higher priority
42 > */
43 > register(languageId: string, configuration: LanguageConfiguration, priority?: number): IDisposable;
44 >
45 > getLanguageConfiguration(languageId: string): ResolvedLanguageConfiguration;
46 >
47 > }
48 >
49 > export class LanguageConfigurationServiceChangeEvent {
50 > constructor(public readonly languageId: string | undefined) { }
51 >
52 > public affects(languageId: string): boolean {
53 return !this.languageId ? true : this.languageId === languageId;
54 }
56 >
57 > export const ILanguageConfigurationService = createDecorator<ILanguageConfigurationService>('languageConfigurationService');
58 >
59 > export class LanguageConfigurationService extends Disposable implements ILanguageConfigurationService {
60 > _serviceBrand: undefined;
61 >
62 > private readonly _registry = this._register(new LanguageConfigurationRegistry());
63 >
64 > private readonly onDidChangeEmitter = this._register(new Emitter<LanguageConfigurationServiceChangeEvent>());
65 > public readonly onDidChange = this.onDidChangeEmitter.event;
66 >
67 > private readonly configurations = new Map<string, ResolvedLanguageConfiguration>();
68 >
69 > constructor(
70 > @IConfigurationService private readonly configurationService: IConfigurationService, languageConfigurationRegistry.ts ×8
71 > @ILanguageService private readonly languageService: ILanguageService
72 > ) {
73 > super();
74 >
75 > const languageConfigKeys = new Set(Object.values(customizedLanguageConfigKeys));
76 >
77 > this._register(this.configurationService.onDidChangeConfiguration((e) => {
78 const globalConfigChanged = e.change.keys.some((k) =>
79 languageConfigKeys.has(k)
80 );
81 const localConfigChanged = e.change.overrides
82 .filter(([overrideLangName, keys]) =>
83 keys.some((k) => languageConfigKeys.has(k))
84 )
85 .map(([overrideLangName]) => overrideLangName);
86
87 if (globalConfigChanged) {
88 this.configurations.clear();
89 this.onDidChangeEmitter.fire(new LanguageConfigurationServiceChangeEvent(undefined));
90 } else {
91 for (const languageId of localConfigChanged) {
92 if (this.languageService.isRegisteredLanguageId(languageId)) {
93 this.configurations.delete(languageId);
94 this.onDidChangeEmitter.fire(new LanguageConfigurationServiceChangeEvent(languageId));
95 }
96 }
97 }
99 >
100 > this._register(this._registry.onDidChange((e) => {
101 this.configurations.delete(e.languageId);
102 this.onDidChangeEmitter.fire(new LanguageConfigurationServiceChangeEvent(e.languageId));
104 > }
106 > public register(languageId: string, configuration: LanguageConfiguration, priority?: number): IDisposable {
107 return this._registry.register(languageId, configuration, priority);
108 }
110 > public getLanguageConfiguration(languageId: string): ResolvedLanguageConfiguration {
111 > let result = this.configurations.get(languageId); languageConfigurationRegistry.ts ×8
112 > if (!result) {
113 > result = computeConfig(languageId, this._registry, this.configurationService, this.languageService);
114 > this.configurations.set(languageId, result);
115 > }
116 > return result;
117 > }
119 >
120 > function computeConfig( languageConfigurationRegistry.ts ×8
121 > languageId: string,
122 > registry: LanguageConfigurationRegistry,
123 > configurationService: IConfigurationService,
124 > languageService: ILanguageService,
125 > ): ResolvedLanguageConfiguration {
126 > let languageConfig = registry.getLanguageConfiguration(languageId);
127 >
128 > if (!languageConfig) {
129 if (!languageService.isRegisteredLanguageId(languageId)) {
130 // this happens for the null language, which can be returned by monarch.
131 // Instead of throwing an error, we just return a default config.
132 return new ResolvedLanguageConfiguration(languageId, {});
133 }
134 languageConfig = new ResolvedLanguageConfiguration(languageId, {});
135 }
137 > const customizedConfig = getCustomizedLanguageConfig(languageConfig.languageId, configurationService);
138 > const data = combineLanguageConfigurations([languageConfig.underlyingConfig, customizedConfig]);
139 > const config = new ResolvedLanguageConfiguration(languageConfig.languageId, data);
140 > return config;
141 > }
143 > const customizedLanguageConfigKeys = {
144 > brackets: 'editor.language.brackets',
145 > colorizedBracketPairs: 'editor.language.colorizedBracketPairs'
146 > };
147 >
148 > function getCustomizedLanguageConfig(languageId: string, configurationService: IConfigurationService): LanguageConfiguration { languageConfigurationRegistry.ts ×8
149 > const brackets = configurationService.getValue(customizedLanguageConfigKeys.brackets, {
150 > overrideIdentifier: languageId,
151 > });
152 >
153 > const colorizedBracketPairs = configurationService.getValue(customizedLanguageConfigKeys.colorizedBracketPairs, {
154 > overrideIdentifier: languageId,
155 > });
156 >
157 > return {
158 > brackets: validateBracketPairs(brackets),
159 > colorizedBracketPairs: validateBracketPairs(colorizedBracketPairs),
160 > };
161 > }
163 > function validateBracketPairs(data: unknown): CharacterPair[] | undefined { languageConfigurationRegistry.ts ×8
164 > if (!Array.isArray(data)) {
165 > return undefined;
166 > }
167 return data.map(pair => {
168 if (!Array.isArray(pair) || pair.length !== 2) {
169 return undefined;
170 }
171 return [pair[0], pair[1]] as CharacterPair;
172 }).filter((p): p is CharacterPair => !!p);
173 }
175 > export function getIndentationAtPosition(model: ITextModel, lineNumber: number, column: number): string {
176 const lineText = model.getLineContent(lineNumber);
177 let indentation = strings.getLeadingWhitespace(lineText);
178 if (indentation.length > column - 1) {
179 indentation = indentation.substring(0, column - 1);
180 }
181 return indentation;
182 }
184 > class ComposedLanguageConfiguration {
185 > private readonly _entries: LanguageConfigurationContribution[];
186 > private _order: number;
187 > private _resolved: ResolvedLanguageConfiguration | null = null;
188 >
189 > constructor(public readonly languageId: string) {
190 > this._entries = []; languageConfigurationRegistry.ts ×5
191 > this._order = 0;
192 > this._resolved = null;
193 > }
195 > public register(
196 > configuration: LanguageConfiguration, languageConfigurationRegistry.ts ×5
197 > priority: number
198 > ): IDisposable {
199 > const entry = new LanguageConfigurationContribution(
200 > configuration,
201 > priority,
202 > ++this._order
203 > );
204 > this._entries.push(entry);
205 > this._resolved = null;
206 > return markAsSingleton(toDisposable(() => {
207 > for (let i = 0; i < this._entries.length; i++) {
208 > if (this._entries[i] === entry) {
209 > this._entries.splice(i, 1);
210 > this._resolved = null;
211 > break;
212 > }
213 > }
214 > }));
215 > }
217 > public getResolvedConfiguration(): ResolvedLanguageConfiguration | null {
218 > if (!this._resolved) { languageConfigurationRegistry.ts ×4
219 > const config = this._resolve();
220 > if (config) {
221 > this._resolved = new ResolvedLanguageConfiguration(
222 > this.languageId,
223 > config
224 > );
225 > }
226 > }
227 > return this._resolved;
228 > }
230 > private _resolve(): LanguageConfiguration | null {
231 > if (this._entries.length === 0) { languageConfigurationRegistry.ts ×4
232 return null;
233 }
234 > this._entries.sort(LanguageConfigurationContribution.cmp); languageConfigurationRegistry.ts ×4
235 > return combineLanguageConfigurations(this._entries.map(e => e.configuration));
236 > }
238 >
239 > function combineLanguageConfigurations(configs: LanguageConfiguration[]): LanguageConfiguration { languageConfigurationRegistry.ts ×4
240 > let result: ExplicitLanguageConfiguration = {
241 > comments: undefined,
242 > brackets: undefined,
243 > wordPattern: undefined,
244 > indentationRules: undefined,
245 > onEnterRules: undefined,
246 > autoClosingPairs: undefined,
247 > surroundingPairs: undefined,
248 > autoCloseBefore: undefined,
249 > folding: undefined,
250 > colorizedBracketPairs: undefined,
251 > __electricCharacterSupport: undefined,
252 > };
253 > for (const entry of configs) {
254 > result = {
255 > comments: entry.comments || result.comments,
256 > brackets: entry.brackets || result.brackets,
257 > wordPattern: entry.wordPattern || result.wordPattern,
258 > indentationRules: entry.indentationRules || result.indentationRules,
259 > onEnterRules: entry.onEnterRules || result.onEnterRules,
260 > autoClosingPairs: entry.autoClosingPairs || result.autoClosingPairs,
261 > surroundingPairs: entry.surroundingPairs || result.surroundingPairs,
262 > autoCloseBefore: entry.autoCloseBefore || result.autoCloseBefore,
263 > folding: entry.folding || result.folding,
264 > colorizedBracketPairs: entry.colorizedBracketPairs || result.colorizedBracketPairs,
265 > __electricCharacterSupport: entry.__electricCharacterSupport || result.__electricCharacterSupport,
266 > };
267 > }
268 >
269 > return result;
270 > }
272 > class LanguageConfigurationContribution {
273 > constructor(
274 > public readonly configuration: LanguageConfiguration, languageConfigurationRegistry.ts ×5
275 > public readonly priority: number,
276 > public readonly order: number
277 > ) { }
279 > public static cmp(a: LanguageConfigurationContribution, b: LanguageConfigurationContribution) {
280 > if (a.priority === b.priority) { languageConfigurationRegistry.ts ×5
281 > // higher order last languageConfigurationExtensionPoint.ts ×38
282 > return a.order - b.order;
283 > }
284 > // higher priority last languageConfigurationRegistry.ts ×1
285 > return a.priority - b.priority;
288 >
289 > export class LanguageConfigurationChangeEvent {
290 > constructor(public readonly languageId: string) { }
291 > }
292 >
293 > export class LanguageConfigurationRegistry extends Disposable {
294 > private readonly _entries = new Map<string, ComposedLanguageConfiguration>();
295 >
296 > private readonly _onDidChange = this._register(new Emitter<LanguageConfigurationChangeEvent>());
297 > public readonly onDidChange: Event<LanguageConfigurationChangeEvent> = this._onDidChange.event;
298 >
299 > constructor() {
301 > this._register(this.register(PLAINTEXT_LANGUAGE_ID, {
302 > brackets: [
303 > ['(', ')'],
304 > ['[', ']'],
305 > ['{', '}'],
306 > ],
307 > surroundingPairs: [
308 > { open: '{', close: '}' },
309 > { open: '[', close: ']' },
310 > { open: '(', close: ')' },
311 > { open: '<', close: '>' },
312 > { open: '\"', close: '\"' },
313 > { open: '\'', close: '\'' },
314 > { open: '`', close: '`' },
315 > ],
316 > colorizedBracketPairs: [],
317 > folding: {
318 > offSide: true
319 > }
320 > }, 0));
321 > }
323 > /**
324 > * @param priority Use a higher number for higher priority
325 > */
326 > public register(languageId: string, configuration: LanguageConfiguration, priority: number = 0): IDisposable {
327 > let entries = this._entries.get(languageId); languageConfigurationRegistry.ts ×5
328 > if (!entries) {
329 > entries = new ComposedLanguageConfiguration(languageId);
330 > this._entries.set(languageId, entries);
331 > }
332 >
333 > const disposable = entries.register(configuration, priority);
334 > this._onDidChange.fire(new LanguageConfigurationChangeEvent(languageId));
335 >
336 > return markAsSingleton(toDisposable(() => {
337 > disposable.dispose();
338 > this._onDidChange.fire(new LanguageConfigurationChangeEvent(languageId));
339 > }));
340 > }
342 > public getLanguageConfiguration(languageId: string): ResolvedLanguageConfiguration | null {
343 > const entries = this._entries.get(languageId); languageConfigurationRegistry.ts ×7
344 > return entries?.getResolvedConfiguration() || null;
345 > }
347 >
348 > /**
349 > * Immutable.
350 > */
351 > export class ResolvedLanguageConfiguration {
352 > private _brackets: RichEditBrackets | null;
353 > private _electricCharacter: BracketElectricCharacterSupport | null;
354 > private readonly _onEnterSupport: OnEnterSupport | null;
355 >
356 > public readonly comments: ICommentsConfiguration | null;
357 > public readonly characterPair: CharacterPairSupport;
358 > public readonly wordDefinition: RegExp;
359 > public readonly indentRulesSupport: IndentRulesSupport | null;
360 > public readonly indentationRules: IndentationRule | undefined;
361 > public readonly foldingRules: FoldingRules;
362 > public readonly bracketsNew: LanguageBracketsConfiguration;
363 >
364 > constructor(
365 > public readonly languageId: string, languageConfigurationRegistry.ts ×7
366 > public readonly underlyingConfig: LanguageConfiguration
367 > ) {
368 > this._brackets = null;
369 > this._electricCharacter = null;
370 > this._onEnterSupport =
371 > this.underlyingConfig.brackets ||
372 > this.underlyingConfig.indentationRules || languageConfigurationRegistry.ts ×2
373 > this.underlyingConfig.onEnterRules
374 > ? new OnEnterSupport(this.underlyingConfig) languageBracketsConfiguration.ts ×7
376 > this.comments = ResolvedLanguageConfiguration._handleComments(this.underlyingConfig); languageConfigurationRegistry.ts ×7
377 > this.characterPair = new CharacterPairSupport(this.underlyingConfig);
378 >
379 > this.wordDefinition = this.underlyingConfig.wordPattern || DEFAULT_WORD_REGEXP;
380 > this.indentationRules = this.underlyingConfig.indentationRules;
381 > if (this.underlyingConfig.indentationRules) {
382 > this.indentRulesSupport = new IndentRulesSupport( languageConfigurationExtensionPoint.ts ×38
383 > this.underlyingConfig.indentationRules
384 > );
386 > this.indentRulesSupport = null; languageConfigurationRegistry.ts ×1
387 > }
388 > this.foldingRules = this.underlyingConfig.folding || {}; languageConfigurationRegistry.ts ×7
389 >
390 > this.bracketsNew = new LanguageBracketsConfiguration(
391 > languageId,
392 > this.underlyingConfig
393 > );
394 > }
396 > public getWordDefinition(): RegExp {
397 > return ensureValidWordDefinition(this.wordDefinition); tokenizationTextModelPart.ts ×6
398 > }
400 > public get brackets(): RichEditBrackets | null {
401 if (!this._brackets && this.underlyingConfig.brackets) {
402 this._brackets = new RichEditBrackets(
403 this.languageId,
404 this.underlyingConfig.brackets
405 );
406 }
407 return this._brackets;
408 }
410 > public get electricCharacter(): BracketElectricCharacterSupport | null {
411 if (!this._electricCharacter) {
412 this._electricCharacter = new BracketElectricCharacterSupport(
413 this.brackets
414 );
415 }
416 return this._electricCharacter;
417 }
419 > public onEnter(
420 autoIndent: EditorAutoIndentStrategy,
421 previousLineText: string,
422 beforeEnterText: string,
423 afterEnterText: string
424 ): EnterAction | null {
425 if (!this._onEnterSupport) {
426 return null;
427 }
428 return this._onEnterSupport.onEnter(
429 autoIndent,
430 previousLineText,
431 beforeEnterText,
432 afterEnterText
433 );
434 }
436 > public getAutoClosingPairs(): AutoClosingPairs {
437 return new AutoClosingPairs(this.characterPair.getAutoClosingPairs());
438 }
440 > public getAutoCloseBeforeSet(forQuotes: boolean): string {
441 return this.characterPair.getAutoCloseBeforeSet(forQuotes);
442 }
444 > public getSurroundingPairs(): IAutoClosingPair[] {
445 return this.characterPair.getSurroundingPairs();
446 }
448 > private static _handleComments(
449 > conf: LanguageConfiguration languageConfigurationRegistry.ts ×7
450 > ): ICommentsConfiguration | null {
451 > const commentRule = conf.comments;
452 > if (!commentRule) {
454 > }
456 > // comment configuration
457 > const comments: ICommentsConfiguration = {};
458 >
459 > if (commentRule.lineComment) {
460 > if (typeof commentRule.lineComment === 'string') {
461 > comments.lineCommentToken = commentRule.lineComment;
462 > } else {
463 comments.lineCommentToken = commentRule.lineComment.comment;
464 comments.lineCommentNoIndent = commentRule.lineComment.noIndent;
465 }
467 > if (commentRule.blockComment) {
468 > const [blockStart, blockEnd] = commentRule.blockComment; languageConfigurationExtensionPoint.ts ×38
469 > comments.blockCommentStartToken = blockStart;
470 > comments.blockCommentEndToken = blockEnd;
471 > }
473 > return comments;
476 >
477 > registerSingleton(ILanguageConfigurationService, LanguageConfigurationService, InstantiationType.Delayed);