/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { TPromise } from 'vs/base/common/winjs.base'; import * as arrays from 'vs/base/common/arrays'; import * as types from 'vs/base/common/types'; import * as objects from 'vs/base/common/objects'; import URI from 'vs/base/common/uri'; import { StrictResourceMap } from 'vs/base/common/map'; import { Workspace } from 'vs/platform/workspace/common/workspace'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import Event from 'vs/base/common/event'; export const IConfigurationService = createDecorator('configurationService'); export interface IConfigurationOverrides { overrideIdentifier?: string; resource?: URI; } export type IConfigurationValues = { [key: string]: IConfigurationValue }; export interface IConfigurationService { _serviceBrand: any; getConfigurationData(): IConfigurationData; /** * Fetches the appropriate section of the configuration JSON file. * This will be an object keyed off the section name. */ getConfiguration(section?: string, overrides?: IConfigurationOverrides): T; /** * Resolves a configuration key to its values in the different scopes * the setting is defined. */ lookup(key: string, overrides?: IConfigurationOverrides): IConfigurationValue; /** * Returns the defined keys of configurations in the different scopes * the key is defined. */ keys(overrides?: IConfigurationOverrides): IConfigurationKeys; /** * Similar to #getConfiguration() but ensures that the latest configuration * from disk is fetched. */ reloadConfiguration(section?: string): TPromise; /** * Event that fires when the configuration changes. */ onDidUpdateConfiguration: Event; /** * Returns the defined values of configurations in the different scopes. */ values(): IConfigurationValues; } export enum ConfigurationSource { Default = 1, User, Workspace } export interface IConfigurationServiceEvent { /** * The type of source that triggered this event. */ source: ConfigurationSource; /** * The part of the configuration contributed by the source of this event. */ sourceConfig: any; } export interface IConfigurationValue { value: T; default: T; user: T; workspace: T; folder: T; } export interface IConfigurationKeys { default: string[]; user: string[]; workspace: string[]; folder: string[]; } /** * A helper function to get the configuration value with a specific settings path (e.g. config.some.setting) */ export function getConfigurationValue(config: any, settingPath: string, defaultValue?: T): T { function accessSetting(config: any, path: string[]): any { let current = config; for (let i = 0; i < path.length; i++) { if (typeof current !== 'object' || current === null) { return undefined; } current = current[path[i]]; } return current; } const path = settingPath.split('.'); const result = accessSetting(config, path); return typeof result === 'undefined' ? defaultValue : result; } export function merge(base: any, add: any, overwrite: boolean): void { Object.keys(add).forEach(key => { if (key in base) { if (types.isObject(base[key]) && types.isObject(add[key])) { merge(base[key], add[key], overwrite); } else if (overwrite) { base[key] = add[key]; } } else { base[key] = add[key]; } }); } export interface IConfiguraionModel { contents: T; keys: string[]; overrides: IOverrides[]; } export interface IOverrides { contents: T; identifiers: string[]; } export class ConfigurationModel implements IConfiguraionModel { constructor(protected _contents: T = {}, protected _keys: string[] = [], protected _overrides: IOverrides[] = []) { } public get contents(): T { return this._contents; } public get overrides(): IOverrides[] { return this._overrides; } public get keys(): string[] { return this._keys; } public getContentsFor(section: string): V { return objects.clone(this.contents[section]); } public override(identifier: string): ConfigurationModel { const result = new ConfigurationModel(); const contents = objects.clone(this.contents); if (this._overrides) { for (const override of this._overrides) { if (override.identifiers.indexOf(identifier) !== -1) { merge(contents, override.contents, true); } } } result._contents = contents; return result; } public merge(other: ConfigurationModel, overwrite: boolean = true): ConfigurationModel { const mergedModel = new ConfigurationModel(); this.doMerge(mergedModel, this, overwrite); this.doMerge(mergedModel, other, overwrite); return mergedModel; } protected doMerge(source: ConfigurationModel, target: ConfigurationModel, overwrite: boolean = true) { merge(source.contents, objects.clone(target.contents), overwrite); const overrides = objects.clone(source._overrides); for (const override of target._overrides) { const [sourceOverride] = overrides.filter(o => arrays.equals(o.identifiers, override.identifiers)); if (sourceOverride) { merge(sourceOverride.contents, override.contents, overwrite); } else { overrides.push(override); } } source._overrides = overrides; } } export interface IConfigurationData { defaults: IConfiguraionModel; user: IConfiguraionModel; workspace: IConfiguraionModel; folders: { [folder: string]: IConfiguraionModel }; } export class Configuration { private _globalConfiguration: ConfigurationModel; private _workspaceConsolidatedConfiguration: ConfigurationModel; private _legacyWorkspaceConsolidatedConfiguration: ConfigurationModel; protected _foldersConsolidatedConfigurations: StrictResourceMap>; constructor(protected _defaults: ConfigurationModel, protected _user: ConfigurationModel, protected _workspaceConfiguration: ConfigurationModel = new ConfigurationModel(), protected folders: StrictResourceMap> = new StrictResourceMap>(), protected _workspace?: Workspace) { this.merge(); } get defaults(): ConfigurationModel { return this._defaults; } get user(): ConfigurationModel { return this._user; } protected merge(): void { this._globalConfiguration = new ConfigurationModel().merge(this._defaults).merge(this._user); this._workspaceConsolidatedConfiguration = new ConfigurationModel().merge(this._globalConfiguration).merge(this._workspaceConfiguration); this._legacyWorkspaceConsolidatedConfiguration = null; this._foldersConsolidatedConfigurations = new StrictResourceMap>(); for (const folder of this.folders.keys()) { this.mergeFolder(folder); } } protected mergeFolder(folder: URI) { this._foldersConsolidatedConfigurations.set(folder, new ConfigurationModel().merge(this._workspaceConsolidatedConfiguration).merge(this.folders.get(folder))); } getValue(section: string = '', overrides: IConfigurationOverrides = {}): C { const configModel = this.getConsolidateConfigurationModel(overrides); return section ? configModel.getContentsFor(section) : configModel.contents; } lookup(key: string, overrides: IConfigurationOverrides = {}): IConfigurationValue { // make sure to clone the configuration so that the receiver does not tamper with the values const consolidateConfigurationModel = this.getConsolidateConfigurationModel(overrides); const folderConfigurationModel = this.getFolderConfigurationModelForResource(overrides.resource); return { default: objects.clone(getConfigurationValue(overrides.overrideIdentifier ? this._defaults.override(overrides.overrideIdentifier).contents : this._defaults.contents, key)), user: objects.clone(getConfigurationValue(overrides.overrideIdentifier ? this._user.override(overrides.overrideIdentifier).contents : this._user.contents, key)), workspace: objects.clone(this._workspace ? getConfigurationValue(overrides.overrideIdentifier ? this._workspaceConfiguration.override(overrides.overrideIdentifier).contents : this._workspaceConfiguration.contents, key) : void 0), //Check on workspace exists or not because _workspaceConfiguration is never null folder: objects.clone(folderConfigurationModel ? getConfigurationValue(overrides.overrideIdentifier ? folderConfigurationModel.override(overrides.overrideIdentifier).contents : folderConfigurationModel.contents, key) : void 0), value: objects.clone(getConfigurationValue(consolidateConfigurationModel.contents, key)) }; } lookupLegacy(key: string): IConfigurationValue { if (!this._legacyWorkspaceConsolidatedConfiguration) { this._legacyWorkspaceConsolidatedConfiguration = this._workspace ? new ConfigurationModel().merge(this._workspaceConfiguration).merge(this.folders.get(this._workspace.roots[0])) : null; } const consolidateConfigurationModel = this.getConsolidateConfigurationModel({}); return { default: objects.clone(getConfigurationValue(this._defaults.contents, key)), user: objects.clone(getConfigurationValue(this._user.contents, key)), workspace: objects.clone(this._legacyWorkspaceConsolidatedConfiguration ? getConfigurationValue(this._legacyWorkspaceConsolidatedConfiguration.contents, key) : void 0), folder: void 0, value: objects.clone(getConfigurationValue(consolidateConfigurationModel.contents, key)) }; } keys(overrides: IConfigurationOverrides = {}): IConfigurationKeys { const folderConfigurationModel = this.getFolderConfigurationModelForResource(overrides.resource); return { default: this._defaults.keys, user: this._user.keys, workspace: this._workspaceConfiguration.keys, folder: folderConfigurationModel ? folderConfigurationModel.keys : [] }; } values(): IConfigurationValues { const result = Object.create(null); const keyset = this.keys(); const keys = [...keyset.workspace, ...keyset.user, ...keyset.default].sort(); let lastKey: string; for (const key of keys) { if (key !== lastKey) { lastKey = key; result[key] = this.lookup(key); } } return result; } values2(): Map> { const result: Map> = new Map>(); const keyset = this.keys(); const keys = [...keyset.workspace, ...keyset.user, ...keyset.default].sort(); let lastKey: string; for (const key of keys) { if (key !== lastKey) { lastKey = key; result.set(key, this.lookup(key)); } } return result; } private getConsolidateConfigurationModel(overrides: IConfigurationOverrides): ConfigurationModel { let configurationModel = this.getConsolidatedConfigurationModelForResource(overrides); return overrides.overrideIdentifier ? configurationModel.override(overrides.overrideIdentifier) : configurationModel; } private getConsolidatedConfigurationModelForResource({ resource }: IConfigurationOverrides): ConfigurationModel { if (!this._workspace) { return this._globalConfiguration; } if (!resource) { return this._workspaceConsolidatedConfiguration; } const root = this._workspace.getRoot(resource); if (!root) { return this._workspaceConsolidatedConfiguration; } return this._foldersConsolidatedConfigurations.get(root) || this._workspaceConsolidatedConfiguration; } private getFolderConfigurationModelForResource(resource: URI): ConfigurationModel { if (!this._workspace || !resource) { return null; } const root = this._workspace.getRoot(resource); return root ? this.folders.get(root) : null; } public toData(): IConfigurationData { return { defaults: { contents: this._defaults.contents, overrides: this._defaults.overrides, keys: this._defaults.keys }, user: { contents: this._user.contents, overrides: this._user.overrides, keys: this._user.keys }, workspace: { contents: this._workspaceConfiguration.contents, overrides: this._workspaceConfiguration.overrides, keys: this._workspaceConfiguration.keys }, folders: this.folders.keys().reduce((result, folder) => { const { contents, overrides, keys } = this.folders.get(folder); result[folder.toString()] = { contents, overrides, keys }; return result; }, Object.create({})) }; } public static parse(data: IConfigurationData, workspace: Workspace): Configuration { const defaultConfiguration = Configuration.parseConfigurationModel(data.defaults); const userConfiguration = Configuration.parseConfigurationModel(data.user); const workspaceConfiguration = Configuration.parseConfigurationModel(data.workspace); const folders: StrictResourceMap> = Object.keys(data.folders).reduce((result, key) => { result.set(URI.parse(key), Configuration.parseConfigurationModel(data.folders[key])); return result; }, new StrictResourceMap>()); return new Configuration(defaultConfiguration, userConfiguration, workspaceConfiguration, folders, workspace); } private static parseConfigurationModel(model: IConfiguraionModel): ConfigurationModel { return new ConfigurationModel(model.contents, model.keys, model.overrides); } }