diff --git a/src/vs/base/common/parsers.ts b/src/vs/base/common/parsers.ts index 9bcfa66446a..fb34ec93e7f 100644 --- a/src/vs/base/common/parsers.ts +++ b/src/vs/base/common/parsers.ts @@ -130,7 +130,7 @@ export abstract class AbstractSystemVariables implements ISystemVariables { public resolve(value: IStringDictionary>): IStringDictionary>; public resolve(value: any): any { if (Types.isString(value)) { - return this.__resolveString(value); + return this.resolveString(value); } else if (Types.isArray(value)) { return this.__resolveArray(value); } else if (Types.isObject(value)) { @@ -143,7 +143,7 @@ export abstract class AbstractSystemVariables implements ISystemVariables { resolveAny(value: T): T; resolveAny(value: any): any { if (Types.isString(value)) { - return this.__resolveString(value); + return this.resolveString(value); } else if (Types.isArray(value)) { return this.__resolveAnyArray(value); } else if (Types.isObject(value)) { @@ -153,7 +153,7 @@ export abstract class AbstractSystemVariables implements ISystemVariables { return value; } - private __resolveString(value: string): string { + protected resolveString(value: string): string { let regexp = /\$\{(.*?)\}/g; return value.replace(regexp, (match: string, name: string) => { let newValue = (this)[name]; @@ -185,7 +185,7 @@ export abstract class AbstractSystemVariables implements ISystemVariables { } private __resolveArray(value: string[]): string[] { - return value.map(s => this.__resolveString(s)); + return value.map(s => this.resolveString(s)); } private __resolveAnyArray(value: T[]): T[]; diff --git a/src/vs/workbench/parts/debug/node/debugAdapter.ts b/src/vs/workbench/parts/debug/node/debugAdapter.ts index 8c20e881fea..4a96232ace0 100644 --- a/src/vs/workbench/parts/debug/node/debugAdapter.ts +++ b/src/vs/workbench/parts/debug/node/debugAdapter.ts @@ -8,7 +8,7 @@ import objects = require('vs/base/common/objects'); import paths = require('vs/base/common/paths'); import platform = require('vs/base/common/platform'); import debug = require('vs/workbench/parts/debug/common/debug'); -import { SystemVariables } from 'vs/workbench/parts/lib/node/systemVariables'; +import { ISystemVariables } from 'vs/base/common/parsers'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; export class Adapter { @@ -25,7 +25,7 @@ export class Adapter { public enableBreakpointsFor: { languageIds: string[] }; public aiKey: string; - constructor(rawAdapter: debug.IRawAdapter, systemVariables: SystemVariables, public extensionDescription: IExtensionDescription) { + constructor(rawAdapter: debug.IRawAdapter, systemVariables: ISystemVariables, public extensionDescription: IExtensionDescription) { if (rawAdapter.windows) { rawAdapter.win = rawAdapter.windows; } diff --git a/src/vs/workbench/parts/debug/node/debugConfigurationManager.ts b/src/vs/workbench/parts/debug/node/debugConfigurationManager.ts index cfe39aaedad..922754d4f78 100644 --- a/src/vs/workbench/parts/debug/node/debugConfigurationManager.ts +++ b/src/vs/workbench/parts/debug/node/debugConfigurationManager.ts @@ -25,12 +25,12 @@ import { IFileService } from 'vs/platform/files/common/files'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ICommandService } from 'vs/platform/commands/common/commands'; import debug = require('vs/workbench/parts/debug/common/debug'); -import { SystemVariables } from 'vs/workbench/parts/lib/node/systemVariables'; import { Adapter } from 'vs/workbench/parts/debug/node/debugAdapter'; import { IWorkspaceContextService } from 'vs/workbench/services/workspace/common/contextService'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IQuickOpenService } from 'vs/workbench/services/quickopen/common/quickOpenService'; - +import { ConfigVariables } from 'vs/workbench/parts/lib/node/configVariables'; +import { ISystemVariables } from 'vs/base/common/parsers'; // debuggers extension point export const debuggersExtPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint('debuggers', { @@ -152,9 +152,8 @@ const jsonRegistry = platfor jsonRegistry.registerSchema(schemaId, schema); export class ConfigurationManager implements debug.IConfigurationManager { - public configuration: debug.IConfig; - private systemVariables: SystemVariables; + private systemVariables: ISystemVariables; private adapters: Adapter[]; private allModeIdsForBreakpoints: { [key: string]: boolean }; private _onDidConfigurationChange: Emitter; @@ -169,8 +168,8 @@ export class ConfigurationManager implements debug.IConfigurationManager { @IQuickOpenService private quickOpenService: IQuickOpenService, @ICommandService private commandService: ICommandService ) { + this.systemVariables = this.contextService.getWorkspace() ? new ConfigVariables(this.configurationService, this.editorService, this.contextService) : null; this._onDidConfigurationChange = new Emitter(); - this.systemVariables = this.contextService.getWorkspace() ? new SystemVariables(this.editorService, this.contextService) : null; this.setConfiguration(configName); this.adapters = []; this.registerListeners(); diff --git a/src/vs/workbench/parts/lib/node/configVariables.ts b/src/vs/workbench/parts/lib/node/configVariables.ts new file mode 100644 index 00000000000..0c9920b5749 --- /dev/null +++ b/src/vs/workbench/parts/lib/node/configVariables.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * 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 * as Types from 'vs/base/common/types'; +import { SystemVariables } from './systemVariables'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IWorkspaceContextService } from 'vs/workbench/services/workspace/common/contextService'; +import URI from 'vs/base/common/uri'; + +export class ConfigVariables extends SystemVariables { + constructor(private configurationService: IConfigurationService, editorService: IWorkbenchEditorService, contextService: IWorkspaceContextService, workspaceRoot: URI = null, envVariables: { [key: string]: string } = process.env) { + super(editorService, contextService, workspaceRoot, envVariables); + } + + protected resolveString(value: string): string { + value = super.resolveString(value); + + let regexp = /\$\{config\.(.*?)\}/g; + return value.replace(regexp, (match: string, name: string) => { + let config = this.configurationService.getConfiguration(); + let newValue = new Function('_', 'try {return _.' + name + ';} catch (ex) { return "";}')(config); + return Types.isString(newValue) ? newValue : ''; + }); + } +} \ No newline at end of file diff --git a/src/vs/workbench/parts/lib/node/systemVariables.ts b/src/vs/workbench/parts/lib/node/systemVariables.ts index 1abb522c018..cf0c926eae2 100644 --- a/src/vs/workbench/parts/lib/node/systemVariables.ts +++ b/src/vs/workbench/parts/lib/node/systemVariables.ts @@ -18,13 +18,16 @@ export class SystemVariables extends AbstractSystemVariables { private _execPath: string; // Optional workspaceRoot there to be used in tests. - constructor(private editorService: IWorkbenchEditorService, contextService: IWorkspaceContextService, workspaceRoot: URI = null) { + constructor(private editorService: IWorkbenchEditorService, contextService: IWorkspaceContextService, workspaceRoot: URI = null, envVariables: { [key: string]: string } = process.env) { super(); - let fsPath = workspaceRoot ? workspaceRoot.fsPath : contextService.getWorkspace().resource.fsPath; + let fsPath = ''; + if (workspaceRoot || (contextService && contextService.getWorkspace())) { + fsPath = workspaceRoot ? workspaceRoot.fsPath : contextService.getWorkspace().resource.fsPath; + } this._workspaceRoot = Paths.normalize(fsPath, true); this._execPath = contextService ? contextService.getConfiguration().env.execPath : null; - Object.keys(process.env).forEach(key => { - this[`env.${ key }`] = process.env[key]; + Object.keys(envVariables).forEach(key => { + this[`env.${key}`] = envVariables[key]; }); } diff --git a/src/vs/workbench/parts/lib/test/node/configVariables.test.ts b/src/vs/workbench/parts/lib/test/node/configVariables.test.ts new file mode 100644 index 00000000000..825d875987e --- /dev/null +++ b/src/vs/workbench/parts/lib/test/node/configVariables.test.ts @@ -0,0 +1,101 @@ +/*--------------------------------------------------------------------------------------------- + * 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 * as assert from 'assert'; +import URI from 'vs/base/common/uri'; +import * as Platform from 'vs/base/common/platform'; +import { ConfigVariables } from 'vs/workbench/parts/lib/node/configVariables'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import {TPromise} from 'vs/base/common/winjs.base'; + +suite('ConfigVariables tests', () => { + test('ConfigVariables: substitute one', () => { + let configurationService: IConfigurationService; + configurationService = new MockConfigurationService({ + editor: { + fontFamily: 'foo' + }, + terminal: { + integrated: { + fontFamily: 'bar' + } + } + }); + + let systemVariables: ConfigVariables = new ConfigVariables(configurationService, null, null, URI.parse('file:///VSCode/workspaceLocation')); + assert.strictEqual(systemVariables.resolve('abc ${config.editor.fontFamily} xyz'), 'abc foo xyz'); + }); + + test('ConfigVariables: substitute many', () => { + let configurationService: IConfigurationService; + configurationService = new MockConfigurationService({ + editor: { + fontFamily: 'foo' + }, + terminal: { + integrated: { + fontFamily: 'bar' + } + } + }); + + let systemVariables: ConfigVariables = new ConfigVariables(configurationService, null, null, URI.parse('file:///VSCode/workspaceLocation')); + assert.strictEqual(systemVariables.resolve('abc ${config.editor.fontFamily} ${config.terminal.integrated.fontFamily} xyz'), 'abc foo bar xyz'); + }); + test('SystemVariables: substitute one env variable', () => { + let configurationService: IConfigurationService; + configurationService = new MockConfigurationService({ + editor: { + fontFamily: 'foo' + }, + terminal: { + integrated: { + fontFamily: 'bar' + } + } + }); + + let envVariables: { [key: string]: string } = { key1: 'Value for Key1', key2: 'Value for Key2' }; + let systemVariables: ConfigVariables = new ConfigVariables(configurationService, null, null, URI.parse('file:///VSCode/workspaceLocation'), envVariables); + if (Platform.isWindows) { + assert.strictEqual(systemVariables.resolve('abc ${config.editor.fontFamily} ${workspaceRoot} ${env.key1} xyz'), 'abc foo \\VSCode\\workspaceLocation Value for Key1 xyz'); + } else { + assert.strictEqual(systemVariables.resolve('abc ${config.editor.fontFamily} ${workspaceRoot} ${env.key1} xyz'), 'abc foo /VSCode/workspaceLocation Value for Key1 xyz'); + } + }); + + test('SystemVariables: substitute many env variable', () => { + let configurationService: IConfigurationService; + configurationService = new MockConfigurationService({ + editor: { + fontFamily: 'foo' + }, + terminal: { + integrated: { + fontFamily: 'bar' + } + } + }); + + let envVariables: { [key: string]: string } = { key1: 'Value for Key1', key2: 'Value for Key2' }; + let systemVariables: ConfigVariables = new ConfigVariables(configurationService, null, null, URI.parse('file:///VSCode/workspaceLocation'), envVariables); + if (Platform.isWindows) { + assert.strictEqual(systemVariables.resolve('${config.editor.fontFamily} ${config.terminal.integrated.fontFamily} ${workspaceRoot} - ${workspaceRoot} ${env.key1} - ${env.key2}'), 'foo bar \\VSCode\\workspaceLocation - \\VSCode\\workspaceLocation Value for Key1 - Value for Key2'); + } else { + assert.strictEqual(systemVariables.resolve('${config.editor.fontFamily} ${config.terminal.integrated.fontFamily} ${workspaceRoot} - ${workspaceRoot} ${env.key1} - ${env.key2}'), 'foo bar /VSCode/workspaceLocation - /VSCode/workspaceLocation Value for Key1 - Value for Key2'); + } + }); +}); + +class MockConfigurationService implements IConfigurationService { + public serviceId = IConfigurationService; + public constructor(private configuration: any = {}) { } + public loadConfiguration(section?: string): TPromise { return TPromise.as(this.getConfiguration()); } + public getConfiguration(): any { return this.configuration; } + public hasWorkspaceConfiguration(): boolean { return false; } + public onDidUpdateConfiguration() { return { dispose() { } }; } + public setUserConfiguration(key: any, value: any): Thenable { return TPromise.as(null); } +} \ No newline at end of file diff --git a/src/vs/workbench/parts/lib/test/node/systemVariables.test.ts b/src/vs/workbench/parts/lib/test/node/systemVariables.test.ts index debae512745..859beef7969 100644 --- a/src/vs/workbench/parts/lib/test/node/systemVariables.test.ts +++ b/src/vs/workbench/parts/lib/test/node/systemVariables.test.ts @@ -28,4 +28,23 @@ suite('SystemVariables tests', () => { assert.strictEqual(systemVariables.resolve('${workspaceRoot} - ${workspaceRoot}'), '/VSCode/workspaceLocation - /VSCode/workspaceLocation'); } }); + test('SystemVariables: substitute one env variable', () => { + let envVariables: { [key: string]: string } = { key1: 'Value for Key1', key2: 'Value for Key2' }; + let systemVariables: SystemVariables = new SystemVariables(null, null, URI.parse('file:///VSCode/workspaceLocation'), envVariables); + if (Platform.isWindows) { + assert.strictEqual(systemVariables.resolve('abc ${workspaceRoot} ${env.key1} xyz'), 'abc \\VSCode\\workspaceLocation Value for Key1 xyz'); + } else { + assert.strictEqual(systemVariables.resolve('abc ${workspaceRoot} ${env.key1} xyz'), 'abc /VSCode/workspaceLocation Value for Key1 xyz'); + } + }); + + test('SystemVariables: substitute many env variable', () => { + let envVariables: { [key: string]: string } = { key1: 'Value for Key1', key2: 'Value for Key2' }; + let systemVariables: SystemVariables = new SystemVariables(null, null, URI.parse('file:///VSCode/workspaceLocation'), envVariables); + if (Platform.isWindows) { + assert.strictEqual(systemVariables.resolve('${workspaceRoot} - ${workspaceRoot} ${env.key1} - ${env.key2}'), '\\VSCode\\workspaceLocation - \\VSCode\\workspaceLocation Value for Key1 - Value for Key2'); + } else { + assert.strictEqual(systemVariables.resolve('${workspaceRoot} - ${workspaceRoot} ${env.key1} - ${env.key2}'), '/VSCode/workspaceLocation - /VSCode/workspaceLocation Value for Key1 - Value for Key2'); + } + }); }); \ No newline at end of file diff --git a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts index 613e3e9f8e2..f2879355a87 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts @@ -56,7 +56,7 @@ import { IPartService } from 'vs/workbench/services/part/common/partService'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkspaceContextService } from 'vs/workbench/services/workspace/common/contextService'; -import { SystemVariables } from 'vs/workbench/parts/lib/node/systemVariables'; +import { ConfigVariables } from 'vs/workbench/parts/lib/node/configVariables'; import { ITextFileService, EventType } from 'vs/workbench/parts/files/common/files'; import { IOutputService, IOutputChannelRegistry, Extensions as OutputExt, IOutputChannel } from 'vs/workbench/parts/output/common/output'; @@ -216,7 +216,7 @@ class ConfigureTaskRunnerAction extends Action { const outputChannel = this.outputService.getChannel(TaskService.OutputChannelId); outputChannel.show(); outputChannel.append(nls.localize('ConfigureTaskRunnerAction.autoDetecting', 'Auto detecting tasks for {0}', selection.id) + '\n'); - let detector = new ProcessRunnerDetector(this.fileService, this.contextService, new SystemVariables(this.editorService, this.contextService)); + let detector = new ProcessRunnerDetector(this.fileService, this.contextService, new ConfigVariables(this.configurationService, this.editorService, this.contextService)); contentPromise = detector.detect(false, selection.id).then((value) => { let config = value.config; if (value.stderr && value.stderr.length > 0) { @@ -637,7 +637,7 @@ class TaskService extends EventEmitter implements ITaskService { this._taskSystem = new NullTaskSystem(); this._taskSystemPromise = TPromise.as(this._taskSystem); } else { - let variables = new SystemVariables(this.editorService, this.contextService); + let variables = new ConfigVariables(this.configurationService, this.editorService, this.contextService); let clearOutput = true; this._taskSystemPromise = TPromise.as(this.configurationService.getConfiguration('tasks')).then((config: TaskConfiguration) => { let parseErrors: string[] = config ? (config).$parseErrors : null; diff --git a/src/vs/workbench/parts/tasks/node/processRunnerSystem.ts b/src/vs/workbench/parts/tasks/node/processRunnerSystem.ts index 296ff776e1e..87e2fc2a1a4 100644 --- a/src/vs/workbench/parts/tasks/node/processRunnerSystem.ts +++ b/src/vs/workbench/parts/tasks/node/processRunnerSystem.ts @@ -18,7 +18,7 @@ import { TerminateResponse, SuccessData, ErrorData } from 'vs/base/common/proces import { LineProcess, LineData } from 'vs/base/node/processes'; import { IOutputService, IOutputChannel } from 'vs/workbench/parts/output/common/output'; -import { SystemVariables } from 'vs/workbench/parts/lib/node/systemVariables'; +import { ISystemVariables } from 'vs/base/common/parsers'; import { IMarkerService } from 'vs/platform/markers/common/markers'; import { ValidationStatus } from 'vs/base/common/parsers'; @@ -37,7 +37,7 @@ export class ProcessRunnerSystem extends EventEmitter implements ITaskSystem { public static TelemetryEventName: string = 'taskService'; private fileConfig: FileConfig.ExternalTaskRunnerConfiguration; - private variables: SystemVariables; + private variables: ISystemVariables; private markerService: IMarkerService; private modelService: IModelService; private outputService: IOutputService; @@ -53,7 +53,7 @@ export class ProcessRunnerSystem extends EventEmitter implements ITaskSystem { private childProcess: LineProcess; private activeTaskIdentifier: string; - constructor(fileConfig:FileConfig.ExternalTaskRunnerConfiguration, variables:SystemVariables, markerService:IMarkerService, modelService: IModelService, telemetryService: ITelemetryService, outputService:IOutputService, outputChannelId:string, clearOutput: boolean = true) { + constructor(fileConfig:FileConfig.ExternalTaskRunnerConfiguration, variables:ISystemVariables, markerService:IMarkerService, modelService: IModelService, telemetryService: ITelemetryService, outputService:IOutputService, outputChannelId:string, clearOutput: boolean = true) { super(); this.fileConfig = fileConfig; this.variables = variables; @@ -388,15 +388,7 @@ export class ProcessRunnerSystem extends EventEmitter implements ITaskSystem { } private resolveVariable(value: string): string { - let regexp =/\$\{(.*?)\}/g; - return value.replace(regexp, (match:string, name:string) => { - let value = (this.variables)[name]; - if (value) { - return value; - } else { - return match; - } - }); + return this.variables.resolve(value); } public log(value: string): void {