diff --git a/src/vs/workbench/services/configurationResolver/common/configurationResolver.ts b/src/vs/workbench/services/configurationResolver/common/configurationResolver.ts index 37a44d15196e3..f8bf1cb3ecade 100644 --- a/src/vs/workbench/services/configurationResolver/common/configurationResolver.ts +++ b/src/vs/workbench/services/configurationResolver/common/configurationResolver.ts @@ -79,3 +79,38 @@ export interface CommandInputInfo { } export type ConfiguredInput = PromptStringInputInfo | PickStringInputInfo | CommandInputInfo; + +export enum VariableKind { + Unknown = 'unknown', + + Env = 'env', + Config = 'config', + Command = 'command', + Input = 'input', + ExtensionInstallFolder = 'extensionInstallFolder', + + WorkspaceFolder = 'workspaceFolder', + Cwd = 'cwd', + WorkspaceFolderBasename = 'workspaceFolderBasename', + UserHome = 'userHome', + LineNumber = 'lineNumber', + SelectedText = 'selectedText', + File = 'file', + FileWorkspaceFolder = 'fileWorkspaceFolder', + RelativeFile = 'relativeFile', + RelativeFileDirname = 'relativeFileDirname', + FileDirname = 'fileDirname', + FileExtname = 'fileExtname', + FileBasename = 'fileBasename', + FileBasenameNoExtension = 'fileBasenameNoExtension', + FileDirnameBasename = 'fileDirnameBasename', + ExecPath = 'execPath', + ExecInstallFolder = 'execInstallFolder', + PathSeparator = 'pathSeparator' +} + +export class VariableError extends Error { + constructor(public readonly variable: VariableKind, message?: string) { + super(message); + } +} diff --git a/src/vs/workbench/services/configurationResolver/common/variableResolver.ts b/src/vs/workbench/services/configurationResolver/common/variableResolver.ts index b3183db14bc4d..5b512c36a6a12 100644 --- a/src/vs/workbench/services/configurationResolver/common/variableResolver.ts +++ b/src/vs/workbench/services/configurationResolver/common/variableResolver.ts @@ -12,7 +12,7 @@ import { IProcessEnvironment, isWindows, isMacintosh, isLinux } from 'vs/base/co import { normalizeDriveLetter } from 'vs/base/common/labels'; import { localize } from 'vs/nls'; import { URI as uri } from 'vs/base/common/uri'; -import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; +import { IConfigurationResolverService, VariableError, VariableKind } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ILabelService } from 'vs/platform/label/common/label'; import { replaceAsync } from 'vs/base/common/strings'; @@ -190,37 +190,37 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe } // common error handling for all variables that require an open editor - const getFilePath = (): string => { + const getFilePath = (variableKind: VariableKind): string => { const filePath = this._context.getFilePath(); if (filePath) { return filePath; } - throw new Error(localize('canNotResolveFile', "Variable {0} can not be resolved. Please open an editor.", match)); + throw new VariableError(variableKind, (localize('canNotResolveFile', "Variable {0} can not be resolved. Please open an editor.", match))); }; // common error handling for all variables that require an open editor - const getFolderPathForFile = (): string => { + const getFolderPathForFile = (variableKind: VariableKind): string => { - const filePath = getFilePath(); // throws error if no editor open + const filePath = getFilePath(variableKind); // throws error if no editor open if (this._context.getWorkspaceFolderPathForFile) { const folderPath = this._context.getWorkspaceFolderPathForFile(); if (folderPath) { return folderPath; } } - throw new Error(localize('canNotResolveFolderForFile', "Variable {0}: can not find workspace folder of '{1}'.", match, paths.basename(filePath))); + throw new VariableError(variableKind, localize('canNotResolveFolderForFile', "Variable {0}: can not find workspace folder of '{1}'.", match, paths.basename(filePath))); }; // common error handling for all variables that require an open folder and accept a folder name argument - const getFolderUri = (): uri => { + const getFolderUri = (variableKind: VariableKind): uri => { if (argument) { const folder = this._context.getFolderUri(argument); if (folder) { return folder; } - throw new Error(localize('canNotFindFolder', "Variable {0} can not be resolved. No such folder '{1}'.", match, argument)); + throw new VariableError(variableKind, localize('canNotFindFolder', "Variable {0} can not be resolved. No such folder '{1}'.", match, argument)); } if (folderUri) { @@ -228,9 +228,9 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe } if (this._context.getWorkspaceFolderCount() > 1) { - throw new Error(localize('canNotResolveWorkspaceFolderMultiRoot', "Variable {0} can not be resolved in a multi folder workspace. Scope this variable using ':' and a workspace folder name.", match)); + throw new VariableError(variableKind, localize('canNotResolveWorkspaceFolderMultiRoot', "Variable {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', "Variable {0} can not be resolved. Please open a folder.", match)); + throw new VariableError(variableKind, localize('canNotResolveWorkspaceFolder', "Variable {0} can not be resolved. Please open a folder.", match)); }; @@ -248,56 +248,56 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe // For `env` we should do the same as a normal shell does - evaluates undefined envs to an empty string #46436 return ''; } - throw new Error(localize('missingEnvVarName', "Variable {0} can not be resolved because no environment variable name is given.", match)); + throw new VariableError(VariableKind.Env, localize('missingEnvVarName', "Variable {0} can not be resolved because no environment variable name is given.", match)); case 'config': if (argument) { const config = this._context.getConfigurationValue(folderUri, argument); if (types.isUndefinedOrNull(config)) { - throw new Error(localize('configNotFound', "Variable {0} can not be resolved because setting '{1}' not found.", match, argument)); + throw new VariableError(VariableKind.Config, localize('configNotFound', "Variable {0} can not be resolved because setting '{1}' not found.", match, argument)); } if (types.isObject(config)) { - throw new Error(localize('configNoString', "Variable {0} can not be resolved because '{1}' is a structured value.", match, argument)); + throw new VariableError(VariableKind.Config, localize('configNoString', "Variable {0} can not be resolved because '{1}' is a structured value.", match, argument)); } return config; } - throw new Error(localize('missingConfigName', "Variable {0} can not be resolved because no settings name is given.", match)); + throw new VariableError(VariableKind.Config, localize('missingConfigName', "Variable {0} can not be resolved because no settings name is given.", match)); case 'command': - return this.resolveFromMap(match, argument, commandValueMapping, 'command'); + return this.resolveFromMap(VariableKind.Command, match, argument, commandValueMapping, 'command'); case 'input': - return this.resolveFromMap(match, argument, commandValueMapping, 'input'); + return this.resolveFromMap(VariableKind.Input, match, argument, commandValueMapping, 'input'); case 'extensionInstallFolder': if (argument) { const ext = await this._context.getExtension(argument); if (!ext) { - throw new Error(localize('extensionNotInstalled', "Variable {0} can not be resolved because the extension {1} is not installed.", match, argument)); + throw new VariableError(VariableKind.ExtensionInstallFolder, localize('extensionNotInstalled', "Variable {0} can not be resolved because the extension {1} is not installed.", match, argument)); } return this.fsPath(ext.extensionLocation); } - throw new Error(localize('missingExtensionName', "Variable {0} can not be resolved because no extension name is given.", match)); + throw new VariableError(VariableKind.ExtensionInstallFolder, localize('missingExtensionName', "Variable {0} can not be resolved because no extension name is given.", match)); default: { switch (variable) { case 'workspaceRoot': case 'workspaceFolder': - return normalizeDriveLetter(this.fsPath(getFolderUri())); + return normalizeDriveLetter(this.fsPath(getFolderUri(VariableKind.WorkspaceFolder))); case 'cwd': - return ((folderUri || argument) ? normalizeDriveLetter(this.fsPath(getFolderUri())) : process.cwd()); + return ((folderUri || argument) ? normalizeDriveLetter(this.fsPath(getFolderUri(VariableKind.Cwd))) : process.cwd()); case 'workspaceRootFolderName': case 'workspaceFolderBasename': - return paths.basename(this.fsPath(getFolderUri())); + return paths.basename(this.fsPath(getFolderUri(VariableKind.WorkspaceFolderBasename))); case 'userHome': { if (environment.userHome) { return environment.userHome; } - throw new Error(localize('canNotResolveUserHome', "Variable {0} can not be resolved. UserHome path is not defined", match)); + throw new VariableError(VariableKind.UserHome, localize('canNotResolveUserHome', "Variable {0} can not be resolved. UserHome path is not defined", match)); } case 'lineNumber': { @@ -305,50 +305,50 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe if (lineNumber) { return lineNumber; } - throw new Error(localize('canNotResolveLineNumber', "Variable {0} can not be resolved. Make sure to have a line selected in the active editor.", match)); + throw new VariableError(VariableKind.LineNumber, localize('canNotResolveLineNumber', "Variable {0} can not be resolved. Make sure to have a line selected in the active editor.", match)); } case 'selectedText': { const selectedText = this._context.getSelectedText(); if (selectedText) { return selectedText; } - throw new Error(localize('canNotResolveSelectedText', "Variable {0} can not be resolved. Make sure to have some text selected in the active editor.", match)); + throw new VariableError(VariableKind.SelectedText, localize('canNotResolveSelectedText', "Variable {0} can not be resolved. Make sure to have some text selected in the active editor.", match)); } case 'file': - return getFilePath(); + return getFilePath(VariableKind.File); case 'fileWorkspaceFolder': - return getFolderPathForFile(); + return getFolderPathForFile(VariableKind.FileWorkspaceFolder); case 'relativeFile': if (folderUri || argument) { - return paths.relative(this.fsPath(getFolderUri()), getFilePath()); + return paths.relative(this.fsPath(getFolderUri(VariableKind.RelativeFile)), getFilePath(VariableKind.RelativeFile)); } - return getFilePath(); + return getFilePath(VariableKind.RelativeFile); case 'relativeFileDirname': { - const dirname = paths.dirname(getFilePath()); + const dirname = paths.dirname(getFilePath(VariableKind.RelativeFileDirname)); if (folderUri || argument) { - const relative = paths.relative(this.fsPath(getFolderUri()), dirname); + const relative = paths.relative(this.fsPath(getFolderUri(VariableKind.RelativeFileDirname)), dirname); return relative.length === 0 ? '.' : relative; } return dirname; } case 'fileDirname': - return paths.dirname(getFilePath()); + return paths.dirname(getFilePath(VariableKind.FileDirname)); case 'fileExtname': - return paths.extname(getFilePath()); + return paths.extname(getFilePath(VariableKind.FileExtname)); case 'fileBasename': - return paths.basename(getFilePath()); + return paths.basename(getFilePath(VariableKind.FileBasename)); case 'fileBasenameNoExtension': { - const basename = paths.basename(getFilePath()); + const basename = paths.basename(getFilePath(VariableKind.FileBasenameNoExtension)); return (basename.slice(0, basename.length - paths.extname(basename).length)); } case 'fileDirnameBasename': - return paths.basename(paths.dirname(getFilePath())); + return paths.basename(paths.dirname(getFilePath(VariableKind.FileDirnameBasename))); case 'execPath': { const ep = this._context.getExecPath(); @@ -370,7 +370,7 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe default: try { const key = argument ? `${variable}:${argument}` : variable; - return this.resolveFromMap(match, key, commandValueMapping, undefined); + return this.resolveFromMap(VariableKind.Unknown, match, key, commandValueMapping, undefined); } catch (error) { return match; } @@ -379,13 +379,13 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe } } - private resolveFromMap(match: string, argument: string | undefined, commandValueMapping: IStringDictionary | undefined, prefix: string | undefined): string { + private resolveFromMap(variableKind: VariableKind, match: string, argument: string | undefined, commandValueMapping: IStringDictionary | undefined, prefix: string | undefined): string { if (argument && commandValueMapping) { const v = (prefix === undefined) ? commandValueMapping[argument] : commandValueMapping[prefix + ':' + argument]; if (typeof v === 'string') { return v; } - throw new Error(localize('noValueForCommand', "Variable {0} can not be resolved because the command has no value.", match)); + throw new VariableError(variableKind, localize('noValueForCommand', "Variable {0} can not be resolved because the command has no value.", match)); } return match; }