configuration.ts 26.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
'use strict';

import URI from 'vs/base/common/uri';
import * as paths from 'vs/base/common/paths';
import { TPromise } from 'vs/base/common/winjs.base';
import Event, { Emitter } from 'vs/base/common/event';
11
import { StrictResourceMap } from 'vs/base/common/map';
B
Benjamin Pasero 已提交
12
import { distinct, equals } from 'vs/base/common/arrays';
13 14 15
import * as objects from 'vs/base/common/objects';
import * as errors from 'vs/base/common/errors';
import * as collections from 'vs/base/common/collections';
B
Benjamin Pasero 已提交
16 17
import { Disposable } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
18 19 20
import { RunOnceScheduler } from 'vs/base/common/async';
import { readFile } from 'vs/base/node/pfs';
import * as extfs from 'vs/base/node/extfs';
B
Benjamin Pasero 已提交
21
import { IWorkspaceContextService, IWorkspace, Workspace, ILegacyWorkspace, LegacyWorkspace } from 'vs/platform/workspace/common/workspace';
22
import { FileChangeType, FileChangesEvent } from 'vs/platform/files/common/files';
23
import { isLinux } from 'vs/base/common/platform';
24
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
25 26
import { CustomConfigurationModel } from 'vs/platform/configuration/common/model';
import { ScopedConfigurationModel, FolderConfigurationModel, FolderSettingsModel } from 'vs/workbench/services/configuration/common/configurationModels';
27
import { IConfigurationServiceEvent, ConfigurationSource, IConfigurationKeys, IConfigurationValue, ConfigurationModel, IConfigurationOverrides, Configuration as BaseConfiguration, IConfigurationValues, IConfigurationData } from 'vs/platform/configuration/common/configuration';
28
import { IWorkspaceConfigurationService, WORKSPACE_CONFIG_FOLDER_DEFAULT_NAME, WORKSPACE_STANDALONE_CONFIGURATIONS, WORKSPACE_CONFIG_DEFAULT_PATH } from 'vs/workbench/services/configuration/common/configuration';
29
import { ConfigurationService as GlobalConfigurationService } from 'vs/platform/configuration/node/configurationService';
B
Benjamin Pasero 已提交
30
import { basename } from 'path';
31 32 33
import nls = require('vs/nls');
import { Registry } from 'vs/platform/registry/common/platform';
import { ExtensionsRegistry, ExtensionMessageCollector } from 'vs/platform/extensions/common/extensionsRegistry';
B
Benjamin Pasero 已提交
34
import { IConfigurationNode, IConfigurationRegistry, Extensions, editorConfigurationSchemaId, IDefaultConfigurationExtension, validateProperty, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53

interface IStat {
	resource: URI;
	isDirectory?: boolean;
	children?: { resource: URI; }[];
}

interface IContent {
	resource: URI;
	value: string;
}

interface IWorkspaceConfiguration<T> {
	workspace: T;
	consolidated: any;
}

type IWorkspaceFoldersConfiguration = { [rootFolder: string]: { folders: string[]; } };

54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
const configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);

// BEGIN VSCode extension point `configuration`
const configurationExtPoint = ExtensionsRegistry.registerExtensionPoint<IConfigurationNode>('configuration', [], {
	description: nls.localize('vscode.extension.contributes.configuration', 'Contributes configuration settings.'),
	type: 'object',
	defaultSnippets: [{ body: { title: '', properties: {} } }],
	properties: {
		title: {
			description: nls.localize('vscode.extension.contributes.configuration.title', 'A summary of the settings. This label will be used in the settings file as separating comment.'),
			type: 'string'
		},
		properties: {
			description: nls.localize('vscode.extension.contributes.configuration.properties', 'Description of the configuration properties.'),
			type: 'object',
			additionalProperties: {
				anyOf: [
					{ $ref: 'http://json-schema.org/draft-04/schema#' },
					{
						type: 'object',
						properties: {
							isExecutable: {
								type: 'boolean'
							}
						}
					}
				]
			}
		}
	}
});
configurationExtPoint.setHandler(extensions => {
	const configurations: IConfigurationNode[] = [];


	for (let i = 0; i < extensions.length; i++) {
		const configuration = <IConfigurationNode>objects.clone(extensions[i].value);
		const collector = extensions[i].collector;

		if (configuration.type && configuration.type !== 'object') {
			collector.warn(nls.localize('invalid.type', "if set, 'configuration.type' must be set to 'object"));
		} else {
			configuration.type = 'object';
		}

		if (configuration.title && (typeof configuration.title !== 'string')) {
			collector.error(nls.localize('invalid.title', "'configuration.title' must be a string"));
		}

		validateProperties(configuration, collector);

		configuration.id = extensions[i].description.id;
		configurations.push(configuration);
	}

	configurationRegistry.registerConfigurations(configurations, false);
});
// END VSCode extension point `configuration`

