UNPKG

@sussudio/platform

Version:

Internal APIs for VS Code's service injection the base services.

1,201 lines (1,200 loc) 42 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 '@sussudio/base/common/arrays.mjs'; import { Emitter, Event } from '@sussudio/base/common/event.mjs'; import * as json from '@sussudio/base/common/json.mjs'; import { Disposable } from '@sussudio/base/common/lifecycle.mjs'; import { getOrSet, ResourceMap } from '@sussudio/base/common/map.mjs'; import * as objects from '@sussudio/base/common/objects.mjs'; import * as types from '@sussudio/base/common/types.mjs'; import { URI } from '@sussudio/base/common/uri.mjs'; import { addToValueTree, getConfigurationValue, removeFromValueTree, toValuesTree } from './configuration.mjs'; import { Extensions, overrideIdentifiersFromKey, OVERRIDE_PROPERTY_REGEX } from './configurationRegistry.mjs'; import { Registry } from '../../registry/common/platform.mjs'; export class ConfigurationModel { _contents; _keys; _overrides; raw; frozen = false; overrideConfigurations = new Map(); constructor(_contents = {}, _keys = [], _overrides = [], raw) { this._contents = _contents; this._keys = _keys; this._overrides = _overrides; this.raw = raw; } _rawConfiguration; 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(''); 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.checkAndFreeze(this._contents); } get overrides() { return this.checkAndFreeze(this._overrides); } get keys() { return this.checkAndFreeze(this._keys); } isEmpty() { return this._keys.length === 0 && Object.keys(this._contents).length === 0 && this._overrides.length === 0; } isFrozen() { return this.frozen; } getValue(section) { return section ? getConfigurationValue(this.contents, section) : this.contents; } inspect(section, overrideIdentifier) { const value = this.rawConfiguration.getValue(section); const override = overrideIdentifier ? this.rawConfiguration.getOverrideValue(section, overrideIdentifier) : undefined; const merged = overrideIdentifier ? this.rawConfiguration.override(overrideIdentifier).getValue(section) : value; return { value, override, merged }; } getOverrideValue(section, overrideIdentifier) { const overrideContents = this.getContentsForOverrideIdentifer(overrideIdentifier); return overrideContents ? section ? getConfigurationValue(overrideContents, section) : overrideContents : undefined; } getKeysForOverrideIdentifier(identifier) { const keys = []; for (const override of this.overrides) { if (override.identifiers.includes(identifier)) { keys.push(...override.keys); } } return arrays.distinct(keys); } getAllOverrideIdentifiers() { const result = []; for (const override of this.overrides) { result.push(...override.identifiers); } return arrays.distinct(result); } 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, ); } freeze() { this.frozen = true; return this; } 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); } 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]); } } checkAndFreeze(data) { if (this.frozen && !Object.isFrozen(data)) { return objects.deepFreeze(data); } return data; } 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 (arrays.equals(override.identifiers, [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, }; } // Update methods setValue(key, value) { this.addKey(key); addToValueTree(this.contents, key, value, (e) => { throw new Error(e); }); } removeValue(key) { if (this.removeKey(key)) { removeFromValueTree(this.contents, key); } } addKey(key) { let index = this.keys.length; for (let i = 0; i < index; i++) { if (key.indexOf(this.keys[i]) === 0) { index = i; } } this.keys.splice(index, 1, key); } removeKey(key) { const index = this.keys.indexOf(key); if (index !== -1) { this.keys.splice(index, 1); return true; } return false; } } export class ConfigurationModelParser { _name; _raw = null; _configurationModel = null; _restrictedConfigurations = []; _parseErrors = []; constructor(_name) { this._name = _name; } get configurationModel() { return this._configurationModel || new ConfigurationModel(); } get restrictedConfigurations() { return this._restrictedConfigurations; } get errors() { return this._parseErrors; } parse(content, options) { if (!types.isUndefinedOrNull(content)) { const raw = this.doParseContent(content); this.parseRaw(raw, options); } } reparse(options) { if (this._raw) { this.parseRaw(this._raw, options); } } 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._restrictedConfigurations = restricted || []; } doParseContent(content) { let raw = {}; let currentProperty = null; let currentParent = []; const previousParents = []; const parseErrors = []; function onValue(value) { if (Array.isArray(currentParent)) { currentParent.push(value); } else if (currentProperty !== null) { currentParent[currentProperty] = value; } } const visitor = { onObjectBegin: () => { const object = {}; onValue(object); previousParents.push(currentParent); currentParent = object; currentProperty = null; }, onObjectProperty: (name) => { currentProperty = name; }, onObjectEnd: () => { currentParent = previousParents.pop(); }, onArrayBegin: () => { const array = []; onValue(array); previousParents.push(currentParent); currentParent = array; currentProperty = null; }, onArrayEnd: () => { currentParent = previousParents.pop(); }, onLiteralValue: onValue, onError: (error, offset, length) => { parseErrors.push({ error, offset, length }); }, }; if (content) { try { json.visit(content, visitor); raw = currentParent[0] || {}; } catch (e) { console.error(`Error while parsing settings file ${this._name}: ${e}`); this._parseErrors = [e]; } } return raw; } 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) => console.error(`Conflict in settings file ${this._name}: ${message}`), ); const keys = Object.keys(raw); const overrides = this.toOverrides(raw, (message) => console.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) { 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); } // Load unregistered configurations always. if ( (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; } } export class UserSettings extends Disposable { userSettingsResource; scopes; fileService; parser; parseOptions; _onDidChange = this._register(new Emitter()); onDidChange = this._onDidChange.event; constructor(userSettingsResource, scopes, extUri, fileService) { super(); this.userSettingsResource = userSettingsResource; this.scopes = scopes; this.fileService = fileService; this.parser = new ConfigurationModelParser(this.userSettingsResource.toString()); this.parseOptions = { scopes: this.scopes }; this._register(this.fileService.watch(extUri.dirname(this.userSettingsResource))); // Also listen to the resource incase the resource is a symlink - https://github.com/microsoft/vscode/issues/118134 this._register(this.fileService.watch(this.userSettingsResource)); this._register( Event.any( Event.filter(this.fileService.onDidFilesChange, (e) => e.contains(this.userSettingsResource)), Event.filter( this.fileService.onDidRunOperation, (e) => (e.isOperation(0 /* FileOperation.CREATE */) || e.isOperation(3 /* FileOperation.COPY */) || e.isOperation(1 /* FileOperation.DELETE */) || e.isOperation(4 /* FileOperation.WRITE */)) && extUri.isEqual(e.resource, userSettingsResource), ), )(() => this._onDidChange.fire()), ); } async loadConfiguration() { try { const content = await this.fileService.readFile(this.userSettingsResource); this.parser.parse(content.value.toString() || '{}', this.parseOptions); return this.parser.configurationModel; } catch (e) { return new ConfigurationModel(); } } reparse() { this.parser.reparse(this.parseOptions); return this.parser.configurationModel; } getRestrictedSettings() { return this.parser.restrictedConfigurations; } } class ConfigurationInspectValue { key; overrides; value; overrideIdentifiers; defaultConfiguration; policyConfiguration; applicationConfiguration; userConfiguration; localUserConfiguration; remoteUserConfiguration; workspaceConfiguration; folderConfigurationModel; memoryInspectValue; constructor( key, overrides, value, overrideIdentifiers, defaultConfiguration, policyConfiguration, applicationConfiguration, userConfiguration, localUserConfiguration, remoteUserConfiguration, workspaceConfiguration, folderConfigurationModel, memoryInspectValue, ) { 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.memoryInspectValue = memoryInspectValue; } _defaultInspectValue; get defaultInspectValue() { if (!this._defaultInspectValue) { this._defaultInspectValue = this.defaultConfiguration.inspect(this.key, this.overrides.overrideIdentifier); } return this._defaultInspectValue; } get defaultValue() { return this.defaultInspectValue.merged; } get default() { return this.defaultInspectValue.value !== undefined || this.defaultInspectValue.override !== undefined ? { value: this.defaultInspectValue.value, override: this.defaultInspectValue.override } : undefined; } _policyInspectValue; get policyInspectValue() { if (this._policyInspectValue === undefined) { this._policyInspectValue = this.policyConfiguration ? this.policyConfiguration.inspect(this.key) : null; } return this._policyInspectValue; } get policyValue() { return this.policyInspectValue?.merged; } get policy() { return this.policyInspectValue?.value !== undefined ? { value: this.policyInspectValue.value } : undefined; } _applicationInspectValue; get applicationInspectValue() { if (this._applicationInspectValue === undefined) { this._applicationInspectValue = this.applicationConfiguration ? this.applicationConfiguration.inspect(this.key) : null; } return this._applicationInspectValue; } get applicationValue() { return this.applicationInspectValue?.merged; } get application() { return this.applicationInspectValue?.value !== undefined || this.applicationInspectValue?.override !== undefined ? { value: this.applicationInspectValue.value, override: this.applicationInspectValue.override } : undefined; } _userInspectValue; get userInspectValue() { if (!this._userInspectValue) { this._userInspectValue = this.userConfiguration.inspect(this.key, this.overrides.overrideIdentifier); } return this._userInspectValue; } get userValue() { return this.userInspectValue.merged; } get user() { return this.userInspectValue.value !== undefined || this.userInspectValue.override !== undefined ? { value: this.userInspectValue.value, override: this.userInspectValue.override } : undefined; } _userLocalInspectValue; get userLocalInspectValue() { if (!this._userLocalInspectValue) { this._userLocalInspectValue = this.localUserConfiguration.inspect(this.key, this.overrides.overrideIdentifier); } return this._userLocalInspectValue; } get userLocalValue() { return this.userLocalInspectValue.merged; } get userLocal() { return this.userLocalInspectValue.value !== undefined || this.userLocalInspectValue.override !== undefined ? { value: this.userLocalInspectValue.value, override: this.userLocalInspectValue.override } : undefined; } _userRemoteInspectValue; get userRemoteInspectValue() { if (!this._userRemoteInspectValue) { this._userRemoteInspectValue = this.remoteUserConfiguration.inspect(this.key, this.overrides.overrideIdentifier); } return this._userRemoteInspectValue; } get userRemoteValue() { return this.userRemoteInspectValue.merged; } get userRemote() { return this.userRemoteInspectValue.value !== undefined || this.userRemoteInspectValue.override !== undefined ? { value: this.userRemoteInspectValue.value, override: this.userRemoteInspectValue.override } : undefined; } _workspaceInspectValue; get workspaceInspectValue() { if (this._workspaceInspectValue === undefined) { this._workspaceInspectValue = this.workspaceConfiguration ? this.workspaceConfiguration.inspect(this.key, this.overrides.overrideIdentifier) : null; } return this._workspaceInspectValue; } get workspaceValue() { return this.workspaceInspectValue?.merged; } get workspace() { return this.workspaceInspectValue?.value !== undefined || this.workspaceInspectValue?.override !== undefined ? { value: this.workspaceInspectValue.value, override: this.workspaceInspectValue.override } : undefined; } _workspaceFolderInspectValue; get workspaceFolderInspectValue() { if (this._workspaceFolderInspectValue === undefined) { this._workspaceFolderInspectValue = this.folderConfigurationModel ? this.folderConfigurationModel.inspect(this.key, this.overrides.overrideIdentifier) : null; } return this._workspaceFolderInspectValue; } get workspaceFolderValue() { return this.workspaceFolderInspectValue?.merged; } get workspaceFolder() { return this.workspaceFolderInspectValue?.value !== undefined || this.workspaceFolderInspectValue?.override !== undefined ? { value: this.workspaceFolderInspectValue.value, override: this.workspaceFolderInspectValue.override } : undefined; } get memoryValue() { return this.memoryInspectValue.merged; } get memory() { return this.memoryInspectValue.value !== undefined || this.memoryInspectValue.override !== undefined ? { value: this.memoryInspectValue.value, override: this.memoryInspectValue.override } : undefined; } } export class Configuration { _defaultConfiguration; _policyConfiguration; _applicationConfiguration; _localUserConfiguration; _remoteUserConfiguration; _workspaceConfiguration; _folderConfigurations; _memoryConfiguration; _memoryConfigurationByResource; _freeze; _workspaceConsolidatedConfiguration = null; _foldersConsolidatedConfigurations = new ResourceMap(); constructor( _defaultConfiguration, _policyConfiguration, _applicationConfiguration, _localUserConfiguration, _remoteUserConfiguration = new ConfigurationModel(), _workspaceConfiguration = new ConfigurationModel(), _folderConfigurations = new ResourceMap(), _memoryConfiguration = new ConfigurationModel(), _memoryConfigurationByResource = new ResourceMap(), _freeze = true, ) { 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._freeze = _freeze; } 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 = new ConfigurationModel(); 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.freeze(), this.applicationConfiguration.isEmpty() ? undefined : this.applicationConfiguration.freeze(), this.userConfiguration.freeze(), this.localUserConfiguration.freeze(), this.remoteUserConfiguration.freeze(), workspace ? this._workspaceConfiguration.freeze() : undefined, folderConfigurationModel ? folderConfigurationModel.freeze() : undefined, memoryConfigurationModel.inspect(key, overrides.overrideIdentifier), ); } keys(workspace) { const folderConfigurationModel = this.getFolderConfigurationModelForResource(undefined, workspace); return { default: this._defaultConfiguration.freeze().keys, user: this.userConfiguration.freeze().keys, workspace: this._workspaceConfiguration.freeze().keys, workspaceFolder: folderConfigurationModel ? folderConfigurationModel.freeze().keys : [], }; } updateDefaultConfiguration(defaultConfiguration) { this._defaultConfiguration = defaultConfiguration; this._workspaceConsolidatedConfiguration = null; this._foldersConsolidatedConfigurations.clear(); } updatePolicyConfiguration(policyConfiguration) { this._policyConfiguration = policyConfiguration; } updateApplicationConfiguration(applicationConfiguration) { this._applicationConfiguration = applicationConfiguration; this._workspaceConsolidatedConfiguration = null; this._foldersConsolidatedConfigurations.clear(); } updateLocalUserConfiguration(localUserConfiguration) { this._localUserConfiguration = localUserConfiguration; this._userConfiguration = null; this._workspaceConsolidatedConfiguration = null; this._foldersConsolidatedConfigurations.clear(); } updateRemoteUserConfiguration(remoteUserConfiguration) { this._remoteUserConfiguration = remoteUserConfiguration; this._userConfiguration = null; this._workspaceConsolidatedConfiguration = null; this._foldersConsolidatedConfigurations.clear(); } updateWorkspaceConfiguration(workspaceConfiguration) { this._workspaceConfiguration = workspaceConfiguration; this._workspaceConsolidatedConfiguration = null; this._foldersConsolidatedConfigurations.clear(); } updateFolderConfiguration(resource, configuration) { this._folderConfigurations.set(resource, configuration); this._foldersConsolidatedConfigurations.delete(resource); } deleteFolderConfiguration(resource) { this.folderConfigurations.delete(resource); this._foldersConsolidatedConfigurations.delete(resource); } compareAndUpdateDefaultConfiguration(defaults, keys) { const overrides = []; if (!keys) { const { added, updated, removed } = compare(this._defaultConfiguration, defaults); keys = [...added, ...updated, ...removed]; } for (const key of keys) { for (const overrideIdentifier of overrideIdentifiersFromKey(key)) { const fromKeys = this._defaultConfiguration.getKeysForOverrideIdentifier(overrideIdentifier); const toKeys = defaults.getKeysForOverrideIdentifier(overrideIdentifier); const keys = [ ...toKeys.filter((key) => fromKeys.indexOf(key) === -1), ...fromKeys.filter((key) => toKeys.indexOf(key) === -1), ...fromKeys.filter( (key) => !objects.equals( this._defaultConfiguration.override(overrideIdentifier).getValue(key), defaults.override(overrideIdentifier).getValue(key), ), ), ]; overrides.push([overrideIdentifier, keys]); } } this.updateDefaultConfiguration(defaults); return { keys, overrides }; } compareAndUpdatePolicyConfiguration(policyConfiguration) { const { added, updated, removed } = compare(this._policyConfiguration, policyConfiguration); const keys = [...added, ...updated, ...removed]; if (keys.length) { this.updatePolicyConfiguration(policyConfiguration); } return { keys, overrides: [] }; } compareAndUpdateApplicationConfiguration(application) { const { added, updated, removed, overrides } = compare(this.applicationConfiguration, application); const keys = [...added, ...updated, ...removed]; if (keys.length) { this.updateApplicationConfiguration(application); } return { keys, overrides }; } compareAndUpdateLocalUserConfiguration(user) { const { added, updated, removed, overrides } = compare(this.localUserConfiguration, user); const keys = [...added, ...updated, ...removed]; if (keys.length) { this.updateLocalUserConfiguration(user); } return { keys, overrides }; } compareAndUpdateRemoteUserConfiguration(user) { const { added, updated, removed, overrides } = compare(this.remoteUserConfiguration, user); const keys = [...added, ...updated, ...removed]; if (keys.length) { this.updateRemoteUserConfiguration(user); } return { keys, overrides }; } compareAndUpdateWorkspaceConfiguration(workspaceConfiguration) { const { added, updated, removed, overrides } = compare(this.workspaceConfiguration, workspaceConfiguration); const keys = [...added, ...updated, ...removed]; if (keys.length) { this.updateWorkspaceConfiguration(workspaceConfiguration); } return { keys, overrides }; } compareAndUpdateFolderConfiguration(resource, folderConfiguration) { const currentFolderConfiguration = this.folderConfigurations.get(resource); const { added, updated, removed, overrides } = compare(currentFolderConfiguration, folderConfiguration); const keys = [...added, ...updated, ...removed]; if (keys.length || !currentFolderConfiguration) { this.updateFolderConfiguration(resource, folderConfiguration); } return { keys, overrides }; } compareAndDeleteFolderConfiguration(folder) { const folderConfig = this.folderConfigurations.get(folder); if (!folderConfig) { throw new Error('Unknown folder'); } this.deleteFolderConfiguration(folder); const { added, updated, removed, overrides } = compare(folderConfig, undefined); return { keys: [...added, ...updated, ...removed], overrides }; } get defaults() { return this._defaultConfiguration; } get applicationConfiguration() { return this._applicationConfiguration; } _userConfiguration = null; get userConfiguration() { if (!this._userConfiguration) { this._userConfiguration = this._remoteUserConfiguration.isEmpty() ? this._localUserConfiguration : this._localUserConfiguration.merge(this._remoteUserConfiguration); if (this._freeze) { this._userConfiguration.freeze(); } } return this._userConfiguration; } get localUserConfiguration() { return this._localUserConfiguration; } get remoteUserConfiguration() { return this._remoteUserConfiguration; } get workspaceConfiguration() { return this._workspaceConfiguration; } get folderConfigurations() { return this._folderConfigurations; } 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, ); if (this._freeze) { this._workspaceConfiguration = this._workspaceConfiguration.freeze(); } } 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); if (this._freeze) { folderConsolidatedConfiguration = folderConsolidatedConfiguration.freeze(); } 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; }, []), }; } allKeys() { const keys = new Set(); this._defaultConfiguration.freeze().keys.forEach((key) => keys.add(key)); this.userConfiguration.freeze().keys.forEach((key) => keys.add(key)); this._workspaceConfiguration.freeze().keys.forEach((key) => keys.add(key)); this._folderConfigurations.forEach((folderConfiguraiton) => folderConfiguraiton.freeze().keys.forEach((key) => keys.add(key)), ); return [...keys.values()]; } allOverrideIdentifiers() { const keys = new Set(); this._defaultConfiguration .freeze() .getAllOverrideIdentifiers() .forEach((key) => keys.add(key)); this.userConfiguration .freeze() .getAllOverrideIdentifiers() .forEach((key) => keys.add(key)); this._workspaceConfiguration .freeze() .getAllOverrideIdentifiers() .forEach((key) => keys.add(key)); this._folderConfigurations.forEach((folderConfiguraiton) => folderConfiguraiton .freeze() .getAllOverrideIdentifiers() .forEach((key) => keys.add(key)), ); return [...keys.values()]; } getAllKeysForOverrideIdentifier(overrideIdentifier) { const keys = new Set(); this._defaultConfiguration.getKeysForOverrideIdentifier(overrideIdentifier).forEach((key) => keys.add(key)); this.userConfiguration.getKeysForOverrideIdentifier(overrideIdentifier).forEach((key) => keys.add(key)); this._workspaceConfiguration.getKeysForOverrideIdentifier(overrideIdentifier).forEach((key) => keys.add(key)); this._folderConfigurations.forEach((folderConfiguraiton) => folderConfiguraiton.getKeysForOverrideIdentifier(overrideIdentifier).forEach((key) => keys.add(key)), ); return [...keys.values()]; } static parse(data) { const defaultConfiguration = this.parseConfigurationModel(data.defaults); const policyConfiguration = this.parseConfigurationModel(data.policy); const applicationConfiguration = this.parseConfigurationModel(data.application); const userConfiguration = this.parseConfigurationModel(data.user); const workspaceConfiguration = this.parseConfigurationModel(data.workspace); const folders = data.folders.reduce((result, value) => { result.set(URI.revive(value[0]), this.parseConfigurationModel(value[1])); return result; }, new ResourceMap()); return new Configuration( defaultConfiguration, policyConfiguration, applicationConfiguration, userConfiguration, new ConfigurationModel(), workspaceConfiguration, folders, new ConfigurationModel(), new ResourceMap(), false, ); } static parseConfigurationModel(model) { return new ConfigurationModel(model.contents, model.keys, model.overrides).freeze(); } } export function mergeChanges(...changes) { if (changes.length === 0) { return { keys: [], overrides: [] }; } if (changes.length === 1) { return changes[0]; } const keysSet = new Set(); const overridesMap = new Map(); for (const change of changes) { change.keys.forEach((key) => keysSet.add(key)); change.overrides.forEach(([identifier, keys]) => { const result = getOrSet(overridesMap, identifier, new Set()); keys.forEach((key) => result.add(key)); }); } const overrides = []; overridesMap.forEach((keys, identifier) => overrides.push([identifier, [...keys.values()]])); return { keys: [...keysSet.values()], overrides }; } export class ConfigurationChangeEvent { change; previous; currentConfiguraiton; currentWorkspace; _marker = '\n'; _markerCode1 = this._marker.charCodeAt(0); _markerCode2 = '.'.charCodeAt(0); _affectsConfigStr; affectedKeys = new Set(); source; sourceConfig; constructor(change, previous, currentConfiguraiton, currentWorkspace) { this.change = change; this.previous = previous; this.currentConfiguraiton = currentConfiguraiton; this.currentWorkspace = currentWorkspace; 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; } } _previousConfiguration = undefined; get previousConfiguration() { if (!this._previousConfiguration && this.previous) { this._previousConfiguration = Configuration.parse(this.previous.data); } 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; } } function compare(from, to) { const { added, removed, updated } = compareConfigurationContents(to?.rawConfiguration, from?.rawConfiguration); const overrides = []; const fromOverrideIdentifiers = from?.getAllOverrideIdentifiers() || []; const toOverrideIdentifiers = to?.getAllOverrideIdentifiers() || []; if (to) { const addedOverrideIdentifiers = toOverrideIdentifiers.filter((key) => !fromOverrideIdentifiers.includes(key)); for (const identifier of addedOverrideIdentifiers) { overrides.push([identifier, to.getKeysForOverrideIdentifier(identifier)]); } } if (from) { const removedOverrideIdentifiers = fromOverrideIdentifiers.filter((key) => !toOverrideIdentifiers.includes(key)); for (const identifier of removedOverrideIdentifiers) { overrides.push([identifier, from.getKeysForOverrideIdentifier(identifier)]); } } if (to && from) { for (const identifier of fromOverrideIdentifiers) { if (toOverrideIdentifiers.includes(identifier)) { const result = compareConfigurationContents( { contents: from.getOverrideValue(undefined, identifier) || {}, keys: from.getKeysForOverrideIdentifier(identifier), }, { contents: to.getOverrideValue(undefined, identifier) || {}, keys: to.getKeysForOverrideIdentifier(identifier), }, ); overrides.push([identifier, [...result.added, ...result.removed, ...result.updated]]); } } } return { added, removed, updated, overrides }; } function compareConfigurationContents(to, from) { const added = to ? (from ? to.keys.filter((key) => from.keys.indexOf(key) === -1) : [...to.keys]) : []; const removed = from ? (to ? from.keys.filter((key) => to.keys.indexOf(key) === -1) : [...from.keys]) : []; const updated = []; if (to && from) { for (const key of from.keys) { if (to.keys.indexOf(key) !== -1) { const value1 = getConfigurationValue(from.contents, key); const value2 = getConfigurationValue(to.contents, key); if (!objects.equals(value1, value2)) { updated.push(key); } } } } return { added, removed, updated }; }