提交 cb0f976d 编写于 作者: A Andre Weinand

refactor variable resolving

上级 2d459600
......@@ -25,13 +25,12 @@ import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/node/extHostDocumen
import { IAdapterExecutable, ITerminalSettings, IDebuggerContribution, IConfig, IDebugAdapter } from 'vs/workbench/parts/debug/common/debug';
import { getTerminalLauncher, hasChildprocesses, prepareCommand } from 'vs/workbench/parts/debug/node/terminals';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { VariableResolver } from 'vs/workbench/services/configurationResolver/node/variableResolver';
import { IStringDictionary } from 'vs/base/common/collections';
import { AbstractVariableResolverService } from 'vs/workbench/services/configurationResolver/node/variableResolver';
import { ExtHostConfiguration } from './extHostConfiguration';
import { convertToVSCPaths, convertToDAPaths } from 'vs/workbench/parts/debug/common/debugUtils';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import { ExtHostTerminalService } from 'vs/workbench/api/node/extHostTerminalService';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
export class ExtHostDebugService implements ExtHostDebugServiceShape {
......@@ -176,7 +175,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
throw new Error('Not implemented');
}
};
return asWinJsPromise(token => DebugAdapter.substituteVariables(ws, config, this._variableResolver));
return asWinJsPromise(token => this._variableResolver.resolveAny(ws, config));
}
public $startDASession(handle: number, debugType: string, adpaterExecutable: IAdapterExecutable | null, debugPort: number): TPromise<void> {
......@@ -588,15 +587,12 @@ export class ExtHostDebugConsole implements vscode.DebugConsole {
}
}
export class ExtHostVariableResolverService implements IConfigurationResolverService {
_serviceBrand: any;
_variableResolver: VariableResolver;
export class ExtHostVariableResolverService extends AbstractVariableResolverService {
constructor(workspace: ExtHostWorkspace, editors: ExtHostDocumentsAndEditors, configuration: ExtHostConfiguration) {
this._variableResolver = new VariableResolver({
constructor(workspaceService: ExtHostWorkspace, editorService: ExtHostDocumentsAndEditors, configurationService: ExtHostConfiguration) {
super({
getFolderUri: (folderName: string): URI => {
const folders = workspace.getWorkspaceFolders();
const folders = workspaceService.getWorkspaceFolders();
const found = folders.filter(f => f.name === folderName);
if (found && found.length > 0) {
return found[0].uri;
......@@ -604,16 +600,16 @@ export class ExtHostVariableResolverService implements IConfigurationResolverSer
return undefined;
},
getWorkspaceFolderCount: (): number => {
return workspace.getWorkspaceFolders().length;
return workspaceService.getWorkspaceFolders().length;
},
getConfigurationValue: (folderUri: URI, section: string) => {
return configuration.getConfiguration(undefined, folderUri).get<string>(section);
return configurationService.getConfiguration(undefined, folderUri).get<string>(section);
},
getExecPath: (): string | undefined => {
return undefined; // does not exist in EH
},
getFilePath: (): string | undefined => {
const activeEditor = editors.activeEditor();
const activeEditor = editorService.activeEditor();
if (activeEditor) {
const resource = activeEditor.document.uri;
if (resource.scheme === Schemas.file) {
......@@ -623,38 +619,19 @@ export class ExtHostVariableResolverService implements IConfigurationResolverSer
return undefined;
},
getSelectedText: (): string | undefined => {
const activeEditor = editors.activeEditor();
const activeEditor = editorService.activeEditor();
if (activeEditor && !activeEditor.selection.isEmpty) {
return activeEditor.document.getText(activeEditor.selection);
}
return undefined;
},
getLineNumber: (): string => {
const activeEditor = editors.activeEditor();
const activeEditor = editorService.activeEditor();
if (activeEditor) {
return String(activeEditor.selection.end.line + 1);
}
return undefined;
}
}, process.env);
}
public resolve(root: IWorkspaceFolder, value: string): string;
public resolve(root: IWorkspaceFolder, value: string[]): string[];
public resolve(root: IWorkspaceFolder, value: IStringDictionary<string>): IStringDictionary<string>;
public resolve(root: IWorkspaceFolder, value: any): any {
return this._variableResolver.resolveAny(root ? root.uri : undefined, value);
}
public resolveAny<T>(root: IWorkspaceFolder, value: T, commandMapping?: IStringDictionary<string>): T {
return this._variableResolver.resolveAny(root ? root.uri : undefined, value, commandMapping);
}
public executeCommandVariables(configuration: any, variables: IStringDictionary<string>): TPromise<IStringDictionary<string>> {
throw new Error('findAndExecuteCommandVariables not implemented.');
}
public resolveWithCommands(folder: IWorkspaceFolder, config: any): TPromise<any> {
throw new Error('resolveWithCommands not implemented.');
});
}
}
......@@ -18,10 +18,7 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { ExtensionsChannelId } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
import { IOutputService } from 'vs/workbench/parts/output/common/output';
import { IDebugAdapter, IAdapterExecutable, IDebuggerContribution, IPlatformSpecificAdapterContribution, IConfig } from 'vs/workbench/parts/debug/common/debug';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IStringDictionary } from 'vs/base/common/collections';
import { IDebugAdapter, IAdapterExecutable, IDebuggerContribution, IPlatformSpecificAdapterContribution } from 'vs/workbench/parts/debug/common/debug';
/**
* Abstract implementation of the low level API for a debug adapter.
......@@ -440,26 +437,4 @@ export class DebugAdapter extends StreamDebugAdapter {
};
}
}
static substituteVariables(workspaceFolder: IWorkspaceFolder, config: IConfig, resolverService: IConfigurationResolverService, commandValueMapping?: IStringDictionary<string>): IConfig {
const result = objects.deepClone(config) as IConfig;
// hoist platform specific attributes to top level
if (platform.isWindows && result.windows) {
Object.keys(result.windows).forEach(key => result[key] = result.windows[key]);
} else if (platform.isMacintosh && result.osx) {
Object.keys(result.osx).forEach(key => result[key] = result.osx[key]);
} else if (platform.isLinux && result.linux) {
Object.keys(result.linux).forEach(key => result[key] = result.linux[key]);
}
// delete all platform specific sections
delete result.windows;
delete result.osx;
delete result.linux;
// substitute all variables in string values
return resolverService.resolveAny(workspaceFolder, result, commandValueMapping);
}
}
......@@ -13,9 +13,20 @@ export const IConfigurationResolverService = createDecorator<IConfigurationResol
export interface IConfigurationResolverService {
_serviceBrand: any;
resolve(root: IWorkspaceFolder, value: string): string;
resolve(root: IWorkspaceFolder, value: string[]): string[];
resolve(root: IWorkspaceFolder, value: IStringDictionary<string>): IStringDictionary<string>;
resolveAny<T>(root: IWorkspaceFolder, value: T, commandMapping?: IStringDictionary<string>): T;
resolve(folder: IWorkspaceFolder, value: string): string;
resolve(folder: IWorkspaceFolder, value: string[]): string[];
resolve(folder: IWorkspaceFolder, value: IStringDictionary<string>): IStringDictionary<string>;
/**
* Recursively resolves all variables in the given config and returns a copy of it with substituted values.
* Command variables are only substituted if a "commandValueMapping" dictionary is given and if it contains an entry for the command.
*/
resolveAny(folder: IWorkspaceFolder, config: any, commandValueMapping?: IStringDictionary<string>): any;
/**
* Recursively resolves all variables (including commands) in the given config and returns a copy of it with substituted values.
* If a "variables" dictionary (with names -> command ids) is given,
* command variables are first mapped through it before being resolved.
*/
resolveWithCommands(folder: IWorkspaceFolder, config: any, variables?: IStringDictionary<string>): TPromise<any>;
}
......@@ -7,27 +7,22 @@ import uri from 'vs/base/common/uri';
import * as nls from 'vs/nls';
import * as paths from 'vs/base/common/paths';
import * as platform from 'vs/base/common/platform';
import * as objects from 'vs/base/common/objects';
import { Schemas } from 'vs/base/common/network';
import { TPromise } from 'vs/base/common/winjs.base';
import { sequence } from 'vs/base/common/async';
import { toResource } from 'vs/workbench/common/editor';
import { IStringDictionary, size } 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 { IWorkspaceFolder, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { VariableResolver } from 'vs/workbench/services/configurationResolver/node/variableResolver';
import { AbstractVariableResolverService } from 'vs/workbench/services/configurationResolver/node/variableResolver';
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
import { isUndefinedOrNull } from 'vs/base/common/types';
export class ConfigurationResolverService implements IConfigurationResolverService {
_serviceBrand: any;
private resolver: VariableResolver;
export class ConfigurationResolverService extends AbstractVariableResolverService {
constructor(
envVariables: platform.IProcessEnvironment,
......@@ -37,7 +32,7 @@ export class ConfigurationResolverService implements IConfigurationResolverServi
@ICommandService private commandService: ICommandService,
@IWorkspaceContextService workspaceContextService: IWorkspaceContextService
) {
this.resolver = new VariableResolver({
super({
getFolderUri: (folderName: string): uri => {
const folder = workspaceContextService.getWorkspace().folders.filter(f => f.name === folderName).pop();
return folder ? folder.uri : undefined;
......@@ -84,21 +79,10 @@ export class ConfigurationResolverService implements IConfigurationResolverServi
}, envVariables);
}
public resolve(root: IWorkspaceFolder, value: string): string;
public resolve(root: IWorkspaceFolder, value: string[]): string[];
public resolve(root: IWorkspaceFolder, value: IStringDictionary<string>): IStringDictionary<string>;
public resolve(root: IWorkspaceFolder, value: any): any {
return this.resolver.resolveAny(root ? root.uri : undefined, value);
}
public resolveAny(root: IWorkspaceFolder, value: any, commandValueMapping?: IStringDictionary<string>): any {
return this.resolver.resolveAny(root ? root.uri : undefined, value, commandValueMapping);
}
public resolveWithCommands(folder: IWorkspaceFolder, config: any, variables?: IStringDictionary<string>): TPromise<any> {
// then substitute remaining variables in VS Code core
config = this.substituteVariables(folder, config);
config = this.resolveAny(folder, config);
// now evaluate command variables (which might have a UI)
return this.executeCommandVariables(config, variables).then(commandValueMapping => {
......@@ -109,37 +93,18 @@ export class ConfigurationResolverService implements IConfigurationResolverServi
// finally substitute evaluated command variables (if there are any)
if (size<string>(commandValueMapping) > 0) {
return this.substituteVariables(folder, config, commandValueMapping);
return this.resolveAny(folder, config, commandValueMapping);
} else {
return config;
}
});
}
private substituteVariables(workspaceFolder: IWorkspaceFolder, config: any, commandValueMapping?: IStringDictionary<string>): any {
const result = objects.deepClone(config) as any;
// hoist platform specific attributes to top level
if (platform.isWindows && result.windows) {
Object.keys(result.windows).forEach(key => result[key] = result.windows[key]);
} else if (platform.isMacintosh && result.osx) {
Object.keys(result.osx).forEach(key => result[key] = result.osx[key]);
} else if (platform.isLinux && result.linux) {
Object.keys(result.linux).forEach(key => result[key] = result.linux[key]);
}
// delete all platform specific sections
delete result.windows;
delete result.osx;
delete result.linux;
// substitute all variables in string values
return this.resolveAny(workspaceFolder, result, commandValueMapping);
}
/**
* Finds and executes all command variables (see #6569)
* Finds and executes all command variables in the given configuration and returns their values as a dictionary.
* Please note: this method does not substitute the command variables (so the configuration is not modified).
* The returned dictionary can be passed to "resolvePlatform" for the substitution.
* See #6569.
*/
private executeCommandVariables(configuration: any, variableToCommandMap: IStringDictionary<string>): TPromise<IStringDictionary<string>> {
......
......@@ -5,14 +5,18 @@
import * as paths from 'vs/base/common/paths';
import * as types from 'vs/base/common/types';
import * as objects from 'vs/base/common/objects';
import { IStringDictionary } from 'vs/base/common/collections';
import { relative } from 'path';
import { IProcessEnvironment, isWindows } from 'vs/base/common/platform';
import { IProcessEnvironment, isWindows, isMacintosh, isLinux } from 'vs/base/common/platform';
import { normalizeDriveLetter } from 'vs/base/common/labels';
import { localize } from 'vs/nls';
import uri from 'vs/base/common/uri';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { TPromise } from 'vs/base/common/winjs.base';
export interface IVariableAccessor {
export interface IVariableResolveContext {
getFolderUri(folderName: string): uri | undefined;
getWorkspaceFolderCount(): number;
getConfigurationValue(folderUri: uri, section: string): string | undefined;
......@@ -22,47 +26,78 @@ export interface IVariableAccessor {
getLineNumber(): string;
}
export class VariableResolver {
export class AbstractVariableResolverService implements IConfigurationResolverService {
static VARIABLE_REGEXP = /\$\{(.*?)\}/g;
private envVariables: IProcessEnvironment;
_serviceBrand: any;
constructor(
private accessor: IVariableAccessor,
envVariables: IProcessEnvironment
private _context: IVariableResolveContext,
private _envVariables: IProcessEnvironment = process.env
) {
if (isWindows) {
this.envVariables = Object.create(null);
Object.keys(envVariables).forEach(key => {
this.envVariables[key.toLowerCase()] = envVariables[key];
this._envVariables = Object.create(null);
Object.keys(_envVariables).forEach(key => {
this._envVariables[key.toLowerCase()] = _envVariables[key];
});
} else {
this.envVariables = envVariables;
}
}
resolveAny(folderUri: uri, value: any, commandValueMapping?: IStringDictionary<string>): any {
public resolve(root: IWorkspaceFolder, value: string): string;
public resolve(root: IWorkspaceFolder, value: string[]): string[];
public resolve(root: IWorkspaceFolder, value: IStringDictionary<string>): IStringDictionary<string>;
public resolve(root: IWorkspaceFolder, value: any): any {
return this.recursiveResolve(root ? root.uri : undefined, value);
}
public resolveAny(workspaceFolder: IWorkspaceFolder, config: any, commandValueMapping?: IStringDictionary<string>): any {
const result = objects.deepClone(config) as any;
// hoist platform specific attributes to top level
if (isWindows && result.windows) {
Object.keys(result.windows).forEach(key => result[key] = result.windows[key]);
} else if (isMacintosh && result.osx) {
Object.keys(result.osx).forEach(key => result[key] = result.osx[key]);
} else if (isLinux && result.linux) {
Object.keys(result.linux).forEach(key => result[key] = result.linux[key]);
}
// delete all platform specific sections
delete result.windows;
delete result.osx;
delete result.linux;
// substitute all variables recursively in string values
return this.recursiveResolve(workspaceFolder ? workspaceFolder.uri : undefined, result, commandValueMapping);
}
public resolveWithCommands(folder: IWorkspaceFolder, config: any): TPromise<any> {
throw new Error('resolveWithCommands not implemented.');
}
private recursiveResolve(folderUri: uri, value: any, commandValueMapping?: IStringDictionary<string>): any {
if (types.isString(value)) {
return this.resolve(folderUri, value, commandValueMapping);
return this.resolveString(folderUri, value, commandValueMapping);
} else if (types.isArray(value)) {
return value.map(s => this.resolveAny(folderUri, s, commandValueMapping));
return value.map(s => this.recursiveResolve(folderUri, s, commandValueMapping));
} else if (types.isObject(value)) {
let result: IStringDictionary<string | IStringDictionary<string> | string[]> = Object.create(null);
Object.keys(value).forEach(key => {
const resolvedKey = this.resolve(folderUri, key, commandValueMapping);
result[resolvedKey] = this.resolveAny(folderUri, value[key], commandValueMapping);
const resolvedKey = this.resolveString(folderUri, key, commandValueMapping);
result[resolvedKey] = this.recursiveResolve(folderUri, value[key], commandValueMapping);
});
return result;
}
return value;
}
resolve(folderUri: uri, value: string, commandValueMapping: IStringDictionary<string>): string {
private resolveString(folderUri: uri, value: string, commandValueMapping: IStringDictionary<string>): string {
const filePath = this.accessor.getFilePath();
const filePath = this._context.getFilePath();
return value.replace(VariableResolver.VARIABLE_REGEXP, (match: string, variable: string) => {
return value.replace(AbstractVariableResolverService.VARIABLE_REGEXP, (match: string, variable: string) => {
let argument: string;
const parts = variable.split(':');
......@@ -78,7 +113,7 @@ export class VariableResolver {
if (isWindows) {
argument = argument.toLowerCase();
}
const env = this.envVariables[argument];
const env = this._envVariables[argument];
if (types.isString(env)) {
return env;
}
......@@ -89,7 +124,7 @@ export class VariableResolver {
case 'config':
if (argument) {
const config = this.accessor.getConfigurationValue(folderUri, argument);
const config = this._context.getConfigurationValue(folderUri, argument);
if (types.isUndefinedOrNull(config)) {
throw new Error(localize('configNotFound', "'{0}' can not be resolved because setting '{1}' not found.", match, argument));
}
......@@ -120,7 +155,7 @@ export class VariableResolver {
case 'workspaceFolderBasename':
case 'relativeFile':
if (argument) {
const folder = this.accessor.getFolderUri(argument);
const folder = this._context.getFolderUri(argument);
if (folder) {
folderUri = folder;
} else {
......@@ -128,7 +163,7 @@ export class VariableResolver {
}
}
if (!folderUri) {
if (this.accessor.getWorkspaceFolderCount() > 1) {
if (this._context.getWorkspaceFolderCount() > 1) {
throw new Error(localize('canNotResolveWorkspaceFolderMultiRoot', "'{0}' can not be resolved in a multi folder workspace. Scope this variable using ':' and a workspace folder name.", match));
}
throw new Error(localize('canNotResolveWorkspaceFolder', "'{0}' can not be resolved. Please open a folder.", match));
......@@ -167,14 +202,14 @@ export class VariableResolver {
return paths.basename(folderUri.fsPath);
case 'lineNumber':
const lineNumber = this.accessor.getLineNumber();
const lineNumber = this._context.getLineNumber();
if (lineNumber) {
return lineNumber;
}
throw new Error(localize('canNotResolveLineNumber', "'{0}' can not be resolved. Make sure to have a line selected in the active editor.", match));
case 'selectedText':
const selectedText = this.accessor.getSelectedText();
const selectedText = this._context.getSelectedText();
if (selectedText) {
return selectedText;
}
......@@ -203,7 +238,7 @@ export class VariableResolver {
return basename.slice(0, basename.length - paths.extname(basename).length);
case 'execPath':
const ep = this.accessor.getExecPath();
const ep = this._context.getExecPath();
if (ep) {
return ep;
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册