// BEGIN VSCode extension point `configurationDefaults`
const defaultConfigurationExtPoint = ExtensionsRegistry.registerExtensionPoint<IConfigurationNode>('configurationDefaults', [], {
	description: nls.localize('vscode.extension.contributes.defaultConfiguration', 'Contributes default editor configuration settings by language.'),
	type: 'object',
	defaultSnippets: [{ body: {} }],
	patternProperties: {
		'\\[.*\\]$': {
			type: 'object',
			default: {},
			$ref: editorConfigurationSchemaId,
		}
	}
});
defaultConfigurationExtPoint.setHandler(extensions => {
	const defaultConfigurations: IDefaultConfigurationExtension[] = extensions.map(extension => {
		const id = extension.description.id;
		const name = extension.description.name;
		const defaults = objects.clone(extension.value);
		return <IDefaultConfigurationExtension>{
			id, name, defaults
		};
	});
	configurationRegistry.registerDefaultConfigurations(defaultConfigurations);
});
// END VSCode extension point `configurationDefaults`

function validateProperties(configuration: IConfigurationNode, collector: ExtensionMessageCollector): void {
	let properties = configuration.properties;
	if (properties) {
		if (typeof properties !== 'object') {
			collector.error(nls.localize('invalid.properties', "'configuration.properties' must be an object"));
			configuration.properties = {};
		}
		for (let key in properties) {
			const message = validateProperty(key);
			if (message) {
				collector.warn(message);
				delete properties[key];
			}
		}
	}
	let subNodes = configuration.allOf;
	if (subNodes) {
		for (let node of subNodes) {
			validateProperties(node, collector);
		}
	}
}

162
export class WorkspaceConfigurationService extends Disposable implements IWorkspaceContextService, IWorkspaceConfigurationService {
163 164 165

	public _serviceBrand: any;

166 167
	private readonly _onDidChangeWorkspaceRoots: Emitter<void> = this._register(new Emitter<void>());
	public readonly onDidChangeWorkspaceRoots: Event<void> = this._onDidChangeWorkspaceRoots.event;
168 169 170 171 172 173

	private readonly _onDidUpdateConfiguration: Emitter<IConfigurationServiceEvent> = this._register(new Emitter<IConfigurationServiceEvent>());
	public readonly onDidUpdateConfiguration: Event<IConfigurationServiceEvent> = this._onDidUpdateConfiguration.event;

