configurationService.ts 33.6 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 { ResourceMap } 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';
S
#47154  
Sandeep Somavarapu 已提交
19
import { IFileService } from 'vs/platform/files/common/files';
B
Benjamin Pasero 已提交
20
import { isLinux } from 'vs/base/common/platform';
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';
29
import { getWorkspaceLabel, IWorkspaceIdentifier, isWorkspaceIdentifier, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } 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 { UserConfiguration } from 'vs/platform/configuration/node/configuration';
S
Sandeep Somavarapu 已提交
41 42
import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema';
import { localize } from 'vs/nls';
43
import { isEqual, hasToIgnoreCase } from 'vs/base/common/resources';
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: ResourceMap<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
#47154  
Sandeep Somavarapu 已提交
70
	private fileService: IFileService;
S
Sandeep Somavarapu 已提交
71
	private configurationEditingService: ConfigurationEditingService;
S
Sandeep Somavarapu 已提交
72
	private jsonEditingService: JSONEditingService;
73

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

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

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

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

89 90
	// Workspace Context Service Impl

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

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

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

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

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

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

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

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

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

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

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

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

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

152 153 154 155 156
		// 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 已提交
157 158
			}

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

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

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

167 168 169 170
			// 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);
171

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

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

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

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

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

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

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

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

206 207 208 209 210 211 212 213
				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 已提交
214

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

		return TPromise.as(void 0);
	}

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

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

238 239
	// Workspace Configuration Service Impl

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

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

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

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

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

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

298
	initialize(arg: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWindowConfiguration, postInitialisationTask: () => void = () => null): TPromise<any> {
299
		return this.createWorkspace(arg)
S
Sandeep Somavarapu 已提交
300
			.then(workspace => this.updateWorkspaceAndInitializeConfiguration(workspace, postInitialisationTask));
301 302
	}

S
#47154  
Sandeep Somavarapu 已提交
303 304
	acquireFileService(fileService: IFileService): void {
		this.fileService = fileService;
305
		const changedWorkspaceFolders: IWorkspaceFolder[] = [];
306 307 308 309 310 311 312 313 314 315 316 317
		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);
				}
			});
318 319
	}

S
#47154  
Sandeep Somavarapu 已提交
320 321 322
	acquireInstantiationService(instantiationService: IInstantiationService): void {
		this.configurationEditingService = instantiationService.createInstance(ConfigurationEditingService);
		this.jsonEditingService = instantiationService.createInstance(JSONEditingService);
323 324
	}

325
	private createWorkspace(arg: IWorkspaceIdentifier | URI | IWindowConfiguration): TPromise<Workspace> {
326
		if (isWorkspaceIdentifier(arg)) {
327
			return this.createMulitFolderWorkspace(arg);
328 329
		}

330
		if (isSingleFolderWorkspaceIdentifier(arg)) {
331
			return this.createSingleFolderWorkspace(arg);
332 333
		}

334
		return this.createEmptyWorkspace(arg);
335 336
	}

337
	private createMulitFolderWorkspace(workspaceIdentifier: IWorkspaceIdentifier): TPromise<Workspace> {
338
		const workspaceConfigPath = URI.file(workspaceIdentifier.configPath);
339
		return this.workspaceConfiguration.load(workspaceConfigPath)
340
			.then(() => {
341
				const workspaceFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), URI.file(dirname(workspaceConfigPath.fsPath)));
342
				const workspaceId = workspaceIdentifier.id;
343
				const workspaceName = getWorkspaceLabel({ id: workspaceId, configPath: workspaceConfigPath.fsPath }, this.environmentService);
344
				return new Workspace(workspaceId, workspaceName, workspaceFolders, workspaceConfigPath);
345 346 347
			});
	}

348
	private createSingleFolderWorkspace(folder: URI): TPromise<Workspace> {
349 350 351 352 353
		if (folder.scheme === Schemas.file) {
			return stat(folder.fsPath)
				.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!
					const id = createHash('md5').update(folder.fsPath).update(ctime ? String(ctime) : '').digest('hex');
S
Sandeep Somavarapu 已提交
354
					return new Workspace(id, getWorkspaceLabel(folder, this.environmentService), toWorkspaceFolders([{ path: folder.fsPath }]), null, ctime);
355 356 357
				});
		} else {
			const id = createHash('md5').update(folder.toString()).digest('hex');
S
Sandeep Somavarapu 已提交
358
			return TPromise.as(new Workspace(id, getWorkspaceLabel(folder, this.environmentService), toWorkspaceFolders([{ uri: folder.toString() }]), null));
359
		}
