configuration.ts 24.4 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';
B
Benjamin Pasero 已提交
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 {
195 196 197 198 199
		if (!this.workspace) {
			return; // no additional folders for empty workspaces
		}

		// Resovled configured folders for workspace
200 201
		let [master] = this.workspace.roots;
		let configuredFolders: URI[] = [master];
202 203
		const config = this.getConfiguration<IWorkspaceFoldersConfiguration>('workspace');
		if (config) {
204
			const workspaceConfig = config[master.toString()];
205 206 207 208 209 210 211 212 213 214 215 216 217
			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
218
		const changed = !equals(this.workspace.roots, configuredFolders, (r1, r2) => r1.toString() === r2.toString());
219

220
		if (changed) {
221 222 223 224

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

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

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

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

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

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

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

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

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

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

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

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

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

273 274
	public lookup<C>(key: string, overrideIdentifier?: string): IConfigurationValue<C> {
		return this._configuration.lookup<C>(key, overrideIdentifier);
275 276
	}

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

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

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

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

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

	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
307
				.then(changed => changed ? this.trigger(ConfigurationSource.Workspace) : void 0); // Trigger event if changed
308
		}
309
	}
310

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

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

326
		let configurationChanged = false;
327

328 329 330 331 332 333
		// 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;
334
				}
335 336 337 338 339 340 341 342 343
			}
		}

		// 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)
344
				.then(changed => changed ? this.trigger(ConfigurationSource.Workspace) : void 0);
345 346 347 348 349
		}
	}

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

	private initCachesForFolders(folders: URI[]): void {
		for (const folder of folders) {
			this.cachedFolderConfigs.set(folder, new FolderConfiguration(folder, this.workspaceSettingsRootFolder, this.workspace));
357
			this._configuration.updateFolderConfiguration(folder, new FolderConfigurationModel<any>(new FolderSettingsModel<any>(null), []), false);
358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373
		}
	}

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

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

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

387
class FolderConfiguration<T> extends Disposable {
388

389
	private static RELOAD_CONFIGURATION_DELAY = 50;
390

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

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

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

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

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

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

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

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

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

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

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

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

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

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

		return null;
	}

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

538
		return null;
539 540
	}

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

546
		return false;
547
	}
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
}

// 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)) }; })
				});
			}
		});
	});
584
}
585

586
class Configuration<T> extends BaseConfiguration<T> {
587

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

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

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

		return !this.equals(current);
	}

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

	deleteFolderConfiguration(folder: URI): boolean {
610
		if (this.workspace && this.workspaceUri.fsPath === folder.fsPath) {
611 612 613 614 615
			// Do not remove workspace configuration
			return false;
		}

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

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

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

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

		return true;
	}
}