/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as ObjectPath from 'objectpath'; import nls = require('vs/nls'); import * as paths from 'vs/base/common/paths'; import * as types from 'vs/base/common/types'; import uri from 'vs/base/common/uri'; import {TPromise} from 'vs/base/common/winjs.base'; import {sequence} from 'vs/base/common/async'; import {IStringDictionary} from 'vs/base/common/collections'; import {IConfigurationResolverService} from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import {IEnvironmentService} from 'vs/platform/environment/common/environment'; import {IConfigurationService} from 'vs/platform/configuration/common/configuration'; import {ICommandService} from 'vs/platform/commands/common/commands'; import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; import {asFileEditorInput} from 'vs/workbench/common/editor'; export class ConfigurationResolverService implements IConfigurationResolverService { _serviceBrand: any; private _workspaceRoot: string; private _execPath: string; constructor( workspaceRoot: uri, envVariables: { [key: string]: string }, @IWorkbenchEditorService private editorService: IWorkbenchEditorService, @IEnvironmentService environmentService: IEnvironmentService, @IConfigurationService private configurationService: IConfigurationService, @ICommandService private commandService: ICommandService ) { this._workspaceRoot = paths.normalize(workspaceRoot ? workspaceRoot.fsPath : '', true); this._execPath = environmentService.execPath; Object.keys(envVariables).forEach(key => { this[`env.${key}`] = envVariables[key]; }); } private get execPath(): string { return this._execPath; } private get cwd(): string { return this.workspaceRoot; } private get workspaceRoot(): string { return this._workspaceRoot; } private get workspaceRootFolderName(): string { return this.workspaceRoot ? paths.basename(this.workspaceRoot) : ''; } private get file(): string { return this.getFilePath(); } private get relativeFile(): string { return (this.workspaceRoot) ? paths.relative(this.workspaceRoot, this.file) : this.file; } private get fileBasename(): string { return paths.basename(this.getFilePath()); } private get fileDirname(): string { return paths.dirname(this.getFilePath()); } private get fileExtname(): string { return paths.extname(this.getFilePath()); } private getFilePath(): string { let input = this.editorService.getActiveEditorInput(); if (!input) { return ''; } let fileEditorInput = asFileEditorInput(input); if (!fileEditorInput) { return ''; } let resource = fileEditorInput.getResource(); return paths.normalize(resource.fsPath, true); } public resolve(value: string): string; public resolve(value: string[]): string[]; public resolve(value: IStringDictionary): IStringDictionary; public resolve(value: any): any { if (types.isString(value)) { return this.resolveString(value); } else if (types.isArray(value)) { return this.resolveArray(value); } else if (types.isObject(value)) { return this.resolveLiteral(value); } return value; } public resolveAny(value: T): T; public resolveAny(value: any): any { if (types.isString(value)) { return this.resolveString(value); } else if (types.isArray(value)) { return this.resolveAnyArray(value); } else if (types.isObject(value)) { return this.resolveAnyLiteral(value); } return value; } private resolveString(value: string): string { let regexp = /\$\{(.*?)\}/g; const originalValue = value; const resolvedString = value.replace(regexp, (match: string, name: string) => { let newValue = (this)[name]; if (types.isString(newValue)) { return newValue; } else { return match && match.indexOf('env.') > 0 ? '' : match; } }); return this.resolveConfigVariable(resolvedString, originalValue); } private resolveConfigVariable(value: string, originalValue: string): string { let regexp = /\$\{config(\.|(?=\[))(.+?)\}/g; return value.replace(regexp, (match: string, lead: string, name: string) => { let config = this.configurationService.getConfiguration(); let newValue: any; try { const keys: string[] = ObjectPath.parse(name); if (!keys || keys.length <= 0) { return ''; } while (keys.length > 1) { const key = keys.shift(); if (!config || !config.hasOwnProperty(key)) { return ''; } config = config[key]; } newValue = config && config.hasOwnProperty(keys[0]) ? config[keys[0]] : ''; } catch (e) { return ''; } if (types.isString(newValue)) { // Prevent infinite recursion and also support nested references (or tokens) return newValue === originalValue ? '' : this.resolveString(newValue); } else { return this.resolve(newValue) + ''; } }); } private resolveLiteral(values: IStringDictionary | string[]>): IStringDictionary | string[]> { let result: IStringDictionary | string[]> = Object.create(null); Object.keys(values).forEach(key => { let value = values[key]; result[key] = this.resolve(value); }); return result; } private resolveAnyLiteral(values: T): T; private resolveAnyLiteral(values: any): any { let result: IStringDictionary | string[]> = Object.create(null); Object.keys(values).forEach(key => { let value = values[key]; result[key] = this.resolveAny(value); }); return result; } private resolveArray(value: string[]): string[] { return value.map(s => this.resolveString(s)); } private resolveAnyArray(value: T[]): T[]; private resolveAnyArray(value: any[]): any[] { return value.map(s => this.resolveAny(s)); } /** * Resolve all interactive variables in configuration #6569 */ public resolveInteractiveVariables(configuration: any, interactiveVariablesMap: { [key: string]: string }): TPromise { if (!configuration) { return TPromise.as(null); } // We need a map from interactive variables to keys because we only want to trigger an command once per key - // even though it might occure multiple times in configuration #7026. const interactiveVariablesToSubstitutes: { [interactiveVariable: string]: { object: any, key: string }[] } = {}; const findInteractiveVariables = (object: any) => { Object.keys(object).forEach(key => { if (object[key] && typeof object[key] === 'object') { findInteractiveVariables(object[key]); } else if (typeof object[key] === 'string') { const matches = /\${command.(.+)}/.exec(object[key]); if (matches && matches.length === 2) { const interactiveVariable = matches[1]; if (!interactiveVariablesToSubstitutes[interactiveVariable]) { interactiveVariablesToSubstitutes[interactiveVariable] = []; } interactiveVariablesToSubstitutes[interactiveVariable].push({ object, key }); } } }); }; findInteractiveVariables(configuration); const factory: { (): TPromise }[] = Object.keys(interactiveVariablesToSubstitutes).map(interactiveVariable => { return () => { let commandId = null; commandId = interactiveVariablesMap ? interactiveVariablesMap[interactiveVariable] : null; if (!commandId) { return TPromise.wrapError(nls.localize('interactiveVariableNotFound', "Interactive variable {0} is not contributed but is specified in a configuration.", interactiveVariable)); } else { return this.commandService.executeCommand(commandId, configuration).then(result => { if (!result) { // TODO@Isidor remove this hack configuration.silentlyAbort = true; } interactiveVariablesToSubstitutes[interactiveVariable].forEach(substitute => substitute.object[substitute.key] = substitute.object[substitute.key].replace(`\${command.${interactiveVariable}}`, result) ); }); } }; }); return sequence(factory).then(() => configuration); } }