360 361
	}

362
	private createEmptyWorkspace(configuration: IWindowConfiguration): TPromise<Workspace> {
363
		let id = configuration.backupPath ? URI.from({ path: basename(configuration.backupPath), scheme: 'empty' }).toString() : '';
364 365 366
		return TPromise.as(new Workspace(id));
	}

S
Sandeep Somavarapu 已提交
367
	private updateWorkspaceAndInitializeConfiguration(workspace: Workspace, postInitialisationTask: () => void): TPromise<void> {
S
Sandeep Somavarapu 已提交
368
		const hasWorkspaceBefore = !!this.workspace;
369 370 371
		let previousState: WorkbenchState;
		let previousWorkspacePath: string;
		let previousFolders: WorkspaceFolder[];
S
Sandeep Somavarapu 已提交
372 373 374 375 376

		if (hasWorkspaceBefore) {
			previousState = this.getWorkbenchState();
			previousWorkspacePath = this.workspace.configuration ? this.workspace.configuration.fsPath : void 0;
			previousFolders = this.workspace.folders;
S
Sandeep Somavarapu 已提交
377 378 379
			this.workspace.update(workspace);
		} else {
			this.workspace = workspace;
380
		}
S
Sandeep Somavarapu 已提交
381 382

		return this.initializeConfiguration().then(() => {
S
Sandeep Somavarapu 已提交
383

S
Sandeep Somavarapu 已提交
384
			postInitialisationTask(); // Post initialisation task should be run before triggering events.
S
Sandeep Somavarapu 已提交
385

S
Sandeep Somavarapu 已提交
386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401
			// 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 已提交
402 403
			}
		});
404 405
	}

S
Sandeep Somavarapu 已提交
406
	private compareFolders(currentFolders: IWorkspaceFolder[], newFolders: IWorkspaceFolder[]): IWorkspaceFoldersChangeEvent {
407
		const result = { added: [], removed: [], changed: [] } as IWorkspaceFoldersChangeEvent;
408
		result.added = newFolders.filter(newFolder => !currentFolders.some(currentFolder => newFolder.uri.toString() === currentFolder.uri.toString()));
S
Sandeep Somavarapu 已提交
409 410 411 412 413 414 415 416 417 418 419 420
		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);
			}
		}
421 422 423
		return result;
	}

424
	private initializeConfiguration(): TPromise<void> {
S
Sandeep Somavarapu 已提交
425
		this.registerConfigurationSchemas();
426
		return this.loadConfiguration();
427 428
	}

S
Sandeep Somavarapu 已提交
429
	private reloadUserConfiguration(key?: string): TPromise<void> {
S
Sandeep Somavarapu 已提交
430
		return this.userConfiguration.reload();
S
Sandeep Somavarapu 已提交
431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447
	}

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

448 449
	private loadConfiguration(): TPromise<void> {
		// reset caches
450
		this.cachedFolderConfigs = new ResourceMap<FolderConfiguration>();
451

452 453 454 455 456
		const folders = this.workspace.folders;
		return this.loadFolderConfigurations(folders)
			.then((folderConfigurations) => {

				let workspaceConfiguration = this.getWorkspaceConfigurationModel(folderConfigurations);
457
				const folderConfigurationModels = new ResourceMap<ConfigurationModel>();
458 459
				folderConfigurations.forEach((folderConfiguration, index) => folderConfigurationModels.set(folders[index].uri, folderConfiguration));

460
				const currentConfiguration = this._configuration;
461
				this._configuration = new Configuration(this.defaultConfiguration, this.userConfiguration.configurationModel, workspaceConfiguration, folderConfigurationModels, new ConfigurationModel(), new ResourceMap<ConfigurationModel>(), this.getWorkbenchState() !== WorkbenchState.EMPTY ? this.workspace : null); //TODO: Sandy Avoid passing null
462

463 464 465 466
				if (currentConfiguration) {
					const changedKeys = this._configuration.compare(currentConfiguration);
					this.triggerConfigurationChange(new ConfigurationChangeEvent().change(changedKeys), ConfigurationTarget.WORKSPACE);
				} else {
S
Sandeep Somavarapu 已提交
467
					this._onDidChangeConfiguration.fire(new AllKeysConfigurationChangeEvent(this._configuration, ConfigurationTarget.WORKSPACE, this.getTargetConfiguration(ConfigurationTarget.WORKSPACE)));
468
				}
469
			});
470 471
	}

