configuration.ts 40.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
'use strict';

import URI from 'vs/base/common/uri';
import * as paths from 'vs/base/common/paths';
import { TPromise } from 'vs/base/common/winjs.base';
import Event, { Emitter } from 'vs/base/common/event';
11
import { StrictResourceMap } from 'vs/base/common/map';
12 13 14
import * as objects from 'vs/base/common/objects';
import * as errors from 'vs/base/common/errors';
import * as collections from 'vs/base/common/collections';
B
Benjamin Pasero 已提交
15
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
16
import { RunOnceScheduler } from 'vs/base/common/async';
17
import { readFile, stat, writeFile } from 'vs/base/node/pfs';
18
import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
19
import * as extfs from 'vs/base/node/extfs';
20
import { IWorkspaceContextService, Workspace, WorkbenchState, IWorkspaceFolder, toWorkspaceFolders, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace';
21
import { FileChangeType, FileChangesEvent } from 'vs/platform/files/common/files';
22
import { isLinux } from 'vs/base/common/platform';
23
import { ConfigWatcher } from 'vs/base/node/config';
24
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
25
import { CustomConfigurationModel } from 'vs/platform/configuration/common/model';
26
import { WorkspaceConfigurationModel, ScopedConfigurationModel, FolderConfigurationModel, FolderSettingsModel } from 'vs/workbench/services/configuration/common/configurationModels';
27
import { IConfigurationServiceEvent, ConfigurationSource, IConfigurationKeys, IConfigurationValue, ConfigurationModel, IConfigurationOverrides, Configuration as BaseConfiguration, IConfigurationValues, IConfigurationData } from 'vs/platform/configuration/common/configuration';
28
import { IWorkspaceConfigurationService, WORKSPACE_CONFIG_FOLDER_DEFAULT_NAME, WORKSPACE_STANDALONE_CONFIGURATIONS, WORKSPACE_CONFIG_DEFAULT_PATH } from 'vs/workbench/services/configuration/common/configuration';
29
import { ConfigurationService as GlobalConfigurationService } from 'vs/platform/configuration/node/configurationService';
30
import * as nls from 'vs/nls';
31 32
import { Registry } from 'vs/platform/registry/common/platform';
import { ExtensionsRegistry, ExtensionMessageCollector } from 'vs/platform/extensions/common/extensionsRegistry';
S
Sandeep Somavarapu 已提交
33
import { IConfigurationNode, IConfigurationRegistry, Extensions, editorConfigurationSchemaId, IDefaultConfigurationExtension, validateProperty, ConfigurationScope, settingsSchema, resourceSettingsSchema } from 'vs/platform/configuration/common/configurationRegistry';
34
import { createHash } from 'crypto';
35
import { getWorkspaceLabel, IWorkspacesService, IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
36
import { IWindowConfiguration } from 'vs/platform/windows/common/windows';
37
import { IJSONSchema } from 'vs/base/common/jsonSchema';
38 39 40 41
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
import { ICommandService } from 'vs/platform/commands/common/commands';
import product from 'vs/platform/node/product';
import pkg from 'vs/platform/node/package';
42

S
Sandeep Somavarapu 已提交
43 44 45 46 47
const defaultSettingsSchemaId = 'vscode://schemas/settings/default';
const userSettingsSchemaId = 'vscode://schemas/settings/user';
const workspaceSettingsSchemaId = 'vscode://schemas/settings/workspace';
const folderSettingsSchemaId = 'vscode://schemas/settings/folder';

48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
interface IStat {
	resource: URI;
	isDirectory?: boolean;
	children?: { resource: URI; }[];
}

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

interface IWorkspaceConfiguration<T> {
	workspace: T;
	consolidated: any;
}

type IWorkspaceFoldersConfiguration = { [rootFolder: string]: { folders: string[]; } };

66 67
const configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);

68
const configurationEntrySchema: IJSONSchema = {
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
	type: 'object',
	defaultSnippets: [{ body: { title: '', properties: {} } }],
	properties: {
		title: {
			description: nls.localize('vscode.extension.contributes.configuration.title', 'A summary of the settings. This label will be used in the settings file as separating comment.'),
			type: 'string'
		},
		properties: {
			description: nls.localize('vscode.extension.contributes.configuration.properties', 'Description of the configuration properties.'),
			type: 'object',
			additionalProperties: {
				anyOf: [
					{ $ref: 'http://json-schema.org/draft-04/schema#' },
					{
						type: 'object',
						properties: {
							isExecutable: {
								type: 'boolean'
S
Sandeep Somavarapu 已提交
87 88 89
							},
							scope: {
								type: 'string',
S
Sandeep Somavarapu 已提交
90 91
								enum: ['window', 'resource'],
								default: 'window',
S
Sandeep Somavarapu 已提交
92
								enumDescriptions: [
S
Sandeep Somavarapu 已提交
93
									nls.localize('scope.window.description', "Window specific configuration, which can be configured in the User or Workspace settings."),
S
Sandeep Somavarapu 已提交
94
									nls.localize('scope.resource.description', "Resource specific configuration, which can be configured in the User, Workspace or Folder settings.")
S
Sandeep Somavarapu 已提交
95
								],
S
Sandeep Somavarapu 已提交
96
								description: nls.localize('scope.description', "Scope in which the configuration is applicable. Available scopes are `window` and `resource`.")
97 98 99 100 101 102 103
							}
						}
					}
				]
			}
		}
	}
104 105 106 107 108 109 110 111 112 113 114 115 116
};


// BEGIN VSCode extension point `configuration`
const configurationExtPoint = ExtensionsRegistry.registerExtensionPoint<IConfigurationNode>('configuration', [], {
	description: nls.localize('vscode.extension.contributes.configuration', 'Contributes configuration settings.'),
	oneOf: [
		configurationEntrySchema,
		{
			type: 'array',
			items: configurationEntrySchema
		}
	]
117 118 119 120
});
configurationExtPoint.setHandler(extensions => {
	const configurations: IConfigurationNode[] = [];

121 122
	function handleConfiguration(node: IConfigurationNode, id: string, collector: ExtensionMessageCollector) {
		let configuration = objects.clone(node);
123 124 125 126 127 128 129

		if (configuration.title && (typeof configuration.title !== 'string')) {
			collector.error(nls.localize('invalid.title', "'configuration.title' must be a string"));
		}

		validateProperties(configuration, collector);

130
		configuration.id = id;
131
		configurations.push(configuration);
132
	};
133

134 135 136 137 138 139 140 141 142 143
	for (let extension of extensions) {
		const collector = extension.collector;
		const value = <IConfigurationNode | IConfigurationNode[]>extension.value;
		const id = extension.description.id;
		if (!Array.isArray(value)) {
			handleConfiguration(value, id, collector);
		} else {
			value.forEach(v => handleConfiguration(v, id, collector));
		}
	}
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
	configurationRegistry.registerConfigurations(configurations, false);
});
// END VSCode extension point `configuration`

// BEGIN VSCode extension point `configurationDefaults`
const defaultConfigurationExtPoint = ExtensionsRegistry.registerExtensionPoint<IConfigurationNode>('configurationDefaults', [], {
	description: nls.localize('vscode.extension.contributes.defaultConfiguration', 'Contributes default editor configuration settings by language.'),
	type: 'object',
	defaultSnippets: [{ body: {} }],
	patternProperties: {
		'\\[.*\\]$': {
			type: 'object',
			default: {},
			$ref: editorConfigurationSchemaId,
		}
	}
});
defaultConfigurationExtPoint.setHandler(extensions => {
	const defaultConfigurations: IDefaultConfigurationExtension[] = extensions.map(extension => {
		const id = extension.description.id;
		const name = extension.description.name;
		const defaults = objects.clone(extension.value);
		return <IDefaultConfigurationExtension>{
			id, name, defaults
		};
	});
	configurationRegistry.registerDefaultConfigurations(defaultConfigurations);
});
// END VSCode extension point `configurationDefaults`

function validateProperties(configuration: IConfigurationNode, collector: ExtensionMessageCollector): void {
	let properties = configuration.properties;
	if (properties) {
		if (typeof properties !== 'object') {
			collector.error(nls.localize('invalid.properties', "'configuration.properties' must be an object"));
			configuration.properties = {};
		}
		for (let key in properties) {
			const message = validateProperty(key);
S
Sandeep Somavarapu 已提交
183
			const propertyConfiguration = configuration.properties[key];
S
Sandeep Somavarapu 已提交
184
			propertyConfiguration.scope = propertyConfiguration.scope && propertyConfiguration.scope.toString() === 'resource' ? ConfigurationScope.RESOURCE : ConfigurationScope.WINDOW;
185 186 187 188 189 190 191 192
			if (message) {
				collector.warn(message);
				delete properties[key];
			}
		}
	}
	let subNodes = configuration.allOf;
	if (subNodes) {
193
		collector.error(nls.localize('invalid.allOf', "'configuration.allOf' is deprecated and should no longer be used. Instead, pass multiple configuration sections as an array to the 'configuration' contribution point."));
194 195 196 197 198 199
		for (let node of subNodes) {
			validateProperties(node, collector);
		}
	}
}

S
Sandeep Somavarapu 已提交
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
const contributionRegistry = Registry.as<IJSONContributionRegistry>(JSONExtensions.JSONContribution);
contributionRegistry.registerSchema('vscode://schemas/workspaceConfig', {
	default: {
		folders: [
			{
				path: ''
			}
		],
		settings: {
		}
	},
	required: ['folders'],
	properties: {
		'folders': {
			minItems: 1,
			uniqueItems: true,
			description: nls.localize('workspaceConfig.folders.description', "List of folders to be loaded in the workspace. Must be a file path. e.g. `/root/folderA` or `./folderA` for a relative path that will be resolved against the location of the workspace file."),
			items: {
				type: 'object',
				default: { path: '' },
J
Johannes Rieken 已提交
220 221 222 223 224 225 226 227 228 229
				oneOf: [{
					properties: {
						path: {
							type: 'string',
							description: nls.localize('workspaceConfig.path.description', "A file path. e.g. `/root/folderA` or `./folderA` for a relative path that will be resolved against the location of the workspace file.")
						},
						name: {
							type: 'string',
							description: nls.localize('workspaceConfig.name.description', "An optional name for the folder. ")
						}
230
					},
J
Johannes Rieken 已提交
231 232 233 234 235 236 237 238 239 240 241 242 243 244
					required: ['path']
				}, {
					properties: {
						uri: {
							type: 'string',
							description: nls.localize('workspaceConfig.uri.description', "URI of the folder")
						},
						name: {
							type: 'string',
							description: nls.localize('workspaceConfig.name.description', "An optional name for the folder. ")
						}
					},
					required: ['uri']
				}]
S
Sandeep Somavarapu 已提交
245 246 247 248 249 250
			}
		},
		'settings': {
			type: 'object',
			default: {},
			description: nls.localize('workspaceConfig.settings.description', "Workspace settings"),
S
Sandeep Somavarapu 已提交
251
			$ref: workspaceSettingsSchemaId
252 253 254 255 256 257
		},
		'extensions': {
			type: 'object',
			default: {},
			description: nls.localize('workspaceConfig.extensions.description', "Workspace extensions"),
			$ref: 'vscode://schemas/extensions'
S
Sandeep Somavarapu 已提交
258 259 260 261
		}
	}
});

S
Sandeep Somavarapu 已提交
262
export class WorkspaceService extends Disposable implements IWorkspaceConfigurationService, IWorkspaceContextService {
263 264 265

	public _serviceBrand: any;

266 267 268 269 270
	private workspace: Workspace;
	private _configuration: Configuration<any>;
	private baseConfigurationService: GlobalConfigurationService<any>;
	private workspaceConfiguration: WorkspaceConfiguration;
	private cachedFolderConfigs: StrictResourceMap<FolderConfiguration<any>>;
271

272
	protected readonly _onDidUpdateConfiguration: Emitter<IConfigurationServiceEvent> = this._register(new Emitter<IConfigurationServiceEvent>());
273 274
	public readonly onDidUpdateConfiguration: Event<IConfigurationServiceEvent> = this._onDidUpdateConfiguration.event;

275 276
	protected readonly _onDidChangeWorkspaceFolders: Emitter<IWorkspaceFoldersChangeEvent> = this._register(new Emitter<IWorkspaceFoldersChangeEvent>());
	public readonly onDidChangeWorkspaceFolders: Event<IWorkspaceFoldersChangeEvent> = this._onDidChangeWorkspaceFolders.event;
277

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

281 282 283
	protected readonly _onDidChangeWorkbenchState: Emitter<WorkbenchState> = this._register(new Emitter<WorkbenchState>());
	public readonly onDidChangeWorkbenchState: Event<WorkbenchState> = this._onDidChangeWorkbenchState.event;

284
	constructor(private environmentService: IEnvironmentService, private workspacesService: IWorkspacesService, private workspaceSettingsRootFolder: string = WORKSPACE_CONFIG_FOLDER_DEFAULT_NAME) {
285
		super();
286 287 288 289 290 291

		this.workspaceConfiguration = this._register(new WorkspaceConfiguration());
		this._register(this.workspaceConfiguration.onDidUpdateConfiguration(() => this.onWorkspaceConfigurationChanged()));

		this.baseConfigurationService = this._register(new GlobalConfigurationService(environmentService));
		this._register(this.baseConfigurationService.onDidUpdateConfiguration(e => this.onBaseConfigurationChanged(e)));
S
Sandeep Somavarapu 已提交
292
		this._register(configurationRegistry.onDidRegisterConfiguration(e => this.registerConfigurationSchemas()));
293 294
	}

295
	public getWorkspace(): Workspace {
296 297 298
		return this.workspace;
	}

299
	public getWorkbenchState(): WorkbenchState {
300 301 302 303 304 305
		// Workspace has configuration file
		if (this.workspace.configuration) {
			return WorkbenchState.WORKSPACE;
		}

		// Folder has single root
S
Sandeep Somavarapu 已提交
306
		if (this.workspace.folders.length === 1) {
307
			return WorkbenchState.FOLDER;
308
		}
309 310

		// Empty
311
		return WorkbenchState.EMPTY;
312 313
	}

S
Sandeep Somavarapu 已提交
314
	public getWorkspaceFolder(resource: URI): IWorkspaceFolder {
S
Sandeep Somavarapu 已提交
315
		return this.workspace.getFolder(resource);
316 317
	}

318
	public isInsideWorkspace(resource: URI): boolean {
S
Sandeep Somavarapu 已提交
319
		return !!this.getWorkspaceFolder(resource);
320 321
	}

322
	public isCurrentWorkspace(workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): boolean {
323 324
		switch (this.getWorkbenchState()) {
			case WorkbenchState.FOLDER:
325
				return isSingleFolderWorkspaceIdentifier(workspaceIdentifier) && this.pathEquals(this.workspace.folders[0].uri.fsPath, workspaceIdentifier);
326 327
			case WorkbenchState.WORKSPACE:
				return isWorkspaceIdentifier(workspaceIdentifier) && this.workspace.id === workspaceIdentifier.id;
328
		}
329
		return false;
330 331
	}

332 333
	public getConfigurationData<T>(): IConfigurationData<T> {
		return this._configuration.toData();
334 335
	}

336 337
	public getConfiguration<C>(section?: string, overrides?: IConfigurationOverrides): C {
		return this._configuration.getValue<C>(section, overrides);
338 339
	}

S
Sandeep Somavarapu 已提交
340 341
	public lookup<C>(key: string, overrides?: IConfigurationOverrides): IConfigurationValue<C> {
		return this._configuration.lookup<C>(key, overrides);
342 343
	}

344 345
	public keys(overrides?: IConfigurationOverrides): IConfigurationKeys {
		return this._configuration.keys(overrides);
346
	}
347

348
	public values<V>(): IConfigurationValues {
349
		return this._configuration.values();
350 351
	}

352 353
	public reloadConfiguration(section?: string): TPromise<any> {
		const current = this._configuration;
354
		// Reload and reinitialize to ensure we are hitting the disk
355
		return this.baseConfigurationService.reloadConfiguration()
356 357 358 359 360 361 362
			.then(() => {
				if (this.workspace.configuration) {
					return this.workspaceConfiguration.load(this.workspace.configuration)
						.then(() => this.initializeConfiguration(false));
				}
				return this.initializeConfiguration(false);
			})
363
			.then(() => {
364 365 366
				// Check and trigger
				if (!this._configuration.equals(current)) {
					this.triggerConfigurationChange();
367
				}
368
				return this.getConfiguration(section);
369
			});
370 371
	}

372 373 374 375
	public getUnsupportedWorkspaceKeys(): string[] {
		return this.getWorkbenchState() === WorkbenchState.FOLDER ? this._configuration.getFolderConfigurationModel(this.workspace.folders[0].uri).workspaceSettingsConfig.unsupportedKeys : [];
	}

376
	public handleWorkspaceFileEvents(event: FileChangesEvent): void {
377
		TPromise.join(this.workspace.folders.map(folder => this.cachedFolderConfigs.get(folder.uri).handleWorkspaceFileEvents(event))) // handle file event for each folder
378
			.then(folderConfigurations =>
S
Sandeep Somavarapu 已提交
379
				folderConfigurations.map((configuration, index) => ({ configuration, folder: this.workspace.folders[index] }))
380 381 382 383 384 385
					.filter(folderConfiguration => !!folderConfiguration.configuration) // Filter folders which are not impacted by events
					.map(folderConfiguration => this.updateFolderConfiguration(folderConfiguration.folder, folderConfiguration.configuration, true)) // Update the configuration of impacted folders
					.reduce((result, value) => result || value, false)) // Check if the effective configuration of folder is changed
			.then(changed => changed ? this.triggerConfigurationChange() : void 0); // Trigger event if changed
	}

386 387 388 389
	public initialize(arg: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWindowConfiguration): TPromise<any> {
		return this.createWorkspace(arg)
			.then(workspace => this.setWorkspace(workspace))
			.then(() => this.initializeConfiguration(true));
390 391
	}

392
	private createWorkspace(arg: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWindowConfiguration): TPromise<Workspace> {
393
		if (isWorkspaceIdentifier(arg)) {
394
			return this.createMulitFolderWorkspace(arg);
395 396 397
		}

		if (isSingleFolderWorkspaceIdentifier(arg)) {
398
			return this.createSingleFolderWorkspace(arg);
399 400
		}

401
		return this.createEmptyWorkspace(arg);
402 403
	}

404
	private createMulitFolderWorkspace(workspaceIdentifier: IWorkspaceIdentifier): TPromise<Workspace> {
405
		const workspaceConfigPath = URI.file(workspaceIdentifier.configPath);
406
		return this.workspaceConfiguration.load(workspaceConfigPath)
407 408
			.then(() => {
				const workspaceConfigurationModel = this.workspaceConfiguration.workspaceConfigurationModel;
409
				const workspaceFolders = toWorkspaceFolders(workspaceConfigurationModel.folders, URI.file(paths.dirname(workspaceConfigPath.fsPath)));
410
				if (!workspaceFolders.length) {
411
					return TPromise.wrapError<Workspace>(new Error('Invalid workspace configuraton file ' + workspaceConfigPath));
412
				}
413
				const workspaceId = workspaceIdentifier.id;
414
				const workspaceName = getWorkspaceLabel({ id: workspaceId, configPath: workspaceConfigPath.fsPath }, this.environmentService);
415
				return new Workspace(workspaceId, workspaceName, workspaceFolders, workspaceConfigPath);
416 417 418
			});
	}

419
	private createSingleFolderWorkspace(singleFolderWorkspaceIdentifier: ISingleFolderWorkspaceIdentifier): TPromise<Workspace> {
420
		const folderPath = URI.file(singleFolderWorkspaceIdentifier);
421
		return stat(folderPath.fsPath)
422 423
			.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!
424 425
				const id = createHash('md5').update(folderPath.fsPath).update(ctime ? String(ctime) : '').digest('hex');
				const folder = URI.file(folderPath.fsPath);
426
				return new Workspace(id, paths.basename(folderPath.fsPath), toWorkspaceFolders([{ path: folder.fsPath }]), null, ctime);
427 428 429
			});
	}

