Path: blob/main/src/vs/platform/configuration/common/configurationModels.ts
5262 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as arrays from '../../../base/common/arrays.js';6import { IStringDictionary } from '../../../base/common/collections.js';7import { Emitter, Event } from '../../../base/common/event.js';8import * as json from '../../../base/common/json.js';9import { Disposable } from '../../../base/common/lifecycle.js';10import { getOrSet, ResourceMap } from '../../../base/common/map.js';11import * as objects from '../../../base/common/objects.js';12import { IExtUri } from '../../../base/common/resources.js';13import * as types from '../../../base/common/types.js';14import { URI, UriComponents } from '../../../base/common/uri.js';15import { addToValueTree, ConfigurationTarget, getConfigurationValue, IConfigurationChange, IConfigurationChangeEvent, IConfigurationCompareResult, IConfigurationData, IConfigurationModel, IConfigurationOverrides, IConfigurationUpdateOverrides, IConfigurationValue, IInspectValue, IOverrides, removeFromValueTree, toValuesTree } from './configuration.js';16import { ConfigurationScope, Extensions, IConfigurationPropertySchema, IConfigurationRegistry, overrideIdentifiersFromKey, OVERRIDE_PROPERTY_REGEX, IRegisteredConfigurationPropertySchema } from './configurationRegistry.js';17import { FileOperation, IFileService } from '../../files/common/files.js';18import { ILogService } from '../../log/common/log.js';19import { Registry } from '../../registry/common/platform.js';20import { Workspace } from '../../workspace/common/workspace.js';2122function freeze<T>(data: T): T {23return Object.isFrozen(data) ? data : objects.deepFreeze(data);24}2526type InspectValue<V> = IInspectValue<V> & { merged?: V };2728export class ConfigurationModel implements IConfigurationModel {2930static createEmptyModel(logService: ILogService): ConfigurationModel {31return new ConfigurationModel({}, [], [], undefined, logService);32}3334private readonly overrideConfigurations = new Map<string, ConfigurationModel>();3536constructor(37private readonly _contents: IStringDictionary<unknown>,38private readonly _keys: string[],39private readonly _overrides: IOverrides[],40private readonly _raw: IStringDictionary<unknown> | ReadonlyArray<IStringDictionary<unknown> | ConfigurationModel> | undefined,41private readonly logService: ILogService42) {43}4445private _rawConfiguration: ConfigurationModel | undefined;46get rawConfiguration(): ConfigurationModel {47if (!this._rawConfiguration) {48if (this._raw) {49const rawConfigurationModels = (Array.isArray(this._raw) ? this._raw : [this._raw]).map(raw => {50if (raw instanceof ConfigurationModel) {51return raw;52}53const parser = new ConfigurationModelParser('', this.logService);54parser.parseRaw(raw);55return parser.configurationModel;56});57this._rawConfiguration = rawConfigurationModels.reduce((previous, current) => current === previous ? current : previous.merge(current), rawConfigurationModels[0]);58} else {59// raw is same as current60this._rawConfiguration = this;61}62}63return this._rawConfiguration;64}6566get contents(): IStringDictionary<unknown> {67return this._contents;68}6970get overrides(): IOverrides[] {71return this._overrides;72}7374get keys(): string[] {75return this._keys;76}7778get raw(): IStringDictionary<unknown> | IStringDictionary<unknown>[] | undefined {79if (!this._raw) {80return undefined;81}82if (Array.isArray(this._raw) && this._raw.every(raw => raw instanceof ConfigurationModel)) {83return undefined;84}85return this._raw as IStringDictionary<unknown> | IStringDictionary<unknown>[];86}8788isEmpty(): boolean {89return this._keys.length === 0 && Object.keys(this._contents).length === 0 && this._overrides.length === 0;90}9192getValue<V>(section: string | undefined): V | undefined {93return section ? getConfigurationValue<V>(this.contents, section) : this.contents as V;94}9596inspect<V>(section: string | undefined, overrideIdentifier?: string | null): InspectValue<V> {97const that = this;98return {99get value() {100return freeze(that.rawConfiguration.getValue<V>(section));101},102get override() {103return overrideIdentifier ? freeze(that.rawConfiguration.getOverrideValue<V>(section, overrideIdentifier)) : undefined;104},105get merged() {106return freeze(overrideIdentifier ? that.rawConfiguration.override(overrideIdentifier).getValue<V>(section) : that.rawConfiguration.getValue<V>(section));107},108get overrides() {109const overrides: { readonly identifiers: string[]; readonly value: V }[] = [];110for (const { contents, identifiers, keys } of that.rawConfiguration.overrides) {111const value = new ConfigurationModel(contents, keys, [], undefined, that.logService).getValue<V>(section);112if (value !== undefined) {113overrides.push({ identifiers, value });114}115}116return overrides.length ? freeze(overrides) : undefined;117}118};119}120121getOverrideValue<V>(section: string | undefined, overrideIdentifier: string): V | undefined {122const overrideContents = this.getContentsForOverrideIdentifer(overrideIdentifier);123return overrideContents124? section ? getConfigurationValue<V>(overrideContents, section) : overrideContents as V125: undefined;126}127128getKeysForOverrideIdentifier(identifier: string): string[] {129const keys: string[] = [];130for (const override of this.overrides) {131if (override.identifiers.includes(identifier)) {132keys.push(...override.keys);133}134}135return arrays.distinct(keys);136}137138getAllOverrideIdentifiers(): string[] {139const result: string[] = [];140for (const override of this.overrides) {141result.push(...override.identifiers);142}143return arrays.distinct(result);144}145146override(identifier: string): ConfigurationModel {147let overrideConfigurationModel = this.overrideConfigurations.get(identifier);148if (!overrideConfigurationModel) {149overrideConfigurationModel = this.createOverrideConfigurationModel(identifier);150this.overrideConfigurations.set(identifier, overrideConfigurationModel);151}152return overrideConfigurationModel;153}154155merge(...others: ConfigurationModel[]): ConfigurationModel {156const contents = objects.deepClone(this.contents);157const overrides = objects.deepClone(this.overrides);158const keys = [...this.keys];159const raws = this._raw ? Array.isArray(this._raw) ? [...this._raw] : [this._raw] : [this];160161for (const other of others) {162raws.push(...(other._raw ? Array.isArray(other._raw) ? other._raw : [other._raw] : [other]));163if (other.isEmpty()) {164continue;165}166this.mergeContents(contents, other.contents);167168for (const otherOverride of other.overrides) {169const [override] = overrides.filter(o => arrays.equals(o.identifiers, otherOverride.identifiers));170if (override) {171this.mergeContents(override.contents, otherOverride.contents);172override.keys.push(...otherOverride.keys);173override.keys = arrays.distinct(override.keys);174} else {175overrides.push(objects.deepClone(otherOverride));176}177}178for (const key of other.keys) {179if (keys.indexOf(key) === -1) {180keys.push(key);181}182}183}184return new ConfigurationModel(contents, keys, overrides, !raws.length || raws.every(raw => raw instanceof ConfigurationModel) ? undefined : raws, this.logService);185}186187private createOverrideConfigurationModel(identifier: string): ConfigurationModel {188const overrideContents = this.getContentsForOverrideIdentifer(identifier);189190if (!overrideContents || typeof overrideContents !== 'object' || !Object.keys(overrideContents).length) {191// If there are no valid overrides, return self192return this;193}194195const contents: IStringDictionary<unknown> = {};196for (const key of arrays.distinct([...Object.keys(this.contents), ...Object.keys(overrideContents)])) {197198let contentsForKey = this.contents[key];199const overrideContentsForKey = overrideContents[key];200201// If there are override contents for the key, clone and merge otherwise use base contents202if (overrideContentsForKey) {203// Clone and merge only if base contents and override contents are of type object otherwise just override204if (typeof contentsForKey === 'object' && typeof overrideContentsForKey === 'object') {205contentsForKey = objects.deepClone(contentsForKey);206this.mergeContents(contentsForKey as IStringDictionary<unknown>, overrideContentsForKey as IStringDictionary<unknown>);207} else {208contentsForKey = overrideContentsForKey;209}210}211212contents[key] = contentsForKey;213}214215return new ConfigurationModel(contents, this.keys, this.overrides, undefined, this.logService);216}217218private mergeContents(source: IStringDictionary<unknown>, target: IStringDictionary<unknown>): void {219for (const key of Object.keys(target)) {220if (key in source) {221if (types.isObject(source[key]) && types.isObject(target[key])) {222this.mergeContents(source[key] as IStringDictionary<unknown>, target[key] as IStringDictionary<unknown>);223continue;224}225}226source[key] = objects.deepClone(target[key]);227}228}229230private getContentsForOverrideIdentifer(identifier: string): IStringDictionary<unknown> | null {231let contentsForIdentifierOnly: IStringDictionary<unknown> | null = null;232let contents: IStringDictionary<unknown> | null = null;233const mergeContents = (contentsToMerge: IStringDictionary<unknown> | null) => {234if (contentsToMerge) {235if (contents) {236this.mergeContents(contents, contentsToMerge);237} else {238contents = objects.deepClone(contentsToMerge);239}240}241};242for (const override of this.overrides) {243if (override.identifiers.length === 1 && override.identifiers[0] === identifier) {244contentsForIdentifierOnly = override.contents;245} else if (override.identifiers.includes(identifier)) {246mergeContents(override.contents);247}248}249// Merge contents of the identifier only at the end to take precedence.250mergeContents(contentsForIdentifierOnly);251return contents;252}253254toJSON(): IConfigurationModel {255return {256contents: this.contents,257overrides: this.overrides,258keys: this.keys259};260}261262// Update methods263264public addValue(key: string, value: unknown): void {265this.updateValue(key, value, true);266}267268public setValue(key: string, value: unknown): void {269this.updateValue(key, value, false);270}271272public removeValue(key: string): void {273const index = this.keys.indexOf(key);274if (index === -1) {275return;276}277this.keys.splice(index, 1);278removeFromValueTree(this.contents, key);279if (OVERRIDE_PROPERTY_REGEX.test(key)) {280this.overrides.splice(this.overrides.findIndex(o => arrays.equals(o.identifiers, overrideIdentifiersFromKey(key))), 1);281}282}283284private updateValue(key: string, value: unknown, add: boolean): void {285addToValueTree(this.contents, key, value, e => this.logService.error(e));286add = add || this.keys.indexOf(key) === -1;287if (add) {288this.keys.push(key);289}290if (OVERRIDE_PROPERTY_REGEX.test(key)) {291const overrideContents = this.contents[key] as IStringDictionary<unknown>;292const identifiers = overrideIdentifiersFromKey(key);293const override = {294identifiers,295keys: Object.keys(overrideContents),296contents: toValuesTree(overrideContents, message => this.logService.error(message)),297};298const index = this.overrides.findIndex(o => arrays.equals(o.identifiers, identifiers));299if (index !== -1) {300this.overrides[index] = override;301} else {302this.overrides.push(override);303}304}305}306}307308export interface ConfigurationParseOptions {309skipUnregistered?: boolean;310scopes?: ConfigurationScope[];311skipRestricted?: boolean;312include?: string[];313exclude?: string[];314}315316export class ConfigurationModelParser {317318private _raw: IStringDictionary<unknown> | null = null;319private _configurationModel: ConfigurationModel | null = null;320private _restrictedConfigurations: string[] = [];321private _parseErrors: json.ParseError[] = [];322323constructor(324protected readonly _name: string,325protected readonly logService: ILogService326) { }327328get configurationModel(): ConfigurationModel {329return this._configurationModel || ConfigurationModel.createEmptyModel(this.logService);330}331332get restrictedConfigurations(): string[] {333return this._restrictedConfigurations;334}335336get errors(): json.ParseError[] {337return this._parseErrors;338}339340public parse(content: string | null | undefined, options?: ConfigurationParseOptions): void {341if (!types.isUndefinedOrNull(content)) {342const raw = this.doParseContent(content);343this.parseRaw(raw, options);344}345}346347public reparse(options: ConfigurationParseOptions): void {348if (this._raw) {349this.parseRaw(this._raw, options);350}351}352353public parseRaw(raw: IStringDictionary<unknown>, options?: ConfigurationParseOptions): void {354this._raw = raw;355const { contents, keys, overrides, restricted, hasExcludedProperties } = this.doParseRaw(raw, options);356this._configurationModel = new ConfigurationModel(contents, keys, overrides, hasExcludedProperties ? [raw] : undefined /* raw has not changed */, this.logService);357this._restrictedConfigurations = restricted || [];358}359360private doParseContent(content: string): IStringDictionary<unknown> {361let raw: IStringDictionary<unknown> = {};362let currentProperty: string | null = null;363let currentParent: unknown[] | IStringDictionary<unknown> = [];364const previousParents: (unknown[] | IStringDictionary<unknown>)[] = [];365const parseErrors: json.ParseError[] = [];366367function onValue(value: unknown) {368if (Array.isArray(currentParent)) {369currentParent.push(value);370} else if (currentProperty !== null) {371currentParent[currentProperty] = value;372}373}374375const visitor: json.JSONVisitor = {376onObjectBegin: () => {377const object = {};378onValue(object);379previousParents.push(currentParent);380currentParent = object;381currentProperty = null;382},383onObjectProperty: (name: string) => {384currentProperty = name;385},386onObjectEnd: () => {387currentParent = previousParents.pop()!;388},389onArrayBegin: () => {390const array: unknown[] = [];391onValue(array);392previousParents.push(currentParent);393currentParent = array;394currentProperty = null;395},396onArrayEnd: () => {397currentParent = previousParents.pop()!;398},399onLiteralValue: onValue,400onError: (error: json.ParseErrorCode, offset: number, length: number) => {401parseErrors.push({ error, offset, length });402}403};404if (content) {405try {406json.visit(content, visitor);407raw = (currentParent[0] as IStringDictionary<unknown>) || {};408} catch (e) {409this.logService.error(`Error while parsing settings file ${this._name}: ${e}`);410this._parseErrors = [e as json.ParseError];411}412}413414return raw;415}416417protected doParseRaw(raw: IStringDictionary<unknown>, options?: ConfigurationParseOptions): IConfigurationModel & { restricted?: string[]; hasExcludedProperties?: boolean } {418const registry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);419const configurationProperties = registry.getConfigurationProperties();420const excludedConfigurationProperties = registry.getExcludedConfigurationProperties();421const filtered = this.filter(raw, configurationProperties, excludedConfigurationProperties, true, options);422raw = filtered.raw;423const contents = toValuesTree(raw, message => this.logService.error(`Conflict in settings file ${this._name}: ${message}`));424const keys = Object.keys(raw);425const overrides = this.toOverrides(raw, message => this.logService.error(`Conflict in settings file ${this._name}: ${message}`));426return { contents, keys, overrides, restricted: filtered.restricted, hasExcludedProperties: filtered.hasExcludedProperties };427}428429private filter(properties: IStringDictionary<unknown>, configurationProperties: IStringDictionary<IRegisteredConfigurationPropertySchema>, excludedConfigurationProperties: IStringDictionary<IRegisteredConfigurationPropertySchema>, filterOverriddenProperties: boolean, options?: ConfigurationParseOptions): { raw: IStringDictionary<unknown>; restricted: string[]; hasExcludedProperties: boolean } {430let hasExcludedProperties = false;431if (!options?.scopes && !options?.skipRestricted && !options?.skipUnregistered && !options?.exclude?.length) {432return { raw: properties, restricted: [], hasExcludedProperties };433}434const raw: IStringDictionary<unknown> = {};435const restricted: string[] = [];436for (const key in properties) {437if (OVERRIDE_PROPERTY_REGEX.test(key) && filterOverriddenProperties) {438const result = this.filter(properties[key] as IStringDictionary<unknown>, configurationProperties, excludedConfigurationProperties, false, options);439raw[key] = result.raw;440hasExcludedProperties = hasExcludedProperties || result.hasExcludedProperties;441restricted.push(...result.restricted);442} else {443const propertySchema = configurationProperties[key];444if (propertySchema?.restricted) {445restricted.push(key);446}447if (this.shouldInclude(key, propertySchema, excludedConfigurationProperties, options)) {448raw[key] = properties[key];449} else {450hasExcludedProperties = true;451}452}453}454return { raw, restricted, hasExcludedProperties };455}456457private shouldInclude(key: string, propertySchema: IConfigurationPropertySchema | undefined, excludedConfigurationProperties: IStringDictionary<IRegisteredConfigurationPropertySchema>, options: ConfigurationParseOptions): boolean {458if (options.exclude?.includes(key)) {459return false;460}461462if (options.include?.includes(key)) {463return true;464}465466if (options.skipRestricted && propertySchema?.restricted) {467return false;468}469470if (options.skipUnregistered && !propertySchema) {471return false;472}473474const schema = propertySchema ?? excludedConfigurationProperties[key];475const scope = schema ? typeof schema.scope !== 'undefined' ? schema.scope : ConfigurationScope.WINDOW : undefined;476if (scope === undefined || options.scopes === undefined) {477return true;478}479480return options.scopes.includes(scope);481}482483private toOverrides(raw: IStringDictionary<unknown>, conflictReporter: (message: string) => void): IOverrides[] {484const overrides: IOverrides[] = [];485for (const key of Object.keys(raw)) {486if (OVERRIDE_PROPERTY_REGEX.test(key)) {487const overrideRaw: IStringDictionary<unknown> = {};488const rawKey = raw[key] as IStringDictionary<unknown>;489for (const keyInOverrideRaw in rawKey) {490overrideRaw[keyInOverrideRaw] = rawKey[keyInOverrideRaw];491}492overrides.push({493identifiers: overrideIdentifiersFromKey(key),494keys: Object.keys(overrideRaw),495contents: toValuesTree(overrideRaw, conflictReporter)496});497}498}499return overrides;500}501502}503504export class UserSettings extends Disposable {505506private readonly parser: ConfigurationModelParser;507protected readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());508readonly onDidChange: Event<void> = this._onDidChange.event;509510constructor(511private readonly userSettingsResource: URI,512protected parseOptions: ConfigurationParseOptions,513extUri: IExtUri,514private readonly fileService: IFileService,515private readonly logService: ILogService,516) {517super();518this.parser = new ConfigurationModelParser(this.userSettingsResource.toString(), logService);519this._register(this.fileService.watch(extUri.dirname(this.userSettingsResource)));520// Also listen to the resource incase the resource is a symlink - https://github.com/microsoft/vscode/issues/118134521this._register(this.fileService.watch(this.userSettingsResource));522this._register(Event.any(523Event.filter(this.fileService.onDidFilesChange, e => e.contains(this.userSettingsResource)),524Event.filter(this.fileService.onDidRunOperation, e => (e.isOperation(FileOperation.CREATE) || e.isOperation(FileOperation.COPY) || e.isOperation(FileOperation.DELETE) || e.isOperation(FileOperation.WRITE)) && extUri.isEqual(e.resource, userSettingsResource))525)(() => this._onDidChange.fire()));526}527528async loadConfiguration(): Promise<ConfigurationModel> {529try {530const content = await this.fileService.readFile(this.userSettingsResource);531this.parser.parse(content.value.toString() || '{}', this.parseOptions);532return this.parser.configurationModel;533} catch (e) {534return ConfigurationModel.createEmptyModel(this.logService);535}536}537538reparse(parseOptions?: ConfigurationParseOptions): ConfigurationModel {539if (parseOptions) {540this.parseOptions = parseOptions;541}542this.parser.reparse(this.parseOptions);543return this.parser.configurationModel;544}545546getRestrictedSettings(): string[] {547return this.parser.restrictedConfigurations;548}549}550551class ConfigurationInspectValue<V> implements IConfigurationValue<V> {552553constructor(554private readonly key: string,555private readonly overrides: IConfigurationOverrides,556private readonly _value: V | undefined,557readonly overrideIdentifiers: string[] | undefined,558private readonly defaultConfiguration: ConfigurationModel,559private readonly policyConfiguration: ConfigurationModel | undefined,560private readonly applicationConfiguration: ConfigurationModel | undefined,561private readonly userConfiguration: ConfigurationModel,562private readonly localUserConfiguration: ConfigurationModel,563private readonly remoteUserConfiguration: ConfigurationModel,564private readonly workspaceConfiguration: ConfigurationModel | undefined,565private readonly folderConfigurationModel: ConfigurationModel | undefined,566private readonly memoryConfigurationModel: ConfigurationModel567) {568}569570get value(): V | undefined {571return freeze(this._value);572}573574private toInspectValue(inspectValue: IInspectValue<V> | undefined | null): IInspectValue<V> | undefined {575return inspectValue?.value !== undefined || inspectValue?.override !== undefined || inspectValue?.overrides !== undefined ? inspectValue : undefined;576}577578private _defaultInspectValue: InspectValue<V> | undefined;579private get defaultInspectValue(): InspectValue<V> {580if (!this._defaultInspectValue) {581this._defaultInspectValue = this.defaultConfiguration.inspect<V>(this.key, this.overrides.overrideIdentifier);582}583return this._defaultInspectValue;584}585586get defaultValue(): V | undefined {587return this.defaultInspectValue.merged;588}589590get default(): IInspectValue<V> | undefined {591return this.toInspectValue(this.defaultInspectValue);592}593594private _policyInspectValue: InspectValue<V> | undefined | null;595private get policyInspectValue(): InspectValue<V> | null {596if (this._policyInspectValue === undefined) {597this._policyInspectValue = this.policyConfiguration ? this.policyConfiguration.inspect<V>(this.key) : null;598}599return this._policyInspectValue;600}601602get policyValue(): V | undefined {603return this.policyInspectValue?.merged;604}605606get policy(): IInspectValue<V> | undefined {607return this.policyInspectValue?.value !== undefined ? { value: this.policyInspectValue.value } : undefined;608}609610private _applicationInspectValue: InspectValue<V> | undefined | null;611private get applicationInspectValue(): InspectValue<V> | null {612if (this._applicationInspectValue === undefined) {613this._applicationInspectValue = this.applicationConfiguration ? this.applicationConfiguration.inspect<V>(this.key) : null;614}615return this._applicationInspectValue;616}617618get applicationValue(): V | undefined {619return this.applicationInspectValue?.merged;620}621622get application(): IInspectValue<V> | undefined {623return this.toInspectValue(this.applicationInspectValue);624}625626private _userInspectValue: InspectValue<V> | undefined;627private get userInspectValue(): InspectValue<V> {628if (!this._userInspectValue) {629this._userInspectValue = this.userConfiguration.inspect<V>(this.key, this.overrides.overrideIdentifier);630}631return this._userInspectValue;632}633634get userValue(): V | undefined {635return this.userInspectValue.merged;636}637638get user(): IInspectValue<V> | undefined {639return this.toInspectValue(this.userInspectValue);640}641642private _userLocalInspectValue: InspectValue<V> | undefined;643private get userLocalInspectValue(): InspectValue<V> {644if (!this._userLocalInspectValue) {645this._userLocalInspectValue = this.localUserConfiguration.inspect<V>(this.key, this.overrides.overrideIdentifier);646}647return this._userLocalInspectValue;648}649650get userLocalValue(): V | undefined {651return this.userLocalInspectValue.merged;652}653654get userLocal(): IInspectValue<V> | undefined {655return this.toInspectValue(this.userLocalInspectValue);656}657658private _userRemoteInspectValue: InspectValue<V> | undefined;659private get userRemoteInspectValue(): InspectValue<V> {660if (!this._userRemoteInspectValue) {661this._userRemoteInspectValue = this.remoteUserConfiguration.inspect<V>(this.key, this.overrides.overrideIdentifier);662}663return this._userRemoteInspectValue;664}665666get userRemoteValue(): V | undefined {667return this.userRemoteInspectValue.merged;668}669670get userRemote(): IInspectValue<V> | undefined {671return this.toInspectValue(this.userRemoteInspectValue);672}673674private _workspaceInspectValue: InspectValue<V> | undefined | null;675private get workspaceInspectValue(): InspectValue<V> | null {676if (this._workspaceInspectValue === undefined) {677this._workspaceInspectValue = this.workspaceConfiguration ? this.workspaceConfiguration.inspect<V>(this.key, this.overrides.overrideIdentifier) : null;678}679return this._workspaceInspectValue;680}681682get workspaceValue(): V | undefined {683return this.workspaceInspectValue?.merged;684}685686get workspace(): IInspectValue<V> | undefined {687return this.toInspectValue(this.workspaceInspectValue);688}689690private _workspaceFolderInspectValue: InspectValue<V> | undefined | null;691private get workspaceFolderInspectValue(): InspectValue<V> | null {692if (this._workspaceFolderInspectValue === undefined) {693this._workspaceFolderInspectValue = this.folderConfigurationModel ? this.folderConfigurationModel.inspect<V>(this.key, this.overrides.overrideIdentifier) : null;694}695return this._workspaceFolderInspectValue;696}697698get workspaceFolderValue(): V | undefined {699return this.workspaceFolderInspectValue?.merged;700}701702get workspaceFolder(): IInspectValue<V> | undefined {703return this.toInspectValue(this.workspaceFolderInspectValue);704}705706private _memoryInspectValue: InspectValue<V> | undefined;707private get memoryInspectValue(): InspectValue<V> {708if (this._memoryInspectValue === undefined) {709this._memoryInspectValue = this.memoryConfigurationModel.inspect<V>(this.key, this.overrides.overrideIdentifier);710}711return this._memoryInspectValue;712}713714get memoryValue(): V | undefined {715return this.memoryInspectValue.merged;716}717718get memory(): IInspectValue<V> | undefined {719return this.toInspectValue(this.memoryInspectValue);720}721722}723724export class Configuration {725726private _workspaceConsolidatedConfiguration: ConfigurationModel | null = null;727private _foldersConsolidatedConfigurations = new ResourceMap<ConfigurationModel>();728729constructor(730private _defaultConfiguration: ConfigurationModel,731private _policyConfiguration: ConfigurationModel,732private _applicationConfiguration: ConfigurationModel,733private _localUserConfiguration: ConfigurationModel,734private _remoteUserConfiguration: ConfigurationModel,735private _workspaceConfiguration: ConfigurationModel,736private _folderConfigurations: ResourceMap<ConfigurationModel>,737private _memoryConfiguration: ConfigurationModel,738private _memoryConfigurationByResource: ResourceMap<ConfigurationModel>,739private readonly logService: ILogService740) {741}742743getValue(section: string | undefined, overrides: IConfigurationOverrides, workspace: Workspace | undefined): unknown {744const consolidateConfigurationModel = this.getConsolidatedConfigurationModel(section, overrides, workspace);745return consolidateConfigurationModel.getValue(section);746}747748updateValue(key: string, value: unknown, overrides: IConfigurationUpdateOverrides = {}): void {749let memoryConfiguration: ConfigurationModel | undefined;750if (overrides.resource) {751memoryConfiguration = this._memoryConfigurationByResource.get(overrides.resource);752if (!memoryConfiguration) {753memoryConfiguration = ConfigurationModel.createEmptyModel(this.logService);754this._memoryConfigurationByResource.set(overrides.resource, memoryConfiguration);755}756} else {757memoryConfiguration = this._memoryConfiguration;758}759760if (value === undefined) {761memoryConfiguration.removeValue(key);762} else {763memoryConfiguration.setValue(key, value);764}765766if (!overrides.resource) {767this._workspaceConsolidatedConfiguration = null;768}769}770771inspect<C>(key: string, overrides: IConfigurationOverrides, workspace: Workspace | undefined): IConfigurationValue<C> {772const consolidateConfigurationModel = this.getConsolidatedConfigurationModel(key, overrides, workspace);773const folderConfigurationModel = this.getFolderConfigurationModelForResource(overrides.resource, workspace);774const memoryConfigurationModel = overrides.resource ? this._memoryConfigurationByResource.get(overrides.resource) || this._memoryConfiguration : this._memoryConfiguration;775const overrideIdentifiers = new Set<string>();776for (const override of consolidateConfigurationModel.overrides) {777for (const overrideIdentifier of override.identifiers) {778if (consolidateConfigurationModel.getOverrideValue(key, overrideIdentifier) !== undefined) {779overrideIdentifiers.add(overrideIdentifier);780}781}782}783784return new ConfigurationInspectValue<C>(785key,786overrides,787consolidateConfigurationModel.getValue<C>(key),788overrideIdentifiers.size ? [...overrideIdentifiers] : undefined,789this._defaultConfiguration,790this._policyConfiguration.isEmpty() ? undefined : this._policyConfiguration,791this.applicationConfiguration.isEmpty() ? undefined : this.applicationConfiguration,792this.userConfiguration,793this.localUserConfiguration,794this.remoteUserConfiguration,795workspace ? this._workspaceConfiguration : undefined,796folderConfigurationModel ? folderConfigurationModel : undefined,797memoryConfigurationModel798);799800}801802keys(workspace: Workspace | undefined): {803default: string[];804policy: string[];805user: string[];806workspace: string[];807workspaceFolder: string[];808} {809const folderConfigurationModel = this.getFolderConfigurationModelForResource(undefined, workspace);810return {811default: this._defaultConfiguration.keys.slice(0),812policy: this._policyConfiguration.keys.slice(0),813user: this.userConfiguration.keys.slice(0),814workspace: this._workspaceConfiguration.keys.slice(0),815workspaceFolder: folderConfigurationModel ? folderConfigurationModel.keys.slice(0) : []816};817}818819updateDefaultConfiguration(defaultConfiguration: ConfigurationModel): void {820this._defaultConfiguration = defaultConfiguration;821this._workspaceConsolidatedConfiguration = null;822this._foldersConsolidatedConfigurations.clear();823}824825updatePolicyConfiguration(policyConfiguration: ConfigurationModel): void {826this._policyConfiguration = policyConfiguration;827}828829updateApplicationConfiguration(applicationConfiguration: ConfigurationModel): void {830this._applicationConfiguration = applicationConfiguration;831this._workspaceConsolidatedConfiguration = null;832this._foldersConsolidatedConfigurations.clear();833}834835updateLocalUserConfiguration(localUserConfiguration: ConfigurationModel): void {836this._localUserConfiguration = localUserConfiguration;837this._userConfiguration = null;838this._workspaceConsolidatedConfiguration = null;839this._foldersConsolidatedConfigurations.clear();840}841842updateRemoteUserConfiguration(remoteUserConfiguration: ConfigurationModel): void {843this._remoteUserConfiguration = remoteUserConfiguration;844this._userConfiguration = null;845this._workspaceConsolidatedConfiguration = null;846this._foldersConsolidatedConfigurations.clear();847}848849updateWorkspaceConfiguration(workspaceConfiguration: ConfigurationModel): void {850this._workspaceConfiguration = workspaceConfiguration;851this._workspaceConsolidatedConfiguration = null;852this._foldersConsolidatedConfigurations.clear();853}854855updateFolderConfiguration(resource: URI, configuration: ConfigurationModel): void {856this._folderConfigurations.set(resource, configuration);857this._foldersConsolidatedConfigurations.delete(resource);858}859860deleteFolderConfiguration(resource: URI): void {861this.folderConfigurations.delete(resource);862this._foldersConsolidatedConfigurations.delete(resource);863}864865compareAndUpdateDefaultConfiguration(defaults: ConfigurationModel, keys?: string[]): IConfigurationChange {866const overrides: [string, string[]][] = [];867if (!keys) {868const { added, updated, removed } = compare(this._defaultConfiguration, defaults);869keys = [...added, ...updated, ...removed];870}871for (const key of keys) {872for (const overrideIdentifier of overrideIdentifiersFromKey(key)) {873const fromKeys = this._defaultConfiguration.getKeysForOverrideIdentifier(overrideIdentifier);874const toKeys = defaults.getKeysForOverrideIdentifier(overrideIdentifier);875const keys = [876...toKeys.filter(key => fromKeys.indexOf(key) === -1),877...fromKeys.filter(key => toKeys.indexOf(key) === -1),878...fromKeys.filter(key => !objects.equals(this._defaultConfiguration.override(overrideIdentifier).getValue(key), defaults.override(overrideIdentifier).getValue(key)))879];880overrides.push([overrideIdentifier, keys]);881}882}883this.updateDefaultConfiguration(defaults);884return { keys, overrides };885}886887compareAndUpdatePolicyConfiguration(policyConfiguration: ConfigurationModel): IConfigurationChange {888const { added, updated, removed } = compare(this._policyConfiguration, policyConfiguration);889const keys = [...added, ...updated, ...removed];890if (keys.length) {891this.updatePolicyConfiguration(policyConfiguration);892}893return { keys, overrides: [] };894}895896compareAndUpdateApplicationConfiguration(application: ConfigurationModel): IConfigurationChange {897const { added, updated, removed, overrides } = compare(this.applicationConfiguration, application);898const keys = [...added, ...updated, ...removed];899if (keys.length) {900this.updateApplicationConfiguration(application);901}902return { keys, overrides };903}904905compareAndUpdateLocalUserConfiguration(user: ConfigurationModel): IConfigurationChange {906const { added, updated, removed, overrides } = compare(this.localUserConfiguration, user);907const keys = [...added, ...updated, ...removed];908if (keys.length) {909this.updateLocalUserConfiguration(user);910}911return { keys, overrides };912}913914compareAndUpdateRemoteUserConfiguration(user: ConfigurationModel): IConfigurationChange {915const { added, updated, removed, overrides } = compare(this.remoteUserConfiguration, user);916const keys = [...added, ...updated, ...removed];917if (keys.length) {918this.updateRemoteUserConfiguration(user);919}920return { keys, overrides };921}922923compareAndUpdateWorkspaceConfiguration(workspaceConfiguration: ConfigurationModel): IConfigurationChange {924const { added, updated, removed, overrides } = compare(this.workspaceConfiguration, workspaceConfiguration);925const keys = [...added, ...updated, ...removed];926if (keys.length) {927this.updateWorkspaceConfiguration(workspaceConfiguration);928}929return { keys, overrides };930}931932compareAndUpdateFolderConfiguration(resource: URI, folderConfiguration: ConfigurationModel): IConfigurationChange {933const currentFolderConfiguration = this.folderConfigurations.get(resource);934const { added, updated, removed, overrides } = compare(currentFolderConfiguration, folderConfiguration);935const keys = [...added, ...updated, ...removed];936if (keys.length || !currentFolderConfiguration) {937this.updateFolderConfiguration(resource, folderConfiguration);938}939return { keys, overrides };940}941942compareAndDeleteFolderConfiguration(folder: URI): IConfigurationChange {943const folderConfig = this.folderConfigurations.get(folder);944if (!folderConfig) {945throw new Error('Unknown folder');946}947this.deleteFolderConfiguration(folder);948const { added, updated, removed, overrides } = compare(folderConfig, undefined);949return { keys: [...added, ...updated, ...removed], overrides };950}951952get defaults(): ConfigurationModel {953return this._defaultConfiguration;954}955956get applicationConfiguration(): ConfigurationModel {957return this._applicationConfiguration;958}959960private _userConfiguration: ConfigurationModel | null = null;961get userConfiguration(): ConfigurationModel {962if (!this._userConfiguration) {963if (this._remoteUserConfiguration.isEmpty()) {964this._userConfiguration = this._localUserConfiguration;965} else {966const merged = this._localUserConfiguration.merge(this._remoteUserConfiguration);967this._userConfiguration = new ConfigurationModel(merged.contents, merged.keys, merged.overrides, undefined, this.logService);968}969}970return this._userConfiguration;971}972973get localUserConfiguration(): ConfigurationModel {974return this._localUserConfiguration;975}976977get remoteUserConfiguration(): ConfigurationModel {978return this._remoteUserConfiguration;979}980981get workspaceConfiguration(): ConfigurationModel {982return this._workspaceConfiguration;983}984985get folderConfigurations(): ResourceMap<ConfigurationModel> {986return this._folderConfigurations;987}988989private getConsolidatedConfigurationModel(section: string | undefined, overrides: IConfigurationOverrides, workspace: Workspace | undefined): ConfigurationModel {990let configurationModel = this.getConsolidatedConfigurationModelForResource(overrides, workspace);991if (overrides.overrideIdentifier) {992configurationModel = configurationModel.override(overrides.overrideIdentifier);993}994if (!this._policyConfiguration.isEmpty() && this._policyConfiguration.getValue(section) !== undefined) {995// clone by merging996configurationModel = configurationModel.merge();997for (const key of this._policyConfiguration.keys) {998configurationModel.setValue(key, this._policyConfiguration.getValue(key));999}1000}1001return configurationModel;1002}10031004private getConsolidatedConfigurationModelForResource({ resource }: IConfigurationOverrides, workspace: Workspace | undefined): ConfigurationModel {1005let consolidateConfiguration = this.getWorkspaceConsolidatedConfiguration();10061007if (workspace && resource) {1008const root = workspace.getFolder(resource);1009if (root) {1010consolidateConfiguration = this.getFolderConsolidatedConfiguration(root.uri) || consolidateConfiguration;1011}1012const memoryConfigurationForResource = this._memoryConfigurationByResource.get(resource);1013if (memoryConfigurationForResource) {1014consolidateConfiguration = consolidateConfiguration.merge(memoryConfigurationForResource);1015}1016}10171018return consolidateConfiguration;1019}10201021private getWorkspaceConsolidatedConfiguration(): ConfigurationModel {1022if (!this._workspaceConsolidatedConfiguration) {1023this._workspaceConsolidatedConfiguration = this._defaultConfiguration.merge(this.applicationConfiguration, this.userConfiguration, this._workspaceConfiguration, this._memoryConfiguration);1024}1025return this._workspaceConsolidatedConfiguration;1026}10271028private getFolderConsolidatedConfiguration(folder: URI): ConfigurationModel {1029let folderConsolidatedConfiguration = this._foldersConsolidatedConfigurations.get(folder);1030if (!folderConsolidatedConfiguration) {1031const workspaceConsolidateConfiguration = this.getWorkspaceConsolidatedConfiguration();1032const folderConfiguration = this._folderConfigurations.get(folder);1033if (folderConfiguration) {1034folderConsolidatedConfiguration = workspaceConsolidateConfiguration.merge(folderConfiguration);1035this._foldersConsolidatedConfigurations.set(folder, folderConsolidatedConfiguration);1036} else {1037folderConsolidatedConfiguration = workspaceConsolidateConfiguration;1038}1039}1040return folderConsolidatedConfiguration;1041}10421043private getFolderConfigurationModelForResource(resource: URI | null | undefined, workspace: Workspace | undefined): ConfigurationModel | undefined {1044if (workspace && resource) {1045const root = workspace.getFolder(resource);1046if (root) {1047return this._folderConfigurations.get(root.uri);1048}1049}1050return undefined;1051}10521053toData(): IConfigurationData {1054return {1055defaults: {1056contents: this._defaultConfiguration.contents,1057overrides: this._defaultConfiguration.overrides,1058keys: this._defaultConfiguration.keys,1059},1060policy: {1061contents: this._policyConfiguration.contents,1062overrides: this._policyConfiguration.overrides,1063keys: this._policyConfiguration.keys1064},1065application: {1066contents: this.applicationConfiguration.contents,1067overrides: this.applicationConfiguration.overrides,1068keys: this.applicationConfiguration.keys,1069raw: Array.isArray(this.applicationConfiguration.raw) ? undefined : this.applicationConfiguration.raw1070},1071userLocal: {1072contents: this.localUserConfiguration.contents,1073overrides: this.localUserConfiguration.overrides,1074keys: this.localUserConfiguration.keys,1075raw: Array.isArray(this.localUserConfiguration.raw) ? undefined : this.localUserConfiguration.raw1076},1077userRemote: {1078contents: this.remoteUserConfiguration.contents,1079overrides: this.remoteUserConfiguration.overrides,1080keys: this.remoteUserConfiguration.keys,1081raw: Array.isArray(this.remoteUserConfiguration.raw) ? undefined : this.remoteUserConfiguration.raw1082},1083workspace: {1084contents: this._workspaceConfiguration.contents,1085overrides: this._workspaceConfiguration.overrides,1086keys: this._workspaceConfiguration.keys1087},1088folders: [...this._folderConfigurations.keys()].reduce<[UriComponents, IConfigurationModel][]>((result, folder) => {1089const { contents, overrides, keys } = this._folderConfigurations.get(folder)!;1090result.push([folder, { contents, overrides, keys }]);1091return result;1092}, [])1093};1094}10951096allKeys(): string[] {1097const keys: Set<string> = new Set<string>();1098this._defaultConfiguration.keys.forEach(key => keys.add(key));1099this.userConfiguration.keys.forEach(key => keys.add(key));1100this._workspaceConfiguration.keys.forEach(key => keys.add(key));1101this._folderConfigurations.forEach(folderConfiguration => folderConfiguration.keys.forEach(key => keys.add(key)));1102return [...keys.values()];1103}11041105protected allOverrideIdentifiers(): string[] {1106const keys: Set<string> = new Set<string>();1107this._defaultConfiguration.getAllOverrideIdentifiers().forEach(key => keys.add(key));1108this.userConfiguration.getAllOverrideIdentifiers().forEach(key => keys.add(key));1109this._workspaceConfiguration.getAllOverrideIdentifiers().forEach(key => keys.add(key));1110this._folderConfigurations.forEach(folderConfiguration => folderConfiguration.getAllOverrideIdentifiers().forEach(key => keys.add(key)));1111return [...keys.values()];1112}11131114protected getAllKeysForOverrideIdentifier(overrideIdentifier: string): string[] {1115const keys: Set<string> = new Set<string>();1116this._defaultConfiguration.getKeysForOverrideIdentifier(overrideIdentifier).forEach(key => keys.add(key));1117this.userConfiguration.getKeysForOverrideIdentifier(overrideIdentifier).forEach(key => keys.add(key));1118this._workspaceConfiguration.getKeysForOverrideIdentifier(overrideIdentifier).forEach(key => keys.add(key));1119this._folderConfigurations.forEach(folderConfiguration => folderConfiguration.getKeysForOverrideIdentifier(overrideIdentifier).forEach(key => keys.add(key)));1120return [...keys.values()];1121}11221123static parse(data: IConfigurationData, logService: ILogService): Configuration {1124const defaultConfiguration = this.parseConfigurationModel(data.defaults, logService);1125const policyConfiguration = this.parseConfigurationModel(data.policy, logService);1126const applicationConfiguration = this.parseConfigurationModel(data.application, logService);1127const userLocalConfiguration = this.parseConfigurationModel(data.userLocal, logService);1128const userRemoteConfiguration = this.parseConfigurationModel(data.userRemote, logService);1129const workspaceConfiguration = this.parseConfigurationModel(data.workspace, logService);1130const folders: ResourceMap<ConfigurationModel> = data.folders.reduce((result, value) => {1131result.set(URI.revive(value[0]), this.parseConfigurationModel(value[1], logService));1132return result;1133}, new ResourceMap<ConfigurationModel>());1134return new Configuration(1135defaultConfiguration,1136policyConfiguration,1137applicationConfiguration,1138userLocalConfiguration,1139userRemoteConfiguration,1140workspaceConfiguration,1141folders,1142ConfigurationModel.createEmptyModel(logService),1143new ResourceMap<ConfigurationModel>(),1144logService1145);1146}11471148private static parseConfigurationModel(model: IConfigurationModel, logService: ILogService): ConfigurationModel {1149return new ConfigurationModel(model.contents, model.keys, model.overrides, model.raw, logService);1150}11511152}11531154export function mergeChanges(...changes: IConfigurationChange[]): IConfigurationChange {1155if (changes.length === 0) {1156return { keys: [], overrides: [] };1157}1158if (changes.length === 1) {1159return changes[0];1160}1161const keysSet = new Set<string>();1162const overridesMap = new Map<string, Set<string>>();1163for (const change of changes) {1164change.keys.forEach(key => keysSet.add(key));1165change.overrides.forEach(([identifier, keys]) => {1166const result = getOrSet(overridesMap, identifier, new Set<string>());1167keys.forEach(key => result.add(key));1168});1169}1170const overrides: [string, string[]][] = [];1171overridesMap.forEach((keys, identifier) => overrides.push([identifier, [...keys.values()]]));1172return { keys: [...keysSet.values()], overrides };1173}11741175export class ConfigurationChangeEvent implements IConfigurationChangeEvent {11761177private readonly _marker = '\n';1178private readonly _markerCode1 = this._marker.charCodeAt(0);1179private readonly _markerCode2 = '.'.charCodeAt(0);1180private readonly _affectsConfigStr: string;11811182readonly affectedKeys = new Set<string>();1183source!: ConfigurationTarget;11841185constructor(1186readonly change: IConfigurationChange,1187private readonly previous: { workspace?: Workspace; data: IConfigurationData } | undefined,1188private readonly currentConfiguraiton: Configuration,1189private readonly currentWorkspace: Workspace | undefined,1190private readonly logService: ILogService1191) {1192for (const key of change.keys) {1193this.affectedKeys.add(key);1194}1195for (const [, keys] of change.overrides) {1196for (const key of keys) {1197this.affectedKeys.add(key);1198}1199}12001201// Example: '\nfoo.bar\nabc.def\n'1202this._affectsConfigStr = this._marker;1203for (const key of this.affectedKeys) {1204this._affectsConfigStr += key + this._marker;1205}1206}12071208private _previousConfiguration: Configuration | undefined = undefined;1209get previousConfiguration(): Configuration | undefined {1210if (!this._previousConfiguration && this.previous) {1211this._previousConfiguration = Configuration.parse(this.previous.data, this.logService);1212}1213return this._previousConfiguration;1214}12151216affectsConfiguration(section: string, overrides?: IConfigurationOverrides): boolean {1217// we have one large string with all keys that have changed. we pad (marker) the section1218// and check that either find it padded or before a segment character1219const needle = this._marker + section;1220const idx = this._affectsConfigStr.indexOf(needle);1221if (idx < 0) {1222// NOT: (marker + section)1223return false;1224}1225const pos = idx + needle.length;1226if (pos >= this._affectsConfigStr.length) {1227return false;1228}1229const code = this._affectsConfigStr.charCodeAt(pos);1230if (code !== this._markerCode1 && code !== this._markerCode2) {1231// NOT: section + (marker | segment)1232return false;1233}1234if (overrides) {1235const value1 = this.previousConfiguration ? this.previousConfiguration.getValue(section, overrides, this.previous?.workspace) : undefined;1236const value2 = this.currentConfiguraiton.getValue(section, overrides, this.currentWorkspace);1237return !objects.equals(value1, value2);1238}1239return true;1240}1241}12421243function compare(from: ConfigurationModel | undefined, to: ConfigurationModel | undefined): IConfigurationCompareResult {1244const { added, removed, updated } = compareConfigurationContents(to?.rawConfiguration, from?.rawConfiguration);1245const overrides: [string, string[]][] = [];12461247const fromOverrideIdentifiers = from?.getAllOverrideIdentifiers() || [];1248const toOverrideIdentifiers = to?.getAllOverrideIdentifiers() || [];12491250if (to) {1251const addedOverrideIdentifiers = toOverrideIdentifiers.filter(key => !fromOverrideIdentifiers.includes(key));1252for (const identifier of addedOverrideIdentifiers) {1253overrides.push([identifier, to.getKeysForOverrideIdentifier(identifier)]);1254}1255}12561257if (from) {1258const removedOverrideIdentifiers = fromOverrideIdentifiers.filter(key => !toOverrideIdentifiers.includes(key));1259for (const identifier of removedOverrideIdentifiers) {1260overrides.push([identifier, from.getKeysForOverrideIdentifier(identifier)]);1261}1262}12631264if (to && from) {1265for (const identifier of fromOverrideIdentifiers) {1266if (toOverrideIdentifiers.includes(identifier)) {1267const result = compareConfigurationContents({ contents: from.getOverrideValue(undefined, identifier) || {}, keys: from.getKeysForOverrideIdentifier(identifier) }, { contents: to.getOverrideValue(undefined, identifier) || {}, keys: to.getKeysForOverrideIdentifier(identifier) });1268overrides.push([identifier, [...result.added, ...result.removed, ...result.updated]]);1269}1270}1271}12721273return { added, removed, updated, overrides };1274}12751276function compareConfigurationContents(to: { keys: string[]; contents: IStringDictionary<unknown> } | undefined, from: { keys: string[]; contents: IStringDictionary<unknown> } | undefined) {1277const added = to1278? from ? to.keys.filter(key => from.keys.indexOf(key) === -1) : [...to.keys]1279: [];1280const removed = from1281? to ? from.keys.filter(key => to.keys.indexOf(key) === -1) : [...from.keys]1282: [];1283const updated: string[] = [];12841285if (to && from) {1286for (const key of from.keys) {1287if (to.keys.indexOf(key) !== -1) {1288const value1 = getConfigurationValue(from.contents, key);1289const value2 = getConfigurationValue(to.contents, key);1290if (!objects.equals(value1, value2)) {1291updated.push(key);1292}1293}1294}1295}1296return { added, removed, updated };1297}129812991300