UNPKG

monaco-editor-core

Version:

A browser based code editor

354 lines (353 loc) • 15.3 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; import { Emitter } from '../../../base/common/event.js'; import { Disposable, toDisposable } from '../../../base/common/lifecycle.js'; import * as strings from '../../../base/common/strings.js'; import { DEFAULT_WORD_REGEXP, ensureValidWordDefinition } from '../core/wordHelper.js'; import { AutoClosingPairs } from './languageConfiguration.js'; import { CharacterPairSupport } from './supports/characterPair.js'; import { BracketElectricCharacterSupport } from './supports/electricCharacter.js'; import { IndentRulesSupport } from './supports/indentRules.js'; import { OnEnterSupport } from './supports/onEnter.js'; import { RichEditBrackets } from './supports/richEditBrackets.js'; import { createDecorator } from '../../../platform/instantiation/common/instantiation.js'; import { IConfigurationService } from '../../../platform/configuration/common/configuration.js'; import { ILanguageService } from './language.js'; import { registerSingleton } from '../../../platform/instantiation/common/extensions.js'; import { PLAINTEXT_LANGUAGE_ID } from './modesRegistry.js'; import { LanguageBracketsConfiguration } from './supports/languageBracketsConfiguration.js'; export class LanguageConfigurationServiceChangeEvent { constructor(languageId) { this.languageId = languageId; } affects(languageId) { return !this.languageId ? true : this.languageId === languageId; } } export const ILanguageConfigurationService = createDecorator('languageConfigurationService'); let LanguageConfigurationService = class LanguageConfigurationService extends Disposable { constructor(configurationService, languageService) { super(); this.configurationService = configurationService; this.languageService = languageService; this._registry = this._register(new LanguageConfigurationRegistry()); this.onDidChangeEmitter = this._register(new Emitter()); this.onDidChange = this.onDidChangeEmitter.event; this.configurations = new Map(); const languageConfigKeys = new Set(Object.values(customizedLanguageConfigKeys)); this._register(this.configurationService.onDidChangeConfiguration((e) => { const globalConfigChanged = e.change.keys.some((k) => languageConfigKeys.has(k)); const localConfigChanged = e.change.overrides .filter(([overrideLangName, keys]) => keys.some((k) => languageConfigKeys.has(k))) .map(([overrideLangName]) => overrideLangName); if (globalConfigChanged) { this.configurations.clear(); this.onDidChangeEmitter.fire(new LanguageConfigurationServiceChangeEvent(undefined)); } else { for (const languageId of localConfigChanged) { if (this.languageService.isRegisteredLanguageId(languageId)) { this.configurations.delete(languageId); this.onDidChangeEmitter.fire(new LanguageConfigurationServiceChangeEvent(languageId)); } } } })); this._register(this._registry.onDidChange((e) => { this.configurations.delete(e.languageId); this.onDidChangeEmitter.fire(new LanguageConfigurationServiceChangeEvent(e.languageId)); })); } register(languageId, configuration, priority) { return this._registry.register(languageId, configuration, priority); } getLanguageConfiguration(languageId) { let result = this.configurations.get(languageId); if (!result) { result = computeConfig(languageId, this._registry, this.configurationService, this.languageService); this.configurations.set(languageId, result); } return result; } }; LanguageConfigurationService = __decorate([ __param(0, IConfigurationService), __param(1, ILanguageService) ], LanguageConfigurationService); export { LanguageConfigurationService }; function computeConfig(languageId, registry, configurationService, languageService) { let languageConfig = registry.getLanguageConfiguration(languageId); if (!languageConfig) { if (!languageService.isRegisteredLanguageId(languageId)) { // this happens for the null language, which can be returned by monarch. // Instead of throwing an error, we just return a default config. return new ResolvedLanguageConfiguration(languageId, {}); } languageConfig = new ResolvedLanguageConfiguration(languageId, {}); } const customizedConfig = getCustomizedLanguageConfig(languageConfig.languageId, configurationService); const data = combineLanguageConfigurations([languageConfig.underlyingConfig, customizedConfig]); const config = new ResolvedLanguageConfiguration(languageConfig.languageId, data); return config; } const customizedLanguageConfigKeys = { brackets: 'editor.language.brackets', colorizedBracketPairs: 'editor.language.colorizedBracketPairs' }; function getCustomizedLanguageConfig(languageId, configurationService) { const brackets = configurationService.getValue(customizedLanguageConfigKeys.brackets, { overrideIdentifier: languageId, }); const colorizedBracketPairs = configurationService.getValue(customizedLanguageConfigKeys.colorizedBracketPairs, { overrideIdentifier: languageId, }); return { brackets: validateBracketPairs(brackets), colorizedBracketPairs: validateBracketPairs(colorizedBracketPairs), }; } function validateBracketPairs(data) { if (!Array.isArray(data)) { return undefined; } return data.map(pair => { if (!Array.isArray(pair) || pair.length !== 2) { return undefined; } return [pair[0], pair[1]]; }).filter((p) => !!p); } export function getIndentationAtPosition(model, lineNumber, column) { const lineText = model.getLineContent(lineNumber); let indentation = strings.getLeadingWhitespace(lineText); if (indentation.length > column - 1) { indentation = indentation.substring(0, column - 1); } return indentation; } class ComposedLanguageConfiguration { constructor(languageId) { this.languageId = languageId; this._resolved = null; this._entries = []; this._order = 0; this._resolved = null; } register(configuration, priority) { const entry = new LanguageConfigurationContribution(configuration, priority, ++this._order); this._entries.push(entry); this._resolved = null; return toDisposable(() => { for (let i = 0; i < this._entries.length; i++) { if (this._entries[i] === entry) { this._entries.splice(i, 1); this._resolved = null; break; } } }); } getResolvedConfiguration() { if (!this._resolved) { const config = this._resolve(); if (config) { this._resolved = new ResolvedLanguageConfiguration(this.languageId, config); } } return this._resolved; } _resolve() { if (this._entries.length === 0) { return null; } this._entries.sort(LanguageConfigurationContribution.cmp); return combineLanguageConfigurations(this._entries.map(e => e.configuration)); } } function combineLanguageConfigurations(configs) { let result = { comments: undefined, brackets: undefined, wordPattern: undefined, indentationRules: undefined, onEnterRules: undefined, autoClosingPairs: undefined, surroundingPairs: undefined, autoCloseBefore: undefined, folding: undefined, colorizedBracketPairs: undefined, __electricCharacterSupport: undefined, }; for (const entry of configs) { result = { comments: entry.comments || result.comments, brackets: entry.brackets || result.brackets, wordPattern: entry.wordPattern || result.wordPattern, indentationRules: entry.indentationRules || result.indentationRules, onEnterRules: entry.onEnterRules || result.onEnterRules, autoClosingPairs: entry.autoClosingPairs || result.autoClosingPairs, surroundingPairs: entry.surroundingPairs || result.surroundingPairs, autoCloseBefore: entry.autoCloseBefore || result.autoCloseBefore, folding: entry.folding || result.folding, colorizedBracketPairs: entry.colorizedBracketPairs || result.colorizedBracketPairs, __electricCharacterSupport: entry.__electricCharacterSupport || result.__electricCharacterSupport, }; } return result; } class LanguageConfigurationContribution { constructor(configuration, priority, order) { this.configuration = configuration; this.priority = priority; this.order = order; } static cmp(a, b) { if (a.priority === b.priority) { // higher order last return a.order - b.order; } // higher priority last return a.priority - b.priority; } } export class LanguageConfigurationChangeEvent { constructor(languageId) { this.languageId = languageId; } } export class LanguageConfigurationRegistry extends Disposable { constructor() { super(); this._entries = new Map(); this._onDidChange = this._register(new Emitter()); this.onDidChange = this._onDidChange.event; this._register(this.register(PLAINTEXT_LANGUAGE_ID, { brackets: [ ['(', ')'], ['[', ']'], ['{', '}'], ], surroundingPairs: [ { open: '{', close: '}' }, { open: '[', close: ']' }, { open: '(', close: ')' }, { open: '<', close: '>' }, { open: '\"', close: '\"' }, { open: '\'', close: '\'' }, { open: '`', close: '`' }, ], colorizedBracketPairs: [], folding: { offSide: true } }, 0)); } /** * @param priority Use a higher number for higher priority */ register(languageId, configuration, priority = 0) { let entries = this._entries.get(languageId); if (!entries) { entries = new ComposedLanguageConfiguration(languageId); this._entries.set(languageId, entries); } const disposable = entries.register(configuration, priority); this._onDidChange.fire(new LanguageConfigurationChangeEvent(languageId)); return toDisposable(() => { disposable.dispose(); this._onDidChange.fire(new LanguageConfigurationChangeEvent(languageId)); }); } getLanguageConfiguration(languageId) { const entries = this._entries.get(languageId); return entries?.getResolvedConfiguration() || null; } } /** * Immutable. */ export class ResolvedLanguageConfiguration { constructor(languageId, underlyingConfig) { this.languageId = languageId; this.underlyingConfig = underlyingConfig; this._brackets = null; this._electricCharacter = null; this._onEnterSupport = this.underlyingConfig.brackets || this.underlyingConfig.indentationRules || this.underlyingConfig.onEnterRules ? new OnEnterSupport(this.underlyingConfig) : null; this.comments = ResolvedLanguageConfiguration._handleComments(this.underlyingConfig); this.characterPair = new CharacterPairSupport(this.underlyingConfig); this.wordDefinition = this.underlyingConfig.wordPattern || DEFAULT_WORD_REGEXP; this.indentationRules = this.underlyingConfig.indentationRules; if (this.underlyingConfig.indentationRules) { this.indentRulesSupport = new IndentRulesSupport(this.underlyingConfig.indentationRules); } else { this.indentRulesSupport = null; } this.foldingRules = this.underlyingConfig.folding || {}; this.bracketsNew = new LanguageBracketsConfiguration(languageId, this.underlyingConfig); } getWordDefinition() { return ensureValidWordDefinition(this.wordDefinition); } get brackets() { if (!this._brackets && this.underlyingConfig.brackets) { this._brackets = new RichEditBrackets(this.languageId, this.underlyingConfig.brackets); } return this._brackets; } get electricCharacter() { if (!this._electricCharacter) { this._electricCharacter = new BracketElectricCharacterSupport(this.brackets); } return this._electricCharacter; } onEnter(autoIndent, previousLineText, beforeEnterText, afterEnterText) { if (!this._onEnterSupport) { return null; } return this._onEnterSupport.onEnter(autoIndent, previousLineText, beforeEnterText, afterEnterText); } getAutoClosingPairs() { return new AutoClosingPairs(this.characterPair.getAutoClosingPairs()); } getAutoCloseBeforeSet(forQuotes) { return this.characterPair.getAutoCloseBeforeSet(forQuotes); } getSurroundingPairs() { return this.characterPair.getSurroundingPairs(); } static _handleComments(conf) { const commentRule = conf.comments; if (!commentRule) { return null; } // comment configuration const comments = {}; if (commentRule.lineComment) { comments.lineCommentToken = commentRule.lineComment; } if (commentRule.blockComment) { const [blockStart, blockEnd] = commentRule.blockComment; comments.blockCommentStartToken = blockStart; comments.blockCommentEndToken = blockEnd; } return comments; } } registerSingleton(ILanguageConfigurationService, LanguageConfigurationService, 1 /* InstantiationType.Delayed */);