430
	private createEmptyWorkspace(configuration: IWindowConfiguration): TPromise<Workspace> {
431
		let id = configuration.backupPath ? URI.from({ path: paths.basename(configuration.backupPath), scheme: 'empty' }).toString() : '';
432 433 434 435 436 437 438 439 440 441
		return TPromise.as(new Workspace(id));
	}

	private setWorkspace(workspace: Workspace): void {
		if (!this.workspace) {
			this.workspace = workspace;
			return;
		}

		const currentState = this.getWorkbenchState();
442
		const currentWorkspacePath = this.workspace.configuration ? this.workspace.configuration.fsPath : void 0;
443 444 445 446 447 448 449 450 451
		const currentFolders = this.workspace.folders;

		this.workspace.update(workspace);

		const newState = this.getWorkbenchState();
		if (newState !== currentState) {
			this._onDidChangeWorkbenchState.fire(newState);
		}

452 453
		const newWorkspacePath = this.workspace.configuration ? this.workspace.configuration.fsPath : void 0;
		if (newWorkspacePath !== currentWorkspacePath || newState !== currentState) {
454 455 456
			this._onDidChangeWorkspaceName.fire();
		}

457 458 459
		const changes = this.compareFolders(currentFolders, this.workspace.folders);
		if (changes.added.length || changes.removed.length || changes.changed.length) {
			this._onDidChangeWorkspaceFolders.fire(changes);
460 461 462
		}
	}