	private baseConfigurationService: GlobalConfigurationService<any>;

174
	private cachedFolderConfigs: StrictResourceMap<FolderConfiguration<any>>;
175

176
	private readonly legacyWorkspace: LegacyWorkspace;
177

178
	private _configuration: Configuration<any>;
179

180
	constructor(private environmentService: IEnvironmentService, private readonly workspace?: Workspace, private workspaceSettingsRootFolder: string = WORKSPACE_CONFIG_FOLDER_DEFAULT_NAME) {
181 182
		super();

183 184
		this.legacyWorkspace = this.workspace && this.workspace.roots.length ? new LegacyWorkspace(this.workspace.roots[0]) : null;

185
		this._register(this.onDidUpdateConfiguration(e => this.resolveAdditionalFolders()));
186 187 188

		this.baseConfigurationService = this._register(new GlobalConfigurationService(environmentService));
		this._register(this.baseConfigurationService.onDidUpdateConfiguration(e => this.onBaseConfigurationChanged(e)));
189 190 191
		this._register(this.onDidChangeWorkspaceRoots(e => this.onRootsChanged()));

		this.initCaches();
192 193
	}

194
	private resolveAdditionalFolders(): void {
B
Benjamin Pasero 已提交
195 196 197
		// TODO@Ben multi root
		if (!this.workspace || this.environmentService.appQuality === 'stable') {
			return; // no additional folders for empty workspaces or in stable
198 199 200
		}

		// Resovled configured folders for workspace
201 202
		let [master] = this.workspace.roots;
		let configuredFolders: URI[] = [master];
203 204
		const config = this.getConfiguration<IWorkspaceFoldersConfiguration>('workspace');
		if (config) {
205
			const workspaceConfig = config[master.toString(true /* skip encoding */)];
206 207 208 209 210 211 212 213 214 215 216 217 218
			if (workspaceConfig) {
				const additionalFolders = workspaceConfig.folders
					.map(f => URI.parse(f))
					.filter(r => r.scheme === Schemas.file); // only support files for now

				configuredFolders.push(...additionalFolders);
			}
		}

		// Remove duplicates
		configuredFolders = distinct(configuredFolders, r => r.toString());

		// Find changes
219
		const changed = !equals(this.workspace.roots, configuredFolders, (r1, r2) => r1.toString() === r2.toString());
220

221
		if (changed) {
222 223 224

			this.workspace.roots = configuredFolders;
			this.workspace.name = configuredFolders.map(root => basename(root.fsPath) || root.fsPath).join(', ');
225

226
			this._onDidChangeWorkspaceRoots.fire();
227 228 229
		}
	}

B
Benjamin Pasero 已提交
230 231
	public getWorkspace(): ILegacyWorkspace {
		return this.legacyWorkspace;
232 233
	}

234
	public getWorkspace2(): IWorkspace {
235 236 237 238 239 240 241
		return this.workspace;
	}

	public hasWorkspace(): boolean {
		return !!this.workspace;
	}

242
	public getRoot(resource: URI): URI {
243
		return this.workspace ? this.workspace.getRoot(resource) : null;
244 245 246 247 248 249
	}

	private get workspaceUri(): URI {
		return this.workspace ? this.workspace.roots[0] : null;
	}

250
	public isInsideWorkspace(resource: URI): boolean {
251
		return !!this.getRoot(resource);
252 253 254
	}

	public toWorkspaceRelativePath(resource: URI, toOSPath?: boolean): string {
B
Benjamin Pasero 已提交
255
		return this.workspace ? this.legacyWorkspace.toWorkspaceRelativePath(resource, toOSPath) : null;
256 257 258
	}

	public toResource(workspaceRelativePath: string): URI {
B
Benjamin Pasero 已提交
259
		return this.workspace ? this.legacyWorkspace.toResource(workspaceRelativePath) : null;
260 261
	}

262 263
	public getConfigurationData<T>(): IConfigurationData<T> {
		return this._configuration.toData();
264 265
	}

266 267
	public get configuration(): BaseConfiguration<any> {
		return this._configuration;
268 269
	}

270 271
	public getConfiguration<C>(section?: string, overrides?: IConfigurationOverrides): C {
		return this._configuration.getValue<C>(section, overrides);
272 273
	}

S
Sandeep Somavarapu 已提交
274 275
	public lookup<C>(key: string, overrides?: IConfigurationOverrides): IConfigurationValue<C> {
		return this._configuration.lookup<C>(key, overrides);
276 277
	}

278 279 280
	public keys(): IConfigurationKeys {
		return this._configuration.keys();
	}
281

282
	public values<V>(): IConfigurationValues {
283
		return this._configuration.values();
284 285
	}

286 287
	public getUnsupportedWorkspaceKeys(): string[] {
		return this.workspace ? this._configuration.getFolderConfigurationModel(this.workspace.roots[0]).workspaceSettingsConfig.unsupportedKeys : [];
288 289
	}

290 291 292 293
	public reloadConfiguration(section?: string): TPromise<any> {
		const current = this._configuration;

		return this.baseConfigurationService.reloadConfiguration()
294
			.then(() => this.initialize(false)) // Reinitialize to ensure we are hitting the disk
295
			.then(() => !this._configuration.equals(current)) // Check if the configuration is changed
296
			.then(changed => changed ? this.trigger(ConfigurationSource.Workspace, ) : void 0) // Trigger event if changed
297
			.then(() => this.getConfiguration(section));
298 299
	}

300 301 302 303 304 305
	public handleWorkspaceFileEvents(event: FileChangesEvent): void {
		if (this.workspace) {
			TPromise.join(this.workspace.roots.map(folder => this.cachedFolderConfigs.get(folder).handleWorkspaceFileEvents(event))) // handle file event for each folder
				.then(folderConfigurations =>
					folderConfigurations.map((configuration, index) => ({ configuration, folder: this.workspace.roots[index] }))
						.filter(folderConfiguration => !!folderConfiguration.configuration) // Filter folders which are not impacted by events
306
						.map(folderConfiguration => this.updateFolderConfiguration(folderConfiguration.folder, folderConfiguration.configuration, true)) // Update the configuration of impacted folders
307
						.reduce((result, value) => result || value, false)) // Check if the effective configuration of folder is changed
308
				.then(changed => changed ? this.trigger(ConfigurationSource.Workspace) : void 0); // Trigger event if changed
309
		}
310
	}
311

312
	public initialize(trigger: boolean = true): TPromise<any> {
313
		this.initCaches();
314
		return this.doInitialize(this.workspace ? this.workspace.roots : [])
315 316 317 318 319
			.then(() => {
				if (trigger) {
					this.trigger(this.workspace ? ConfigurationSource.Workspace : ConfigurationSource.User);
				}
			});
320 321
	}

322 323 324 325
	private onRootsChanged(): void {
		if (!this.workspace) {
			return;
		}
326

327
		let configurationChanged = false;
328

329 330 331 332 333 334
		// Remove the configurations of deleted folders
		for (const key of this.cachedFolderConfigs.keys()) {
			if (!this.workspace.roots.filter(folder => folder.toString() === key.toString())[0]) {
				this.cachedFolderConfigs.delete(key);
				if (this._configuration.deleteFolderConfiguration(key)) {
					configurationChanged = true;
335 336 337 338
				}
			}
		}

339 340 341 342 343 344
		// Initialize the newly added folders
		const toInitialize = this.workspace.roots.filter(folder => !this.cachedFolderConfigs.has(folder));
		if (toInitialize.length) {
			this.initCachesForFolders(toInitialize);
			this.doInitialize(toInitialize)
				.then(changed => configurationChanged || changed)
345
				.then(changed => changed ? this.trigger(ConfigurationSource.Workspace) : void 0);
346 347 348 349 350
		}
	}