S
Sandeep Somavarapu 已提交
472
	private getWorkspaceConfigurationModel(folderConfigurations: ConfigurationModel[]): ConfigurationModel {
473 474 475 476
		switch (this.getWorkbenchState()) {
			case WorkbenchState.FOLDER:
				return folderConfigurations[0];
			case WorkbenchState.WORKSPACE:
S
Sandeep Somavarapu 已提交
477
				return this.workspaceConfiguration.getConfiguration();
478
			default:
479
				return new ConfigurationModel();
480
		}
481 482
	}

S
Sandeep Somavarapu 已提交
483 484 485 486 487
	private onDefaultConfigurationChanged(keys: string[]): void {
		this.defaultConfiguration = new DefaultConfigurationModel();
		this.registerConfigurationSchemas();
		if (this.workspace && this._configuration) {
			this._configuration.updateDefaultConfiguration(this.defaultConfiguration);
S
Sandeep Somavarapu 已提交
488 489 490 491 492 493
			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 已提交
494 495 496 497
			this.triggerConfigurationChange(new ConfigurationChangeEvent().change(keys), ConfigurationTarget.DEFAULT);
		}
	}

S
Sandeep Somavarapu 已提交
498 499
	private registerConfigurationSchemas(): void {
		if (this.workspace) {
500
			const jsonRegistry = Registry.as<IJSONContributionRegistry>(JSONExtensions.JSONContribution);
S
Sandeep Somavarapu 已提交
501 502 503 504 505 506 507 508 509 510 511 512 513 514
			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 已提交
515 516

			if (WorkbenchState.WORKSPACE === this.getWorkbenchState()) {
S
Sandeep Somavarapu 已提交
517 518 519 520
				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 已提交
521
			} else {
S
Sandeep Somavarapu 已提交
522 523
				jsonRegistry.registerSchema(workspaceSettingsSchemaId, workspaceSettingsSchema);
				jsonRegistry.registerSchema(folderSettingsSchemaId, workspaceSettingsSchema);
S
Sandeep Somavarapu 已提交
524 525 526 527
			}
		}
	}

S
Sandeep Somavarapu 已提交
528 529 530
	private onUserConfigurationChanged(): void {
		let keys = this._configuration.compareAndUpdateUserConfiguration(this.userConfiguration.configurationModel);
		this.triggerConfigurationChange(keys, ConfigurationTarget.USER);
531 532
	}

533 534
	private onWorkspaceConfigurationChanged(): TPromise<void> {
		if (this.workspace && this.workspace.configuration && this._configuration) {
S
Sandeep Somavarapu 已提交
535
			const workspaceConfigurationChangeEvent = this._configuration.compareAndUpdateWorkspaceConfiguration(this.workspaceConfiguration.getConfiguration());
536
			let configuredFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), URI.file(dirname(this.workspace.configuration.fsPath)));
537
			const changes = this.compareFolders(this.workspace.folders, configuredFolders);
538
			if (changes.added.length || changes.removed.length || changes.changed.length) {
539
				this.workspace.folders = configuredFolders;
540
				return this.onFoldersChanged()
541 542
					.then(foldersConfigurationChangeEvent => {
						this.triggerConfigurationChange(foldersConfigurationChangeEvent.change(workspaceConfigurationChangeEvent), ConfigurationTarget.WORKSPACE_FOLDER);
S
Sandeep Somavarapu 已提交
543
						this._onDidChangeWorkspaceFolders.fire(changes);
544 545
					});
			} else {
546
				this.triggerConfigurationChange(workspaceConfigurationChangeEvent, ConfigurationTarget.WORKSPACE);
S
Sandeep Somavarapu 已提交
547
			}
548
		}
549 550 551 552 553 554
		return TPromise.as(null);
	}

	private onWorkspaceFolderConfigurationChanged(folder: IWorkspaceFolder, key?: string): TPromise<void> {
		return this.loadFolderConfigurations([folder])
			.then(([folderConfiguration]) => {
S
Sandeep Somavarapu 已提交
555
				const folderChangedKeys = this._configuration.compareAndUpdateFolderConfiguration(folder.uri, folderConfiguration);
556
				if (this.getWorkbenchState() === WorkbenchState.FOLDER) {
S
Sandeep Somavarapu 已提交
557
					const workspaceChangedKeys = this._configuration.compareAndUpdateWorkspaceConfiguration(folderConfiguration);
558 559 560 561 562 563 564
					this.triggerConfigurationChange(workspaceChangedKeys, ConfigurationTarget.WORKSPACE);
				} else {
					this.triggerConfigurationChange(folderChangedKeys, ConfigurationTarget.WORKSPACE_FOLDER);
				}
			});
	}