S
Sandeep Somavarapu 已提交
463
	private compareFolders(currentFolders: IWorkspaceFolder[], newFolders: IWorkspaceFolder[]): IWorkspaceFoldersChangeEvent {
464 465 466 467 468 469 470 471 472 473 474 475
		const result = { added: [], removed: [], changed: [] };

		result.added = newFolders.filter(newFolder => !currentFolders.some(currentFolder => newFolder.uri.toString() === currentFolder.uri.toString()));
		result.removed = currentFolders.filter(currentFolder => !newFolders.some(newFolder => currentFolder.uri.toString() === newFolder.uri.toString()));

		if (result.added.length === 0 && result.removed.length === 0) {
			result.changed = currentFolders.filter((currentFolder, index) => newFolders[index].uri.toString() !== currentFolder.uri.toString());
		}

		return result;
	}

476
	private initializeConfiguration(trigger: boolean = true): TPromise<any> {
S
Sandeep Somavarapu 已提交
477
		this.registerConfigurationSchemas();
478 479 480 481 482 483 484 485 486 487 488 489 490
		this.resetCaches();
		return this.updateConfiguration()
			.then(() => {
				if (trigger) {
					this.triggerConfigurationChange();
				}
			});
	}

	private resetCaches(): void {
		this.cachedFolderConfigs = new StrictResourceMap<FolderConfiguration<any>>();
		this._configuration = new Configuration(<any>this.baseConfigurationService.configuration(), new ConfigurationModel<any>(), new StrictResourceMap<FolderConfigurationModel<any>>(), this.getWorkbenchState() !== WorkbenchState.EMPTY ? this.workspace : null); //TODO: @Sandy Avoid passing null
		this.initCachesForFolders(this.workspace.folders);
491 492
	}