	private initCaches(): void {
		this.cachedFolderConfigs = new StrictResourceMap<FolderConfiguration<any>>();
351
		this._configuration = new Configuration(<any>this.baseConfigurationService.configuration(), new ConfigurationModel<any>(), new StrictResourceMap<FolderConfigurationModel<any>>(), this.workspace);
352
		this.initCachesForFolders(this.workspace ? this.workspace.roots : []);
353 354
	}

355 356
	private initCachesForFolders(folders: URI[]): void {
		for (const folder of folders) {
357 358
			this.cachedFolderConfigs.set(folder, this._register(new FolderConfiguration(folder, this.workspaceSettingsRootFolder, this.workspace)));
			this.updateFolderConfiguration(folder, new FolderConfigurationModel<any>(new FolderSettingsModel<any>(null), [], ConfigurationScope.FOLDER), false);
359 360
		}
	}
361

362 363
	private doInitialize(folders: URI[]): TPromise<boolean> {
		return TPromise.join(folders.map(folder => this.cachedFolderConfigs.get(folder).loadConfiguration()
364 365 366
			.then(configuration => this.updateFolderConfiguration(folder, configuration, true))))
			.then(changed => changed.reduce((result, value) => result || value, false))
			.then(changed => this.updateWorkspaceConfiguration(true) || changed);
367
	}
368

369 370 371 372 373 374 375
	private onBaseConfigurationChanged(event: IConfigurationServiceEvent): void {
		if (event.source === ConfigurationSource.Default) {
			if (this.workspace) {
				this.workspace.roots.forEach(folder => this._configuration.getFolderConfigurationModel(folder).update());
			}
		}

S
Sandeep Somavarapu 已提交
376
		if (this._configuration.updateBaseConfiguration(<any>this.baseConfigurationService.configuration())) {
377
			this.trigger(event.source, event.sourceConfig);
378 379 380
		}
	}

381 382 383 384
	private trigger(source: ConfigurationSource, sourceConfig?: any): void {
		if (!sourceConfig) {
			sourceConfig = this.workspace ? this._configuration.getFolderConfigurationModel(this.workspace.roots[0]).contents : this._configuration.user.contents;
		}
385
		this._onDidUpdateConfiguration.fire({ source, sourceConfig });
386
	}
387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406

