configuration.ts 13.6 KB
Newer Older
S
Sandeep Somavarapu 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import URI from 'vs/base/common/uri';
import * as paths from 'vs/base/common/paths';
import { TPromise } from 'vs/base/common/winjs.base';
import Event, { Emitter } from 'vs/base/common/event';
import { readFile } from 'vs/base/node/pfs';
import * as errors from 'vs/base/common/errors';
import * as collections from 'vs/base/common/collections';
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
import { RunOnceScheduler } from 'vs/base/common/async';
import { FileChangeType, FileChangesEvent } from 'vs/platform/files/common/files';
import { isLinux } from 'vs/base/common/platform';
import { ConfigWatcher } from 'vs/base/node/config';
S
Sandeep Somavarapu 已提交
18 19 20
import { ConfigurationModel, ConfigurationModelParser } from 'vs/platform/configuration/common/configurationModels';
import { WorkspaceConfigurationModelParser, FolderSettingsModelParser, StandaloneConfigurationModelParser, WorkspaceSettingsModel } from 'vs/workbench/services/configuration/common/configurationModels';
import { WORKSPACE_STANDALONE_CONFIGURATIONS, FOLDER_SETTINGS_PATH, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY } from 'vs/workbench/services/configuration/common/configuration';
S
Sandeep Somavarapu 已提交
21 22 23
import { IStoredWorkspace, IStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces';
import * as extfs from 'vs/base/node/extfs';
import { JSONEditingService } from 'vs/workbench/services/configuration/node/jsonEditingService';
S
Sandeep Somavarapu 已提交
24 25
import { WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
26
import { relative } from 'path';
S
Sandeep Somavarapu 已提交
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77

// node.hs helper functions

interface IStat {
	resource: URI;
	isDirectory?: boolean;
	children?: { resource: URI; }[];
}

interface IContent {
	resource: URI;
	value: string;
}

function resolveContents(resources: URI[]): TPromise<IContent[]> {
	const contents: IContent[] = [];

	return TPromise.join(resources.map(resource => {
		return resolveContent(resource).then(content => {
			contents.push(content);
		});
	})).then(() => contents);
}

function resolveContent(resource: URI): TPromise<IContent> {
	return readFile(resource.fsPath).then(contents => ({ resource, value: contents.toString() }));
}

function resolveStat(resource: URI): TPromise<IStat> {
	return new TPromise<IStat>((c, e) => {
		extfs.readdir(resource.fsPath, (error, children) => {
			if (error) {
				if ((<any>error).code === 'ENOTDIR') {
					c({ resource });
				} else {
					e(error);
				}
			} else {
				c({
					resource,
					isDirectory: true,
					children: children.map(child => { return { resource: URI.file(paths.join(resource.fsPath, child)) }; })
				});
			}
		});
	});
}

export class WorkspaceConfiguration extends Disposable {

	private _workspaceConfigPath: URI;
S
Sandeep Somavarapu 已提交
78
	private _workspaceConfigurationWatcher: ConfigWatcher<WorkspaceConfigurationModelParser>;
S
Sandeep Somavarapu 已提交
79 80 81 82 83 84 85 86 87 88 89 90
	private _workspaceConfigurationWatcherDisposables: IDisposable[] = [];

	private _onDidUpdateConfiguration: Emitter<void> = this._register(new Emitter<void>());
	public readonly onDidUpdateConfiguration: Event<void> = this._onDidUpdateConfiguration.event;

	load(workspaceConfigPath: URI): TPromise<void> {
		if (this._workspaceConfigPath && this._workspaceConfigPath.fsPath === workspaceConfigPath.fsPath) {
			return this.reload();
		}

		this._workspaceConfigPath = workspaceConfigPath;

S
Sandeep Somavarapu 已提交
91
		this.stopListeningToWatcher();
S
Sandeep Somavarapu 已提交
92
		return new TPromise<void>((c, e) => {
S
Sandeep Somavarapu 已提交
93 94
			const defaultConfig = new WorkspaceConfigurationModelParser(this._workspaceConfigPath.fsPath);
			defaultConfig.parse(JSON.stringify({ folders: [] } as IStoredWorkspace, null, '\t'));
S
Sandeep Somavarapu 已提交
95 96 97
			this._workspaceConfigurationWatcher = new ConfigWatcher(this._workspaceConfigPath.fsPath, {
				changeBufferDelay: 300,
				onError: error => errors.onUnexpectedError(error),
S
Sandeep Somavarapu 已提交
98
				defaultConfig,
S
Sandeep Somavarapu 已提交
99
				parse: (content: string, parseErrors: any[]) => {
S
Sandeep Somavarapu 已提交
100 101
					const workspaceConfigurationModel = new WorkspaceConfigurationModelParser(this._workspaceConfigPath.fsPath);
					workspaceConfigurationModel.parse(content);
S
Sandeep Somavarapu 已提交
102 103 104 105
					parseErrors = [...workspaceConfigurationModel.errors];
					return workspaceConfigurationModel;
				}, initCallback: () => c(null)
			});
S
Sandeep Somavarapu 已提交
106
			this.listenToWatcher();
S
Sandeep Somavarapu 已提交
107 108 109
		});
	}

S
Sandeep Somavarapu 已提交
110 111
	private get workspaceConfigurationModelParser(): WorkspaceConfigurationModelParser {
		return this._workspaceConfigurationWatcher ? this._workspaceConfigurationWatcher.getConfig() : new WorkspaceConfigurationModelParser(this._workspaceConfigPath ? this._workspaceConfigPath.fsPath : '');
S
Sandeep Somavarapu 已提交
112 113 114
	}

	reload(): TPromise<void> {
S
Sandeep Somavarapu 已提交
115 116 117 118 119
		this.stopListeningToWatcher();
		return new TPromise<void>(c => this._workspaceConfigurationWatcher.reload(() => {
			this.listenToWatcher();
			c(null);
		}));
S
Sandeep Somavarapu 已提交
120 121 122
	}

	getFolders(): IStoredWorkspaceFolder[] {
S
Sandeep Somavarapu 已提交
123
		return this.workspaceConfigurationModelParser.folders;
S
Sandeep Somavarapu 已提交
124 125 126 127 128 129 130 131
	}

	setFolders(folders: IStoredWorkspaceFolder[], jsonEditingService: JSONEditingService): TPromise<void> {
		return jsonEditingService.write(this._workspaceConfigPath, { key: 'folders', value: folders }, true)
			.then(() => this.reload());
	}

	getConfiguration(): ConfigurationModel {
S
Sandeep Somavarapu 已提交
132
		return this.workspaceConfigurationModelParser.workspaceSettingsModel;
S
Sandeep Somavarapu 已提交
133 134
	}

S
Sandeep Somavarapu 已提交
135
	getWorkspaceSettings(): WorkspaceSettingsModel {
S
Sandeep Somavarapu 已提交
136 137 138 139 140 141
		return this.workspaceConfigurationModelParser.workspaceSettingsModel;
	}

	reprocessWorkspaceSettings(): ConfigurationModel {
		this.workspaceConfigurationModelParser.reprocessWorkspaceSettings();
		return this.getConfiguration();
S
Sandeep Somavarapu 已提交
142 143
	}

S
Sandeep Somavarapu 已提交
144 145 146 147 148 149 150 151 152
	private listenToWatcher() {
		this._workspaceConfigurationWatcherDisposables.push(this._workspaceConfigurationWatcher);
		this._workspaceConfigurationWatcher.onDidUpdateConfiguration(() => this._onDidUpdateConfiguration.fire(), this, this._workspaceConfigurationWatcherDisposables);
	}

	private stopListeningToWatcher() {
		this._workspaceConfigurationWatcherDisposables = dispose(this._workspaceConfigurationWatcherDisposables);
	}

S
Sandeep Somavarapu 已提交
153 154 155 156 157 158 159 160 161 162 163
	dispose(): void {
		dispose(this._workspaceConfigurationWatcherDisposables);
		super.dispose();
	}
}

export class FolderConfiguration extends Disposable {

	private static RELOAD_CONFIGURATION_DELAY = 50;

	private bulkFetchFromWorkspacePromise: TPromise;
S
Sandeep Somavarapu 已提交
164 165 166 167 168
	private workspaceFilePathToConfiguration: { [relativeWorkspacePath: string]: TPromise<ConfigurationModelParser> };

	private _folderSettingsModelParser: FolderSettingsModelParser;
	private _standAloneConfigurations: ConfigurationModel[] = [];
	private _cache: ConfigurationModel = new ConfigurationModel();
S
Sandeep Somavarapu 已提交
169 170

	private reloadConfigurationScheduler: RunOnceScheduler;
S
Sandeep Somavarapu 已提交
171
	private reloadConfigurationEventEmitter: Emitter<ConfigurationModel> = new Emitter<ConfigurationModel>();
S
Sandeep Somavarapu 已提交
172

S
Sandeep Somavarapu 已提交
173
	constructor(private folder: URI, private configFolderRelativePath: string, workbenchState: WorkbenchState) {
S
Sandeep Somavarapu 已提交
174 175
		super();

S
Sandeep Somavarapu 已提交
176
		this._folderSettingsModelParser = new FolderSettingsModelParser(FOLDER_SETTINGS_PATH, WorkbenchState.WORKSPACE === workbenchState ? ConfigurationScope.RESOURCE : void 0);
S
Sandeep Somavarapu 已提交
177 178 179 180
		this.workspaceFilePathToConfiguration = Object.create(null);
		this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.loadConfiguration().then(configuration => this.reloadConfigurationEventEmitter.fire(configuration), errors.onUnexpectedError), FolderConfiguration.RELOAD_CONFIGURATION_DELAY));
	}

S
Sandeep Somavarapu 已提交
181
	loadConfiguration(): TPromise<ConfigurationModel> {
S
Sandeep Somavarapu 已提交
182 183
		// Load workspace locals
		return this.loadWorkspaceConfigFiles().then(workspaceConfigFiles => {
S
Sandeep Somavarapu 已提交
184
			this._standAloneConfigurations = Object.keys(workspaceConfigFiles).filter(key => key !== FOLDER_SETTINGS_PATH).map(key => <ConfigurationModel>workspaceConfigFiles[key].configurationModel);
S
Sandeep Somavarapu 已提交
185
			// Consolidate (support *.json files in the workspace settings folder)
S
Sandeep Somavarapu 已提交
186 187
			this.consolidate();
			return this._cache;
S
Sandeep Somavarapu 已提交
188 189 190
		});
	}

S
Sandeep Somavarapu 已提交
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
	reprocess(): ConfigurationModel {
		const oldKeys = this.getUnsupportedKeys();
		this._folderSettingsModelParser.reprocess();
		const newKeys = this.getUnsupportedKeys();
		if (this.hasKeysChanged(oldKeys, newKeys)) {
			this.consolidate();
		}
		return this._cache;
	}

	getUnsupportedKeys(): string[] {
		return this._folderSettingsModelParser.folderSettingsModel.unsupportedKeys;
	}

	private hasKeysChanged(oldKeys: string[], newKeys: string[]): boolean {
		if (oldKeys.length !== newKeys.length) {
			return true;
		}
		for (const key of oldKeys) {
			if (newKeys.indexOf(key) === -1) {
				return true;
			}
		}
		return false;
	}

	private consolidate(): void {
		this._cache = this._folderSettingsModelParser.folderSettingsModel.merge(...this._standAloneConfigurations);
	}

	private loadWorkspaceConfigFiles(): TPromise<{ [relativeWorkspacePath: string]: ConfigurationModelParser }> {
S
Sandeep Somavarapu 已提交
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
		// once: when invoked for the first time we fetch json files that contribute settings
		if (!this.bulkFetchFromWorkspacePromise) {
			this.bulkFetchFromWorkspacePromise = resolveStat(this.toResource(this.configFolderRelativePath)).then(stat => {
				if (!stat.isDirectory) {
					return TPromise.as([]);
				}

				return resolveContents(stat.children.filter(stat => {
					const isJson = paths.extname(stat.resource.fsPath) === '.json';
					if (!isJson) {
						return false; // only JSON files
					}

					return this.isWorkspaceConfigurationFile(this.toFolderRelativePath(stat.resource)); // only workspace config files
				}).map(stat => stat.resource));
			}, err => [] /* never fail this call */)
				.then((contents: IContent[]) => {
S
Sandeep Somavarapu 已提交
239
					contents.forEach(content => this.workspaceFilePathToConfiguration[this.toFolderRelativePath(content.resource)] = TPromise.as(this.createConfigurationModelParser(content)));
S
Sandeep Somavarapu 已提交
240 241 242 243 244 245 246 247
				}, errors.onUnexpectedError);
		}

		// on change: join on *all* configuration file promises so that we can merge them into a single configuration object. this
		// happens whenever a config file changes, is deleted, or added
		return this.bulkFetchFromWorkspacePromise.then(() => TPromise.join(this.workspaceFilePathToConfiguration));
	}

S
Sandeep Somavarapu 已提交
248
	public handleWorkspaceFileEvents(event: FileChangesEvent): TPromise<ConfigurationModel> {
S
Sandeep Somavarapu 已提交
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284
		const events = event.changes;
		let affectedByChanges = false;

		// Find changes that affect workspace configuration files
		for (let i = 0, len = events.length; i < len; i++) {
			const resource = events[i].resource;
			const isJson = paths.extname(resource.fsPath) === '.json';
			const isDeletedSettingsFolder = (events[i].type === FileChangeType.DELETED && paths.isEqual(paths.basename(resource.fsPath), this.configFolderRelativePath));
			if (!isJson && !isDeletedSettingsFolder) {
				continue; // only JSON files or the actual settings folder
			}

			const workspacePath = this.toFolderRelativePath(resource);
			if (!workspacePath) {
				continue; // event is not inside workspace
			}

			// Handle case where ".vscode" got deleted
			if (workspacePath === this.configFolderRelativePath && events[i].type === FileChangeType.DELETED) {
				this.workspaceFilePathToConfiguration = Object.create(null);
				affectedByChanges = true;
			}

			// only valid workspace config files
			if (!this.isWorkspaceConfigurationFile(workspacePath)) {
				continue;
			}

			// insert 'fetch-promises' for add and update events and
			// remove promises for delete events
			switch (events[i].type) {
				case FileChangeType.DELETED:
					affectedByChanges = collections.remove(this.workspaceFilePathToConfiguration, workspacePath);
					break;
				case FileChangeType.UPDATED:
				case FileChangeType.ADDED:
S
Sandeep Somavarapu 已提交
285
					this.workspaceFilePathToConfiguration[workspacePath] = resolveContent(resource).then(content => this.createConfigurationModelParser(content), errors.onUnexpectedError);
S
Sandeep Somavarapu 已提交
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
					affectedByChanges = true;
			}
		}

		if (!affectedByChanges) {
			return TPromise.as(null);
		}

		return new TPromise((c, e) => {
			let disposable = this.reloadConfigurationEventEmitter.event(configuration => {
				disposable.dispose();
				c(configuration);
			});
			// trigger reload of the configuration if we are affected by changes
			if (!this.reloadConfigurationScheduler.isScheduled()) {
				this.reloadConfigurationScheduler.schedule();
			}
		});
	}

S
Sandeep Somavarapu 已提交
306
	private createConfigurationModelParser(content: IContent): ConfigurationModelParser {
S
Sandeep Somavarapu 已提交
307
		const path = this.toFolderRelativePath(content.resource);
S
Sandeep Somavarapu 已提交
308 309 310
		if (path === FOLDER_SETTINGS_PATH) {
			this._folderSettingsModelParser.parse(content.value);
			return this._folderSettingsModelParser;
S
Sandeep Somavarapu 已提交
311 312 313
		} else {
			const matches = /\/([^\.]*)*\.json/.exec(path);
			if (matches && matches[1]) {
S
Sandeep Somavarapu 已提交
314 315 316
				const standAloneConfigurationModelParser = new StandaloneConfigurationModelParser(content.resource.toString(), matches[1]);
				standAloneConfigurationModelParser.parse(content.value);
				return standAloneConfigurationModelParser;
S
Sandeep Somavarapu 已提交
317 318
			}
		}
S
Sandeep Somavarapu 已提交
319
		return new ConfigurationModelParser(null);
S
Sandeep Somavarapu 已提交
320 321 322
	}

	private isWorkspaceConfigurationFile(folderRelativePath: string): boolean {
S
Sandeep Somavarapu 已提交
323
		return [FOLDER_SETTINGS_PATH, WORKSPACE_STANDALONE_CONFIGURATIONS[TASKS_CONFIGURATION_KEY], WORKSPACE_STANDALONE_CONFIGURATIONS[LAUNCH_CONFIGURATION_KEY]].some(p => p === folderRelativePath);
S
Sandeep Somavarapu 已提交
324 325 326 327 328 329 330 331 332 333 334 335
	}

	private toResource(folderRelativePath: string): URI {
		if (typeof folderRelativePath === 'string') {
			return URI.file(paths.join(this.folder.fsPath, folderRelativePath));
		}

		return null;
	}

	private toFolderRelativePath(resource: URI, toOSPath?: boolean): string {
		if (this.contains(resource)) {
336
			return paths.normalize(relative(this.folder.fsPath, resource.fsPath), toOSPath);
S
Sandeep Somavarapu 已提交
337 338 339 340 341 342 343 344 345 346 347 348 349
		}

		return null;
	}

	private contains(resource: URI): boolean {
		if (resource) {
			return paths.isEqualOrParent(resource.fsPath, this.folder.fsPath, !isLinux /* ignorecase */);
		}

		return false;
	}
}