提交 8574a05e 编写于 作者: A Andre Weinand

support configResolver in EH

上级 1c786715
......@@ -16,6 +16,7 @@ import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostC
import severity from 'vs/base/common/severity';
import { AbstractDebugAdapter, convertToVSCPaths, convertToDAPaths } from 'vs/workbench/parts/debug/node/debugAdapter';
import * as paths from 'vs/base/common/paths';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
@extHostNamedCustomer(MainContext.MainThreadDebugService)
......@@ -66,6 +67,10 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
return da;
}
substituteVariables(folder: IWorkspaceFolder, config: IConfig): TPromise<IConfig> {
return this._proxy.$substituteVariables(folder.uri, config);
}
runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise<void> {
return this._proxy.$runInTerminal(args, config);
}
......
......@@ -107,7 +107,7 @@ export function createApiFactory(
const extHostCommands = rpcProtocol.set(ExtHostContext.ExtHostCommands, new ExtHostCommands(rpcProtocol, extHostHeapService, extHostLogService));
const extHostTreeViews = rpcProtocol.set(ExtHostContext.ExtHostTreeViews, new ExtHostTreeViews(rpcProtocol.getProxy(MainContext.MainThreadTreeViews), extHostCommands));
rpcProtocol.set(ExtHostContext.ExtHostWorkspace, extHostWorkspace);
const extHostDebugService = rpcProtocol.set(ExtHostContext.ExtHostDebugService, new ExtHostDebugService(rpcProtocol, extHostWorkspace, extensionService));
const extHostDebugService = rpcProtocol.set(ExtHostContext.ExtHostDebugService, new ExtHostDebugService(rpcProtocol, extHostWorkspace, extensionService, extHostDocumentsAndEditors, extHostConfiguration));
rpcProtocol.set(ExtHostContext.ExtHostConfiguration, extHostConfiguration);
const extHostDiagnostics = rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, new ExtHostDiagnostics(rpcProtocol));
const extHostLanguageFeatures = rpcProtocol.set(ExtHostContext.ExtHostLanguageFeatures, new ExtHostLanguageFeatures(rpcProtocol, null, extHostDocuments, extHostCommands, extHostHeapService, extHostDiagnostics));
......
......@@ -790,6 +790,7 @@ export interface ISourceMultiBreakpointDto {
}
export interface ExtHostDebugServiceShape {
$substituteVariables(folder: UriComponents | undefined, config: IConfig): TPromise<IConfig>;
$runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise<void>;
$startDASession(handle: number, debugType: string, adapterExecutableInfo: IAdapterExecutable | null): TPromise<void>;
$stopDASession(handle: number): TPromise<void>;
......
......@@ -4,6 +4,9 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as paths from 'vs/base/common/paths';
import { Schemas } from 'vs/base/common/network';
import URI, { UriComponents } from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
import { Event, Emitter } from 'vs/base/common/event';
import { asWinJsPromise } from 'vs/base/common/async';
......@@ -11,16 +14,20 @@ import {
MainContext, MainThreadDebugServiceShape, ExtHostDebugServiceShape, DebugSessionUUID,
IMainContext, IBreakpointsDeltaDto, ISourceMultiBreakpointDto, IFunctionBreakpointDto
} from 'vs/workbench/api/node/extHost.protocol';
import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
import * as vscode from 'vscode';
import URI, { UriComponents } from 'vs/base/common/uri';
import { Disposable, Position, Location, SourceBreakpoint, FunctionBreakpoint } from 'vs/workbench/api/node/extHostTypes';
import { generateUuid } from 'vs/base/common/uuid';
import { DebugAdapter, convertToVSCPaths, convertToDAPaths } from 'vs/workbench/parts/debug/node/debugAdapter';
import * as paths from 'vs/base/common/paths';
import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService';
import { IAdapterExecutable, ITerminalSettings, IDebuggerContribution } from 'vs/workbench/parts/debug/common/debug';
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/node/extHostDocumentsAndEditors';
import { IAdapterExecutable, ITerminalSettings, IDebuggerContribution, IConfig } from 'vs/workbench/parts/debug/common/debug';
import { getTerminalLauncher } 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 { IConfigurationResolverService } from '../../services/configurationResolver/common/configurationResolver';
import { IStringDictionary } from 'vs/base/common/collections';
import { ExtHostConfiguration } from './extHostConfiguration';
export class ExtHostDebugService implements ExtHostDebugServiceShape {
......@@ -56,8 +63,15 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
private _debugAdapters: Map<number, DebugAdapter>;
private _variableResolver: IConfigurationResolverService;
constructor(mainContext: IMainContext, private _workspace: ExtHostWorkspace, private _extensionService: ExtHostExtensionService) {
constructor(mainContext: IMainContext,
private _workspace: ExtHostWorkspace,
private _extensionService: ExtHostExtensionService,
private _editorsService: ExtHostDocumentsAndEditors,
private _configurationService: ExtHostConfiguration
) {
this._handleCounter = 0;
this._handlers = new Map<number, vscode.DebugConfigurationProvider>();
......@@ -110,6 +124,14 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
return void 0;
}
public $substituteVariables(folderUri: UriComponents | undefined, config: IConfig): TPromise<IConfig> {
if (!this._variableResolver) {
this._variableResolver = new ExtHostVariableResolverService(this._workspace, this._editorsService, this._configurationService);
}
const folder = <IWorkspaceFolder>this.getFolder(folderUri);
return asWinJsPromise(token => DebugAdapter.substituteVariables(folder, config, this._variableResolver));
}
public $startDASession(handle: number, debugType: string, adpaterExecutable: IAdapterExecutable | null): TPromise<void> {
const mythis = this;
......@@ -503,3 +525,71 @@ export class ExtHostDebugConsole implements vscode.DebugConsole {
this.append(value + '\n');
}
}
class ExtHostVariableResolverService implements IConfigurationResolverService {
_serviceBrand: any;
_variableResolver: VariableResolver;
constructor(workspace: ExtHostWorkspace, editors: ExtHostDocumentsAndEditors, configuration: ExtHostConfiguration) {
this._variableResolver = new VariableResolver({
getFolderUri: (folderName: string): URI => {
const folders = workspace.getWorkspaceFolders();
const found = folders.filter(f => f.name === folderName);
if (found && found.length > 0) {
return found[0].uri;
}
return undefined;
},
getWorkspaceFolderCount: (): number => {
return workspace.getWorkspaceFolders().length;
},
getConfigurationValue: (folderUri: URI, section: string) => {
return configuration.getConfiguration(undefined, folderUri).get<string>(section);
},
getEnvironmentService: (name: string): string => {
return undefined;
},
getFilePath: (): string | undefined => {
const activeEditor = editors.activeEditor();
if (activeEditor) {
const resource = activeEditor.document.uri;
if (resource.scheme === Schemas.file) {
return paths.normalize(resource.fsPath, true);
}
}
return undefined;
},
getSelectedText: (): string | undefined => {
debugger;
const activeEditor = editors.activeEditor();
if (activeEditor && !activeEditor.selection.isEmpty) {
return activeEditor.document.getText(activeEditor.selection);
}
return undefined;
},
getLineNumber: (): string => {
const activeEditor = editors.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(root: IWorkspaceFolder, value: any): any {
return this._variableResolver.resolveAny(root ? root.uri : undefined, value);
}
resolveInteractiveVariables(configuration: any, interactiveVariablesMap: { [key: string]: string; }): TPromise<any, any> {
throw new Error('Method not implemented.');
}
}
\ No newline at end of file
......@@ -360,23 +360,29 @@ export interface IGlobalConfig {
}
export interface IEnvConfig {
name?: string;
type: string;
request: string;
internalConsoleOptions?: 'neverOpen' | 'openOnSessionStart' | 'openOnFirstSessionStart';
preLaunchTask?: string;
postDebugTask?: string;
__restart?: any;
__sessionId?: string;
debugServer?: number;
noDebug?: boolean;
port?: number;
}
export interface IConfig extends IEnvConfig {
// fundamental attributes
type: string;
request: string;
name?: string;
// platform specifics
windows?: IEnvConfig;
osx?: IEnvConfig;
linux?: IEnvConfig;
// internals
__sessionId?: string;
__restart?: any;
port?: number; // TODO
}
export interface ICompound {
......@@ -398,6 +404,7 @@ export interface IDebugAdapter extends IDisposable {
export interface IDebugAdapterProvider extends ITerminalLauncher {
createDebugAdapter(debugType: string, adapterInfo: IAdapterExecutable | null): IDebugAdapter;
substituteVariables(folder: IWorkspaceFolder, config: IConfig): TPromise<IConfig>;
}
export interface IAdapterExecutable {
......@@ -497,6 +504,7 @@ export interface IConfigurationManager {
registerDebugAdapterProvider(debugTypes: string[], debugAdapterLauncher: IDebugAdapterProvider): IDisposable;
createDebugAdapter(debugType: string, adapterExecutable: IAdapterExecutable | null): IDebugAdapter | undefined;
substituteVariables(debugType: string, folder: IWorkspaceFolder, config: IConfig): TPromise<IConfig>;
runInTerminal(debugType: string, args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise<void>;
}
......@@ -540,12 +548,6 @@ export interface ILaunch {
*/
getConfigurationNames(includeCompounds?: boolean): string[];
/**
* Returns the resolved configuration.
* Replaces os specific values, system variables, interactive variables.
*/
substituteVariables(config: IConfig): TPromise<IConfig>;
/**
* Opens the launch.json file. Creates if it does not exist.
*/
......
......@@ -8,7 +8,6 @@ import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { Event, Emitter } from 'vs/base/common/event';
import { TPromise } from 'vs/base/common/winjs.base';
import * as strings from 'vs/base/common/strings';
import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
import * as objects from 'vs/base/common/objects';
import uri from 'vs/base/common/uri';
import * as paths from 'vs/base/common/paths';
......@@ -26,7 +25,7 @@ import { IFileService } from 'vs/platform/files/common/files';
import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IDebugConfigurationProvider, IDebuggerContribution, ICompound, IDebugConfiguration, IConfig, IEnvConfig, IGlobalConfig, IConfigurationManager, ILaunch, IAdapterExecutable, IDebugAdapterProvider, IDebugAdapter, ITerminalSettings, ITerminalLauncher } from 'vs/workbench/parts/debug/common/debug';
import { IDebugConfigurationProvider, IDebuggerContribution, ICompound, IDebugConfiguration, IConfig, IGlobalConfig, IConfigurationManager, ILaunch, IAdapterExecutable, IDebugAdapterProvider, IDebugAdapter, ITerminalSettings, ITerminalLauncher } from 'vs/workbench/parts/debug/common/debug';
import { Debugger } from 'vs/workbench/parts/debug/node/debugger';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
......@@ -242,7 +241,8 @@ export class ConfigurationManager implements IConfigurationManager {
@IInstantiationService private instantiationService: IInstantiationService,
@ICommandService private commandService: ICommandService,
@IStorageService private storageService: IStorageService,
@ILifecycleService lifecycleService: ILifecycleService
@ILifecycleService lifecycleService: ILifecycleService,
@IConfigurationResolverService private configurationResolverService: IConfigurationResolverService
) {
this.providers = [];
this.debuggers = [];
......@@ -326,6 +326,14 @@ export class ConfigurationManager implements IConfigurationManager {
return undefined;
}
public substituteVariables(debugType: string, folder: IWorkspaceFolder, config: IConfig): TPromise<IConfig> {
let dap = this.getDebugAdapterProvider(debugType);
if (dap) {
return dap.substituteVariables(folder, config);
}
return TPromise.as(config);
}
public runInTerminal(debugType: string, args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise<void> {
let tl: ITerminalLauncher = this.getDebugAdapterProvider(debugType);
......@@ -355,7 +363,7 @@ export class ConfigurationManager implements IConfigurationManager {
if (duplicate) {
duplicate.merge(rawAdapter, extension.description);
} else {
this.debuggers.push(new Debugger(this, rawAdapter, extension.description, this.configurationService, this.commandService));
this.debuggers.push(new Debugger(this, rawAdapter, extension.description, this.configurationService, this.commandService, this.configurationResolverService));
}
});
});
......@@ -548,7 +556,6 @@ class Launch implements ILaunch {
@IFileService private fileService: IFileService,
@IWorkbenchEditorService protected editorService: IWorkbenchEditorService,
@IConfigurationService protected configurationService: IConfigurationService,
@IConfigurationResolverService private configurationResolverService: IConfigurationResolverService,
@IWorkspaceContextService protected contextService: IWorkspaceContextService,
@IExtensionService private extensionService: IExtensionService
) {
......@@ -607,45 +614,6 @@ class Launch implements ILaunch {
return config.configurations.filter(config => config && config.name === name).shift();
}
protected getWorkspaceForResolving(): IWorkspaceFolder {
if (this.workspace) {
return this.workspace;
}
if (this.contextService.getWorkspace().folders.length === 1) {
return this.contextService.getWorkspace().folders[0];
}
return undefined;
}
public substituteVariables(config: IConfig): TPromise<IConfig> {
const result = objects.deepClone(config) as IConfig;
// Set operating system specific properties #1873
const setOSProperties = (flag: boolean, osConfig: IEnvConfig) => {
if (flag && osConfig) {
Object.keys(osConfig).forEach(key => {
result[key] = osConfig[key];
});
}
};
setOSProperties(isWindows, result.windows);
setOSProperties(isMacintosh, result.osx);
setOSProperties(isLinux, result.linux);
// massage configuration attributes - append workspace path to relatvie paths, substitute variables in paths.
try {
Object.keys(result).forEach(key => {
result[key] = this.configurationResolverService.resolveAny(this.getWorkspaceForResolving(), result[key]);
});
} catch (e) {
return TPromise.wrapError(e);
}
const adapter = this.configurationManager.getDebugger(result.type);
return this.configurationResolverService.resolveInteractiveVariables(result, adapter ? adapter.variables : null);
}
public openConfigFile(sideBySide: boolean, type?: string): TPromise<IEditor> {
return this.extensionService.activateByEvent('onDebugInitialConfigurations').then(() => this.extensionService.activateByEvent('onDebug').then(() => {
const resource = this.uri;
......@@ -715,7 +683,7 @@ class WorkspaceLaunch extends Launch implements ILaunch {
@IWorkspaceContextService contextService: IWorkspaceContextService,
@IExtensionService extensionService: IExtensionService
) {
super(configurationManager, undefined, fileService, editorService, configurationService, configurationResolverService, contextService, extensionService);
super(configurationManager, undefined, fileService, editorService, configurationService, contextService, extensionService);
}
get uri(): uri {
......@@ -747,7 +715,7 @@ class UserLaunch extends Launch implements ILaunch {
@IWorkspaceContextService contextService: IWorkspaceContextService,
@IExtensionService extensionService: IExtensionService
) {
super(configurationManager, undefined, fileService, editorService, configurationService, configurationResolverService, contextService, extensionService);
super(configurationManager, undefined, fileService, editorService, configurationService, contextService, extensionService);
}
get uri(): uri {
......
......@@ -784,9 +784,32 @@ export class DebugService implements debug.IDebugService {
});
}
private substituteVariables(launch: debug.ILaunch, config: debug.IConfig): TPromise<debug.IConfig> {
const dbg = this.configurationManager.getDebugger(config.type);
if (dbg) {
let folder: IWorkspaceFolder = undefined;
if (launch.workspace) {
folder = launch.workspace;
} else {
const folders = this.contextService.getWorkspace().folders;
if (folders.length === 1) {
folder = folders[0];
}
}
return dbg.substituteVariables(folder, config).then(config => {
return config;
}, (err: Error) => {
this.showError(err.message);
return undefined; // bail out
});
}
return TPromise.as(config);
}
private createProcess(launch: debug.ILaunch, config: debug.IConfig, sessionId: string): TPromise<void> {
return this.textFileService.saveAll().then(() =>
(launch ? launch.substituteVariables(config).then(config => config, (err: Error) => this.showError(err.message)) : TPromise.as(config)).then(resolvedConfig => {
this.substituteVariables(launch, config).then(resolvedConfig => {
if (!resolvedConfig) {
// User canceled resolving of interactive variables, silently return
return undefined;
......
......@@ -17,7 +17,9 @@ 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 } from 'vs/workbench/parts/debug/common/debug';
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';
/**
* Abstract implementation of the low level API for a debug adapter.
......@@ -406,6 +408,28 @@ export class DebugAdapter extends StreamDebugAdapter {
};
}
}
static substituteVariables(workspaceFolder: IWorkspaceFolder, config: IConfig, resolverService: IConfigurationResolverService): 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);
}
}
// path hooks helpers
......
......@@ -15,6 +15,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IOutputService } from 'vs/workbench/parts/output/common/output';
import { DebugAdapter } from 'vs/workbench/parts/debug/node/debugAdapter';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
export class Debugger {
......@@ -22,7 +23,8 @@ export class Debugger {
constructor(private configurationManager: IConfigurationManager, private debuggerContribution: IDebuggerContribution, public extensionDescription: IExtensionDescription,
@IConfigurationService private configurationService: IConfigurationService,
@ICommandService private commandService: ICommandService
@ICommandService private commandService: ICommandService,
@IConfigurationResolverService private configurationResolverService: IConfigurationResolverService,
) {
this._mergedExtensionDescriptions = [extensionDescription];
}
......@@ -59,6 +61,26 @@ export class Debugger {
});
}
public substituteVariables(folder: IWorkspaceFolder, config: IConfig): TPromise<IConfig> {
let configP: TPromise<IConfig>;
const debugConfigs = this.configurationService.getValue<IDebugConfiguration>('debug');
if (debugConfigs.extensionHostDebugAdapter) {
configP = this.configurationManager.substituteVariables(this.type, folder, config);
} else {
try {
configP = TPromise.as(DebugAdapter.substituteVariables(folder, config, this.configurationResolverService));
} catch (e) {
return TPromise.wrapError(e);
}
}
return configP.then(result => {
// substitute 'command' variables (including interactive)
return this.configurationResolverService.resolveInteractiveVariables(result, this.variables);
});
}
public runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments): TPromise<void> {
const debugConfigs = this.configurationService.getValue<IDebugConfiguration>('debug');
const config = this.configurationService.getValue<ITerminalSettings>('terminal');
......
......@@ -120,7 +120,7 @@ suite('Debug - Debugger', () => {
};
setup(() => {
_debugger = new Debugger(configurationManager, debuggerContribution, extensionDescriptor0, new TestConfigurationService(), null);
_debugger = new Debugger(configurationManager, debuggerContribution, extensionDescriptor0, new TestConfigurationService(), null, null);
});
teardown(() => {
......
......@@ -3,10 +3,12 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import uri from 'vs/base/common/uri';
import * as paths from 'vs/base/common/paths';
import * as types from 'vs/base/common/types';
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 } from 'vs/base/common/collections';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
......@@ -14,210 +16,11 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IWorkspaceFolder, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { toResource } from 'vs/workbench/common/editor';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
import { IProcessEnvironment } from 'vs/base/common/platform';
import { VariableResolver } from 'vs/workbench/services/configurationResolver/node/variableResolver';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { relative } from 'path';
import { IProcessEnvironment, isWindows } from 'vs/base/common/platform';
import { normalizeDriveLetter } from 'vs/base/common/labels';
import { Schemas } from 'vs/base/common/network';
import { localize } from 'vs/nls';
class VariableResolver {
static VARIABLE_REGEXP = /\$\{(.*?)\}/g;
private envVariables: IProcessEnvironment;
constructor(
envVariables: IProcessEnvironment,
private configurationService: IConfigurationService,
private editorService: IWorkbenchEditorService,
private environmentService: IEnvironmentService,
private workspaceContextService: IWorkspaceContextService
) {
if (isWindows) {
this.envVariables = Object.create(null);
Object.keys(envVariables).forEach(key => {
this.envVariables[key.toLowerCase()] = envVariables[key];
});
} else {
this.envVariables = envVariables;
}
}
resolve(context: IWorkspaceFolder, value: string): string {
const filePath = this.getFilePath();
return value.replace(VariableResolver.VARIABLE_REGEXP, (match: string, variable: string) => {
const parts = variable.split(':');
let sufix: string;
if (parts && parts.length > 1) {
variable = parts[0];
sufix = parts[1];
}
switch (variable) {
case 'env': {
if (sufix) {
if (isWindows) {
sufix = sufix.toLowerCase();
}
const env = this.envVariables[sufix];
if (types.isString(env)) {
return env;
}
// For `env` we should do the same as a normal shell does - evaluates missing envs to an empty string #46436
return '';
}
}
case 'config': {
if (sufix) {
const config = this.configurationService.getValue<string>(sufix, context ? { resource: context.uri } : undefined);
if (!types.isUndefinedOrNull(config) && !types.isObject(config)) {
return config;
}
}
}
default: {
if (sufix) {
const folder = this.workspaceContextService.getWorkspace().folders.filter(f => f.name === sufix).pop();
if (folder) {
context = folder;
}
}
switch (variable) {
case 'workspaceRoot':
case 'workspaceFolder': {
if (context) {
return normalizeDriveLetter(context.uri.fsPath);
}
if (this.workspaceContextService.getWorkspace().folders.length > 1) {
throw new Error(localize('canNotResolveWorkspaceFolderMultiRoot', "'${workspaceFolder}' can not be resolved in a multi folder workspace. Scope this variable using ':' and a workspace folder name."));
}
throw new Error(localize('canNotResolveWorkspaceFolder', "'${workspaceFolder}' can not be resolved. Please open a folder."));
} case 'cwd':
return context ? normalizeDriveLetter(context.uri.fsPath) : process.cwd();
case 'workspaceRootFolderName':
case 'workspaceFolderBasename': {
if (context) {
return paths.basename(context.uri.fsPath);
}
if (this.workspaceContextService.getWorkspace().folders.length > 1) {
throw new Error(localize('canNotResolveFolderBasenameMultiRoot', "'${workspaceFolderBasename}' can not be resolved in a multi folder workspace. Scope this variable using ':' and a workspace folder name."));
}
throw new Error(localize('canNotResolveFolderBasename', "'${workspaceFolderBasename}' can not be resolved. Please open a folder."));
} case 'lineNumber': {
const lineNumber = this.getLineNumber();
if (lineNumber) {
return lineNumber;
}
throw new Error(localize('canNotResolveLineNumber', "'${lineNumber}' can not be resolved. Please open an editor."));
} case 'selectedText': {
const selectedText = this.getSelectedText();
if (selectedText) {
return selectedText;
}
throw new Error(localize('canNotResolveSelectedText', "'${selectedText}' can not be resolved. Please open an editor."));
} case 'file': {
if (filePath) {
return filePath;
}
throw new Error(localize('canNotResolveFile', "'${file}' can not be resolved. Please open an editor."));
} case 'relativeFile': {
if (context && filePath) {
return paths.normalize(relative(context.uri.fsPath, filePath));
}
if (filePath) {
return filePath;
}
throw new Error(localize('canNotResolveRelativeFile', "'${relativeFile}' can not be resolved. Please open an editor."));
} case 'fileDirname': {
if (filePath) {
return paths.dirname(filePath);
}
throw new Error(localize('canNotResolveFileDirname', "'${fileDirname}' can not be resolved. Please open an editor."));
} case 'fileExtname': {
if (filePath) {
return paths.extname(filePath);
}
throw new Error(localize('canNotResolveFileExtname', "'${fileExtname}' can not be resolved. Please open an editor."));
} case 'fileBasename': {
if (filePath) {
return paths.basename(filePath);
}
throw new Error(localize('canNotResolveFileBasename', "'${fileBasename}' can not be resolved. Please open an editor."));
} case 'fileBasenameNoExtension': {
if (filePath) {
const basename = paths.basename(filePath);
return basename.slice(0, basename.length - paths.extname(basename).length);
}
throw new Error(localize('canNotResolveFileBasenameNoExtension', "'${fileBasenameNoExtension}' can not be resolved. Please open an editor."));
}
case 'execPath':
return this.environmentService.execPath;
default:
return match;
}
}
}
});
}
private getSelectedText(): string {
const activeEditor = this.editorService.getActiveEditor();
if (activeEditor) {
const editorControl = (<ICodeEditor>activeEditor.getControl());
if (editorControl) {
const editorModel = editorControl.getModel();
const editorSelection = editorControl.getSelection();
if (editorModel && editorSelection) {
return editorModel.getValueInRange(editorSelection);
}
}
}
return undefined;
}
private getFilePath(): string {
let input = this.editorService.getActiveEditorInput();
if (input instanceof DiffEditorInput) {
input = input.modifiedInput;
}
const fileResource = toResource(input, { filter: Schemas.file });
if (!fileResource) {
return undefined;
}
return paths.normalize(fileResource.fsPath, true);
}
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
private getLineNumber(): string {
const activeEditor = this.editorService.getActiveEditor();
if (activeEditor) {
const editorControl = (<ICodeEditor>activeEditor.getControl());
if (editorControl) {
const lineNumber = editorControl.getSelection().positionLineNumber;
return String(lineNumber);
}
}
return undefined;
}
}
export class ConfigurationResolverService implements IConfigurationResolverService {
_serviceBrand: any;
......@@ -231,42 +34,68 @@ export class ConfigurationResolverService implements IConfigurationResolverServi
@ICommandService private commandService: ICommandService,
@IWorkspaceContextService workspaceContextService: IWorkspaceContextService
) {
this.resolver = new VariableResolver(envVariables, configurationService, editorService, environmentService, workspaceContextService);
this.resolver = new VariableResolver({
getFolderUri: (folderName: string): uri => {
const folder = workspaceContextService.getWorkspace().folders.filter(f => f.name === folderName).pop();
return folder ? folder.uri : undefined;
},
getWorkspaceFolderCount: (): number => {
return workspaceContextService.getWorkspace().folders.length;
},
getConfigurationValue: (folderUri: uri, suffix: string) => {
return configurationService.getValue<string>(suffix, folderUri ? { resource: folderUri } : undefined);
},
getEnvironmentService: (name: string) => {
return environmentService[name];
},
getFilePath: (): string | undefined => {
let input = editorService.getActiveEditorInput();
if (input instanceof DiffEditorInput) {
input = input.modifiedInput;
}
const fileResource = toResource(input, { filter: Schemas.file });
if (!fileResource) {
return undefined;
}
return paths.normalize(fileResource.fsPath, true);
},
getSelectedText: (): string | undefined => {
const activeEditor = editorService.getActiveEditor();
if (activeEditor) {
const editorControl = (<ICodeEditor>activeEditor.getControl());
if (editorControl) {
const editorModel = editorControl.getModel();
const editorSelection = editorControl.getSelection();
if (editorModel && editorSelection) {
return editorModel.getValueInRange(editorSelection);
}
}
}
return undefined;
},
getLineNumber: (): string => {
const activeEditor = editorService.getActiveEditor();
if (activeEditor) {
const editorControl = (<ICodeEditor>activeEditor.getControl());
if (editorControl) {
const lineNumber = editorControl.getSelection().positionLineNumber;
return String(lineNumber);
}
}
return undefined;
}
}, 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 {
if (types.isString(value)) {
return this.resolver.resolve(root, value);
} else if (types.isArray(value)) {
return value.map(s => this.resolver.resolve(root, s));
} else if (types.isObject(value)) {
let result: IStringDictionary<string | IStringDictionary<string> | string[]> = Object.create(null);
Object.keys(value).forEach(key => {
result[key] = this.resolve(root, value[key]);
});
return result;
}
return value;
return this.resolver.resolveAny(root ? root.uri : undefined, value);
}
public resolveAny(root: IWorkspaceFolder, value: any): any {
if (types.isString(value)) {
return this.resolver.resolve(root, value);
} else if (types.isArray(value)) {
return value.map(s => this.resolveAny(root, s));
} else if (types.isObject(value)) {
let result: IStringDictionary<string | IStringDictionary<string> | string[]> = Object.create(null);
Object.keys(value).forEach(key => {
result[key] = this.resolveAny(root, value[key]);
});
return result;
}
return value;
return this.resolver.resolveAny(root ? root.uri : undefined, value);
}
/**
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as paths from 'vs/base/common/paths';
import * as types from 'vs/base/common/types';
import { IStringDictionary } from 'vs/base/common/collections';
import { relative } from 'path';
import { IProcessEnvironment, isWindows } from 'vs/base/common/platform';
import { normalizeDriveLetter } from 'vs/base/common/labels';
import { localize } from 'vs/nls';
import uri from 'vs/base/common/uri';
export interface IVariableAccessor {
getFolderUri(folderName: string): uri | undefined;
getWorkspaceFolderCount(): number;
getConfigurationValue(folderUri: uri, section: string): string | undefined;
getEnvironmentService(name: string): string | undefined;
getFilePath(): string | undefined;
getSelectedText(): string | undefined;
getLineNumber(): string;
}
export class VariableResolver {
static VARIABLE_REGEXP = /\$\{(.*?)\}/g;
private envVariables: IProcessEnvironment;
constructor(
private accessor: IVariableAccessor,
envVariables: IProcessEnvironment
) {
if (isWindows) {
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): any {
if (types.isString(value)) {
return this.resolve(folderUri, value);
} else if (types.isArray(value)) {
return value.map(s => this.resolveAny(folderUri, s));
} else if (types.isObject(value)) {
let result: IStringDictionary<string | IStringDictionary<string> | string[]> = Object.create(null);
Object.keys(value).forEach(key => {
result[key] = this.resolveAny(folderUri, value[key]);
});
return result;
}
return value;
}
resolve(folderUri: uri, value: string): string {
const filePath = this.accessor.getFilePath();
return value.replace(VariableResolver.VARIABLE_REGEXP, (match: string, variable: string) => {
let argument: string;
const parts = variable.split(':');
if (parts && parts.length > 1) {
variable = parts[0];
argument = parts[1];
}
switch (variable) {
case 'env':
if (argument) {
if (isWindows) {
argument = argument.toLowerCase();
}
const env = this.envVariables[argument];
if (types.isString(env)) {
return env;
}
// For `env` we should do the same as a normal shell does - evaluates missing envs to an empty string #46436
return '';
}
throw new Error(localize('missingEnvVarName', "'{0}' can not be resolved because no environment variable is given.", match));
case 'config':
if (argument) {
const config = this.accessor.getConfigurationValue(folderUri, argument);
if (!types.isUndefinedOrNull(config) && !types.isObject(config)) {
return config;
}
throw new Error(localize('configNoString', "'{0}' can not be resolved because '{1}' is a structured value.", match, argument));
}
throw new Error(localize('missingConfigName', "'{0}' can not be resolved because no settings name is given.", match));
default: {
if (argument) {
const folder = this.accessor.getFolderUri(argument);
if (folder) {
folderUri = folder;
}
}
switch (variable) {
case 'workspaceRoot':
case 'workspaceFolder':
if (folderUri) {
return normalizeDriveLetter(folderUri.fsPath);
}
if (this.accessor.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));
case 'cwd':
return folderUri ? normalizeDriveLetter(folderUri.fsPath) : process.cwd();
case 'workspaceRootFolderName':
case 'workspaceFolderBasename':
if (folderUri) {
return paths.basename(folderUri.fsPath);
}
if (this.accessor.getWorkspaceFolderCount() > 1) {
throw new Error(localize('canNotResolveFolderBasenameMultiRoot', "'{0}' can not be resolved in a multi folder workspace. Scope this variable using ':' and a workspace folder name.", match));
}
throw new Error(localize('canNotResolveFolderBasename', "'{0}' can not be resolved. Please open a folder.", match));
case 'lineNumber':
const lineNumber = this.accessor.getLineNumber();
if (lineNumber) {
return lineNumber;
}
throw new Error(localize('canNotResolveLineNumber', "'{0}' can not be resolved. Please open an editor.", match));
case 'selectedText':
const selectedText = this.accessor.getSelectedText();
if (selectedText) {
return selectedText;
}
throw new Error(localize('canNotResolveSelectedText', "'{0}' can not be resolved. Please open an editor and select some text.", match));
case 'file':
if (filePath) {
return filePath;
}
throw new Error(localize('canNotResolveFile', "'{0}' can not be resolved. Please open an editor.", match));
case 'relativeFile':
if (folderUri && filePath) {
return paths.normalize(relative(folderUri.fsPath, filePath));
}
if (filePath) {
return filePath;
}
throw new Error(localize('canNotResolveRelativeFile', "'{0}' can not be resolved. Please open an editor.", match));
case 'fileDirname':
if (filePath) {
return paths.dirname(filePath);
}
throw new Error(localize('canNotResolveFileDirname', "'{0}' can not be resolved. Please open an editor.", match));
case 'fileExtname':
if (filePath) {
return paths.extname(filePath);
}
throw new Error(localize('canNotResolveFileExtname', "'{0}' can not be resolved. Please open an editor.", match));
case 'fileBasename':
if (filePath) {
return paths.basename(filePath);
}
throw new Error(localize('canNotResolveFileBasename', "'{0}' can not be resolved. Please open an editor.", match));
case 'fileBasenameNoExtension':
if (filePath) {
const basename = paths.basename(filePath);
return basename.slice(0, basename.length - paths.extname(basename).length);
}
throw new Error(localize('canNotResolveFileBasenameNoExtension', "'{0}' can not be resolved. Please open an editor.", match));
case 'execPath':
return this.accessor.getEnvironmentService('execPath');
default:
return match;
}
}
}
});
}
}
......@@ -196,18 +196,6 @@ suite('Configuration Resolver Service', () => {
assert.strictEqual(service.resolve(workspace, 'abc ${config:editor.fontFamily} ${config:editor.lineNumbers} ${config:editor.insertSpaces} xyz'), 'abc foo 123 false xyz');
});
test('configuration should not evaluate Javascript', () => {
let configurationService: IConfigurationService;
configurationService = new MockConfigurationService({
editor: {
abc: 'foo'
}
});
let service = new ConfigurationResolverService(envVariables, new TestEditorService(), TestEnvironmentService, configurationService, mockCommandService, new TestContextService());
assert.strictEqual(service.resolve(workspace, 'abc ${config:editor[\'abc\'.substr(0)]} xyz'), 'abc ${config:editor[\'abc\'.substr(0)]} xyz');
});
test('uses original variable as fallback', () => {
let configurationService: IConfigurationService;
configurationService = new MockConfigurationService({
......@@ -215,10 +203,8 @@ suite('Configuration Resolver Service', () => {
});
let service = new ConfigurationResolverService(envVariables, new TestEditorService(), TestEnvironmentService, configurationService, mockCommandService, new TestContextService());
assert.strictEqual(service.resolve(workspace, 'abc ${invalidVariable} xyz'), 'abc ${invalidVariable} xyz');
assert.strictEqual(service.resolve(workspace, 'abc ${env:invalidVariable} xyz'), 'abc xyz');
assert.strictEqual(service.resolve(workspace, 'abc ${config:editor.abc.def} xyz'), 'abc ${config:editor.abc.def} xyz');
assert.strictEqual(service.resolve(workspace, 'abc ${config:panel.abc} xyz'), 'abc ${config:panel.abc} xyz');
assert.strictEqual(service.resolve(workspace, 'abc ${unknownVariable} xyz'), 'abc ${unknownVariable} xyz');
assert.strictEqual(service.resolve(workspace, 'abc ${env:unknownVariable} xyz'), 'abc xyz');
});
test('configuration variables with invalid accessor', () => {
......@@ -230,9 +216,14 @@ suite('Configuration Resolver Service', () => {
});
let service = new ConfigurationResolverService(envVariables, new TestEditorService(), TestEnvironmentService, configurationService, mockCommandService, new TestContextService());
assert.strictEqual(service.resolve(workspace, 'abc ${config:} xyz'), 'abc ${config:} xyz');
assert.strictEqual(service.resolve(workspace, 'abc ${config:editor..fontFamily} xyz'), 'abc ${config:editor..fontFamily} xyz');
assert.strictEqual(service.resolve(workspace, 'abc ${config:editor.none.none2} xyz'), 'abc ${config:editor.none.none2} xyz');
assert.throws(() => service.resolve(workspace, 'abc ${env} xyz'));
assert.throws(() => service.resolve(workspace, 'abc ${env:} xyz'));
assert.throws(() => service.resolve(workspace, 'abc ${config} xyz'));
assert.throws(() => service.resolve(workspace, 'abc ${config:} xyz'));
assert.throws(() => service.resolve(workspace, 'abc ${config:editor} xyz'));
assert.throws(() => service.resolve(workspace, 'abc ${config:editor..fontFamily} xyz'));
assert.throws(() => service.resolve(workspace, 'abc ${config:editor.none.none2} xyz'));
});
test('interactive variable simple', () => {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册