/*--------------------------------------------------------------------------------------------- * 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 { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import Event from 'vs/base/common/event'; export const IConfigurationService = createDecorator('configurationService'); export interface IConfigurationOverrides { language?: 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, overrideIdentifier?: string): IConfigurationValue; /** * Returns the defined keys of configurations in the different scopes * the key is defined. */ keys(): 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; } export interface IConfigurationKeys { default: string[]; user: string[]; workspace: 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; overrides: IOverrides[]; } export interface IOverrides { contents: T; identifiers: string[]; } export class ConfigurationModel implements IConfiguraionModel { protected _keys: string[] = []; constructor(protected _contents: T = {}, 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; folders: { [folder: string]: IConfiguraionModel }; workspaceUri: string; } export class Configuration { private _global: ConfigurationModel; private _workspace: ConfigurationModel; protected _foldersConsolidated: StrictResourceMap>; constructor(protected _defaults: ConfigurationModel, protected _user: ConfigurationModel, protected folders: StrictResourceMap> = new StrictResourceMap>(), protected workspaceUri?: URI) { this.merge(); } get defaults(): ConfigurationModel { return this._defaults; } get user(): ConfigurationModel { return this._user; } get workspace(): ConfigurationModel { return this._workspace; } protected merge(): void { this._global = this._workspace = new ConfigurationModel().merge(this._defaults).merge(this._user); this._foldersConsolidated = new StrictResourceMap>(); for (const folder of this.folders.keys()) { this.mergeFolder(folder); } } protected mergeFolder(folder: URI) { if (this.workspaceUri && this.workspaceUri.fsPath === folder.fsPath) { this._workspace = new ConfigurationModel().merge(this._global).merge(this.folders.get(this.workspaceUri)); this._foldersConsolidated.set(folder, this._workspace); } else { this._foldersConsolidated.set(folder, new ConfigurationModel().merge(this._workspace).merge(this.folders.get(folder))); } } getValue(section: string = '', options: IConfigurationOverrides = {}): C { const configModel = this.getConfigurationModel(options); return section ? configModel.getContentsFor(section) : configModel.contents; } lookup(key: string, overrideIdentifier?: string): IConfigurationValue { // make sure to clone the configuration so that the receiver does not tamper with the values return { default: objects.clone(getConfigurationValue(overrideIdentifier ? this._defaults.override(overrideIdentifier).contents : this._defaults.contents, key)), user: objects.clone(getConfigurationValue(overrideIdentifier ? this._user.override(overrideIdentifier).contents : this._user.contents, key)), workspace: objects.clone(this.workspaceUri ? getConfigurationValue(overrideIdentifier ? this.folders.get(this.workspaceUri).override(overrideIdentifier).contents : this.folders.get(this.workspaceUri).contents, key) : void 0), value: objects.clone(getConfigurationValue(overrideIdentifier ? this._workspace.override(overrideIdentifier).contents : this._workspace.contents, key)) }; } keys(): IConfigurationKeys { return { default: this._defaults.keys, user: this._user.keys, workspace: this.workspaceUri ? this.folders.get(this.workspaceUri).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 getConfigurationModel(options: IConfigurationOverrides): ConfigurationModel { let configurationModel = (options.resource ? this._foldersConsolidated.get(options.resource) : this._workspace) || new ConfigurationModel(); return options.language ? configurationModel.override(options.language) : configurationModel; } public toData(): IConfigurationData { return { defaults: { contents: this._defaults.contents, overrides: this._defaults.overrides }, user: { contents: this._user.contents, overrides: this._user.overrides }, folders: this.folders.keys().reduce((result, folder) => { const { contents, overrides } = this.folders.get(folder); result[folder.toString()] = { contents, overrides }; return result; }, Object.create({})), workspaceUri: this.workspaceUri ? this.workspaceUri.toString() : void 0 }; } public static parse(data: IConfigurationData): Configuration { const defaults = Configuration.parseConfigurationModel(data.defaults); const user = Configuration.parseConfigurationModel(data.user); const folders: StrictResourceMap> = Object.keys(data.folders).reduce((result, key) => { result.set(URI.parse(key), Configuration.parseConfigurationModel(data.folders[key])); return result; }, new StrictResourceMap>()); const workspaceUri = data.workspaceUri ? URI.parse(data.workspaceUri) : void 0; return new Configuration(defaults, user, folders, workspaceUri); } private static parseConfigurationModel(model: IConfiguraionModel): ConfigurationModel { return new ConfigurationModel(model.contents, model.overrides); } }