configuration.ts 10.6 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

J
Johannes Rieken 已提交
6
import { TPromise } from 'vs/base/common/winjs.base';
7 8 9
import * as arrays from 'vs/base/common/arrays';
import * as types from 'vs/base/common/types';
import * as objects from 'vs/base/common/objects';
10 11
import URI from 'vs/base/common/uri';
import { StrictResourceMap } from 'vs/base/common/map';
J
Johannes Rieken 已提交
12
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
13
import Event from 'vs/base/common/event';
E
Erich Gamma 已提交
14

B
Benjamin Pasero 已提交
15
export const IConfigurationService = createDecorator<IConfigurationService>('configurationService');
E
Erich Gamma 已提交
16

17 18
export interface IConfigurationOverrides {
	language?: string;
19
	resource?: URI;
20 21
}

22 23
export type IConfigurationValues = { [key: string]: IConfigurationValue<any> };

24
export interface IConfigurationService {
25
	_serviceBrand: any;
E
Erich Gamma 已提交
26

27
	getConfigurationData<T>(): IConfigurationData<T>;
28

E
Erich Gamma 已提交
29 30 31 32
	/**
	 * Fetches the appropriate section of the configuration JSON file.
	 * This will be an object keyed off the section name.
	 */
33
	getConfiguration<T>(section?: string, overrides?: IConfigurationOverrides): T;
E
Erich Gamma 已提交
34

B
Benjamin Pasero 已提交
35 36 37 38
	/**
	 * Resolves a configuration key to its values in the different scopes
	 * the setting is defined.
	 */
39
	lookup<T>(key: string, overrideIdentifier?: string): IConfigurationValue<T>;
B
Benjamin Pasero 已提交
40

B
Benjamin Pasero 已提交
41 42 43 44 45 46
	/**
	 * Returns the defined keys of configurations in the different scopes
	 * the key is defined.
	 */
	keys(): IConfigurationKeys;

47 48 49 50
	/**
	 * Similar to #getConfiguration() but ensures that the latest configuration
	 * from disk is fetched.
	 */
51
	reloadConfiguration<T>(section?: string): TPromise<T>;
52

53 54 55
	/**
	 * Event that fires when the configuration changes.
	 */
56
	onDidUpdateConfiguration: Event<IConfigurationServiceEvent>;
57 58 59 60 61

	/**
	 * Returns the defined values of configurations in the different scopes.
	 */
	values(): IConfigurationValues;
E
Erich Gamma 已提交
62 63
}

64 65 66 67 68 69
export enum ConfigurationSource {
	Default = 1,
	User,
	Workspace
}

E
Erich Gamma 已提交
70
export interface IConfigurationServiceEvent {
71 72 73 74 75 76 77 78
	/**
	 * The type of source that triggered this event.
	 */
	source: ConfigurationSource;
	/**
	 * The part of the configuration contributed by the source of this event.
	 */
	sourceConfig: any;
E
Erich Gamma 已提交
79 80
}

B
Benjamin Pasero 已提交
81 82 83 84
export interface IConfigurationValue<T> {
	value: T;
	default: T;
	user: T;
85
	workspace: T;
B
Benjamin Pasero 已提交
86 87
}

B
Benjamin Pasero 已提交
88 89 90
export interface IConfigurationKeys {
	default: string[];
	user: string[];
91
	workspace: string[];
B
Benjamin Pasero 已提交
92 93
}

94 95 96
/**
 * A helper function to get the configuration value with a specific settings path (e.g. config.some.setting)
 */
97
export function getConfigurationValue<T>(config: any, settingPath: string, defaultValue?: T): T {
E
Erich Gamma 已提交
98 99 100
	function accessSetting(config: any, path: string[]): any {
		let current = config;
		for (let i = 0; i < path.length; i++) {
M
Martin Aeschlimann 已提交
101
			if (typeof current !== 'object' || current === null) {
E
Erich Gamma 已提交
102 103
				return undefined;
			}
M
Martin Aeschlimann 已提交
104
			current = current[path[i]];
E
Erich Gamma 已提交
105
		}
B
Benjamin Pasero 已提交
106
		return <T>current;
E
Erich Gamma 已提交
107 108
	}

B
Benjamin Pasero 已提交
109 110
	const path = settingPath.split('.');
	const result = accessSetting(config, path);
111

B
Benjamin Pasero 已提交
112
	return typeof result === 'undefined' ? defaultValue : result;
113
}
114

115 116 117 118 119 120 121 122 123 124 125 126
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];
		}
	});
127 128
}

129 130 131 132 133
export interface IConfiguraionModel<T> {
	contents: T;
	overrides: IOverrides<T>[];
}

134 135
export interface IOverrides<T> {
	contents: T;
136
	identifiers: string[];
M
Martin Aeschlimann 已提交
137
}
138

139
export class ConfigurationModel<T> implements IConfiguraionModel<T> {
140 141 142 143 144 145 146 147 148 149

	protected _keys: string[] = [];

	constructor(protected _contents: T = <T>{}, protected _overrides: IOverrides<T>[] = []) {
	}

	public get contents(): T {
		return this._contents;
	}

150 151 152 153
	public get overrides(): IOverrides<T>[] {
		return this._overrides;
	}

154 155 156 157 158 159 160 161
	public get keys(): string[] {
		return this._keys;
	}

