@sussudio/platform
Version:
Internal APIs for VS Code's service injection the base services.
1,201 lines (1,200 loc) • 42 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* 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 };
}