configurationService.ts 31.9 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
	getConfiguration<T>(): T;
	getConfiguration<T>(section: string): T;
	getConfiguration<T>(overrides: IConfigurationOverrides): T;
	getConfiguration<T>(section: string, overrides: IConfigurationOverrides): T;
232 233 234
	getConfiguration(arg1?: any, arg2?: any): any {
		const section = typeof arg1 === 'string' ? arg1 : void 0;
		const overrides = isConfigurationOverrides(arg1) ? arg1 : isConfigurationOverrides(arg2) ? arg2 : void 0;
235
		return this._configuration.getSection(section, overrides);
236 237
	}

238
	getValue<T>(key: string, overrides?: IConfigurationOverrides): T {
239
		return this._configuration.getValue(key, overrides);
240 241
	}

242 243 244 245 246
	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>;
247
	updateValue(key: string, value: any, arg3?: any, arg4?: any, donotNotifyError?: any): TPromise<void> {
248 249 250
		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);
251
		return target ? this.writeConfigurationValue(key, value, target, overrides, donotNotifyError)
252
			: TPromise.as(null);
253 254
	}

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

264 265 266 267 268
	inspect<T>(key: string, overrides?: IConfigurationOverrides): {
		default: T,
		user: T,
		workspace: T,
		workspaceFolder: T,
269
		memory?: T,
270 271 272
		value: T
	} {
		return this._configuration.lookup<T>(key);
273 274
	}

275 276 277 278 279 280 281
	keys(): {
		default: string[];
		user: string[];
		workspace: string[];
		workspaceFolder: string[];
	} {
		return this._configuration.keys();
282 283
	}

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

292
	initialize(arg: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWindowConfiguration): TPromise<any> {
293
		return this.createWorkspace(arg)
S
Sandeep Somavarapu 已提交
294
			.then(workspace => this.updateWorkspaceAndInitializeConfiguration(workspace));
295 296
	}

297
	setInstantiationService(instantiationService: IInstantiationService): void {
298
		this.configurationEditingService = instantiationService.createInstance(ConfigurationEditingService);
S
Sandeep Somavarapu 已提交
299
		this.jsonEditingService = instantiationService.createInstance(JSONEditingService);
300 301
	}

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

312
	private createWorkspace(arg: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWindowConfiguration): TPromise<Workspace> {
313
		if (isWorkspaceIdentifier(arg)) {
314
			return this.createMulitFolderWorkspace(arg);
315 316 317
		}

		if (isSingleFolderWorkspaceIdentifier(arg)) {
318
			return this.createSingleFolderWorkspace(arg);
319 320
		}

321
		return this.createEmptyWorkspace(arg);
322 323
	}

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

335
	private createSingleFolderWorkspace(singleFolderWorkspaceIdentifier: ISingleFolderWorkspaceIdentifier): TPromise<Workspace> {
336
		const folderPath = URI.file(singleFolderWorkspaceIdentifier);
337
		return stat(folderPath.fsPath)
338 339
			.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!
340 341
				const id = createHash('md5').update(folderPath.fsPath).update(ctime ? String(ctime) : '').digest('hex');
				const folder = URI.file(folderPath.fsPath);
342
				return new Workspace(id, paths.basename(folderPath.fsPath), toWorkspaceFolders([{ path: folder.fsPath }]), null, ctime);
343 344 345
			});
	}

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

S
Sandeep Somavarapu 已提交
351 352 353 354 355 356
	private updateWorkspaceAndInitializeConfiguration(workspace: Workspace): TPromise<void> {
		let folderChanges: IWorkspaceFoldersChangeEvent;
		if (this.workspace) {
			const currentState = this.getWorkbenchState();
			const currentWorkspacePath = this.workspace.configuration ? this.workspace.configuration.fsPath : void 0;
			const currentFolders = this.workspace.folders;
357

S
Sandeep Somavarapu 已提交
358
			this.workspace.update(workspace);
359

S
Sandeep Somavarapu 已提交
360 361 362 363
			const newState = this.getWorkbenchState();
			if (newState !== currentState) {
				this._onDidChangeWorkbenchState.fire(newState);
			}
364

S
Sandeep Somavarapu 已提交
365 366 367 368
			const newWorkspacePath = this.workspace.configuration ? this.workspace.configuration.fsPath : void 0;
			if (newWorkspacePath !== currentWorkspacePath || newState !== currentState) {
				this._onDidChangeWorkspaceName.fire();
			}
369

S
Sandeep Somavarapu 已提交
370
			folderChanges = this.compareFolders(currentFolders, this.workspace.folders);
371

S
Sandeep Somavarapu 已提交
372 373
		} else {
			this.workspace = workspace;
374
		}
S
Sandeep Somavarapu 已提交
375 376 377 378 379 380 381

		return this.initializeConfiguration().then(() => {
			// Trigger folders change after configuration initialization so that configuration is up to date.
			if (folderChanges && (folderChanges.added.length || folderChanges.removed.length || folderChanges.changed.length)) {
				this._onDidChangeWorkspaceFolders.fire(folderChanges);
			}
		});
382 383
	}