S
Sandeep Somavarapu 已提交
493
	private initCachesForFolders(folders: IWorkspaceFolder[]): void {
494
		for (const folder of folders) {
495
			this.cachedFolderConfigs.set(folder.uri, this._register(new FolderConfiguration(folder.uri, this.workspaceSettingsRootFolder, this.getWorkbenchState() === WorkbenchState.WORKSPACE ? ConfigurationScope.RESOURCE : ConfigurationScope.WINDOW)));
S
Sandeep Somavarapu 已提交
496
			this.updateFolderConfiguration(folder, new FolderConfigurationModel<any>(new FolderSettingsModel<any>(null), [], ConfigurationScope.RESOURCE), false);
497 498 499
		}
	}

S
Sandeep Somavarapu 已提交
500
	private updateConfiguration(folders: IWorkspaceFolder[] = this.workspace.folders): TPromise<boolean> {
501
		return TPromise.join([...folders.map(folder => this.cachedFolderConfigs.get(folder.uri).loadConfiguration()
502 503 504 505 506
			.then(configuration => this.updateFolderConfiguration(folder, configuration, true)))])
			.then(changed => changed.reduce((result, value) => result || value, false))
			.then(changed => this.updateWorkspaceConfiguration(true) || changed);
	}

S
Sandeep Somavarapu 已提交
507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522
	private registerConfigurationSchemas(): void {
		if (this.workspace) {

			contributionRegistry.registerSchema(defaultSettingsSchemaId, settingsSchema);
			contributionRegistry.registerSchema(userSettingsSchemaId, settingsSchema);

			if (WorkbenchState.WORKSPACE === this.getWorkbenchState()) {
				contributionRegistry.registerSchema(workspaceSettingsSchemaId, settingsSchema);
				contributionRegistry.registerSchema(folderSettingsSchemaId, resourceSettingsSchema);
			} else {
				contributionRegistry.registerSchema(workspaceSettingsSchemaId, settingsSchema);
				contributionRegistry.registerSchema(folderSettingsSchemaId, settingsSchema);
			}
		}
	}

