UNPKG

monaco-editor-core

Version:

A browser based code editor

575 lines (574 loc) • 27.8 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as arrays from '../../../base/common/arrays.js'; import { ResourceMap } from '../../../base/common/map.js'; import * as objects from '../../../base/common/objects.js'; import * as types from '../../../base/common/types.js'; import { URI } from '../../../base/common/uri.js'; import { addToValueTree, getConfigurationValue, removeFromValueTree, toValuesTree } from './configuration.js'; import { Extensions, overrideIdentifiersFromKey, OVERRIDE_PROPERTY_REGEX } from './configurationRegistry.js'; import { Registry } from '../../registry/common/platform.js'; function freeze(data) { return Object.isFrozen(data) ? data : objects.deepFreeze(data); } export class ConfigurationModel { static createEmptyModel(logService) { return new ConfigurationModel({}, [], [], undefined, logService); } constructor(_contents, _keys, _overrides, raw, logService) { this._contents = _contents; this._keys = _keys; this._overrides = _overrides; this.raw = raw; this.logService = logService; this.overrideConfigurations = new Map(); } get rawConfiguration() { if (!this._rawConfiguration) { if (this.raw?.length) { const rawConfigurationModels = this.raw.map(raw => { if (raw instanceof ConfigurationModel) { return raw; } const parser = new ConfigurationModelParser('', this.logService); parser.parseRaw(raw); return parser.configurationModel; }); this._rawConfiguration = rawConfigurationModels.reduce((previous, current) => current === previous ? current : previous.merge(current), rawConfigurationModels[0]); } else { // raw is same as current this._rawConfiguration = this; } } return this._rawConfiguration; } get contents() { return this._contents; } get overrides() { return this._overrides; } get keys() { return this._keys; } isEmpty() { return this._keys.length === 0 && Object.keys(this._contents).length === 0 && this._overrides.length === 0; } getValue(section) { return section ? getConfigurationValue(this.contents, section) : this.contents; } inspect(section, overrideIdentifier) { const that = this; return { get value() { return freeze(that.rawConfiguration.getValue(section)); }, get override() { return overrideIdentifier ? freeze(that.rawConfiguration.getOverrideValue(section, overrideIdentifier)) : undefined; }, get merged() { return freeze(overrideIdentifier ? that.rawConfiguration.override(overrideIdentifier).getValue(section) : that.rawConfiguration.getValue(section)); }, get overrides() { const overrides = []; for (const { contents, identifiers, keys } of that.rawConfiguration.overrides) { const value = new ConfigurationModel(contents, keys, [], undefined, that.logService).getValue(section); if (value !== undefined) { overrides.push({ identifiers, value }); } } return overrides.length ? freeze(overrides) : undefined; } }; } getOverrideValue(section, overrideIdentifier) { const overrideContents = this.getContentsForOverrideIdentifer(overrideIdentifier); return overrideContents ? section ? getConfigurationValue(overrideContents, section) : overrideContents : undefined; } override(identifier) { let overrideConfigurationModel = this.overrideConfigurations.get(identifier); if (!overrideConfigurationModel) { overrideConfigurationModel = this.createOverrideConfigurationModel(identifier); this.overrideConfigurations.set(identifier, overrideConfigurationModel); } return overrideConfigurationModel; } merge(...others) { const contents = objects.deepClone(this.contents); const overrides = objects.deepClone(this.overrides); const keys = [...this.keys]; const raws = this.raw?.length ? [...this.raw] : [this]; for (const other of others) { raws.push(...(other.raw?.length ? other.raw : [other])); if (other.isEmpty()) { continue; } this.mergeContents(contents, other.contents); for (const otherOverride of other.overrides) { const [override] = overrides.filter(o => arrays.equals(o.identifiers, otherOverride.identifiers)); if (override) { this.mergeContents(override.contents, otherOverride.contents); override.keys.push(...otherOverride.keys); override.keys = arrays.distinct(override.keys); } else { overrides.push(objects.deepClone(otherOverride)); } } for (const key of other.keys) { if (keys.indexOf(key) === -1) { keys.push(key); } } } return new ConfigurationModel(contents, keys, overrides, raws.every(raw => raw instanceof ConfigurationModel) ? undefined : raws, this.logService); } createOverrideConfigurationModel(identifier) { const overrideContents = this.getContentsForOverrideIdentifer(identifier); if (!overrideContents || typeof overrideContents !== 'object' || !Object.keys(overrideContents).length) { // If there are no valid overrides, return self return this; } const contents = {}; for (const key of arrays.distinct([...Object.keys(this.contents), ...Object.keys(overrideContents)])) { let contentsForKey = this.contents[key]; const overrideContentsForKey = overrideContents[key]; // If there are override contents for the key, clone and merge otherwise use base contents if (overrideContentsForKey) { // Clone and merge only if base contents and override contents are of type object otherwise just override if (typeof contentsForKey === 'object' && typeof overrideContentsForKey === 'object') { contentsForKey = objects.deepClone(contentsForKey); this.mergeContents(contentsForKey, overrideContentsForKey); } else { contentsForKey = overrideContentsForKey; } } contents[key] = contentsForKey; } return new ConfigurationModel(contents, this.keys, this.overrides, undefined, this.logService); } mergeContents(source, target) { for (const key of Object.keys(target)) { if (key in source) { if (types.isObject(source[key]) && types.isObject(target[key])) { this.mergeContents(source[key], target[key]); continue; } } source[key] = objects.deepClone(target[key]); } } getContentsForOverrideIdentifer(identifier) { let contentsForIdentifierOnly = null; let contents = null; const mergeContents = (contentsToMerge) => { if (contentsToMerge) { if (contents) { this.mergeContents(contents, contentsToMerge); } else { contents = objects.deepClone(contentsToMerge); } } }; for (const override of this.overrides) { if (override.identifiers.length === 1 && override.identifiers[0] === identifier) { contentsForIdentifierOnly = override.contents; } else if (override.identifiers.includes(identifier)) { mergeContents(override.contents); } } // Merge contents of the identifier only at the end to take precedence. mergeContents(contentsForIdentifierOnly); return contents; } toJSON() { return { contents: this.contents, overrides: this.overrides, keys: this.keys }; } setValue(key, value) { this.updateValue(key, value, false); } removeValue(key) { const index = this.keys.indexOf(key); if (index === -1) { return; } this.keys.splice(index, 1); removeFromValueTree(this.contents, key); if (OVERRIDE_PROPERTY_REGEX.test(key)) { this.overrides.splice(this.overrides.findIndex(o => arrays.equals(o.identifiers, overrideIdentifiersFromKey(key))), 1); } } updateValue(key, value, add) { addToValueTree(this.contents, key, value, e => this.logService.error(e)); add = add || this.keys.indexOf(key) === -1; if (add) { this.keys.push(key); } if (OVERRIDE_PROPERTY_REGEX.test(key)) { const identifiers = overrideIdentifiersFromKey(key); const override = { identifiers, keys: Object.keys(this.contents[key]), contents: toValuesTree(this.contents[key], message => this.logService.error(message)), }; const index = this.overrides.findIndex(o => arrays.equals(o.identifiers, identifiers)); if (index !== -1) { this.overrides[index] = override; } else { this.overrides.push(override); } } } } export class ConfigurationModelParser { constructor(_name, logService) { this._name = _name; this.logService = logService; this._raw = null; this._configurationModel = null; this._restrictedConfigurations = []; } get configurationModel() { return this._configurationModel || ConfigurationModel.createEmptyModel(this.logService); } parseRaw(raw, options) { this._raw = raw; const { contents, keys, overrides, restricted, hasExcludedProperties } = this.doParseRaw(raw, options); this._configurationModel = new ConfigurationModel(contents, keys, overrides, hasExcludedProperties ? [raw] : undefined /* raw has not changed */, this.logService); this._restrictedConfigurations = restricted || []; } doParseRaw(raw, options) { const configurationProperties = Registry.as(Extensions.Configuration).getConfigurationProperties(); const filtered = this.filter(raw, configurationProperties, true, options); raw = filtered.raw; const contents = toValuesTree(raw, message => this.logService.error(`Conflict in settings file ${this._name}: ${message}`)); const keys = Object.keys(raw); const overrides = this.toOverrides(raw, message => this.logService.error(`Conflict in settings file ${this._name}: ${message}`)); return { contents, keys, overrides, restricted: filtered.restricted, hasExcludedProperties: filtered.hasExcludedProperties }; } filter(properties, configurationProperties, filterOverriddenProperties, options) { let hasExcludedProperties = false; if (!options?.scopes && !options?.skipRestricted && !options?.exclude?.length) { return { raw: properties, restricted: [], hasExcludedProperties }; } const raw = {}; const restricted = []; for (const key in properties) { if (OVERRIDE_PROPERTY_REGEX.test(key) && filterOverriddenProperties) { const result = this.filter(properties[key], configurationProperties, false, options); raw[key] = result.raw; hasExcludedProperties = hasExcludedProperties || result.hasExcludedProperties; restricted.push(...result.restricted); } else { const propertySchema = configurationProperties[key]; const scope = propertySchema ? typeof propertySchema.scope !== 'undefined' ? propertySchema.scope : 3 /* ConfigurationScope.WINDOW */ : undefined; if (propertySchema?.restricted) { restricted.push(key); } if (!options.exclude?.includes(key) /* Check exclude */ && (options.include?.includes(key) /* Check include */ || ((scope === undefined || options.scopes === undefined || options.scopes.includes(scope)) /* Check scopes */ && !(options.skipRestricted && propertySchema?.restricted)))) /* Check restricted */ { raw[key] = properties[key]; } else { hasExcludedProperties = true; } } } return { raw, restricted, hasExcludedProperties }; } toOverrides(raw, conflictReporter) { const overrides = []; for (const key of Object.keys(raw)) { if (OVERRIDE_PROPERTY_REGEX.test(key)) { const overrideRaw = {}; for (const keyInOverrideRaw in raw[key]) { overrideRaw[keyInOverrideRaw] = raw[key][keyInOverrideRaw]; } overrides.push({ identifiers: overrideIdentifiersFromKey(key), keys: Object.keys(overrideRaw), contents: toValuesTree(overrideRaw, conflictReporter) }); } } return overrides; } } class ConfigurationInspectValue { constructor(key, overrides, _value, overrideIdentifiers, defaultConfiguration, policyConfiguration, applicationConfiguration, userConfiguration, localUserConfiguration, remoteUserConfiguration, workspaceConfiguration, folderConfigurationModel, memoryConfigurationModel) { this.key = key; this.overrides = overrides; this._value = _value; this.overrideIdentifiers = overrideIdentifiers; this.defaultConfiguration = defaultConfiguration; this.policyConfiguration = policyConfiguration; this.applicationConfiguration = applicationConfiguration; this.userConfiguration = userConfiguration; this.localUserConfiguration = localUserConfiguration; this.remoteUserConfiguration = remoteUserConfiguration; this.workspaceConfiguration = workspaceConfiguration; this.folderConfigurationModel = folderConfigurationModel; this.memoryConfigurationModel = memoryConfigurationModel; } toInspectValue(inspectValue) { return inspectValue?.value !== undefined || inspectValue?.override !== undefined || inspectValue?.overrides !== undefined ? inspectValue : undefined; } get userInspectValue() { if (!this._userInspectValue) { this._userInspectValue = this.userConfiguration.inspect(this.key, this.overrides.overrideIdentifier); } return this._userInspectValue; } get user() { return this.toInspectValue(this.userInspectValue); } } export class Configuration { constructor(_defaultConfiguration, _policyConfiguration, _applicationConfiguration, _localUserConfiguration, _remoteUserConfiguration, _workspaceConfiguration, _folderConfigurations, _memoryConfiguration, _memoryConfigurationByResource, logService) { this._defaultConfiguration = _defaultConfiguration; this._policyConfiguration = _policyConfiguration; this._applicationConfiguration = _applicationConfiguration; this._localUserConfiguration = _localUserConfiguration; this._remoteUserConfiguration = _remoteUserConfiguration; this._workspaceConfiguration = _workspaceConfiguration; this._folderConfigurations = _folderConfigurations; this._memoryConfiguration = _memoryConfiguration; this._memoryConfigurationByResource = _memoryConfigurationByResource; this.logService = logService; this._workspaceConsolidatedConfiguration = null; this._foldersConsolidatedConfigurations = new ResourceMap(); this._userConfiguration = null; } getValue(section, overrides, workspace) { const consolidateConfigurationModel = this.getConsolidatedConfigurationModel(section, overrides, workspace); return consolidateConfigurationModel.getValue(section); } updateValue(key, value, overrides = {}) { let memoryConfiguration; if (overrides.resource) { memoryConfiguration = this._memoryConfigurationByResource.get(overrides.resource); if (!memoryConfiguration) { memoryConfiguration = ConfigurationModel.createEmptyModel(this.logService); this._memoryConfigurationByResource.set(overrides.resource, memoryConfiguration); } } else { memoryConfiguration = this._memoryConfiguration; } if (value === undefined) { memoryConfiguration.removeValue(key); } else { memoryConfiguration.setValue(key, value); } if (!overrides.resource) { this._workspaceConsolidatedConfiguration = null; } } inspect(key, overrides, workspace) { const consolidateConfigurationModel = this.getConsolidatedConfigurationModel(key, overrides, workspace); const folderConfigurationModel = this.getFolderConfigurationModelForResource(overrides.resource, workspace); const memoryConfigurationModel = overrides.resource ? this._memoryConfigurationByResource.get(overrides.resource) || this._memoryConfiguration : this._memoryConfiguration; const overrideIdentifiers = new Set(); for (const override of consolidateConfigurationModel.overrides) { for (const overrideIdentifier of override.identifiers) { if (consolidateConfigurationModel.getOverrideValue(key, overrideIdentifier) !== undefined) { overrideIdentifiers.add(overrideIdentifier); } } } return new ConfigurationInspectValue(key, overrides, consolidateConfigurationModel.getValue(key), overrideIdentifiers.size ? [...overrideIdentifiers] : undefined, this._defaultConfiguration, this._policyConfiguration.isEmpty() ? undefined : this._policyConfiguration, this.applicationConfiguration.isEmpty() ? undefined : this.applicationConfiguration, this.userConfiguration, this.localUserConfiguration, this.remoteUserConfiguration, workspace ? this._workspaceConfiguration : undefined, folderConfigurationModel ? folderConfigurationModel : undefined, memoryConfigurationModel); } get applicationConfiguration() { return this._applicationConfiguration; } get userConfiguration() { if (!this._userConfiguration) { this._userConfiguration = this._remoteUserConfiguration.isEmpty() ? this._localUserConfiguration : this._localUserConfiguration.merge(this._remoteUserConfiguration); } return this._userConfiguration; } get localUserConfiguration() { return this._localUserConfiguration; } get remoteUserConfiguration() { return this._remoteUserConfiguration; } getConsolidatedConfigurationModel(section, overrides, workspace) { let configurationModel = this.getConsolidatedConfigurationModelForResource(overrides, workspace); if (overrides.overrideIdentifier) { configurationModel = configurationModel.override(overrides.overrideIdentifier); } if (!this._policyConfiguration.isEmpty() && this._policyConfiguration.getValue(section) !== undefined) { configurationModel = configurationModel.merge(this._policyConfiguration); } return configurationModel; } getConsolidatedConfigurationModelForResource({ resource }, workspace) { let consolidateConfiguration = this.getWorkspaceConsolidatedConfiguration(); if (workspace && resource) { const root = workspace.getFolder(resource); if (root) { consolidateConfiguration = this.getFolderConsolidatedConfiguration(root.uri) || consolidateConfiguration; } const memoryConfigurationForResource = this._memoryConfigurationByResource.get(resource); if (memoryConfigurationForResource) { consolidateConfiguration = consolidateConfiguration.merge(memoryConfigurationForResource); } } return consolidateConfiguration; } getWorkspaceConsolidatedConfiguration() { if (!this._workspaceConsolidatedConfiguration) { this._workspaceConsolidatedConfiguration = this._defaultConfiguration.merge(this.applicationConfiguration, this.userConfiguration, this._workspaceConfiguration, this._memoryConfiguration); } return this._workspaceConsolidatedConfiguration; } getFolderConsolidatedConfiguration(folder) { let folderConsolidatedConfiguration = this._foldersConsolidatedConfigurations.get(folder); if (!folderConsolidatedConfiguration) { const workspaceConsolidateConfiguration = this.getWorkspaceConsolidatedConfiguration(); const folderConfiguration = this._folderConfigurations.get(folder); if (folderConfiguration) { folderConsolidatedConfiguration = workspaceConsolidateConfiguration.merge(folderConfiguration); this._foldersConsolidatedConfigurations.set(folder, folderConsolidatedConfiguration); } else { folderConsolidatedConfiguration = workspaceConsolidateConfiguration; } } return folderConsolidatedConfiguration; } getFolderConfigurationModelForResource(resource, workspace) { if (workspace && resource) { const root = workspace.getFolder(resource); if (root) { return this._folderConfigurations.get(root.uri); } } return undefined; } toData() { return { defaults: { contents: this._defaultConfiguration.contents, overrides: this._defaultConfiguration.overrides, keys: this._defaultConfiguration.keys }, policy: { contents: this._policyConfiguration.contents, overrides: this._policyConfiguration.overrides, keys: this._policyConfiguration.keys }, application: { contents: this.applicationConfiguration.contents, overrides: this.applicationConfiguration.overrides, keys: this.applicationConfiguration.keys }, user: { contents: this.userConfiguration.contents, overrides: this.userConfiguration.overrides, keys: this.userConfiguration.keys }, workspace: { contents: this._workspaceConfiguration.contents, overrides: this._workspaceConfiguration.overrides, keys: this._workspaceConfiguration.keys }, folders: [...this._folderConfigurations.keys()].reduce((result, folder) => { const { contents, overrides, keys } = this._folderConfigurations.get(folder); result.push([folder, { contents, overrides, keys }]); return result; }, []) }; } static parse(data, logService) { const defaultConfiguration = this.parseConfigurationModel(data.defaults, logService); const policyConfiguration = this.parseConfigurationModel(data.policy, logService); const applicationConfiguration = this.parseConfigurationModel(data.application, logService); const userConfiguration = this.parseConfigurationModel(data.user, logService); const workspaceConfiguration = this.parseConfigurationModel(data.workspace, logService); const folders = data.folders.reduce((result, value) => { result.set(URI.revive(value[0]), this.parseConfigurationModel(value[1], logService)); return result; }, new ResourceMap()); return new Configuration(defaultConfiguration, policyConfiguration, applicationConfiguration, userConfiguration, ConfigurationModel.createEmptyModel(logService), workspaceConfiguration, folders, ConfigurationModel.createEmptyModel(logService), new ResourceMap(), logService); } static parseConfigurationModel(model, logService) { return new ConfigurationModel(model.contents, model.keys, model.overrides, undefined, logService); } } export class ConfigurationChangeEvent { constructor(change, previous, currentConfiguraiton, currentWorkspace, logService) { this.change = change; this.previous = previous; this.currentConfiguraiton = currentConfiguraiton; this.currentWorkspace = currentWorkspace; this.logService = logService; this._marker = '\n'; this._markerCode1 = this._marker.charCodeAt(0); this._markerCode2 = '.'.charCodeAt(0); this.affectedKeys = new Set(); this._previousConfiguration = undefined; for (const key of change.keys) { this.affectedKeys.add(key); } for (const [, keys] of change.overrides) { for (const key of keys) { this.affectedKeys.add(key); } } // Example: '\nfoo.bar\nabc.def\n' this._affectsConfigStr = this._marker; for (const key of this.affectedKeys) { this._affectsConfigStr += key + this._marker; } } get previousConfiguration() { if (!this._previousConfiguration && this.previous) { this._previousConfiguration = Configuration.parse(this.previous.data, this.logService); } return this._previousConfiguration; } affectsConfiguration(section, overrides) { // we have one large string with all keys that have changed. we pad (marker) the section // and check that either find it padded or before a segment character const needle = this._marker + section; const idx = this._affectsConfigStr.indexOf(needle); if (idx < 0) { // NOT: (marker + section) return false; } const pos = idx + needle.length; if (pos >= this._affectsConfigStr.length) { return false; } const code = this._affectsConfigStr.charCodeAt(pos); if (code !== this._markerCode1 && code !== this._markerCode2) { // NOT: section + (marker | segment) return false; } if (overrides) { const value1 = this.previousConfiguration ? this.previousConfiguration.getValue(section, overrides, this.previous?.workspace) : undefined; const value2 = this.currentConfiguraiton.getValue(section, overrides, this.currentWorkspace); return !objects.equals(value1, value2); } return true; } }