S
Sandeep Somavarapu 已提交
384
	private compareFolders(currentFolders: IWorkspaceFolder[], newFolders: IWorkspaceFolder[]): IWorkspaceFoldersChangeEvent {
385 386
		const result = { added: [], removed: [], changed: [] };
		result.added = newFolders.filter(newFolder => !currentFolders.some(currentFolder => newFolder.uri.toString() === currentFolder.uri.toString()));
S
Sandeep Somavarapu 已提交
387 388 389 390 391 392 393 394 395 396 397 398
		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);
			}
		}
399 400 401
		return result;
	}

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

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

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

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

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

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

438
				const currentConfiguration = this._configuration;
S
Sandeep Somavarapu 已提交
439
				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
440

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

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

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

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

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

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

493 494
	private onWorkspaceConfigurationChanged(): TPromise<void> {
		if (this.workspace && this.workspace.configuration && this._configuration) {
S
Sandeep Somavarapu 已提交
495
			const workspaceConfigurationChangeEvent = this._configuration.compareAndUpdateWorkspaceConfiguration(this.workspaceConfiguration.getConfiguration());
S
Sandeep Somavarapu 已提交
496
			let configuredFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), URI.file(paths.dirname(this.workspace.configuration.fsPath)));
497
			const changes = this.compareFolders(this.workspace.folders, configuredFolders);
498
			if (changes.added.length || changes.removed.length || changes.changed.length) {
499
				this.workspace.folders = configuredFolders;
500
				return this.onFoldersChanged()
501 502
					.then(foldersConfigurationChangeEvent => {
						this.triggerConfigurationChange(foldersConfigurationChangeEvent.change(workspaceConfigurationChangeEvent), ConfigurationTarget.WORKSPACE_FOLDER);
S
Sandeep Somavarapu 已提交
503
						this._onDidChangeWorkspaceFolders.fire(changes);
504 505
					});
			} else {
506
				this.triggerConfigurationChange(workspaceConfigurationChangeEvent, ConfigurationTarget.WORKSPACE);
S
Sandeep Somavarapu 已提交
507
			}
508
		}
509 510 511 512 513 514 515 516
		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 已提交
517
				.then(folderConfiguration => folderConfiguration ? this._configuration.compareAndUpdateFolderConfiguration(folder.uri, folderConfiguration) : new ConfigurationChangeEvent()))
518 519 520 521
		).then(changeEvents => {
			const consolidateChangeEvent = changeEvents.reduce((consolidated, e) => consolidated.change(e), new ConfigurationChangeEvent());
			this.triggerConfigurationChange(consolidateChangeEvent, ConfigurationTarget.WORKSPACE_FOLDER);
		});
522 523
	}

524 525 526 527 528 529
	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 已提交
530 531
					this._configuration.compareAndUpdateFolderConfiguration(folder.uri, folderConfiguration);
					const workspaceChangedKeys = this._configuration.compareAndUpdateWorkspaceConfiguration(folderConfiguration);
532 533 534 535 536 537 538 539 540
					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 已提交
541
				const folderChangedKeys = this._configuration.compareAndUpdateFolderConfiguration(folder.uri, folderConfiguration);
542
				if (this.getWorkbenchState() === WorkbenchState.FOLDER) {
S
Sandeep Somavarapu 已提交
543
					const workspaceChangedKeys = this._configuration.compareAndUpdateWorkspaceConfiguration(folderConfiguration);
544 545 546 547 548 549 550
					this.triggerConfigurationChange(workspaceChangedKeys, ConfigurationTarget.WORKSPACE);
				} else {
					this.triggerConfigurationChange(folderChangedKeys, ConfigurationTarget.WORKSPACE_FOLDER);
				}
			});
	}

551 552
	private onFoldersChanged(): TPromise<ConfigurationChangeEvent> {
		let changeEvent = new ConfigurationChangeEvent();
553

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

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

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

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

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

S
Sandeep Somavarapu 已提交
594
		return this.configurationEditingService.writeConfiguration(target, { key, value }, { scopes: overrides, donotNotifyError })
595 596 597 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
			.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;
625
		}
626 627 628 629 630 631 632 633 634 635

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

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

		return ConfigurationTarget.USER;
636 637
	}

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

645 646 647 648 649 650 651 652
	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;
653
		}
654
		return {};
655
	}
656 657 658 659 660 661 662 663 664

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

		return path1 === path2;
	}
665 666 667 668 669 670 671

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

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

interface IConfigurationExport {
	settings: IExportedConfigurationNode[];
	buildTime: number;
	commit: string;
687
	buildNumber: number;
688 689 690
}

export class DefaultConfigurationExportHelper {
691 692 693 694 695

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

	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> {
709 710 711 712 713 714 715
		const config = this.getConfigModel();

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

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

746
		configurations.forEach(processConfig);
747

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

755
		return result;
756 757
	}
}