	public getContentsFor<V>(section: string): V {
		return objects.clone(this.contents[section]);
	}

162 163
	public override<V>(identifier: string): ConfigurationModel<V> {
		const result = new ConfigurationModel<V>();
164 165 166 167 168 169 170 171 172 173 174 175
		const contents = objects.clone<any>(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;
	}

176 177
	public merge(other: ConfigurationModel<T>, overwrite: boolean = true): ConfigurationModel<T> {
		const mergedModel = new ConfigurationModel<T>();
178 179 180 181 182
		this.doMerge(mergedModel, this, overwrite);
		this.doMerge(mergedModel, other, overwrite);
		return mergedModel;
	}

183
	protected doMerge(source: ConfigurationModel<T>, target: ConfigurationModel<T>, overwrite: boolean = true) {
184 185 186 187 188 189 190 191 192 193 194 195
		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;
	}
196 197
}

198 199 200 201 202 203 204
export interface IConfigurationData<T> {
	defaults: IConfiguraionModel<T>;
	user: IConfiguraionModel<T>;
	folders: { [folder: string]: IConfiguraionModel<T> };
	workspaceUri: string;
}

205
export class Configuration<T> {
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243

	private _global: ConfigurationModel<T>;
	private _workspace: ConfigurationModel<T>;
	protected _foldersConsolidated: StrictResourceMap<ConfigurationModel<T>>;

	constructor(protected _defaults: ConfigurationModel<T>, protected _user: ConfigurationModel<T>, protected folders: StrictResourceMap<ConfigurationModel<T>> = new StrictResourceMap<ConfigurationModel<T>>(), protected workspaceUri?: URI) {
		this.merge();
	}

	get defaults(): ConfigurationModel<T> {
		return this._defaults;
	}

	get user(): ConfigurationModel<T> {
		return this._user;
	}

	get workspace(): ConfigurationModel<T> {
		return this._workspace;
	}

	protected merge(): void {
		this._global = this._workspace = new ConfigurationModel<T>().merge(this._defaults).merge(this._user);
		this._foldersConsolidated = new StrictResourceMap<ConfigurationModel<T>>();
		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<T>().merge(this._global).merge(this.folders.get(this.workspaceUri));
			this._foldersConsolidated.set(folder, this._workspace);
		} else {
			this._foldersConsolidated.set(folder, new ConfigurationModel<T>().merge(this._workspace).merge(this.folders.get(folder)));
		}
	}

244
	getValue<C>(section: string = '', options: IConfigurationOverrides = {}): C {
245
		const configModel = this.getConfigurationModel(options);
246
		return section ? configModel.getContentsFor<C>(section) : configModel.contents;
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298
	}

	lookup<C>(key: string, overrideIdentifier?: string): IConfigurationValue<C> {
		// make sure to clone the configuration so that the receiver does not tamper with the values
		return {
			default: objects.clone(getConfigurationValue<C>(overrideIdentifier ? this._defaults.override(overrideIdentifier).contents : this._defaults.contents, key)),
			user: objects.clone(getConfigurationValue<C>(overrideIdentifier ? this._user.override(overrideIdentifier).contents : this._user.contents, key)),
			workspace: objects.clone(this.workspaceUri ? getConfigurationValue<C>(overrideIdentifier ? this.folders.get(this.workspaceUri).override(overrideIdentifier).contents : this.folders.get(this.workspaceUri).contents, key) : void 0),
			value: objects.clone(getConfigurationValue<C>(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<string, IConfigurationValue<T>> {
		const result: Map<string, IConfigurationValue<T>> = new Map<string, IConfigurationValue<T>>();
		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<T>(key));
			}
		}

		return result;
	}

299
	private getConfigurationModel<C>(options: IConfigurationOverrides): ConfigurationModel<any> {
300
		let configurationModel = (options.resource ? this._foldersConsolidated.get(options.resource) : this._workspace) || new ConfigurationModel();
301
		return options.language ? configurationModel.override<T>(options.language) : configurationModel;
302
	}
303

304
	public toData(): IConfigurationData<any> {
305 306 307 308 309 310 311 312 313 314 315 316 317 318
		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({})),
319
			workspaceUri: this.workspaceUri ? this.workspaceUri.toString() : void 0
320 321 322
		};
	}

323 324 325
	public static parse(data: IConfigurationData<any>): Configuration<any> {
		const defaults = Configuration.parseConfigurationModel(data.defaults);
		const user = Configuration.parseConfigurationModel(data.user);
326
		const folders: StrictResourceMap<ConfigurationModel<any>> = Object.keys(data.folders).reduce((result, key) => {
327
			result.set(URI.parse(key), Configuration.parseConfigurationModel(data.folders[key]));
328 329 330
			return result;
		}, new StrictResourceMap<ConfigurationModel<any>>());
		const workspaceUri = data.workspaceUri ? URI.parse(data.workspaceUri) : void 0;
331
		return new Configuration<any>(defaults, user, folders, workspaceUri);
332 333 334 335 336
	}

	private static parseConfigurationModel(model: IConfiguraionModel<any>): ConfigurationModel<any> {
		return new ConfigurationModel(model.contents, model.overrides);
	}
337
}