523
	private onBaseConfigurationChanged({ source, sourceConfig }: IConfigurationServiceEvent): void {
524 525 526 527 528 529 530
		if (this.workspace) {
			if (source === ConfigurationSource.Default) {
				this.workspace.folders.forEach(folder => this._configuration.getFolderConfigurationModel(folder.uri).update());
			}
			if (this._configuration.updateBaseConfiguration(<any>this.baseConfigurationService.configuration())) {
				this._onDidUpdateConfiguration.fire({ source, sourceConfig });
			}
531 532 533
		}
	}

534 535 536
	private onWorkspaceConfigurationChanged(): void {
		if (this.workspace && this.workspace.configuration) {
			let configuredFolders = toWorkspaceFolders(this.workspaceConfiguration.workspaceConfigurationModel.folders, URI.file(paths.dirname(this.workspace.configuration.fsPath)));
537 538
			const changes = this.compareFolders(this.workspace.folders, configuredFolders);
			if (changes.added.length || changes.removed.length || changes.changed.length) { // TODO@Sandeep be smarter here about detecting changes
539 540 541
				this.workspace.folders = configuredFolders;
				this.onFoldersChanged()
					.then(configurationChanged => {
542
						this._onDidChangeWorkspaceFolders.fire(changes);
543 544 545 546 547 548 549 550 551
						if (configurationChanged) {
							this.triggerConfigurationChange();
						}
					});
			} else {
				const configurationChanged = this.updateWorkspaceConfiguration(true);
				if (configurationChanged) {
					this.triggerConfigurationChange();
				}
S
Sandeep Somavarapu 已提交
552
			}
553 554 555
		}
	}

