configuration.ts 24.5 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';
12 13 14 15 16 17 18 19 20
import { distinct, equals } from "vs/base/common/arrays";
import * as objects from 'vs/base/common/objects';
import * as errors from 'vs/base/common/errors';
import * as collections from 'vs/base/common/collections';
import { Disposable } from "vs/base/common/lifecycle";
import { Schemas } from "vs/base/common/network";
import { RunOnceScheduler } from 'vs/base/common/async';
import { readFile } from 'vs/base/node/pfs';
import * as extfs from 'vs/base/node/extfs';
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';
I
isidor 已提交
30
import { basename } from "path";
31 32 33 34
import nls = require('vs/nls');
import { Registry } from 'vs/platform/registry/common/platform';
import { ExtensionsRegistry, ExtensionMessageCollector } from 'vs/platform/extensions/common/extensionsRegistry';
import { IConfigurationNode, IConfigurationRegistry, Extensions, editorConfigurationSchemaId, IDefaultConfigurationExtension, validateProperty } 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()];
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 306 307
	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
						.map(folderConfiguration => this._configuration.updateFolderConfiguration(folderConfiguration.folder, folderConfiguration.configuration)) // Update the configuration of impacted folders
						.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 StrictResourceMap<FolderConfigurationModel<any>>(), this.workspace);
352
		this.initCachesForFolders(this.workspace ? this.workspace.roots : []);
353 354
	}

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

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

368 369 370 371 372 373 374
	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 已提交
375
		if (this._configuration.updateBaseConfiguration(<any>this.baseConfigurationService.configuration())) {
376
			this.trigger(event.source, event.sourceConfig);
377 378 379
		}
	}

380 381 382 383
	private trigger(source: ConfigurationSource, sourceConfig?: any): void {
		if (!sourceConfig) {
			sourceConfig = this.workspace ? this._configuration.getFolderConfigurationModel(this.workspace.roots[0]).contents : this._configuration.user.contents;
		}
384
		this._onDidUpdateConfiguration.fire({ source, sourceConfig });
385
	}
386
}
387

388
class FolderConfiguration<T> extends Disposable {
389

390
	private static RELOAD_CONFIGURATION_DELAY = 50;
391

392 393 394 395 396 397 398 399
	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();
400

401 402 403
		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));
	}
404

405
	loadConfiguration(): TPromise<FolderConfigurationModel<T>> {
406
		if (!this.workspace) {
407
			return TPromise.wrap(new FolderConfigurationModel<T>(new FolderSettingsModel<T>(null), []));
408 409
		}

410 411 412 413 414 415 416 417 418 419
		// 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]);
			return new FolderConfigurationModel<T>(workspaceSettingsConfig, otherConfigModels);
		});
	}

	private loadWorkspaceConfigFiles<T>(): TPromise<{ [relativeWorkspacePath: string]: ConfigurationModel<T> }> {
420 421
		// once: when invoked for the first time we fetch json files that contribute settings
		if (!this.bulkFetchFromWorkspacePromise) {
422
			this.bulkFetchFromWorkspacePromise = resolveStat(this.toResource(this.configFolderRelativePath)).then(stat => {
423 424 425 426 427 428 429 430 431 432
				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 已提交
433
					return this.isWorkspaceConfigurationFile(this.toFolderRelativePath(stat.resource)); // only workspace config files
434 435 436
				}).map(stat => stat.resource));
			}, err => [] /* never fail this call */)
				.then((contents: IContent[]) => {
B
Benjamin Pasero 已提交
437
					contents.forEach(content => this.workspaceFilePathToConfiguration[this.toFolderRelativePath(content.resource)] = TPromise.as(this.createConfigModel(content)));
438 439 440 441 442 443 444 445
				}, 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));
	}

446
	public handleWorkspaceFileEvents(event: FileChangesEvent): TPromise<FolderConfigurationModel<T>> {
447
		if (!this.workspace) {
448
			return TPromise.wrap(null);
449 450 451 452 453 454 455 456 457
		}

		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';
458
			const isDeletedSettingsFolder = (events[i].type === FileChangeType.DELETED && paths.isEqual(paths.basename(resource.fsPath), this.configFolderRelativePath));
459 460 461 462
			if (!isJson && !isDeletedSettingsFolder) {
				continue; // only JSON files or the actual settings folder
			}

B
Benjamin Pasero 已提交
463
			const workspacePath = this.toFolderRelativePath(resource);
464 465 466 467 468
			if (!workspacePath) {
				continue; // event is not inside workspace
			}

			// Handle case where ".vscode" got deleted
469
			if (workspacePath === this.configFolderRelativePath && events[i].type === FileChangeType.DELETED) {
470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491
				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;
			}
		}

