configurationService.ts 32.1 KB
Newer Older
1 2 3 4 5 6 7 8 9
/*---------------------------------------------------------------------------------------------
 *  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';
S
Sandeep Somavarapu 已提交
10
import { dirname } from 'path';
11
import * as assert from 'vs/base/common/assert';
12
import Event, { Emitter } from 'vs/base/common/event';
13
import { StrictResourceMap } from 'vs/base/common/map';
14
import { equals } from 'vs/base/common/objects';
S
Sandeep Somavarapu 已提交
15 16 17
import { Disposable } from 'vs/base/common/lifecycle';
import { Queue } from 'vs/base/common/async';
import { stat, writeFile } from 'vs/base/node/pfs';
18
import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
19
import { IWorkspaceContextService, Workspace, WorkbenchState, IWorkspaceFolder, toWorkspaceFolders, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace';
S
Sandeep Somavarapu 已提交
20
import { FileChangesEvent } from 'vs/platform/files/common/files';
21
import { isLinux } from 'vs/base/common/platform';
22
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
S
Sandeep Somavarapu 已提交
23
import { ConfigurationChangeEvent, ConfigurationModel, DefaultConfigurationModel } from 'vs/platform/configuration/common/configurationModels';
24
import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, keyFromOverrideIdentifier, isConfigurationOverrides, IConfigurationData } from 'vs/platform/configuration/common/configuration';
S
Sandeep Somavarapu 已提交
25 26
import { Configuration, WorkspaceConfigurationChangeEvent, AllKeysConfigurationChangeEvent } from 'vs/workbench/services/configuration/common/configurationModels';
import { IWorkspaceConfigurationService, FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId } from 'vs/workbench/services/configuration/common/configuration';
27
import { Registry } from 'vs/platform/registry/common/platform';
S
Sandeep Somavarapu 已提交
28
import { IConfigurationNode, IConfigurationRegistry, Extensions, settingsSchema, resourceSettingsSchema } from 'vs/platform/configuration/common/configurationRegistry';
29
import { createHash } from 'crypto';
S
Sandeep Somavarapu 已提交
30
import { getWorkspaceLabel, IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces';
31
import { IWindowConfiguration } from 'vs/platform/windows/common/windows';
32 33 34
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
import { ICommandService } from 'vs/platform/commands/common/commands';
import product from 'vs/platform/node/product';
35 36
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ConfigurationEditingService } from 'vs/workbench/services/configuration/node/configurationEditingService';
S
Sandeep Somavarapu 已提交
37 38 39 40
import { WorkspaceConfiguration, FolderConfiguration } from 'vs/workbench/services/configuration/node/configuration';
import { JSONEditingService } from 'vs/workbench/services/configuration/node/jsonEditingService';
import { Schemas } from 'vs/base/common/network';
import { massageFolderPathForWorkspace } from 'vs/platform/workspaces/node/workspaces';
S
Sandeep Somavarapu 已提交
41
import { distinct } from 'vs/base/common/arrays';
S
Sandeep Somavarapu 已提交
42
import { UserConfiguration } from 'vs/platform/configuration/node/configuration';
43

S
Sandeep Somavarapu 已提交
44
export class WorkspaceService extends Disposable implements IWorkspaceConfigurationService, IWorkspaceContextService {
45 46 47

	public _serviceBrand: any;

48
	private workspace: Workspace;
49
	private _configuration: Configuration;
S
Sandeep Somavarapu 已提交
50 51
	private defaultConfiguration: DefaultConfigurationModel;
	private userConfiguration: UserConfiguration;
52
	private workspaceConfiguration: WorkspaceConfiguration;
53
	private cachedFolderConfigs: StrictResourceMap<FolderConfiguration>;
54

S
Sandeep Somavarapu 已提交
55 56
	private workspaceEditingQueue: Queue<void>;

57 58
	protected readonly _onDidChangeConfiguration: Emitter<IConfigurationChangeEvent> = this._register(new Emitter<IConfigurationChangeEvent>());
	public readonly onDidChangeConfiguration: Event<IConfigurationChangeEvent> = this._onDidChangeConfiguration.event;
59

60 61
	protected readonly _onDidChangeWorkspaceFolders: Emitter<IWorkspaceFoldersChangeEvent> = this._register(new Emitter<IWorkspaceFoldersChangeEvent>());
	public readonly onDidChangeWorkspaceFolders: Event<IWorkspaceFoldersChangeEvent> = this._onDidChangeWorkspaceFolders.event;
62

S
Sandeep Somavarapu 已提交
63 64 65
	protected readonly _onDidChangeWorkspaceName: Emitter<void> = this._register(new Emitter<void>());
	public readonly onDidChangeWorkspaceName: Event<void> = this._onDidChangeWorkspaceName.event;

66 67 68
	protected readonly _onDidChangeWorkbenchState: Emitter<WorkbenchState> = this._register(new Emitter<WorkbenchState>());
	public readonly onDidChangeWorkbenchState: Event<WorkbenchState> = this._onDidChangeWorkbenchState.event;

S
Sandeep Somavarapu 已提交
69
	private configurationEditingService: ConfigurationEditingService;
S
Sandeep Somavarapu 已提交
70
	private jsonEditingService: JSONEditingService;
71

S
Sandeep Somavarapu 已提交
72
	constructor(private environmentService: IEnvironmentService, private workspaceSettingsRootFolder: string = FOLDER_CONFIG_FOLDER_NAME) {
73
		super();
74

S
Sandeep Somavarapu 已提交
75 76
		this.defaultConfiguration = new DefaultConfigurationModel();
		this.userConfiguration = this._register(new UserConfiguration(environmentService.appSettingsPath));
77
		this.workspaceConfiguration = this._register(new WorkspaceConfiguration());
S
Sandeep Somavarapu 已提交
78
		this._register(this.userConfiguration.onDidChangeConfiguration(() => this.onUserConfigurationChanged()));
79 80
		this._register(this.workspaceConfiguration.onDidUpdateConfiguration(() => this.onWorkspaceConfigurationChanged()));

81
		this._register(Registry.as<IConfigurationRegistry>(Extensions.Configuration).onDidRegisterConfiguration(e => this.registerConfigurationSchemas()));
S
Sandeep Somavarapu 已提交
82
		this._register(Registry.as<IConfigurationRegistry>(Extensions.Configuration).onDidRegisterConfiguration(configurationProperties => this.onDefaultConfigurationChanged(configurationProperties)));
S
Sandeep Somavarapu 已提交
83 84

		this.workspaceEditingQueue = new Queue<void>();
85 86
	}

87 88
	// Workspace Context Service Impl

89
	public getWorkspace(): Workspace {
90 91 92
		return this.workspace;
	}

93
	public getWorkbenchState(): WorkbenchState {
94 95 96 97 98 99
		// Workspace has configuration file
		if (this.workspace.configuration) {
			return WorkbenchState.WORKSPACE;
		}

		// Folder has single root
S
Sandeep Somavarapu 已提交
100
		if (this.workspace.folders.length === 1) {
101
			return WorkbenchState.FOLDER;
102
		}
103 104

		// Empty
105
		return WorkbenchState.EMPTY;
106 107
	}

S
Sandeep Somavarapu 已提交
108
	public getWorkspaceFolder(resource: URI): IWorkspaceFolder {
S
Sandeep Somavarapu 已提交
109
		return this.workspace.getFolder(resource);
110 111
	}

112
	public addFolders(foldersToAdd: IWorkspaceFolderCreationData[]): TPromise<void> {
S
Sandeep Somavarapu 已提交
113
		assert.ok(this.jsonEditingService, 'Workbench is not initialized yet');
114
		return this.workspaceEditingQueue.queue(() => this.doAddFolders(foldersToAdd));
S
Sandeep Somavarapu 已提交
115 116 117 118 119 120 121
	}

	public removeFolders(foldersToRemove: URI[]): TPromise<void> {
		assert.ok(this.jsonEditingService, 'Workbench is not initialized yet');
		return this.workspaceEditingQueue.queue(() => this.doRemoveFolders(foldersToRemove));
	}

122
	public isInsideWorkspace(resource: URI): boolean {
S
Sandeep Somavarapu 已提交
123
		return !!this.getWorkspaceFolder(resource);
124 125
	}

126
	public isCurrentWorkspace(workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): boolean {
127 128
		switch (this.getWorkbenchState()) {
			case WorkbenchState.FOLDER:
129
				return isSingleFolderWorkspaceIdentifier(workspaceIdentifier) && this.pathEquals(this.workspace.folders[0].uri.fsPath, workspaceIdentifier);
130 131
			case WorkbenchState.WORKSPACE:
				return isWorkspaceIdentifier(workspaceIdentifier) && this.workspace.id === workspaceIdentifier.id;
132
		}
133
		return false;
134 135
	}

136
	private doAddFolders(foldersToAdd: IWorkspaceFolderCreationData[]): TPromise<void> {
S
Sandeep Somavarapu 已提交
137 138 139 140 141 142 143 144 145 146 147 148 149
		if (this.getWorkbenchState() !== WorkbenchState.WORKSPACE) {
			return TPromise.as(void 0); // we need a workspace to begin with
		}

		const currentWorkspaceFolders = this.getWorkspace().folders;
		const currentWorkspaceFolderUris = currentWorkspaceFolders.map(folder => folder.uri);
		const currentStoredFolders = currentWorkspaceFolders.map(folder => folder.raw);

		const storedFoldersToAdd: IStoredWorkspaceFolder[] = [];

		const workspaceConfigFolder = dirname(this.getWorkspace().configuration.fsPath);

		foldersToAdd.forEach(folderToAdd => {
150
			if (this.contains(currentWorkspaceFolderUris, folderToAdd.uri)) {
S
Sandeep Somavarapu 已提交
151 152 153
				return; // already existing
			}

154 155
			let storedFolder: IStoredWorkspaceFolder;

S
Sandeep Somavarapu 已提交
156
			// File resource: use "path" property
157
			if (folderToAdd.uri.scheme === Schemas.file) {
158
				storedFolder = {
159
					path: massageFolderPathForWorkspace(folderToAdd.uri.fsPath, workspaceConfigFolder, currentStoredFolders)
160
				};
S
Sandeep Somavarapu 已提交
161 162 163 164
			}

			// Any other resource: use "uri" property
			else {
165
				storedFolder = {
166
					uri: folderToAdd.uri.toString(true)
167
				};
S
Sandeep Somavarapu 已提交
168
			}
169

170 171
			if (folderToAdd.name) {
				storedFolder.name = folderToAdd.name;
172 173 174
			}

			storedFoldersToAdd.push(storedFolder);
S
Sandeep Somavarapu 已提交
175 176 177
		});

		if (storedFoldersToAdd.length > 0) {
S
Sandeep Somavarapu 已提交
178
			return this.setFolders([...currentStoredFolders, ...storedFoldersToAdd]);
S
Sandeep Somavarapu 已提交
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
		}

		return TPromise.as(void 0);
	}

	private doRemoveFolders(foldersToRemove: URI[]): TPromise<void> {
		if (this.getWorkbenchState() !== WorkbenchState.WORKSPACE) {
			return TPromise.as(void 0); // we need a workspace to begin with
		}

		const currentWorkspaceFolders = this.getWorkspace().folders;
		const currentStoredFolders = currentWorkspaceFolders.map(folder => folder.raw);

		const newStoredFolders: IStoredWorkspaceFolder[] = currentStoredFolders.filter((folder, index) => {
			if (!isStoredWorkspaceFolder(folder)) {
				return true; // keep entries which are unrelated
			}

			return !this.contains(foldersToRemove, currentWorkspaceFolders[index].uri); // keep entries which are unrelated
		});

		if (newStoredFolders.length !== currentStoredFolders.length) {
S
Sandeep Somavarapu 已提交
201
			return this.setFolders(newStoredFolders);
S
Sandeep Somavarapu 已提交
202 203 204 205 206
		}

		return TPromise.as(void 0);
	}

S
Sandeep Somavarapu 已提交
207 208 209 210 211
	private setFolders(folders: IStoredWorkspaceFolder[]): TPromise<void> {
		return this.workspaceConfiguration.setFolders(folders, this.jsonEditingService)
			.then(() => this.onWorkspaceConfigurationChanged());
	}

S
Sandeep Somavarapu 已提交
212 213 214 215 216 217 218 219 220 221
	private contains(resources: URI[], toCheck: URI): boolean {
		return resources.some(resource => {
			if (isLinux) {
				return resource.toString() === toCheck.toString();
			}

			return resource.toString().toLowerCase() === toCheck.toString().toLowerCase();
		});
	}

222 223
	// Workspace Configuration Service Impl

224 225 226 227
	getConfigurationData(): IConfigurationData {
		return this._configuration.toData();
	}

228 229 230 231 232 233 234 235
	getValue<T>(): T;
	getValue<T>(section: string): T;
	getValue<T>(overrides: IConfigurationOverrides): T;
	getValue<T>(section: string, overrides: IConfigurationOverrides): T;
	getValue(arg1?: any, arg2?: any): any {
		const section = typeof arg1 === 'string' ? arg1 : void 0;
		const overrides = isConfigurationOverrides(arg1) ? arg1 : isConfigurationOverrides(arg2) ? arg2 : void 0;
		return this._configuration.getValue(section, overrides);
236 237
	}

238 239 240 241 242
	updateValue(key: string, value: any): TPromise<void>;
	updateValue(key: string, value: any, overrides: IConfigurationOverrides): TPromise<void>;
	updateValue(key: string, value: any, target: ConfigurationTarget): TPromise<void>;
	updateValue(key: string, value: any, overrides: IConfigurationOverrides, target: ConfigurationTarget): TPromise<void>;
	updateValue(key: string, value: any, overrides: IConfigurationOverrides, target: ConfigurationTarget, donotNotifyError: boolean): TPromise<void>;
243
	updateValue(key: string, value: any, arg3?: any, arg4?: any, donotNotifyError?: any): TPromise<void> {
244 245 246
		assert.ok(this.configurationEditingService, 'Workbench is not initialized yet');
		const overrides = isConfigurationOverrides(arg3) ? arg3 : void 0;
		const target = this.deriveConfigurationTarget(key, value, overrides, overrides ? arg4 : arg3);
247
		return target ? this.writeConfigurationValue(key, value, target, overrides, donotNotifyError)
248
			: TPromise.as(null);
249 250
	}

251 252 253 254
	reloadConfiguration(folder?: IWorkspaceFolder, key?: string): TPromise<void> {
		if (folder) {
			return this.reloadWorkspaceFolderConfiguration(folder, key);
		}
S
Sandeep Somavarapu 已提交
255
		return this.reloadUserConfiguration()
S
Sandeep Somavarapu 已提交
256
			.then(() => this.reloadWorkspaceConfiguration())
S
Sandeep Somavarapu 已提交
257
			.then(() => this.loadConfiguration());
258
	}
259

260 261 262 263 264
	inspect<T>(key: string, overrides?: IConfigurationOverrides): {
		default: T,
		user: T,
		workspace: T,
		workspaceFolder: T,
265
		memory?: T,
266 267
		value: T
	} {
268
		return this._configuration.inspect<T>(key, overrides);
269 270
	}

271 272 273 274 275 276 277
	keys(): {
		default: string[];
		user: string[];
		workspace: string[];
		workspaceFolder: string[];
	} {
		return this._configuration.keys();
278 279
	}

280
	getUnsupportedWorkspaceKeys(): string[] {
S
Sandeep Somavarapu 已提交
281 282
		const unsupportedWorkspaceKeys = [...this.workspaceConfiguration.getWorkspaceSettings().unsupportedKeys];
		for (const folder of this.workspace.folders) {
S
Sandeep Somavarapu 已提交
283
			unsupportedWorkspaceKeys.push(...this.cachedFolderConfigs.get(folder.uri).getUnsupportedKeys());
S
Sandeep Somavarapu 已提交
284 285
		}
		return distinct(unsupportedWorkspaceKeys);
286 287
	}

288
	initialize(arg: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWindowConfiguration): TPromise<any> {
289
		return this.createWorkspace(arg)
S
Sandeep Somavarapu 已提交
290
			.then(workspace => this.updateWorkspaceAndInitializeConfiguration(workspace));
291 292
	}

293
	setInstantiationService(instantiationService: IInstantiationService): void {
294
		this.configurationEditingService = instantiationService.createInstance(ConfigurationEditingService);
S
Sandeep Somavarapu 已提交
295
		this.jsonEditingService = instantiationService.createInstance(JSONEditingService);
296 297
	}

298
	handleWorkspaceFileEvents(event: FileChangesEvent): TPromise<void> {
299 300
		switch (this.getWorkbenchState()) {
			case WorkbenchState.FOLDER:
301
				return this.onSingleFolderFileChanges(event);
302
			case WorkbenchState.WORKSPACE:
303
				return this.onWorkspaceFileChanges(event);
304
		}
305
		return TPromise.as(void 0);
306 307
	}

308
	private createWorkspace(arg: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWindowConfiguration): TPromise<Workspace> {
309
		if (isWorkspaceIdentifier(arg)) {
310
			return this.createMulitFolderWorkspace(arg);
311 312 313
		}

		if (isSingleFolderWorkspaceIdentifier(arg)) {
314
			return this.createSingleFolderWorkspace(arg);
315 316
		}

317
		return this.createEmptyWorkspace(arg);
318 319
	}

320
	private createMulitFolderWorkspace(workspaceIdentifier: IWorkspaceIdentifier): TPromise<Workspace> {
321
		const workspaceConfigPath = URI.file(workspaceIdentifier.configPath);
322
		return this.workspaceConfiguration.load(workspaceConfigPath)
323
			.then(() => {
S
Sandeep Somavarapu 已提交
324
				const workspaceFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), URI.file(paths.dirname(workspaceConfigPath.fsPath)));
325
				const workspaceId = workspaceIdentifier.id;
326
				const workspaceName = getWorkspaceLabel({ id: workspaceId, configPath: workspaceConfigPath.fsPath }, this.environmentService);
327
				return new Workspace(workspaceId, workspaceName, workspaceFolders, workspaceConfigPath);
328 329 330
			});
	}

331
	private createSingleFolderWorkspace(singleFolderWorkspaceIdentifier: ISingleFolderWorkspaceIdentifier): TPromise<Workspace> {
332
		const folderPath = URI.file(singleFolderWorkspaceIdentifier);
333
		return stat(folderPath.fsPath)
334 335
			.then(workspaceStat => {
				const ctime = isLinux ? workspaceStat.ino : workspaceStat.birthtime.getTime(); // On Linux, birthtime is ctime, so we cannot use it! We use the ino instead!
336 337
				const id = createHash('md5').update(folderPath.fsPath).update(ctime ? String(ctime) : '').digest('hex');
				const folder = URI.file(folderPath.fsPath);
338
				return new Workspace(id, paths.basename(folderPath.fsPath), toWorkspaceFolders([{ path: folder.fsPath }]), null, ctime);
339 340 341
			});
	}

342
	private createEmptyWorkspace(configuration: IWindowConfiguration): TPromise<Workspace> {
343
		let id = configuration.backupPath ? URI.from({ path: paths.basename(configuration.backupPath), scheme: 'empty' }).toString() : '';
344 345 346
		return TPromise.as(new Workspace(id));
	}

S
Sandeep Somavarapu 已提交
347
	private updateWorkspaceAndInitializeConfiguration(workspace: Workspace): TPromise<void> {
S
Sandeep Somavarapu 已提交
348 349 350 351 352 353 354 355 356
		const hasWorkspaceBefore = !!this.workspace;
		let previousState;
		let previousWorkspacePath;
		let previousFolders;

		if (hasWorkspaceBefore) {
			previousState = this.getWorkbenchState();
			previousWorkspacePath = this.workspace.configuration ? this.workspace.configuration.fsPath : void 0;
			previousFolders = this.workspace.folders;
S
Sandeep Somavarapu 已提交
357 358 359
			this.workspace.update(workspace);
		} else {
			this.workspace = workspace;
360
		}
S
Sandeep Somavarapu 已提交
361 362

		return this.initializeConfiguration().then(() => {
S
Sandeep Somavarapu 已提交
363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378
			// Trigger changes after configuration initialization so that configuration is up to date.
			if (hasWorkspaceBefore) {
				const newState = this.getWorkbenchState();
				if (previousState && newState !== previousState) {
					this._onDidChangeWorkbenchState.fire(newState);
				}

				const newWorkspacePath = this.workspace.configuration ? this.workspace.configuration.fsPath : void 0;
				if (previousWorkspacePath && newWorkspacePath !== previousWorkspacePath || newState !== previousState) {
					this._onDidChangeWorkspaceName.fire();
				}

				const folderChanges = this.compareFolders(previousFolders, this.workspace.folders);
				if (folderChanges && (folderChanges.added.length || folderChanges.removed.length || folderChanges.changed.length)) {
					this._onDidChangeWorkspaceFolders.fire(folderChanges);
				}
S
Sandeep Somavarapu 已提交
379 380
			}
		});
381 382
	}

S
Sandeep Somavarapu 已提交
383
	private compareFolders(currentFolders: IWorkspaceFolder[], newFolders: IWorkspaceFolder[]): IWorkspaceFoldersChangeEvent {
384 385
		const result = { added: [], removed: [], changed: [] };
		result.added = newFolders.filter(newFolder => !currentFolders.some(currentFolder => newFolder.uri.toString() === currentFolder.uri.toString()));
S
Sandeep Somavarapu 已提交
386 387 388 389 390 391 392 393 394 395 396 397
		for (let currentIndex = 0; currentIndex < currentFolders.length; currentIndex++) {
			let currentFolder = currentFolders[currentIndex];
			let newIndex = 0;
			for (newIndex = 0; newIndex < newFolders.length && currentFolder.uri.toString() !== newFolders[newIndex].uri.toString(); newIndex++) { }
			if (newIndex < newFolders.length) {
				if (currentIndex !== newIndex || currentFolder.name !== newFolders[newIndex].name) {
					result.changed.push(currentFolder);
				}
			} else {
				result.removed.push(currentFolder);
			}
		}
398 399 400
		return result;
	}

401
	private initializeConfiguration(): TPromise<void> {
S
Sandeep Somavarapu 已提交
402
		this.registerConfigurationSchemas();
403
		return this.loadConfiguration();
404 405
	}

S
Sandeep Somavarapu 已提交
406
	private reloadUserConfiguration(key?: string): TPromise<void> {
S
Sandeep Somavarapu 已提交
407
		return this.userConfiguration.reload();
S
Sandeep Somavarapu 已提交
408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424
	}

	private reloadWorkspaceConfiguration(key?: string): TPromise<void> {
		const workbenchState = this.getWorkbenchState();
		if (workbenchState === WorkbenchState.FOLDER) {
			return this.onWorkspaceFolderConfigurationChanged(this.workspace.folders[0], key);
		}
		if (workbenchState === WorkbenchState.WORKSPACE) {
			return this.workspaceConfiguration.reload().then(() => this.onWorkspaceConfigurationChanged());
		}
		return TPromise.as(null);
	}

	private reloadWorkspaceFolderConfiguration(folder: IWorkspaceFolder, key?: string): TPromise<void> {
		return this.onWorkspaceFolderConfigurationChanged(folder, key);
	}

425 426
	private loadConfiguration(): TPromise<void> {
		// reset caches
427
		this.cachedFolderConfigs = new StrictResourceMap<FolderConfiguration>();
428

429 430 431 432 433
		const folders = this.workspace.folders;
		return this.loadFolderConfigurations(folders)
			.then((folderConfigurations) => {

				let workspaceConfiguration = this.getWorkspaceConfigurationModel(folderConfigurations);
S
Sandeep Somavarapu 已提交
434
				const folderConfigurationModels = new StrictResourceMap<ConfigurationModel>();
435 436
				folderConfigurations.forEach((folderConfiguration, index) => folderConfigurationModels.set(folders[index].uri, folderConfiguration));

437
				const currentConfiguration = this._configuration;
S
Sandeep Somavarapu 已提交
438
				this._configuration = new Configuration(this.defaultConfiguration, this.userConfiguration.configurationModel, workspaceConfiguration, folderConfigurationModels, new ConfigurationModel(), new StrictResourceMap<ConfigurationModel>(), this.getWorkbenchState() !== WorkbenchState.EMPTY ? this.workspace : null); //TODO: Sandy Avoid passing null
439

440 441 442 443
				if (currentConfiguration) {
					const changedKeys = this._configuration.compare(currentConfiguration);
					this.triggerConfigurationChange(new ConfigurationChangeEvent().change(changedKeys), ConfigurationTarget.WORKSPACE);
				} else {
S
Sandeep Somavarapu 已提交
444
					this._onDidChangeConfiguration.fire(new AllKeysConfigurationChangeEvent(this._configuration, ConfigurationTarget.WORKSPACE, this.getTargetConfiguration(ConfigurationTarget.WORKSPACE)));
445
				}
446
			});
447 448
	}

S
Sandeep Somavarapu 已提交
449
	private getWorkspaceConfigurationModel(folderConfigurations: ConfigurationModel[]): ConfigurationModel {
450 451 452 453
		switch (this.getWorkbenchState()) {
			case WorkbenchState.FOLDER:
				return folderConfigurations[0];
			case WorkbenchState.WORKSPACE:
S
Sandeep Somavarapu 已提交
454
				return this.workspaceConfiguration.getConfiguration();
455
			default:
456
				return new ConfigurationModel();
457
		}
458 459
	}

S
Sandeep Somavarapu 已提交
460 461 462 463 464
	private onDefaultConfigurationChanged(keys: string[]): void {
		this.defaultConfiguration = new DefaultConfigurationModel();
		this.registerConfigurationSchemas();
		if (this.workspace && this._configuration) {
			this._configuration.updateDefaultConfiguration(this.defaultConfiguration);
S
Sandeep Somavarapu 已提交
465 466 467 468 469 470
			if (this.getWorkbenchState() === WorkbenchState.FOLDER) {
				this._configuration.updateWorkspaceConfiguration(this.cachedFolderConfigs.get(this.workspace.folders[0].uri).reprocess());
			} else {
				this._configuration.updateWorkspaceConfiguration(this.workspaceConfiguration.reprocessWorkspaceSettings());
				this.workspace.folders.forEach(folder => this._configuration.updateFolderConfiguration(folder.uri, this.cachedFolderConfigs.get(folder.uri).reprocess()));
			}
S
Sandeep Somavarapu 已提交
471 472 473 474
			this.triggerConfigurationChange(new ConfigurationChangeEvent().change(keys), ConfigurationTarget.DEFAULT);
		}
	}

S
Sandeep Somavarapu 已提交
475 476
	private registerConfigurationSchemas(): void {
		if (this.workspace) {
477 478 479
			const jsonRegistry = Registry.as<IJSONContributionRegistry>(JSONExtensions.JSONContribution);
			jsonRegistry.registerSchema(defaultSettingsSchemaId, settingsSchema);
			jsonRegistry.registerSchema(userSettingsSchemaId, settingsSchema);
S
Sandeep Somavarapu 已提交
480 481

			if (WorkbenchState.WORKSPACE === this.getWorkbenchState()) {
482 483
				jsonRegistry.registerSchema(workspaceSettingsSchemaId, settingsSchema);
				jsonRegistry.registerSchema(folderSettingsSchemaId, resourceSettingsSchema);
S
Sandeep Somavarapu 已提交
484
			} else {
485 486
				jsonRegistry.registerSchema(workspaceSettingsSchemaId, settingsSchema);
				jsonRegistry.registerSchema(folderSettingsSchemaId, settingsSchema);
S
Sandeep Somavarapu 已提交
487 488 489 490
			}
		}
	}

S
Sandeep Somavarapu 已提交
491 492 493
	private onUserConfigurationChanged(): void {
		let keys = this._configuration.compareAndUpdateUserConfiguration(this.userConfiguration.configurationModel);
		this.triggerConfigurationChange(keys, ConfigurationTarget.USER);
494 495
	}

496 497
	private onWorkspaceConfigurationChanged(): TPromise<void> {
		if (this.workspace && this.workspace.configuration && this._configuration) {
S
Sandeep Somavarapu 已提交
498
			const workspaceConfigurationChangeEvent = this._configuration.compareAndUpdateWorkspaceConfiguration(this.workspaceConfiguration.getConfiguration());
S
Sandeep Somavarapu 已提交
499
			let configuredFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), URI.file(paths.dirname(this.workspace.configuration.fsPath)));
500
			const changes = this.compareFolders(this.workspace.folders, configuredFolders);
501
			if (changes.added.length || changes.removed.length || changes.changed.length) {
502
				this.workspace.folders = configuredFolders;
503
				return this.onFoldersChanged()
504 505
					.then(foldersConfigurationChangeEvent => {
						this.triggerConfigurationChange(foldersConfigurationChangeEvent.change(workspaceConfigurationChangeEvent), ConfigurationTarget.WORKSPACE_FOLDER);
S
Sandeep Somavarapu 已提交
506
						this._onDidChangeWorkspaceFolders.fire(changes);
507 508
					});
			} else {
509
				this.triggerConfigurationChange(workspaceConfigurationChangeEvent, ConfigurationTarget.WORKSPACE);
S
Sandeep Somavarapu 已提交
510
			}
511
		}
512 513 514 515 516 517 518 519
		return TPromise.as(null);
	}

	private onWorkspaceFileChanges(event: FileChangesEvent): TPromise<void> {
		return TPromise.join(this.workspace.folders.map(folder =>
			// handle file event for each folder
			this.cachedFolderConfigs.get(folder.uri).handleWorkspaceFileEvents(event)
				// Update folder configuration if handled
S
Sandeep Somavarapu 已提交
520
				.then(folderConfiguration => folderConfiguration ? this._configuration.compareAndUpdateFolderConfiguration(folder.uri, folderConfiguration) : new ConfigurationChangeEvent()))
521 522 523 524
		).then(changeEvents => {
			const consolidateChangeEvent = changeEvents.reduce((consolidated, e) => consolidated.change(e), new ConfigurationChangeEvent());
			this.triggerConfigurationChange(consolidateChangeEvent, ConfigurationTarget.WORKSPACE_FOLDER);
		});
525 526
	}

527 528 529 530 531 532
	private onSingleFolderFileChanges(event: FileChangesEvent): TPromise<void> {
		const folder = this.workspace.folders[0];
		return this.cachedFolderConfigs.get(folder.uri).handleWorkspaceFileEvents(event)
			.then(folderConfiguration => {
				if (folderConfiguration) {
					// File change handled
S
Sandeep Somavarapu 已提交
533 534
					this._configuration.compareAndUpdateFolderConfiguration(folder.uri, folderConfiguration);
					const workspaceChangedKeys = this._configuration.compareAndUpdateWorkspaceConfiguration(folderConfiguration);
535 536 537 538 539 540 541 542 543
					this.triggerConfigurationChange(workspaceChangedKeys, ConfigurationTarget.WORKSPACE);
				}
			});
	}

	private onWorkspaceFolderConfigurationChanged(folder: IWorkspaceFolder, key?: string): TPromise<void> {
		this.disposeFolderConfiguration(folder);
		return this.loadFolderConfigurations([folder])
			.then(([folderConfiguration]) => {
S
Sandeep Somavarapu 已提交
544
				const folderChangedKeys = this._configuration.compareAndUpdateFolderConfiguration(folder.uri, folderConfiguration);
545
				if (this.getWorkbenchState() === WorkbenchState.FOLDER) {
S
Sandeep Somavarapu 已提交
546
					const workspaceChangedKeys = this._configuration.compareAndUpdateWorkspaceConfiguration(folderConfiguration);
547 548 549 550 551 552 553
					this.triggerConfigurationChange(workspaceChangedKeys, ConfigurationTarget.WORKSPACE);
				} else {
					this.triggerConfigurationChange(folderChangedKeys, ConfigurationTarget.WORKSPACE_FOLDER);
				}
			});
	}

554 555
	private onFoldersChanged(): TPromise<ConfigurationChangeEvent> {
		let changeEvent = new ConfigurationChangeEvent();
556

557 558
		// Remove the configurations of deleted folders
		for (const key of this.cachedFolderConfigs.keys()) {
559
			if (!this.workspace.folders.filter(folder => folder.uri.toString() === key.toString())[0]) {
560
				this.cachedFolderConfigs.delete(key);
S
Sandeep Somavarapu 已提交
561
				changeEvent = changeEvent.change(this._configuration.compareAndDeleteFolderConfiguration(key));
562 563 564
			}
		}

565
		const toInitialize = this.workspace.folders.filter(folder => !this.cachedFolderConfigs.has(folder.uri));
566
		if (toInitialize.length) {
567 568 569
			return this.loadFolderConfigurations(toInitialize)
				.then(folderConfigurations => {
					folderConfigurations.forEach((folderConfiguration, index) => {
S
Sandeep Somavarapu 已提交
570
						changeEvent = changeEvent.change(this._configuration.compareAndUpdateFolderConfiguration(toInitialize[index].uri, folderConfiguration));
571
					});
572
					return changeEvent;
573
				});
574
		}
575
		return TPromise.as(changeEvent);
576 577
	}

S
Sandeep Somavarapu 已提交
578
	private loadFolderConfigurations(folders: IWorkspaceFolder[]): TPromise<ConfigurationModel[]> {
579
		return TPromise.join([...folders.map(folder => {
S
Sandeep Somavarapu 已提交
580
			const folderConfiguration = new FolderConfiguration(folder.uri, this.workspaceSettingsRootFolder, this.getWorkbenchState());
581 582 583
			this.cachedFolderConfigs.set(folder.uri, this._register(folderConfiguration));
			return folderConfiguration.loadConfiguration();
		})]);
584 585
	}

586
	private writeConfigurationValue(key: string, value: any, target: ConfigurationTarget, overrides: IConfigurationOverrides, donotNotifyError: boolean): TPromise<void> {
587 588 589 590 591 592
		if (target === ConfigurationTarget.DEFAULT) {
			return TPromise.wrapError(new Error('Invalid configuration target'));
		}

		if (target === ConfigurationTarget.MEMORY) {
			this._configuration.updateValue(key, value, overrides);
593
			this.triggerConfigurationChange(new ConfigurationChangeEvent().change(overrides.overrideIdentifier ? [keyFromOverrideIdentifier(overrides.overrideIdentifier)] : [key], overrides.resource), target);
594 595 596
			return TPromise.as(null);
		}

S
Sandeep Somavarapu 已提交
597
		return this.configurationEditingService.writeConfiguration(target, { key, value }, { scopes: overrides, donotNotifyError })
598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627
			.then(() => {
				switch (target) {
					case ConfigurationTarget.USER:
						return this.reloadUserConfiguration();
					case ConfigurationTarget.WORKSPACE:
						return this.reloadWorkspaceConfiguration();
					case ConfigurationTarget.WORKSPACE_FOLDER:
						const workspaceFolder = overrides && overrides.resource ? this.workspace.getFolder(overrides.resource) : null;
						if (workspaceFolder) {
							return this.reloadWorkspaceFolderConfiguration(this.workspace.getFolder(overrides.resource), key);
						}
				}
				return null;
			});
	}

	private deriveConfigurationTarget(key: string, value: any, overrides: IConfigurationOverrides, target: ConfigurationTarget): ConfigurationTarget {
		if (target) {
			return target;
		}

		if (value === void 0) {
			// Ignore. But expected is to remove the value from all targets
			return void 0;
		}

		const inspect = this.inspect(key, overrides);
		if (equals(value, inspect.value)) {
			// No change. So ignore.
			return void 0;
628
		}
629 630 631 632 633 634 635 636 637 638

		if (inspect.workspaceFolder !== void 0) {
			return ConfigurationTarget.WORKSPACE_FOLDER;
		}

		if (inspect.workspace !== void 0) {
			return ConfigurationTarget.WORKSPACE;
		}

		return ConfigurationTarget.USER;
639 640
	}

641 642 643
	private triggerConfigurationChange(configurationEvent: ConfigurationChangeEvent, target: ConfigurationTarget): void {
		if (configurationEvent.affectedKeys.length) {
			configurationEvent.telemetryData(target, this.getTargetConfiguration(target));
644
			this._onDidChangeConfiguration.fire(new WorkspaceConfigurationChangeEvent(configurationEvent, this.workspace));
645
		}
646
	}
647

648 649 650 651 652 653 654 655
	private getTargetConfiguration(target: ConfigurationTarget): any {
		switch (target) {
			case ConfigurationTarget.DEFAULT:
				return this._configuration.defaults.contents;
			case ConfigurationTarget.USER:
				return this._configuration.user.contents;
			case ConfigurationTarget.WORKSPACE:
				return this._configuration.workspace.contents;
656
		}
657
		return {};
658
	}
659 660 661 662 663 664 665 666 667

	private pathEquals(path1: string, path2: string): boolean {
		if (!isLinux) {
			path1 = path1.toLowerCase();
			path2 = path2.toLowerCase();
		}

		return path1 === path2;
	}
668 669 670 671 672 673 674

	private disposeFolderConfiguration(folder: IWorkspaceFolder): void {
		const folderConfiguration = this.cachedFolderConfigs.get(folder.uri);
		if (folderConfiguration) {
			folderConfiguration.dispose();
		}
	}
675
}
676

677 678 679 680 681 682 683 684 685 686 687 688 689
interface IExportedConfigurationNode {
	name: string;
	description: string;
	default: any;
	type: string | string[];
	enum?: any[];
	enumDescriptions?: string[];
}

interface IConfigurationExport {
	settings: IExportedConfigurationNode[];
	buildTime: number;
	commit: string;
690
	buildNumber: number;
691 692 693
}

export class DefaultConfigurationExportHelper {
694 695 696 697 698

	constructor(
		@IEnvironmentService environmentService: IEnvironmentService,
		@IExtensionService private extensionService: IExtensionService,
		@ICommandService private commandService: ICommandService) {
699 700
		if (environmentService.args['export-default-configuration']) {
			this.writeConfigModelAndQuit(environmentService.args['export-default-configuration']);
701 702 703 704 705 706 707 708 709 710 711
		}
	}

	private writeConfigModelAndQuit(targetPath: string): TPromise<void> {
		return this.extensionService.onReady()
			.then(() => this.writeConfigModel(targetPath))
			.then(() => this.commandService.executeCommand('workbench.action.quit'))
			.then(() => { });
	}

	private writeConfigModel(targetPath: string): TPromise<void> {
712 713 714 715 716 717 718
		const config = this.getConfigModel();

		const resultString = JSON.stringify(config, undefined, '  ');
		return writeFile(targetPath, resultString);
	}

	private getConfigModel(): IConfigurationExport {
719
		const configurations = Registry.as<IConfigurationRegistry>(Extensions.Configuration).getConfigurations().slice();
720
		const settings: IExportedConfigurationNode[] = [];
721 722 723 724
		const processConfig = (config: IConfigurationNode) => {
			if (config.properties) {
				for (let name in config.properties) {
					const prop = config.properties[name];
725
					const propDetails: IExportedConfigurationNode = {
726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748
						name,
						description: prop.description,
						default: prop.default,
						type: prop.type
					};

					if (prop.enum) {
						propDetails.enum = prop.enum;
					}

					if (prop.enumDescriptions) {
						propDetails.enumDescriptions = prop.enumDescriptions;
					}

					settings.push(propDetails);
				}
			}

			if (config.allOf) {
				config.allOf.forEach(processConfig);
			}
		};

749
		configurations.forEach(processConfig);
750

751 752 753 754
		const result: IConfigurationExport = {
			settings: settings.sort((a, b) => a.name.localeCompare(b.name)),
			buildTime: Date.now(),
			commit: product.commit,
755
			buildNumber: product.settingsSearchBuildId
756
		};
757

758
		return result;
759 760
	}
}