S
Sandeep Somavarapu 已提交
556
	private onFoldersChanged(): TPromise<boolean> {
S
Sandeep Somavarapu 已提交
557
		let configurationChangedOnRemoval = false;
558

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

569
		// Initialize the newly added folders
570
		const toInitialize = this.workspace.folders.filter(folder => !this.cachedFolderConfigs.has(folder.uri));
571 572
		if (toInitialize.length) {
			this.initCachesForFolders(toInitialize);
S
Sandeep Somavarapu 已提交
573 574
			return this.updateConfiguration(toInitialize)
				.then(changed => configurationChangedOnRemoval || changed);
S
Sandeep Somavarapu 已提交
575 576
		} else if (configurationChangedOnRemoval) {
			this.updateWorkspaceConfiguration(false);
S
Sandeep Somavarapu 已提交
577
			return TPromise.as(true);
578
		}
S
Sandeep Somavarapu 已提交
579
		return TPromise.as(false);
580 581
	}

S
Sandeep Somavarapu 已提交
582
	private updateFolderConfiguration(folder: IWorkspaceFolder, folderConfiguration: FolderConfigurationModel<any>, compare: boolean): boolean {
583
		let configurationChanged = this._configuration.updateFolderConfiguration(folder.uri, folderConfiguration, compare);
584
		if (this.getWorkbenchState() === WorkbenchState.FOLDER) {
585 586 587 588
			// Workspace configuration changed
			configurationChanged = this.updateWorkspaceConfiguration(compare) || configurationChanged;
		}
		return configurationChanged;
589 590
	}

591
	private updateWorkspaceConfiguration(compare: boolean): boolean {
592 593 594 595 596 597
		const workbennchState = this.getWorkbenchState();
		if (workbennchState === WorkbenchState.EMPTY) {
			return false;
		}

		const workspaceConfiguration = workbennchState === WorkbenchState.WORKSPACE ? this.workspaceConfiguration.workspaceConfigurationModel.workspaceConfiguration : this._configuration.getFolderConfigurationModel(this.workspace.folders[0].uri);
598
		return this._configuration.updateWorkspaceConfiguration(workspaceConfiguration, compare);
599
	}
600

601
	private triggerConfigurationChange(): void {
602 603 604
		if (this.getWorkbenchState() === WorkbenchState.EMPTY) {
			this._onDidUpdateConfiguration.fire({ source: ConfigurationSource.User, sourceConfig: this._configuration.user.contents });
		} else {
605
			this._onDidUpdateConfiguration.fire({ source: ConfigurationSource.Workspace, sourceConfig: this.workspace.folders.length ? this._configuration.getFolderConfigurationModel(this.workspace.folders[0].uri).contents : void 0 }); // TODO@Sandeep debt?
606
		}
607
	}
608 609 610 611 612 613 614 615 616

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

		return path1 === path2;
	}
617
}
618

619
class WorkspaceConfiguration extends Disposable {
620

621
	private _workspaceConfigPath: URI;
622
	private _workspaceConfigurationWatcher: ConfigWatcher<WorkspaceConfigurationModel<any>>;
623
	private _workspaceConfigurationWatcherDisposables: IDisposable[] = [];
624 625 626 627

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

628

629 630
	load(workspaceConfigPath: URI): TPromise<void> {
		if (this._workspaceConfigPath && this._workspaceConfigPath.fsPath === workspaceConfigPath.fsPath) {
631
			return this._reload();
632
		}
633

634 635 636
		this._workspaceConfigPath = workspaceConfigPath;

		this._workspaceConfigurationWatcherDisposables = dispose(this._workspaceConfigurationWatcherDisposables);
637
		return new TPromise<void>((c, e) => {
638
			this._workspaceConfigurationWatcher = new ConfigWatcher(this._workspaceConfigPath.fsPath, {
639
				changeBufferDelay: 300, onError: error => errors.onUnexpectedError(error), defaultConfig: new WorkspaceConfigurationModel(null, this._workspaceConfigPath.fsPath), parse: (content: string, parseErrors: any[]) => {
640
					const workspaceConfigurationModel = new WorkspaceConfigurationModel(content, this._workspaceConfigPath.fsPath);
641 642 643 644
					parseErrors = [...workspaceConfigurationModel.errors];
					return workspaceConfigurationModel;
				}, initCallback: () => c(null)
			});
B
Benjamin Pasero 已提交
645
			this._workspaceConfigurationWatcherDisposables.push(this._workspaceConfigurationWatcher);
646
			this._workspaceConfigurationWatcher.onDidUpdateConfiguration(() => this._onDidUpdateConfiguration.fire(), this, this._workspaceConfigurationWatcherDisposables);
647
		});
648 649
	}

650 651 652 653 654
	get workspaceConfigurationModel(): WorkspaceConfigurationModel<any> {
		return this._workspaceConfigurationWatcher ? this._workspaceConfigurationWatcher.getConfig() : new WorkspaceConfigurationModel();
	}

	private _reload(): TPromise<void> {
655 656 657 658 659 660
		return new TPromise<void>(c => this._workspaceConfigurationWatcher.reload(() => c(null)));
	}

	dispose(): void {
		dispose(this._workspaceConfigurationWatcherDisposables);
		super.dispose();
661
	}
662
}
663

664
class FolderConfiguration<T> extends Disposable {
665

666
	private static RELOAD_CONFIGURATION_DELAY = 50;
667

668 669 670 671 672 673
	private bulkFetchFromWorkspacePromise: TPromise<any>;
	private workspaceFilePathToConfiguration: { [relativeWorkspacePath: string]: TPromise<ConfigurationModel<any>> };

	private reloadConfigurationScheduler: RunOnceScheduler;
	private reloadConfigurationEventEmitter: Emitter<FolderConfigurationModel<T>> = new Emitter<FolderConfigurationModel<T>>();

674
	constructor(private folder: URI, private configFolderRelativePath: string, private scope: ConfigurationScope) {
675
		super();
676

677 678 679
		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));
	}