565 566
	private onFoldersChanged(): TPromise<ConfigurationChangeEvent> {
		let changeEvent = new ConfigurationChangeEvent();
567

568 569
		// Remove the configurations of deleted folders
		for (const key of this.cachedFolderConfigs.keys()) {
570
			if (!this.workspace.folders.filter(folder => folder.uri.toString() === key.toString())[0]) {
571 572
				const folderConfiguration = this.cachedFolderConfigs.get(key);
				folderConfiguration.dispose();
573
				this.cachedFolderConfigs.delete(key);
S
Sandeep Somavarapu 已提交
574
				changeEvent = changeEvent.change(this._configuration.compareAndDeleteFolderConfiguration(key));
575 576 577
			}
		}

578
		const toInitialize = this.workspace.folders.filter(folder => !this.cachedFolderConfigs.has(folder.uri));
579
		if (toInitialize.length) {
580 581 582
			return this.loadFolderConfigurations(toInitialize)
				.then(folderConfigurations => {
					folderConfigurations.forEach((folderConfiguration, index) => {
S
Sandeep Somavarapu 已提交
583
						changeEvent = changeEvent.change(this._configuration.compareAndUpdateFolderConfiguration(toInitialize[index].uri, folderConfiguration));
584
					});
585
					return changeEvent;
586
				});
587
		}
588
		return TPromise.as(changeEvent);
589 590
	}

S
Sandeep Somavarapu 已提交
591
	private loadFolderConfigurations(folders: IWorkspaceFolder[]): TPromise<ConfigurationModel[]> {
592
		return TPromise.join([...folders.map(folder => {
593 594 595 596 597 598
			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));
			}
599 600
			return folderConfiguration.loadConfiguration();
		})]);
601 602
	}

603
	private writeConfigurationValue(key: string, value: any, target: ConfigurationTarget, overrides: IConfigurationOverrides, donotNotifyError: boolean): TPromise<void> {
604 605 606 607 608 609
		if (target === ConfigurationTarget.DEFAULT) {
			return TPromise.wrapError(new Error('Invalid configuration target'));
		}

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

S
Sandeep Somavarapu 已提交
614
		return this.configurationEditingService.writeConfiguration(target, { key, value }, { scopes: overrides, donotNotifyError })
615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644
			.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;
645
		}
646 647 648 649 650 651 652 653 654 655

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

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

		return ConfigurationTarget.USER;
656 657
	}

658 659 660
	private triggerConfigurationChange(configurationEvent: ConfigurationChangeEvent, target: ConfigurationTarget): void {
		if (configurationEvent.affectedKeys.length) {
			configurationEvent.telemetryData(target, this.getTargetConfiguration(target));
661
			this._onDidChangeConfiguration.fire(new WorkspaceConfigurationChangeEvent(configurationEvent, this.workspace));
662
		}
663
	}
664

665 666 667 668 669 670 671 672
	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;
673
		}
674
		return {};
675
	}
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
		}
	}

	private writeConfigModelAndQuit(targetPath: string): TPromise<void> {
706
		return this.extensionService.whenInstalledExtensionsRegistered()
707 708 709 710 711 712
			.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 721
		const configRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
		const configurations = configRegistry.getConfigurations().slice();
722
		const settings: IExportedConfigurationNode[] = [];
723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742

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

743 744 745
		const processConfig = (config: IConfigurationNode) => {
			if (config.properties) {
				for (let name in config.properties) {
746
					processProperty(name, config.properties[name]);
747 748 749 750 751 752 753 754
				}
			}

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

755
		configurations.forEach(processConfig);
756

757 758 759 760 761
		const excludedProps = configRegistry.getExcludedConfigurationProperties();
		for (let name in excludedProps) {
			processProperty(name, excludedProps[name]);
		}

762 763 764 765
		const result: IConfigurationExport = {
			settings: settings.sort((a, b) => a.name.localeCompare(b.name)),
			buildTime: Date.now(),
			commit: product.commit,
766
			buildNumber: product.settingsSearchBuildId
767
		};
768

769
		return result;
770 771
	}
}