configurationService.ts 33.7 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';
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 { UserConfiguration } from 'vs/platform/configuration/node/configuration';
B
Benjamin Pasero 已提交
41
import { getBaseLabel } from 'vs/base/common/labels';
S
Sandeep Somavarapu 已提交
42 43
import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema';
import { localize } from 'vs/nls';
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) && this.pathEquals(this.workspace.folders[0].uri.fsPath, 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
	}

S
Sandeep Somavarapu 已提交
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 | ISingleFolderWorkspaceIdentifier | 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(singleFolderWorkspaceIdentifier: ISingleFolderWorkspaceIdentifier): TPromise<Workspace> {
349
		const folderPath = URI.file(singleFolderWorkspaceIdentifier);
350
		return stat(folderPath.fsPath)
351
			.then(workspaceStat => {
B
Benjamin Pasero 已提交
352
				const ctime = isLinux ? workspaceStat.ino : workspaceStat.birthtime.getTime(); // On Linux, birthtime is ctime, so we cannot use it! We use the ino instead!
353 354
				const id = createHash('md5').update(folderPath.fsPath).update(ctime ? String(ctime) : '').digest('hex');
				const folder = URI.file(folderPath.fsPath);
B
Benjamin Pasero 已提交
355
				return new Workspace(id, getBaseLabel(folder), toWorkspaceFolders([{ path: folder.fsPath }]), null, ctime);
356 357 358
			});
	}

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

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

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

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

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

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

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

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

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

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

445 446
	private loadConfiguration(): TPromise<void> {
		// reset caches
447
		this.cachedFolderConfigs = new ResourceMap<FolderConfiguration>();
448

449 450 451 452 453
		const folders = this.workspace.folders;
		return this.loadFolderConfigurations(folders)
			.then((folderConfigurations) => {

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

457
				const currentConfiguration = this._configuration;
458
				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
459

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

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

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

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

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

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

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

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

562 563
	private onFoldersChanged(): TPromise<ConfigurationChangeEvent> {
		let changeEvent = new ConfigurationChangeEvent();
564

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

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

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

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

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

S
Sandeep Somavarapu 已提交
611
		return this.configurationEditingService.writeConfiguration(target, { key, value }, { scopes: overrides, donotNotifyError })
612 613 614 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
			.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;
642
		}
643 644 645 646 647 648 649 650 651 652

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

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

		return ConfigurationTarget.USER;
653 654
	}

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

662 663 664 665 666 667 668 669
	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;
670
		}
671
		return {};
672
	}
673 674 675 676 677 678 679 680 681

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

		return path1 === path2;
	}
682
}
683

684 685 686 687 688 689 690 691 692 693 694 695 696
interface IExportedConfigurationNode {
	name: string;
	description: string;
	default: any;
	type: string | string[];
	enum?: any[];
	enumDescriptions?: string[];
}

interface IConfigurationExport {
	settings: IExportedConfigurationNode[];
	buildTime: number;
	commit: string;
697
	buildNumber: number;
698 699 700
}

export class DefaultConfigurationExportHelper {
701 702 703 704 705

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

	private writeConfigModelAndQuit(targetPath: string): TPromise<void> {
712
		return this.extensionService.whenInstalledExtensionsRegistered()
713 714 715 716 717 718
			.then(() => this.writeConfigModel(targetPath))
			.then(() => this.commandService.executeCommand('workbench.action.quit'))
			.then(() => { });
	}

	private writeConfigModel(targetPath: string): TPromise<void> {
719 720 721 722 723 724 725
		const config = this.getConfigModel();

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

	private getConfigModel(): IConfigurationExport {
726 727
		const configRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
		const configurations = configRegistry.getConfigurations().slice();
728
		const settings: IExportedConfigurationNode[] = [];
729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748

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

749 750 751
		const processConfig = (config: IConfigurationNode) => {
			if (config.properties) {
				for (let name in config.properties) {
752
					processProperty(name, config.properties[name]);
753 754 755 756 757 758 759 760
				}
			}

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

761
		configurations.forEach(processConfig);
762

763 764 765 766 767
		const excludedProps = configRegistry.getExcludedConfigurationProperties();
		for (let name in excludedProps) {
			processProperty(name, excludedProps[name]);
		}

768 769 770 771
		const result: IConfigurationExport = {
			settings: settings.sort((a, b) => a.name.localeCompare(b.name)),
			buildTime: Date.now(),
			commit: product.commit,
772
			buildNumber: product.settingsSearchBuildId
773
		};
774

775
		return result;
776 777
	}
}