	private updateFolderConfiguration(folder: URI, folderConfiguration: FolderConfigurationModel<any>, compare: boolean): boolean {
		let configurationChanged = false;
		if (this.workspace) {
			configurationChanged = this._configuration.updateFolderConfiguration(folder, folderConfiguration, compare);
			if (this.workspace.roots[0].fsPath === folder.fsPath) {
				// Workspace configuration changed
				configurationChanged = this.updateWorkspaceConfiguration(compare) || configurationChanged;
			}
		}
		return configurationChanged;
	}

	private updateWorkspaceConfiguration(compare: boolean): boolean {
		if (this.workspace) {
			const firstFolderConfigurationModel = this._configuration.getFolderConfigurationModel(this.workspace.roots[0]);
			return this._configuration.updateWorkspaceConfiguration(this.workspace.roots.length === 1 ? firstFolderConfigurationModel : firstFolderConfigurationModel.workspaceSettingsConfig.createWorkspaceConfigurationModel(), compare);
		}
		return false;
	}
407
}
408

409
class FolderConfiguration<T> extends Disposable {
410

411
	private static RELOAD_CONFIGURATION_DELAY = 50;
412

413 414 415 416 417 418 419 420
	private bulkFetchFromWorkspacePromise: TPromise<any>;
	private workspaceFilePathToConfiguration: { [relativeWorkspacePath: string]: TPromise<ConfigurationModel<any>> };

	private reloadConfigurationScheduler: RunOnceScheduler;
	private reloadConfigurationEventEmitter: Emitter<FolderConfigurationModel<T>> = new Emitter<FolderConfigurationModel<T>>();

	constructor(private folder: URI, private configFolderRelativePath: string, private workspace: Workspace) {
		super();
421

422 423 424
		this.workspaceFilePathToConfiguration = Object.create(null);
		this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.loadConfiguration().then(configuration => this.reloadConfigurationEventEmitter.fire(configuration), errors.onUnexpectedError), FolderConfiguration.RELOAD_CONFIGURATION_DELAY));
	}
425

426
	loadConfiguration(): TPromise<FolderConfigurationModel<T>> {
427
		if (!this.workspace) {
428
			return TPromise.wrap(new FolderConfigurationModel<T>(new FolderSettingsModel<T>(null), [], ConfigurationScope.FOLDER));
429 430
		}

431 432 433 434 435
		// Load workspace locals
		return this.loadWorkspaceConfigFiles().then(workspaceConfigFiles => {
			// Consolidate (support *.json files in the workspace settings folder)
			const workspaceSettingsConfig = <FolderSettingsModel<T>>workspaceConfigFiles[WORKSPACE_CONFIG_DEFAULT_PATH] || new FolderSettingsModel<T>(null);
			const otherConfigModels = Object.keys(workspaceConfigFiles).filter(key => key !== WORKSPACE_CONFIG_DEFAULT_PATH).map(key => <ScopedConfigurationModel<T>>workspaceConfigFiles[key]);
436
			return new FolderConfigurationModel<T>(workspaceSettingsConfig, otherConfigModels, this.workspace.roots.length === 1 ? ConfigurationScope.WORKSPACE : ConfigurationScope.FOLDER);
437 438 439 440
		});
	}

	private loadWorkspaceConfigFiles<T>(): TPromise<{ [relativeWorkspacePath: string]: ConfigurationModel<T> }> {
441 442
		// once: when invoked for the first time we fetch json files that contribute settings
		if (!this.bulkFetchFromWorkspacePromise) {
443
			this.bulkFetchFromWorkspacePromise = resolveStat(this.toResource(this.configFolderRelativePath)).then(stat => {
444 445 446 447 448 449 450 451 452 453
				if (!stat.isDirectory) {
					return TPromise.as([]);
				}

				return resolveContents(stat.children.filter(stat => {
					const isJson = paths.extname(stat.resource.fsPath) === '.json';
					if (!isJson) {
						return false; // only JSON files
					}

B
Benjamin Pasero 已提交
454
					return this.isWorkspaceConfigurationFile(this.toFolderRelativePath(stat.resource)); // only workspace config files
455 456 457
				}).map(stat => stat.resource));
			}, err => [] /* never fail this call */)
				.then((contents: IContent[]) => {
B
Benjamin Pasero 已提交
458
					contents.forEach(content => this.workspaceFilePathToConfiguration[this.toFolderRelativePath(content.resource)] = TPromise.as(this.createConfigModel(content)));
459 460 461 462 463 464 465 466
				}, errors.onUnexpectedError);
		}

		// on change: join on *all* configuration file promises so that we can merge them into a single configuration object. this
		// happens whenever a config file changes, is deleted, or added
		return this.bulkFetchFromWorkspacePromise.then(() => TPromise.join(this.workspaceFilePathToConfiguration));
	}