680

681 682 683 684 685 686
	loadConfiguration(): TPromise<FolderConfigurationModel<T>> {
		// Load workspace locals
		return this.loadWorkspaceConfigFiles().then(workspaceConfigFiles => {
			// Consolidate (support *.json files in the workspace settings folder)
			const workspaceSettingsConfig = <FolderSettingsModel<T>>workspaceConfigFiles[WORKSPACE_CONFIG_DEFAULT_PATH] || new FolderSettingsModel<T>(null);
			const otherConfigModels = Object.keys(workspaceConfigFiles).filter(key => key !== WORKSPACE_CONFIG_DEFAULT_PATH).map(key => <ScopedConfigurationModel<T>>workspaceConfigFiles[key]);
687
			return new FolderConfigurationModel<T>(workspaceSettingsConfig, otherConfigModels, this.scope);
688 689 690 691
		});
	}

	private loadWorkspaceConfigFiles<T>(): TPromise<{ [relativeWorkspacePath: string]: ConfigurationModel<T> }> {
692 693
		// once: when invoked for the first time we fetch json files that contribute settings
		if (!this.bulkFetchFromWorkspacePromise) {
694
			this.bulkFetchFromWorkspacePromise = resolveStat(this.toResource(this.configFolderRelativePath)).then(stat => {
695 696 697 698 699 700 701 702 703 704
				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
					}

B
Benjamin Pasero 已提交
705
					return this.isWorkspaceConfigurationFile(this.toFolderRelativePath(stat.resource)); // only workspace config files
706 707 708
				}).map(stat => stat.resource));
			}, err => [] /* never fail this call */)
				.then((contents: IContent[]) => {
B
Benjamin Pasero 已提交
709
					contents.forEach(content => this.workspaceFilePathToConfiguration[this.toFolderRelativePath(content.resource)] = TPromise.as(this.createConfigModel(content)));
710 711 712 713 714 715 716 717
				}, 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));
	}

718
	public handleWorkspaceFileEvents(event: FileChangesEvent): TPromise<FolderConfigurationModel<T>> {
719 720 721 722 723 724 725
		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';
726
			const isDeletedSettingsFolder = (events[i].type === FileChangeType.DELETED && paths.isEqual(paths.basename(resource.fsPath), this.configFolderRelativePath));
727 728 729 730
			if (!isJson && !isDeletedSettingsFolder) {
				continue; // only JSON files or the actual settings folder
			}

B
Benjamin Pasero 已提交
731
			const workspacePath = this.toFolderRelativePath(resource);
732 733 734 735 736
			if (!workspacePath) {
				continue; // event is not inside workspace
			}

			// Handle case where ".vscode" got deleted
737
			if (workspacePath === this.configFolderRelativePath && events[i].type === FileChangeType.DELETED) {
738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759
				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:
					this.workspaceFilePathToConfiguration[workspacePath] = resolveContent(resource).then(content => this.createConfigModel(content), errors.onUnexpectedError);
					affectedByChanges = true;
			}
		}

760 761
		if (!affectedByChanges) {
			return TPromise.as(null);
762
		}
763 764 765 766 767 768 769 770 771 772 773

		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();
			}
		});
774 775
	}

776
	private createConfigModel<T>(content: IContent): ConfigurationModel<T> {
B
Benjamin Pasero 已提交
777
		const path = this.toFolderRelativePath(content.resource);
778
		if (path === WORKSPACE_CONFIG_DEFAULT_PATH) {
779
			return new FolderSettingsModel<T>(content.value, content.resource.toString());
780 781 782
		} else {
			const matches = /\/([^\.]*)*\.json/.exec(path);
			if (matches && matches[1]) {
783
				return new ScopedConfigurationModel<T>(content.value, content.resource.toString(), matches[1]);
784 785 786
			}
		}

787
		return new CustomConfigurationModel<T>(null);
788 789
	}

B
Benjamin Pasero 已提交
790 791
	private isWorkspaceConfigurationFile(folderRelativePath: string): boolean {
		return [WORKSPACE_CONFIG_DEFAULT_PATH, WORKSPACE_STANDALONE_CONFIGURATIONS.launch, WORKSPACE_STANDALONE_CONFIGURATIONS.tasks].some(p => p === folderRelativePath);
792 793
	}

B
Benjamin Pasero 已提交
794 795 796
	private toResource(folderRelativePath: string): URI {
		if (typeof folderRelativePath === 'string') {
			return URI.file(paths.join(this.folder.fsPath, folderRelativePath));
797 798 799
		}

		return null;
800 801
	}

B
Benjamin Pasero 已提交
802
	private toFolderRelativePath(resource: URI, toOSPath?: boolean): string {
803 804
		if (this.contains(resource)) {
			return paths.normalize(paths.relative(this.folder.fsPath, resource.fsPath), toOSPath);
805 806
		}

807
		return null;
808 809
	}

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

815
		return false;
816
	}
817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852
}

// node.hs helper functions

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)) }; })
				});
			}
		});
	});
I
isidor 已提交
853
}
854