492 493
		if (!affectedByChanges) {
			return TPromise.as(null);
494
		}
495 496 497 498 499 500 501 502 503 504 505

		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();
			}
		});
506 507
	}

508
	private createConfigModel<T>(content: IContent): ConfigurationModel<T> {
B
Benjamin Pasero 已提交
509
		const path = this.toFolderRelativePath(content.resource);
510
		if (path === WORKSPACE_CONFIG_DEFAULT_PATH) {
511
			return new FolderSettingsModel<T>(content.value, content.resource.toString());
512 513 514
		} else {
			const matches = /\/([^\.]*)*\.json/.exec(path);
			if (matches && matches[1]) {
515
				return new ScopedConfigurationModel<T>(content.value, content.resource.toString(), matches[1]);
516 517 518
			}
		}

519
		return new CustomConfigurationModel<T>(null);
520 521
	}

B
Benjamin Pasero 已提交
522 523
	private isWorkspaceConfigurationFile(folderRelativePath: string): boolean {
		return [WORKSPACE_CONFIG_DEFAULT_PATH, WORKSPACE_STANDALONE_CONFIGURATIONS.launch, WORKSPACE_STANDALONE_CONFIGURATIONS.tasks].some(p => p === folderRelativePath);
524 525
	}

B
Benjamin Pasero 已提交
526 527 528
	private toResource(folderRelativePath: string): URI {
		if (typeof folderRelativePath === 'string') {
			return URI.file(paths.join(this.folder.fsPath, folderRelativePath));
529 530 531
		}

		return null;
532 533
	}

B
Benjamin Pasero 已提交
534
	private toFolderRelativePath(resource: URI, toOSPath?: boolean): string {
535 536
		if (this.contains(resource)) {
			return paths.normalize(paths.relative(this.folder.fsPath, resource.fsPath), toOSPath);
537 538
		}

539
		return null;
540 541
	}

542 543
	private contains(resource: URI): boolean {
		if (resource) {
544
			return paths.isEqualOrParent(resource.fsPath, this.folder.fsPath, !isLinux /* ignorecase */);
545 546
		}

547
		return false;
548
	}
549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584
}

// 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 已提交
585
}
586 587 588

class Configuration<T> extends BaseConfiguration<T> {

589 590
	constructor(private _baseConfiguration: Configuration<T>, protected folders: StrictResourceMap<FolderConfigurationModel<T>>, workspace: Workspace) {
		super(_baseConfiguration.defaults, _baseConfiguration.user, folders, workspace);
591 592
	}

593
	updateBaseConfiguration(baseConfiguration: Configuration<T>): boolean {
594
		const current = new Configuration(this._baseConfiguration, this.folders, this._workspace);
595 596 597 598 599 600 601 602

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

		return !this.equals(current);
	}

603
	updateFolderConfiguration(resource: URI, configuration: FolderConfigurationModel<T>, compare: boolean = true): boolean {
604
		this.folders.set(resource, configuration);
605
		const current = this.getValue(null, { resource });
606
		this.mergeFolder(resource);
607
		return compare && !objects.equals(current, this.getValue(null, { resource }));
608 609 610
	}

	deleteFolderConfiguration(folder: URI): boolean {
S
Sandeep Somavarapu 已提交
611
		if (this.workspaceUri && this.workspaceUri.fsPath === folder.fsPath) {
612 613 614 615 616
			// Do not remove workspace configuration
			return false;
		}

		this.folders.delete(folder);
617
		return this._foldersConsolidatedConfigurations.delete(folder);
618 619 620 621 622 623 624 625 626 627 628 629 630 631 632
	}

	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;
		}

633
		if (this._foldersConsolidatedConfigurations.size !== other._foldersConsolidatedConfigurations.size) {
634 635 636
			return false;
		}

637
		for (const resource of this._foldersConsolidatedConfigurations.keys()) {
638
			if (!objects.equals(this.getValue(null, { resource }), other.getValue(null, { resource }))) {
639 640 641 642 643 644
				return false;
			}
		}

		return true;
	}
I
isidor 已提交
645
}