467
	public handleWorkspaceFileEvents(event: FileChangesEvent): TPromise<FolderConfigurationModel<T>> {
468
		if (!this.workspace) {
469
			return TPromise.wrap(null);
470 471 472 473 474 475 476 477 478
		}

		const events = event.changes;
		let affectedByChanges = false;

		// Find changes that affect workspace configuration files
		for (let i = 0, len = events.length; i < len; i++) {
			const resource = events[i].resource;
			const isJson = paths.extname(resource.fsPath) === '.json';
479
			const isDeletedSettingsFolder = (events[i].type === FileChangeType.DELETED && paths.isEqual(paths.basename(resource.fsPath), this.configFolderRelativePath));
480 481 482 483
			if (!isJson && !isDeletedSettingsFolder) {
				continue; // only JSON files or the actual settings folder
			}

B
Benjamin Pasero 已提交
484
			const workspacePath = this.toFolderRelativePath(resource);
485 486 487 488 489
			if (!workspacePath) {
				continue; // event is not inside workspace
			}

			// Handle case where ".vscode" got deleted
490
			if (workspacePath === this.configFolderRelativePath && events[i].type === FileChangeType.DELETED) {
491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512
				this.workspaceFilePathToConfiguration = Object.create(null);
				affectedByChanges = true;
			}

			// only valid workspace config files
			if (!this.isWorkspaceConfigurationFile(workspacePath)) {
				continue;
			}

			// insert 'fetch-promises' for add and update events and
			// remove promises for delete events
			switch (events[i].type) {
				case FileChangeType.DELETED:
					affectedByChanges = collections.remove(this.workspaceFilePathToConfiguration, workspacePath);
					break;
				case FileChangeType.UPDATED:
				case FileChangeType.ADDED:
					this.workspaceFilePathToConfiguration[workspacePath] = resolveContent(resource).then(content => this.createConfigModel(content), errors.onUnexpectedError);
					affectedByChanges = true;
			}
		}

513 514
		if (!affectedByChanges) {
			return TPromise.as(null);
515
		}
516 517 518 519 520 521 522 523 524 525 526

		return new TPromise((c, e) => {
			let disposable = this.reloadConfigurationEventEmitter.event(configuration => {
				disposable.dispose();
				c(configuration);
			});
			// trigger reload of the configuration if we are affected by changes
			if (!this.reloadConfigurationScheduler.isScheduled()) {
				this.reloadConfigurationScheduler.schedule();
			}
		});
527 528
	}

529
	private createConfigModel<T>(content: IContent): ConfigurationModel<T> {
B
Benjamin Pasero 已提交
530
		const path = this.toFolderRelativePath(content.resource);
531
		if (path === WORKSPACE_CONFIG_DEFAULT_PATH) {
532
			return new FolderSettingsModel<T>(content.value, content.resource.toString());
533 534 535
		} else {
			const matches = /\/([^\.]*)*\.json/.exec(path);
			if (matches && matches[1]) {
536
				return new ScopedConfigurationModel<T>(content.value, content.resource.toString(), matches[1]);
537 538 539
			}
		}

540
		return new CustomConfigurationModel<T>(null);
541 542
	}

B
Benjamin Pasero 已提交
543 544
	private isWorkspaceConfigurationFile(folderRelativePath: string): boolean {
		return [WORKSPACE_CONFIG_DEFAULT_PATH, WORKSPACE_STANDALONE_CONFIGURATIONS.launch, WORKSPACE_STANDALONE_CONFIGURATIONS.tasks].some(p => p === folderRelativePath);
545 546
	}

B
Benjamin Pasero 已提交
547 548 549
	private toResource(folderRelativePath: string): URI {
		if (typeof folderRelativePath === 'string') {
			return URI.file(paths.join(this.folder.fsPath, folderRelativePath));
550 551 552
		}

		return null;
553 554
	}