855
export class Configuration<T> extends BaseConfiguration<T> {
856

857
	constructor(private _baseConfiguration: BaseConfiguration<T>, workspaceConfiguration: ConfigurationModel<T>, protected folders: StrictResourceMap<FolderConfigurationModel<T>>, workspace: Workspace) {
858
		super(_baseConfiguration.defaults, _baseConfiguration.user, workspaceConfiguration, folders, workspace);
859 860
	}

861
	updateBaseConfiguration(baseConfiguration: BaseConfiguration<T>): boolean {
862
		const current = new Configuration(this._baseConfiguration, this._workspaceConfiguration, this.folders, this._workspace);
863

S
Sandeep Somavarapu 已提交
864 865 866
		this._baseConfiguration = baseConfiguration;
		this._defaults = this._baseConfiguration.defaults;
		this._user = this._baseConfiguration.user;
867 868 869 870 871
		this.merge();

		return !this.equals(current);
	}

872 873 874 875 876 877 878 879 880 881
	updateWorkspaceConfiguration(workspaceConfiguration: ConfigurationModel<T>, compare: boolean = true): boolean {
		const current = new Configuration(this._baseConfiguration, this._workspaceConfiguration, this.folders, this._workspace);

		this._workspaceConfiguration = workspaceConfiguration;
		this.merge();

		return compare && !this.equals(current);
	}

	updateFolderConfiguration(resource: URI, configuration: FolderConfigurationModel<T>, compare: boolean): boolean {
882
		const current = this.getValue(null, { resource });
883 884

		this.folders.set(resource, configuration);
885
		this.mergeFolder(resource);
886

887
		return compare && !objects.equals(current, this.getValue(null, { resource }));
888 889 890
	}

	deleteFolderConfiguration(folder: URI): boolean {
891
		if (this._workspace && this._workspace.folders.length > 0 && this._workspace.folders[0].uri.toString() === folder.toString()) {
892 893 894 895
			// Do not remove workspace configuration
			return false;
		}

S
Sandeep Somavarapu 已提交
896
		const changed = this.folders.get(folder).keys.length > 0;
897
		this.folders.delete(folder);
S
Sandeep Somavarapu 已提交
898 899
		this._foldersConsolidatedConfigurations.delete(folder);
		return changed;
900 901 902 903 904 905 906 907 908 909 910 911 912 913 914
	}

	getFolderConfigurationModel(folder: URI): FolderConfigurationModel<T> {
		return <FolderConfigurationModel<T>>this.folders.get(folder);
	}

	equals(other: any): boolean {
		if (!other || !(other instanceof Configuration)) {
			return false;
		}

		if (!objects.equals(this.getValue(), other.getValue())) {
			return false;
		}

915
		if (this._foldersConsolidatedConfigurations.size !== other._foldersConsolidatedConfigurations.size) {
916 917 918
			return false;
		}

919
		for (const resource of this._foldersConsolidatedConfigurations.keys()) {
920
			if (!objects.equals(this.getValue(null, { resource }), other.getValue(null, { resource }))) {
921 922 923 924 925 926
				return false;
			}
		}

		return true;
	}
I
isidor 已提交
927
}
928

929 930 931 932 933 934 935 936 937 938 939 940 941
interface IExportedConfigurationNode {
	name: string;
	description: string;
	default: any;
	type: string | string[];
	enum?: any[];
	enumDescriptions?: string[];
}

interface IConfigurationExport {
	settings: IExportedConfigurationNode[];
	buildTime: number;
	commit: string;
942
	version: number;
943 944 945
}

export class DefaultConfigurationExportHelper {
946 947 948 949 950

	constructor(
		@IEnvironmentService environmentService: IEnvironmentService,
		@IExtensionService private extensionService: IExtensionService,
		@ICommandService private commandService: ICommandService) {
951 952
		if (environmentService.args['export-default-configuration']) {
			this.writeConfigModelAndQuit(environmentService.args['export-default-configuration']);
953 954 955 956 957 958 959 960 961 962 963
		}
	}

	private writeConfigModelAndQuit(targetPath: string): TPromise<void> {
		return this.extensionService.onReady()
			.then(() => this.writeConfigModel(targetPath))
			.then(() => this.commandService.executeCommand('workbench.action.quit'))
			.then(() => { });
	}

	private writeConfigModel(targetPath: string): TPromise<void> {
964 965 966 967 968 969 970
		const config = this.getConfigModel();

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

	private getConfigModel(): IConfigurationExport {
971
		const configurations = Registry.as<IConfigurationRegistry>(Extensions.Configuration).getConfigurations().slice();
972
		const settings: IExportedConfigurationNode[] = [];
973 974 975 976
		const processConfig = (config: IConfigurationNode) => {
			if (config.properties) {
				for (let name in config.properties) {
					const prop = config.properties[name];
977
					const propDetails: IExportedConfigurationNode = {
978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000
						name,
						description: prop.description,
						default: prop.default,
						type: prop.type
					};

					if (prop.enum) {
						propDetails.enum = prop.enum;
					}

					if (prop.enumDescriptions) {
						propDetails.enumDescriptions = prop.enumDescriptions;
					}

					settings.push(propDetails);
				}
			}

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

1001
		configurations.forEach(processConfig);
1002

1003 1004 1005 1006
		const result: IConfigurationExport = {
			settings: settings.sort((a, b) => a.name.localeCompare(b.name)),
			buildTime: Date.now(),
			commit: product.commit,
1007
			version: versionStringToNumber(pkg.version)
1008
		};
1009

1010
		return result;
1011 1012
	}
}
1013 1014 1015 1016 1017 1018 1019 1020 1021 1022

function versionStringToNumber(versionStr: string): number {
	const semverRegex = /(\d+)\.(\d+)\.(\d+)/;
	const match = versionStr.match(semverRegex);
	if (!match) {
		return 0;
	}

	return parseInt(match[1], 10) * 10000 + parseInt(match[2], 10) * 100 + parseInt(match[3], 10);
}