configurationService.ts 34.3 KB
Newer Older
1 2 3 4 5 6 7 8
/*---------------------------------------------------------------------------------------------
 *  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 { TPromise } from 'vs/base/common/winjs.base';
9
import { dirname, basename } from 'path';
10
import * as assert from 'vs/base/common/assert';
M
Matt Bierner 已提交
11
import { Event, Emitter } from 'vs/base/common/event';
12
import { StrictResourceMap } from 'vs/base/common/map';
S
Sandeep Somavarapu 已提交
13
import { equals, deepClone } from 'vs/base/common/objects';
S
Sandeep Somavarapu 已提交
14 15 16
import { Disposable } from 'vs/base/common/lifecycle';
import { Queue } from 'vs/base/common/async';
import { stat, writeFile } from 'vs/base/node/pfs';
17
import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
18
import { IWorkspaceContextService, Workspace, WorkbenchState, IWorkspaceFolder, toWorkspaceFolders, IWorkspaceFoldersChangeEvent, WorkspaceFolder } from 'vs/platform/workspace/common/workspace';
B
Benjamin Pasero 已提交
19
import { isLinux, isWindows, isMacintosh } from 'vs/base/common/platform';
S
#47154  
Sandeep Somavarapu 已提交
20
import { IFileService } from 'vs/platform/files/common/files';
21
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
S
Sandeep Somavarapu 已提交
22
import { ConfigurationChangeEvent, ConfigurationModel, DefaultConfigurationModel } from 'vs/platform/configuration/common/configurationModels';
23
import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, keyFromOverrideIdentifier, isConfigurationOverrides, IConfigurationData } from 'vs/platform/configuration/common/configuration';
S
Sandeep Somavarapu 已提交
24 25
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';
26
import { Registry } from 'vs/platform/registry/common/platform';
S
Sandeep Somavarapu 已提交
27
import { IConfigurationNode, IConfigurationRegistry, Extensions, IConfigurationPropertySchema, allSettings, windowSettings, resourceSettings, applicationSettings } from 'vs/platform/configuration/common/configurationRegistry';
28
import { createHash } from 'crypto';
S
Sandeep Somavarapu 已提交
29
import { getWorkspaceLabel, IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces';
30
import { IWindowConfiguration } from 'vs/platform/windows/common/windows';
31
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
32 33
import { ICommandService } from 'vs/platform/commands/common/commands';
import product from 'vs/platform/node/product';
34 35
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ConfigurationEditingService } from 'vs/workbench/services/configuration/node/configurationEditingService';
36
import { WorkspaceConfiguration, FolderConfiguration } from 'vs/workbench/services/configuration/node/configuration';
S
Sandeep Somavarapu 已提交
37 38 39
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 已提交
40
import { distinct } from 'vs/base/common/arrays';
S
Sandeep Somavarapu 已提交
41
import { UserConfiguration } from 'vs/platform/configuration/node/configuration';
B
Benjamin Pasero 已提交
42
import { getBaseLabel } from 'vs/base/common/labels';
S
Sandeep Somavarapu 已提交
43 44
import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema';
import { localize } from 'vs/nls';
45

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

	public _serviceBrand: any;

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

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

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

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

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

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

S
#47154  
Sandeep Somavarapu 已提交
71
	private fileService: IFileService;
S
Sandeep Somavarapu 已提交
72
	private configurationEditingService: ConfigurationEditingService;
S
Sandeep Somavarapu 已提交
73
	private jsonEditingService: JSONEditingService;
74

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

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

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

		this.workspaceEditingQueue = new Queue<void>();
88 89
	}

90 91
	// Workspace Context Service Impl

92
	public getWorkspace(): Workspace {
93 94 95
		return this.workspace;
	}

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

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

		// Empty
108
		return WorkbenchState.EMPTY;
109 110
	}

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

B
Benjamin Pasero 已提交
115
	public addFolders(foldersToAdd: IWorkspaceFolderCreationData[], index?: number): TPromise<void> {
116
		return this.updateFolders(foldersToAdd, [], index);
S
Sandeep Somavarapu 已提交
117 118 119
	}

	public removeFolders(foldersToRemove: URI[]): TPromise<void> {
120 121 122 123
		return this.updateFolders([], foldersToRemove);
	}

	public updateFolders(foldersToAdd: IWorkspaceFolderCreationData[], foldersToRemove: URI[], index?: number): TPromise<void> {
S
Sandeep Somavarapu 已提交
124
		assert.ok(this.jsonEditingService, 'Workbench is not initialized yet');
125
		return this.workspaceEditingQueue.queue(() => this.doUpdateFolders(foldersToAdd, foldersToRemove, index));
S
Sandeep Somavarapu 已提交
126 127
	}

128
	public isInsideWorkspace(resource: URI): boolean {
S
Sandeep Somavarapu 已提交
129
		return !!this.getWorkspaceFolder(resource);
130 131
	}

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

142
	private doUpdateFolders(foldersToAdd: IWorkspaceFolderCreationData[], foldersToRemove: URI[], index?: number): TPromise<void> {
S
Sandeep Somavarapu 已提交
143 144 145 146
		if (this.getWorkbenchState() !== WorkbenchState.WORKSPACE) {
			return TPromise.as(void 0); // we need a workspace to begin with
		}

147 148 149
		if (foldersToAdd.length + foldersToRemove.length === 0) {
			return TPromise.as(void 0); // nothing to do
		}
S
Sandeep Somavarapu 已提交
150

151
		let foldersHaveChanged = false;
S
Sandeep Somavarapu 已提交
152

153 154 155 156 157
		// Remove first (if any)
		let currentWorkspaceFolders = this.getWorkspace().folders;
		let newStoredFolders: IStoredWorkspaceFolder[] = currentWorkspaceFolders.map(f => f.raw).filter((folder, index) => {
			if (!isStoredWorkspaceFolder(folder)) {
				return true; // keep entries which are unrelated
S
Sandeep Somavarapu 已提交
158 159
			}

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

163
		foldersHaveChanged = currentWorkspaceFolders.length !== newStoredFolders.length;
S
Sandeep Somavarapu 已提交
164

165 166
		// Add afterwards (if any)
		if (foldersToAdd.length) {
167

168 169 170 171
			// Recompute current workspace folders if we have folders to add
			const workspaceConfigFolder = dirname(this.getWorkspace().configuration.fsPath);
			currentWorkspaceFolders = toWorkspaceFolders(newStoredFolders, URI.file(workspaceConfigFolder));
			const currentWorkspaceFolderUris = currentWorkspaceFolders.map(folder => folder.uri);
172

173
			const storedFoldersToAdd: IStoredWorkspaceFolder[] = [];
S
Sandeep Somavarapu 已提交
174

175 176 177 178
			foldersToAdd.forEach(folderToAdd => {
				if (this.contains(currentWorkspaceFolderUris, folderToAdd.uri)) {
					return; // already existing
				}
B
Benjamin Pasero 已提交
179

180
				let storedFolder: IStoredWorkspaceFolder;
B
Benjamin Pasero 已提交
181

182 183 184 185 186 187
				// File resource: use "path" property
				if (folderToAdd.uri.scheme === Schemas.file) {
					storedFolder = {
						path: massageFolderPathForWorkspace(folderToAdd.uri.fsPath, workspaceConfigFolder, newStoredFolders)
					};
				}
S
Sandeep Somavarapu 已提交
188

189 190 191 192 193 194
				// Any other resource: use "uri" property
				else {
					storedFolder = {
						uri: folderToAdd.uri.toString(true)
					};
				}
S
Sandeep Somavarapu 已提交
195

196 197 198
				if (folderToAdd.name) {
					storedFolder.name = folderToAdd.name;
				}
S
Sandeep Somavarapu 已提交
199

200 201
				storedFoldersToAdd.push(storedFolder);
			});
S
Sandeep Somavarapu 已提交
202

203 204 205
			// Apply to array of newStoredFolders
			if (storedFoldersToAdd.length > 0) {
				foldersHaveChanged = true;
S
Sandeep Somavarapu 已提交
206

207 208 209 210 211 212 213 214
				if (typeof index === 'number' && index >= 0 && index < newStoredFolders.length) {
					newStoredFolders = newStoredFolders.slice(0);
					newStoredFolders.splice(index, 0, ...storedFoldersToAdd);
				} else {
					newStoredFolders = [...newStoredFolders, ...storedFoldersToAdd];
				}
			}
		}
S
Sandeep Somavarapu 已提交
215

216 217
		// Set folders if we recorded a change
		if (foldersHaveChanged) {
S
Sandeep Somavarapu 已提交
218
			return this.setFolders(newStoredFolders);
S
Sandeep Somavarapu 已提交
219 220 221 222 223
		}

		return TPromise.as(void 0);
	}

S
Sandeep Somavarapu 已提交
224 225 226 227 228
	private setFolders(folders: IStoredWorkspaceFolder[]): TPromise<void> {
		return this.workspaceConfiguration.setFolders(folders, this.jsonEditingService)
			.then(() => this.onWorkspaceConfigurationChanged());
	}

S
Sandeep Somavarapu 已提交
229 230 231 232 233 234 235 236 237 238
	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();
		});
	}

239 240
	// Workspace Configuration Service Impl

241
	getConfigurationData(): IConfigurationData {
242 243 244
		const configurationData = this._configuration.toData();
		configurationData.isComplete = this.cachedFolderConfigs.values().every(c => c.loaded);
		return configurationData;
245 246
	}

247 248 249 250 251 252 253 254
	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);
255 256
	}

257 258 259 260 261
	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>;
262
	updateValue(key: string, value: any, arg3?: any, arg4?: any, donotNotifyError?: any): TPromise<void> {
263 264 265
		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);
266
		return target ? this.writeConfigurationValue(key, value, target, overrides, donotNotifyError)
267
			: TPromise.as(null);
268 269
	}

270 271 272 273
	reloadConfiguration(folder?: IWorkspaceFolder, key?: string): TPromise<void> {
		if (folder) {
			return this.reloadWorkspaceFolderConfiguration(folder, key);
		}
S
Sandeep Somavarapu 已提交
274
		return this.reloadUserConfiguration()
S
Sandeep Somavarapu 已提交
275
			.then(() => this.reloadWorkspaceConfiguration())
S
Sandeep Somavarapu 已提交
276
			.then(() => this.loadConfiguration());
277
	}
278

279 280 281 282 283
	inspect<T>(key: string, overrides?: IConfigurationOverrides): {
		default: T,
		user: T,
		workspace: T,
		workspaceFolder: T,
284
		memory?: T,
285 286
		value: T
	} {
287
		return this._configuration.inspect<T>(key, overrides);
288 289
	}

290 291 292 293 294 295 296
	keys(): {
		default: string[];
		user: string[];
		workspace: string[];
		workspaceFolder: string[];
	} {
		return this._configuration.keys();
297 298
	}

299
	getUnsupportedWorkspaceKeys(): string[] {
300
		const unsupportedWorkspaceKeys = [...this.workspaceConfiguration.getUnsupportedKeys()];
S
Sandeep Somavarapu 已提交
301
		for (const folder of this.workspace.folders) {
S
Sandeep Somavarapu 已提交
302
			unsupportedWorkspaceKeys.push(...this.cachedFolderConfigs.get(folder.uri).getUnsupportedKeys());
S
Sandeep Somavarapu 已提交
303 304
		}
		return distinct(unsupportedWorkspaceKeys);
305 306
	}

307
	initialize(arg: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWindowConfiguration): TPromise<any> {
308
		return this.createWorkspace(arg)
S
Sandeep Somavarapu 已提交
309
			.then(workspace => this.updateWorkspaceAndInitializeConfiguration(workspace));
310 311
	}

S
#47154  
Sandeep Somavarapu 已提交
312 313
	acquireFileService(fileService: IFileService): void {
		this.fileService = fileService;
314
		const changedWorkspaceFolders: IWorkspaceFolder[] = [];
315 316 317 318 319 320 321 322 323 324 325 326
		TPromise.join(this.cachedFolderConfigs.values()
			.map(folderConfiguration => folderConfiguration.adopt(fileService)
				.then(result => {
					if (result) {
						changedWorkspaceFolders.push(folderConfiguration.workspaceFolder);
					}
				})))
			.then(() => {
				for (const workspaceFolder of changedWorkspaceFolders) {
					this.onWorkspaceFolderConfigurationChanged(workspaceFolder);
				}
			});
327 328
	}

S
#47154  
Sandeep Somavarapu 已提交
329 330 331
	acquireInstantiationService(instantiationService: IInstantiationService): void {
		this.configurationEditingService = instantiationService.createInstance(ConfigurationEditingService);
		this.jsonEditingService = instantiationService.createInstance(JSONEditingService);
332 333
	}

334
	private createWorkspace(arg: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWindowConfiguration): TPromise<Workspace> {
335
		if (isWorkspaceIdentifier(arg)) {
336
			return this.createMulitFolderWorkspace(arg);
337 338 339
		}

		if (isSingleFolderWorkspaceIdentifier(arg)) {
340
			return this.createSingleFolderWorkspace(arg);
341 342
		}

343
		return this.createEmptyWorkspace(arg);
344 345
	}

346
	private createMulitFolderWorkspace(workspaceIdentifier: IWorkspaceIdentifier): TPromise<Workspace> {
347
		const workspaceConfigPath = URI.file(workspaceIdentifier.configPath);
348
		return this.workspaceConfiguration.load(workspaceConfigPath)
349
			.then(() => {
350
				const workspaceFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), URI.file(dirname(workspaceConfigPath.fsPath)));
351
				const workspaceId = workspaceIdentifier.id;
352
				const workspaceName = getWorkspaceLabel({ id: workspaceId, configPath: workspaceConfigPath.fsPath }, this.environmentService);
353
				return new Workspace(workspaceId, workspaceName, workspaceFolders, workspaceConfigPath);
354 355 356
			});
	}

357
	private createSingleFolderWorkspace(singleFolderWorkspaceIdentifier: ISingleFolderWorkspaceIdentifier): TPromise<Workspace> {
358
		const folderPath = URI.file(singleFolderWorkspaceIdentifier);
359
		return stat(folderPath.fsPath)
360
			.then(workspaceStat => {
B
Benjamin Pasero 已提交
361 362 363 364 365 366 367 368 369 370 371 372 373
				let ctime: number;
				if (isLinux) {
					ctime = workspaceStat.ino; // Linux: birthtime is ctime, so we cannot use it! We use the ino instead!
				} else if (isMacintosh) {
					ctime = workspaceStat.birthtime.getTime(); // macOS: birthtime is fine to use as is
				} else if (isWindows) {
					if (typeof workspaceStat.birthtimeMs === 'number') {
						ctime = Math.floor(workspaceStat.birthtimeMs); // Windows: fix precision issue in node.js 8.x to get 7.x results (see https://github.com/nodejs/node/issues/19897)
					} else {
						ctime = workspaceStat.birthtime.getTime();
					}
				}

374 375
				const id = createHash('md5').update(folderPath.fsPath).update(ctime ? String(ctime) : '').digest('hex');
				const folder = URI.file(folderPath.fsPath);
B
Benjamin Pasero 已提交
376
				return new Workspace(id, getBaseLabel(folder), toWorkspaceFolders([{ path: folder.fsPath }]), null, ctime);
377 378 379
			});
	}

380
	private createEmptyWorkspace(configuration: IWindowConfiguration): TPromise<Workspace> {
381
		let id = configuration.backupPath ? URI.from({ path: basename(configuration.backupPath), scheme: 'empty' }).toString() : '';
382 383 384
		return TPromise.as(new Workspace(id));
	}

S
Sandeep Somavarapu 已提交
385
	private updateWorkspaceAndInitializeConfiguration(workspace: Workspace): TPromise<void> {
S
Sandeep Somavarapu 已提交
386
		const hasWorkspaceBefore = !!this.workspace;
387 388 389
		let previousState: WorkbenchState;
		let previousWorkspacePath: string;
		let previousFolders: WorkspaceFolder[];
S
Sandeep Somavarapu 已提交
390 391 392 393 394

		if (hasWorkspaceBefore) {
			previousState = this.getWorkbenchState();
			previousWorkspacePath = this.workspace.configuration ? this.workspace.configuration.fsPath : void 0;
			previousFolders = this.workspace.folders;
S
Sandeep Somavarapu 已提交
395 396 397
			this.workspace.update(workspace);
		} else {
			this.workspace = workspace;
398
		}
S
Sandeep Somavarapu 已提交
399 400

		return this.initializeConfiguration().then(() => {
S
Sandeep Somavarapu 已提交
401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416
			// 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 已提交
417 418
			}
		});
419 420
	}

S
Sandeep Somavarapu 已提交
421
	private compareFolders(currentFolders: IWorkspaceFolder[], newFolders: IWorkspaceFolder[]): IWorkspaceFoldersChangeEvent {
422
		const result = { added: [], removed: [], changed: [] } as IWorkspaceFoldersChangeEvent;
423
		result.added = newFolders.filter(newFolder => !currentFolders.some(currentFolder => newFolder.uri.toString() === currentFolder.uri.toString()));
S
Sandeep Somavarapu 已提交
424 425 426 427 428 429 430 431 432 433 434 435
		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);
			}
		}
436 437 438
		return result;
	}

439
	private initializeConfiguration(): TPromise<void> {
S
Sandeep Somavarapu 已提交
440
		this.registerConfigurationSchemas();
441
		return this.loadConfiguration();
442 443
	}

S
Sandeep Somavarapu 已提交
444
	private reloadUserConfiguration(key?: string): TPromise<void> {
S
Sandeep Somavarapu 已提交
445
		return this.userConfiguration.reload();
S
Sandeep Somavarapu 已提交
446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462
	}

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

463 464
	private loadConfiguration(): TPromise<void> {
		// reset caches
465
		this.cachedFolderConfigs = new StrictResourceMap<FolderConfiguration>();
466

467 468 469 470 471
		const folders = this.workspace.folders;
		return this.loadFolderConfigurations(folders)
			.then((folderConfigurations) => {

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

475
				const currentConfiguration = this._configuration;
S
Sandeep Somavarapu 已提交
476
				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
477

478 479 480 481
				if (currentConfiguration) {
					const changedKeys = this._configuration.compare(currentConfiguration);
					this.triggerConfigurationChange(new ConfigurationChangeEvent().change(changedKeys), ConfigurationTarget.WORKSPACE);
				} else {
S
Sandeep Somavarapu 已提交
482
					this._onDidChangeConfiguration.fire(new AllKeysConfigurationChangeEvent(this._configuration, ConfigurationTarget.WORKSPACE, this.getTargetConfiguration(ConfigurationTarget.WORKSPACE)));
483
				}
484
			});
485 486
	}

S
Sandeep Somavarapu 已提交
487
	private getWorkspaceConfigurationModel(folderConfigurations: ConfigurationModel[]): ConfigurationModel {
488 489 490 491
		switch (this.getWorkbenchState()) {
			case WorkbenchState.FOLDER:
				return folderConfigurations[0];
			case WorkbenchState.WORKSPACE:
S
Sandeep Somavarapu 已提交
492
				return this.workspaceConfiguration.getConfiguration();
493
			default:
494
				return new ConfigurationModel();
495
		}
496 497
	}

S
Sandeep Somavarapu 已提交
498 499 500 501 502
	private onDefaultConfigurationChanged(keys: string[]): void {
		this.defaultConfiguration = new DefaultConfigurationModel();
		this.registerConfigurationSchemas();
		if (this.workspace && this._configuration) {
			this._configuration.updateDefaultConfiguration(this.defaultConfiguration);
S
Sandeep Somavarapu 已提交
503 504 505 506 507 508
			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 已提交
509 510 511 512
			this.triggerConfigurationChange(new ConfigurationChangeEvent().change(keys), ConfigurationTarget.DEFAULT);
		}
	}

S
Sandeep Somavarapu 已提交
513 514
	private registerConfigurationSchemas(): void {
		if (this.workspace) {
515
			const jsonRegistry = Registry.as<IJSONContributionRegistry>(JSONExtensions.JSONContribution);
S
Sandeep Somavarapu 已提交
516 517 518 519 520 521 522 523 524 525 526 527 528 529
			const convertToNotSuggestedProperties = (properties: IJSONSchemaMap, errorMessage: string): IJSONSchemaMap => {
				return Object.keys(properties).reduce((result: IJSONSchemaMap, property) => {
					result[property] = deepClone(properties[property]);
					result[property].deprecationMessage = errorMessage;
					return result;
				}, {});
			};

			const allSettingsSchema: IJSONSchema = { properties: allSettings.properties, patternProperties: allSettings.patternProperties, additionalProperties: false, errorMessage: 'Unknown configuration setting' };
			const unsupportedApplicationSettings = convertToNotSuggestedProperties(applicationSettings.properties, localize('unsupportedApplicationSetting', "This setting can be applied only in User Settings"));
			const workspaceSettingsSchema: IJSONSchema = { properties: { ...unsupportedApplicationSettings, ...windowSettings.properties, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: false, errorMessage: 'Unknown configuration setting' };

			jsonRegistry.registerSchema(defaultSettingsSchemaId, allSettingsSchema);
			jsonRegistry.registerSchema(userSettingsSchemaId, allSettingsSchema);
S
Sandeep Somavarapu 已提交
530 531

			if (WorkbenchState.WORKSPACE === this.getWorkbenchState()) {
S
Sandeep Somavarapu 已提交
532 533 534 535
				const unsupportedWindowSettings = convertToNotSuggestedProperties(windowSettings.properties, localize('unsupportedWindowSetting', "This setting cannot be applied now. It will be applied when you open this folder directly."));
				const folderSettingsSchema: IJSONSchema = { properties: { ...unsupportedApplicationSettings, ...unsupportedWindowSettings, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: false, errorMessage: 'Unknown configuration setting' };
				jsonRegistry.registerSchema(workspaceSettingsSchemaId, workspaceSettingsSchema);
				jsonRegistry.registerSchema(folderSettingsSchemaId, folderSettingsSchema);
S
Sandeep Somavarapu 已提交
536
			} else {
S
Sandeep Somavarapu 已提交
537 538
				jsonRegistry.registerSchema(workspaceSettingsSchemaId, workspaceSettingsSchema);
				jsonRegistry.registerSchema(folderSettingsSchemaId, workspaceSettingsSchema);
S
Sandeep Somavarapu 已提交
539 540 541 542
			}
		}
	}

S
Sandeep Somavarapu 已提交
543 544 545
	private onUserConfigurationChanged(): void {
		let keys = this._configuration.compareAndUpdateUserConfiguration(this.userConfiguration.configurationModel);
		this.triggerConfigurationChange(keys, ConfigurationTarget.USER);
546 547
	}

548 549
	private onWorkspaceConfigurationChanged(): TPromise<void> {
		if (this.workspace && this.workspace.configuration && this._configuration) {
S
Sandeep Somavarapu 已提交
550
			const workspaceConfigurationChangeEvent = this._configuration.compareAndUpdateWorkspaceConfiguration(this.workspaceConfiguration.getConfiguration());
551
			let configuredFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), URI.file(dirname(this.workspace.configuration.fsPath)));
552
			const changes = this.compareFolders(this.workspace.folders, configuredFolders);
553
			if (changes.added.length || changes.removed.length || changes.changed.length) {
554
				this.workspace.folders = configuredFolders;
555
				return this.onFoldersChanged()
556 557
					.then(foldersConfigurationChangeEvent => {
						this.triggerConfigurationChange(foldersConfigurationChangeEvent.change(workspaceConfigurationChangeEvent), ConfigurationTarget.WORKSPACE_FOLDER);
S
Sandeep Somavarapu 已提交
558
						this._onDidChangeWorkspaceFolders.fire(changes);
559 560
					});
			} else {
561
				this.triggerConfigurationChange(workspaceConfigurationChangeEvent, ConfigurationTarget.WORKSPACE);
S
Sandeep Somavarapu 已提交
562
			}
563
		}
564 565 566 567 568 569
		return TPromise.as(null);
	}

	private onWorkspaceFolderConfigurationChanged(folder: IWorkspaceFolder, key?: string): TPromise<void> {
		return this.loadFolderConfigurations([folder])
			.then(([folderConfiguration]) => {
S
Sandeep Somavarapu 已提交
570
				const folderChangedKeys = this._configuration.compareAndUpdateFolderConfiguration(folder.uri, folderConfiguration);
571
				if (this.getWorkbenchState() === WorkbenchState.FOLDER) {
S
Sandeep Somavarapu 已提交
572
					const workspaceChangedKeys = this._configuration.compareAndUpdateWorkspaceConfiguration(folderConfiguration);
573 574 575 576 577 578 579
					this.triggerConfigurationChange(workspaceChangedKeys, ConfigurationTarget.WORKSPACE);
				} else {
					this.triggerConfigurationChange(folderChangedKeys, ConfigurationTarget.WORKSPACE_FOLDER);
				}
			});
	}

580 581
	private onFoldersChanged(): TPromise<ConfigurationChangeEvent> {
		let changeEvent = new ConfigurationChangeEvent();
582

583 584
		// Remove the configurations of deleted folders
		for (const key of this.cachedFolderConfigs.keys()) {
585
			if (!this.workspace.folders.filter(folder => folder.uri.toString() === key.toString())[0]) {
586 587
				const folderConfiguration = this.cachedFolderConfigs.get(key);
				folderConfiguration.dispose();
588
				this.cachedFolderConfigs.delete(key);
S
Sandeep Somavarapu 已提交
589
				changeEvent = changeEvent.change(this._configuration.compareAndDeleteFolderConfiguration(key));
590 591 592
			}
		}

593
		const toInitialize = this.workspace.folders.filter(folder => !this.cachedFolderConfigs.has(folder.uri));
594
		if (toInitialize.length) {
595 596 597
			return this.loadFolderConfigurations(toInitialize)
				.then(folderConfigurations => {
					folderConfigurations.forEach((folderConfiguration, index) => {
S
Sandeep Somavarapu 已提交
598
						changeEvent = changeEvent.change(this._configuration.compareAndUpdateFolderConfiguration(toInitialize[index].uri, folderConfiguration));
599
					});
600
					return changeEvent;
601
				});
602
		}
603
		return TPromise.as(changeEvent);
604 605
	}

S
Sandeep Somavarapu 已提交
606
	private loadFolderConfigurations(folders: IWorkspaceFolder[]): TPromise<ConfigurationModel[]> {
607
		return TPromise.join([...folders.map(folder => {
608 609 610 611 612 613
			let folderConfiguration = this.cachedFolderConfigs.get(folder.uri);
			if (!folderConfiguration) {
				folderConfiguration = new FolderConfiguration(folder, this.workspaceSettingsRootFolder, this.getWorkbenchState(), this.environmentService, this.fileService);
				this._register(folderConfiguration.onDidChange(() => this.onWorkspaceFolderConfigurationChanged(folder)));
				this.cachedFolderConfigs.set(folder.uri, this._register(folderConfiguration));
			}
614 615
			return folderConfiguration.loadConfiguration();
		})]);
616 617
	}

618
	private writeConfigurationValue(key: string, value: any, target: ConfigurationTarget, overrides: IConfigurationOverrides, donotNotifyError: boolean): TPromise<void> {
619 620 621 622 623 624
		if (target === ConfigurationTarget.DEFAULT) {
			return TPromise.wrapError(new Error('Invalid configuration target'));
		}

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

S
Sandeep Somavarapu 已提交
629
		return this.configurationEditingService.writeConfiguration(target, { key, value }, { scopes: overrides, donotNotifyError })
630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659
			.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;
660
		}
661 662 663 664 665 666 667 668 669 670

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

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

		return ConfigurationTarget.USER;
671 672
	}

673 674 675
	private triggerConfigurationChange(configurationEvent: ConfigurationChangeEvent, target: ConfigurationTarget): void {
		if (configurationEvent.affectedKeys.length) {
			configurationEvent.telemetryData(target, this.getTargetConfiguration(target));
676
			this._onDidChangeConfiguration.fire(new WorkspaceConfigurationChangeEvent(configurationEvent, this.workspace));
677
		}
678
	}
679

680 681 682 683 684 685 686 687
	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;
688
		}
689
		return {};
690
	}
691 692 693 694 695 696 697 698 699

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

		return path1 === path2;
	}
700
}
701

702 703 704 705 706 707 708 709 710 711 712 713 714
interface IExportedConfigurationNode {
	name: string;
	description: string;
	default: any;
	type: string | string[];
	enum?: any[];
	enumDescriptions?: string[];
}

interface IConfigurationExport {
	settings: IExportedConfigurationNode[];
	buildTime: number;
	commit: string;
715
	buildNumber: number;
716 717 718
}

export class DefaultConfigurationExportHelper {
719 720 721 722 723

	constructor(
		@IEnvironmentService environmentService: IEnvironmentService,
		@IExtensionService private extensionService: IExtensionService,
		@ICommandService private commandService: ICommandService) {
724 725
		if (environmentService.args['export-default-configuration']) {
			this.writeConfigModelAndQuit(environmentService.args['export-default-configuration']);
726 727 728 729
		}
	}

	private writeConfigModelAndQuit(targetPath: string): TPromise<void> {
730
		return this.extensionService.whenInstalledExtensionsRegistered()
731 732 733 734 735 736
			.then(() => this.writeConfigModel(targetPath))
			.then(() => this.commandService.executeCommand('workbench.action.quit'))
			.then(() => { });
	}

	private writeConfigModel(targetPath: string): TPromise<void> {
737 738 739 740 741 742 743
		const config = this.getConfigModel();

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

	private getConfigModel(): IConfigurationExport {
744 745
		const configRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
		const configurations = configRegistry.getConfigurations().slice();
746
		const settings: IExportedConfigurationNode[] = [];
747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766

		const processProperty = (name: string, prop: IConfigurationPropertySchema) => {
			const propDetails: IExportedConfigurationNode = {
				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);
		};

767 768 769
		const processConfig = (config: IConfigurationNode) => {
			if (config.properties) {
				for (let name in config.properties) {
770
					processProperty(name, config.properties[name]);
771 772 773 774 775 776 777 778
				}
			}

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

779
		configurations.forEach(processConfig);
780

781 782 783 784 785
		const excludedProps = configRegistry.getExcludedConfigurationProperties();
		for (let name in excludedProps) {
			processProperty(name, excludedProps[name]);
		}

786 787 788 789
		const result: IConfigurationExport = {
			settings: settings.sort((a, b) => a.name.localeCompare(b.name)),
			buildTime: Date.now(),
			commit: product.commit,
790
			buildNumber: product.settingsSearchBuildId
791
		};
792

793
		return result;
794 795
	}
}