B
Benjamin Pasero 已提交
555
	private toFolderRelativePath(resource: URI, toOSPath?: boolean): string {
556 557
		if (this.contains(resource)) {
			return paths.normalize(paths.relative(this.folder.fsPath, resource.fsPath), toOSPath);
558 559
		}

560
		return null;
561 562
	}

563 564
	private contains(resource: URI): boolean {
		if (resource) {
565
			return paths.isEqualOrParent(resource.fsPath, this.folder.fsPath, !isLinux /* ignorecase */);
566 567
		}

568
		return false;
569
	}
570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605
}

// node.hs helper functions

function resolveContents(resources: URI[]): TPromise<IContent[]> {
	const contents: IContent[] = [];

	return TPromise.join(resources.map(resource => {
		return resolveContent(resource).then(content => {
			contents.push(content);
		});
	})).then(() => contents);
}

function resolveContent(resource: URI): TPromise<IContent> {
	return readFile(resource.fsPath).then(contents => ({ resource, value: contents.toString() }));
}

function resolveStat(resource: URI): TPromise<IStat> {
	return new TPromise<IStat>((c, e) => {
		extfs.readdir(resource.fsPath, (error, children) => {
			if (error) {
				if ((<any>error).code === 'ENOTDIR') {
					c({ resource });
				} else {
					e(error);
				}
			} else {
				c({
					resource,
					isDirectory: true,
					children: children.map(child => { return { resource: URI.file(paths.join(resource.fsPath, child)) }; })
				});
			}
		});
	});
I
isidor 已提交
606
}
607 608 609

class Configuration<T> extends BaseConfiguration<T> {

610 611
	constructor(private _baseConfiguration: Configuration<T>, workspaceConfiguration: ConfigurationModel<T>, protected folders: StrictResourceMap<FolderConfigurationModel<T>>, workspace: Workspace) {
		super(_baseConfiguration.defaults, _baseConfiguration.user, workspaceConfiguration, folders, workspace);
612 613
	}

614
	updateBaseConfiguration(baseConfiguration: Configuration<T>): boolean {
615
		const current = new Configuration(this._baseConfiguration, this._workspaceConfiguration, this.folders, this._workspace);
616 617 618 619 620 621 622 623

		this._defaults = baseConfiguration.defaults;
		this._user = baseConfiguration.user;
		this.merge();

		return !this.equals(current);
	}

624 625 626 627 628 629 630 631 632 633
	updateWorkspaceConfiguration(workspaceConfiguration: ConfigurationModel<T>, compare: boolean = true): boolean {
		const current = new Configuration(this._baseConfiguration, this._workspaceConfiguration, this.folders, this._workspace);

		this._workspaceConfiguration = workspaceConfiguration;
		this.merge();

		return compare && !this.equals(current);
	}

	updateFolderConfiguration(resource: URI, configuration: FolderConfigurationModel<T>, compare: boolean): boolean {
634
		const current = this.getValue(null, { resource });
635 636

		this.folders.set(resource, configuration);
637
		this.mergeFolder(resource);
638

639
		return compare && !objects.equals(current, this.getValue(null, { resource }));
640 641 642
	}

	deleteFolderConfiguration(folder: URI): boolean {
643
		if (this._workspace && this._workspace.roots[0].fsPath === folder.fsPath) {
644 645 646 647 648
			// Do not remove workspace configuration
			return false;
		}

		this.folders.delete(folder);
649
		return this._foldersConsolidatedConfigurations.delete(folder);
650 651 652 653 654 655 656 657 658 659 660 661 662 663 664
	}

	getFolderConfigurationModel(folder: URI): FolderConfigurationModel<T> {
		return <FolderConfigurationModel<T>>this.folders.get(folder);
	}

	equals(other: any): boolean {
		if (!other || !(other instanceof Configuration)) {
			return false;
		}

		if (!objects.equals(this.getValue(), other.getValue())) {
			return false;
		}

665
		if (this._foldersConsolidatedConfigurations.size !== other._foldersConsolidatedConfigurations.size) {
666 667 668
			return false;
		}

669
		for (const resource of this._foldersConsolidatedConfigurations.keys()) {
670
			if (!objects.equals(this.getValue(null, { resource }), other.getValue(null, { resource }))) {
671 672 673 674 675 676
				return false;
			}
		}

		return true;
	}
I
isidor 已提交
677
}