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';
B
Benjamin Pasero 已提交
43
import { getBaseLabel } from 'vs/base/common/labels';
44

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

	public _serviceBrand: any;

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

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

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

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

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

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

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

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

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

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

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

88 89
	// Workspace Context Service Impl

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

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

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

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

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

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

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

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

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

137
	private doAddFolders(foldersToAdd: IWorkspaceFolderCreationData[]): TPromise<void> {
S
Sandeep Somavarapu 已提交
138 139 140 141 142 143 144 145 146 147 148 149 150
		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 => {
151
			if (this.contains(currentWorkspaceFolderUris, folderToAdd.uri)) {
S
Sandeep Somavarapu 已提交
152 153 154
				return; // already existing
			}

155 156
			let storedFolder: IStoredWorkspaceFolder;

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

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

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

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

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

		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 已提交
202
			return this.setFolders(newStoredFolders);
S
Sandeep Somavarapu 已提交
203 204 205 206 207
		}

		return TPromise.as(void 0);
	}

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

S
Sandeep Somavarapu 已提交
213 214 215 216 217 218 219 220 221 222
	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();
		});
	}

223 224
	// Workspace Configuration Service Impl

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

229 230 231 232 233 234 235 236
	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);
237 238
	}

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

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

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

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

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

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

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

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

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

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

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

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

332
	private createSingleFolderWorkspace(singleFolderWorkspaceIdentifier: ISingleFolderWorkspaceIdentifier): TPromise<Workspace> {
333
		const folderPath = URI.file(singleFolderWorkspaceIdentifier);
334
		return stat(folderPath.fsPath)
335 336
			.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!
337 338
				const id = createHash('md5').update(folderPath.fsPath).update(ctime ? String(ctime) : '').digest('hex');
				const folder = URI.file(folderPath.fsPath);
B
Benjamin Pasero 已提交
339
				return new Workspace(id, getBaseLabel(folder), toWorkspaceFolders([{ path: folder.fsPath }]), null, ctime);
340 341 342
			});
	}

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

S
Sandeep Somavarapu 已提交
348
	private updateWorkspaceAndInitializeConfiguration(workspace: Workspace): TPromise<void> {
S
Sandeep Somavarapu 已提交
349 350 351 352 353 354 355 356 357
		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 已提交
358 359 360
			this.workspace.update(workspace);
		} else {
			this.workspace = workspace;
361
		}
S
Sandeep Somavarapu 已提交
362 363

		return this.initializeConfiguration().then(() => {
S
Sandeep Somavarapu 已提交
364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379
			// 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 已提交
380 381
			}
		});
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
	private onDefaultConfigurationChanged(keys: string[]): void {
		this.defaultConfiguration = new DefaultConfigurationModel();
		this.registerConfigurationSchemas();
		if (this.workspace && this._configuration) {
			this._configuration.updateDefaultConfiguration(this.defaultConfiguration);
S
Sandeep Somavarapu 已提交
466 467 468 469 470 471
			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 已提交
472 473 474 475
			this.triggerConfigurationChange(new ConfigurationChangeEvent().change(keys), ConfigurationTarget.DEFAULT);
		}
	}

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

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

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

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

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

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

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

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

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

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

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

S
Sandeep Somavarapu 已提交
598
		return this.configurationEditingService.writeConfiguration(target, { key, value }, { scopes: overrides, donotNotifyError })
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 628
			.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;
629
		}
630 631 632 633 634 635 636 637 638 639

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

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

		return ConfigurationTarget.USER;
640 641
	}

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

649 650 651 652 653 654 655 656
	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;
657
		}
658
		return {};
659
	}
660 661 662 663 664 665 666 667 668

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

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

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

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

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

export class DefaultConfigurationExportHelper {
695 696 697 698 699

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

	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> {
713 714 715 716 717 718 719
		const config = this.getConfigModel();

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

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

750
		configurations.forEach(processConfig);
751

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

759
		return result;
760 761
	}
}