Skip to content

Commit

Permalink
configuration resolver service: resolveInteractiveVariables
Browse files Browse the repository at this point in the history
fixes #8754
  • Loading branch information
isidorn committed Sep 21, 2016
1 parent 99d17ca commit f986db0
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 66 deletions.
60 changes: 4 additions & 56 deletions src/vs/workbench/parts/debug/node/debugConfigurationManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

import path = require('path');
import nls = require('vs/nls');
import {sequence} from 'vs/base/common/async';
import {TPromise} from 'vs/base/common/winjs.base';
import strings = require('vs/base/common/strings');
import types = require('vs/base/common/types');
Expand All @@ -23,7 +22,6 @@ import jsonContributionRegistry = require('vs/platform/jsonschemas/common/jsonCo
import {IConfigurationService} from 'vs/platform/configuration/common/configuration';
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 {Adapter} from 'vs/workbench/parts/debug/node/debugAdapter';
import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace';
Expand Down Expand Up @@ -184,7 +182,6 @@ export class ConfigurationManager implements debug.IConfigurationManager {
@IConfigurationService private configurationService: IConfigurationService,
@IEnvironmentService private environmentService: IEnvironmentService,
@IQuickOpenService private quickOpenService: IQuickOpenService,
@ICommandService private commandService: ICommandService,
@IConfigurationResolverService private configurationResolverService: IConfigurationResolverService
) {
this._onDidConfigurationChange = new Emitter<debug.IConfig>();
Expand Down Expand Up @@ -266,59 +263,6 @@ export class ConfigurationManager implements debug.IConfigurationManager {
return this.adapters.filter(adapter => strings.equalsIgnoreCase(adapter.type, this.configuration.type)).pop();
}

/**
* Resolve all interactive variables in configuration #6569
*/
public resolveInteractiveVariables(): TPromise<debug.IConfig> {
if (!this.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(this.configuration);

const factory: { (): TPromise<any> }[] = Object.keys(interactiveVariablesToSubstitutes).map(interactiveVariable => {
return () => {
let commandId = null;
if (this.adapter !== null) {
commandId = this.adapter.variables ? this.adapter.variables[interactiveVariable] : null;
}
if (!commandId) {
return TPromise.wrapError(nls.localize('interactiveVariableNotFound', "Adapter {0} does not contribute variable {1} that is specified in launch configuration.", this.adapter !== null ? this.adapter.type : null, interactiveVariable));
} else {
return this.commandService.executeCommand<string>(commandId, this.configuration).then(result => {
if (!result) {
this.configuration.silentlyAbort = true;
}
interactiveVariablesToSubstitutes[interactiveVariable].forEach(substitute =>
substitute.object[substitute.key] = substitute.object[substitute.key].replace(`\${command.${interactiveVariable}}`, result)
);
});
}
};
});

return sequence(factory).then(() => this.configuration);
}

public setConfiguration(nameOrConfig: string|debug.IConfig): TPromise<void> {
return this.loadLaunchConfig().then(config => {
if (types.isObject(nameOrConfig)) {
Expand Down Expand Up @@ -460,4 +404,8 @@ export class ConfigurationManager implements debug.IConfigurationManager {
public loadLaunchConfig(): TPromise<debug.IGlobalConfig> {
return TPromise.as(this.configurationService.getConfiguration<debug.IGlobalConfig>('launch'));
}

public resolveInteractiveVariables(): TPromise<any> {
return this.configurationResolverService.resolveInteractiveVariables(this.configuration, this.adapter ? this.adapter.variables : null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import {TPromise} from 'vs/base/common/winjs.base';
import {IStringDictionary} from 'vs/base/common/collections';
import {createDecorator} from 'vs/platform/instantiation/common/instantiation';

Expand All @@ -17,4 +18,5 @@ export interface IConfigurationResolverService {
resolve(value: IStringDictionary<string[]>): IStringDictionary<string[]>;
resolve(value: IStringDictionary<IStringDictionary<string>>): IStringDictionary<IStringDictionary<string>>;
resolveAny<T>(value: T): T;
resolveInteractiveVariables(configuration: any, interactiveVariablesMap: { [key: string]: string }): TPromise<any>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

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';

Expand All @@ -23,7 +27,8 @@ export class ConfigurationResolverService implements IConfigurationResolverServi
envVariables: { [key: string]: string },
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IEnvironmentService private environmentService: IEnvironmentService,
@IConfigurationService private configurationService: IConfigurationService
@IConfigurationService private configurationService: IConfigurationService,
@ICommandService private commandService: ICommandService
) {
this._workspaceRoot = paths.normalize(workspaceRoot ? workspaceRoot.fsPath : '', true);
this._execPath = environmentService.execPath;
Expand Down Expand Up @@ -163,4 +168,56 @@ export class ConfigurationResolverService implements IConfigurationResolverServi
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<any> {
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<any> }[] = 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<string>(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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ import platform = require('vs/base/common/platform');
import {IConfigurationService, getConfigurationValue} from 'vs/platform/configuration/common/configuration';
import {IConfigurationResolverService} from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import {ConfigurationResolverService} from 'vs/workbench/services/configurationResolver/node/configurationResolverService';
import {TestEnvironmentService, TestConfigurationService, TestEditorService} from 'vs/test/utils/servicesTestUtils';
import {TestEnvironmentService, TestConfigurationService, TestEditorService, } from 'vs/test/utils/servicesTestUtils';
import {TPromise} from 'vs/base/common/winjs.base';

suite('Configuration Resolver Service', () => {
let configurationResolverService: IConfigurationResolverService;
let envVariables: { [key: string]: string } = { key1: 'Value for Key1', key2: 'Value for Key2' };

setup(() => {
configurationResolverService = new ConfigurationResolverService(uri.parse('file:///VSCode/workspaceLocation'), envVariables, new TestEditorService(), TestEnvironmentService, new TestConfigurationService());
configurationResolverService = new ConfigurationResolverService(uri.parse('file:///VSCode/workspaceLocation'), envVariables, new TestEditorService(), TestEnvironmentService, new TestConfigurationService(), null);
});

teardown(() => {
Expand Down Expand Up @@ -70,7 +70,7 @@ suite('Configuration Resolver Service', () => {
}
});

let service = new ConfigurationResolverService(uri.parse('file:///VSCode/workspaceLocation'), envVariables, new TestEditorService(), TestEnvironmentService, configurationService);
let service = new ConfigurationResolverService(uri.parse('file:///VSCode/workspaceLocation'), envVariables, new TestEditorService(), TestEnvironmentService, configurationService, null);
assert.strictEqual(service.resolve('abc ${config.editor.fontFamily} xyz'), 'abc foo xyz');
});

Expand All @@ -87,7 +87,7 @@ suite('Configuration Resolver Service', () => {
}
});

let service = new ConfigurationResolverService(uri.parse('file:///VSCode/workspaceLocation'), envVariables, new TestEditorService(), TestEnvironmentService, configurationService);
let service = new ConfigurationResolverService(uri.parse('file:///VSCode/workspaceLocation'), envVariables, new TestEditorService(), TestEnvironmentService, configurationService, null);
assert.strictEqual(service.resolve('abc ${config.editor.fontFamily} ${config.terminal.integrated.fontFamily} xyz'), 'abc foo bar xyz');
});

Expand All @@ -104,7 +104,7 @@ suite('Configuration Resolver Service', () => {
}
});

let service = new ConfigurationResolverService(uri.parse('file:///VSCode/workspaceLocation'), envVariables, new TestEditorService(), TestEnvironmentService, configurationService);
let service = new ConfigurationResolverService(uri.parse('file:///VSCode/workspaceLocation'), envVariables, new TestEditorService(), TestEnvironmentService, configurationService, null);
if (platform.isWindows) {
assert.strictEqual(service.resolve('abc ${config.editor.fontFamily} ${config.terminal.integrated.fontFamily} xyz'), 'abc foo \\VSCode\\workspaceLocation bar bar xyz');
} else {
Expand All @@ -125,7 +125,7 @@ suite('Configuration Resolver Service', () => {
}
});

let service = new ConfigurationResolverService(uri.parse('file:///VSCode/workspaceLocation'), envVariables, new TestEditorService(), TestEnvironmentService, configurationService);
let service = new ConfigurationResolverService(uri.parse('file:///VSCode/workspaceLocation'), envVariables, new TestEditorService(), TestEnvironmentService, configurationService, null);
if (platform.isWindows) {
assert.strictEqual(service.resolve('abc ${config.editor.fontFamily} ${config.terminal.integrated.fontFamily} xyz'), 'abc foo \\VSCode\\workspaceLocation bar bar xyz');
} else {
Expand All @@ -146,7 +146,7 @@ suite('Configuration Resolver Service', () => {
}
});

let service = new ConfigurationResolverService(uri.parse('file:///VSCode/workspaceLocation'), envVariables, new TestEditorService(), TestEnvironmentService, configurationService);
let service = new ConfigurationResolverService(uri.parse('file:///VSCode/workspaceLocation'), envVariables, new TestEditorService(), TestEnvironmentService, configurationService, null);
if (platform.isWindows) {
assert.strictEqual(service.resolve('abc ${config.editor.fontFamily} ${workspaceRoot} ${env.key1} xyz'), 'abc foo \\VSCode\\workspaceLocation Value for Key1 xyz');
} else {
Expand All @@ -167,7 +167,7 @@ suite('Configuration Resolver Service', () => {
}
});

let service = new ConfigurationResolverService(uri.parse('file:///VSCode/workspaceLocation'), envVariables, new TestEditorService(), TestEnvironmentService, configurationService);
let service = new ConfigurationResolverService(uri.parse('file:///VSCode/workspaceLocation'), envVariables, new TestEditorService(), TestEnvironmentService, configurationService, null);
if (platform.isWindows) {
assert.strictEqual(service.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 {
Expand Down Expand Up @@ -201,7 +201,7 @@ suite('Configuration Resolver Service', () => {
}
});

let service = new ConfigurationResolverService(uri.parse('file:///VSCode/workspaceLocation'), envVariables, new TestEditorService(), TestEnvironmentService, configurationService);
let service = new ConfigurationResolverService(uri.parse('file:///VSCode/workspaceLocation'), envVariables, new TestEditorService(), TestEnvironmentService, configurationService, null);
assert.strictEqual(service.resolve('abc ${config.editor.fontFamily} ${config.editor.lineNumbers} ${config.editor.insertSpaces} ${config.json.schemas[0].fileMatch[1]} xyz'), 'abc foo 123 false {{/myOtherfile}} xyz');
});
});
Expand Down

0 comments on commit f986db0

Please sign in to comment.