// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. // Originally copied from https://github.com/Microsoft/vscode/blob/1.27.1/src/vs/workbench/services/configurationResolver/node/variableResolver.ts import * as _ from "lodash"; import * as path from "path"; import * as vscode from "vscode"; export interface IProcessEnv { [key: string]: string | undefined; } function isWin(): boolean { return /^win/.test(process.platform); } function hasDriveLetter(_path: string): boolean { return isWindows && _path && _path[1] === ":"; } function normalizeDriveLetter(_path: string): string { if (hasDriveLetter(_path)) { return _path.charAt(0).toUpperCase() + _path.slice(1); } return _path; } const isWindows = isWin(); export class VariableResolver { private static VARIABLE_REGEXP = /\$\{(.*?)\}/g; public constructor(private readonly _envVariables: IProcessEnv = process.env) { if (isWindows) { this._envVariables = Object.create(null); Object.keys(_envVariables).forEach((key) => { this._envVariables[key.toLowerCase()] = _envVariables[key]; }); } } public resolveString(folderUri: vscode.Uri, value: string): string { const filePath = this.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 (_.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(`missingEnvVarName: '${match}' can not be resolved because no environment variable name is given.`); case "config": if (argument) { const config = this.getConfigurationValue(folderUri, argument); if (_.isUndefined(config) || _.isNull(config)) { throw new Error(`configNotFound: '${match}' can not be resolved because setting '${argument}' not found.`); } if (_.isObject(config)) { throw new Error(`configNoString: '${match}' can not be resolved because '${argument}' is a structured value.`); } return config; } throw new Error(`missingConfigName: '${match}' can not be resolved because no settings name is given.`); default: { // common error handling for all variables that require an open folder and accept a folder name argument switch (variable) { case "workspaceRoot": case "workspaceFolder": case "workspaceRootFolderName": case "workspaceFolderBasename": case "relativeFile": if (argument) { const folder = this.getFolderUri(argument); if (folder) { folderUri = folder; } else { throw new Error(`canNotFindFolder: '${match}' can not be resolved. No such folder '${argument}'.`); } } if (!folderUri) { if (this.getWorkspaceFolderCount() > 1) { throw new Error(`canNotResolveWorkspaceFolderMultiRoot: '${match}' ` + `can not be resolved in a multi folder workspace. ` + `Scope this variable using ":' and a workspace folder name.`); } throw new Error(`canNotResolveWorkspaceFolder: '${match}' can not be resolved. Please open a folder.`); } break; default: break; } // common error handling for all variables that require an open file switch (variable) { case "relativeFile": case "fileDirname": case "fileExtname": case "fileBasename": case "fileBasenameNoExtension": if (!filePath) { throw new Error(`canNotResolveFile: '${match}' can not be resolved. Please open an editor.`); } break; default: break; } switch (variable) { case "workspaceRoot": case "workspaceFolder": return normalizeDriveLetter(folderUri.fsPath); case "cwd": return folderUri ? normalizeDriveLetter(folderUri.fsPath) : process.cwd(); case "workspaceRootFolderName": case "workspaceFolderBasename": return path.basename(folderUri.fsPath); case "lineNumber": const lineNumber = this.getLineNumber(); if (lineNumber) { return lineNumber; } throw new Error(`canNotResolveLineNumber: '${match}' can not be resolved.` + ` Make sure to have a line selected in the active editor.`); case "selectedText": const selectedText = this.getSelectedText(); if (selectedText) { return selectedText; } throw new Error(`canNotResolveSelectedText: '${match}' can not be resolved.` + ` Make sure to have some text selected in the active editor.`); case "file": return filePath || ""; case "relativeFile": if (folderUri) { return path.normalize(path.relative(folderUri.fsPath, filePath)); } return filePath; case "fileDirname": return path.dirname(filePath); case "fileExtname": return path.extname(filePath); case "fileBasename": return path.basename(filePath); case "fileBasenameNoExtension": const basename = path.basename(filePath); return basename.slice(0, basename.length - path.extname(basename).length); case "execPath": const ep = process.execPath; if (ep) { return ep; } return match; default: return match; } } } }); } private getFilePath(): string | undefined { const activeEditor = vscode.window.activeTextEditor; if (activeEditor) { const resource = activeEditor.document.uri; if (resource.scheme === "file") { return path.normalize(resource.fsPath); } } return undefined; } private getSelectedText(): string | undefined { const activeTextEditor = vscode.window.activeTextEditor; if (activeTextEditor && activeTextEditor.selection) { return activeTextEditor.document.getText(activeTextEditor.selection); } return undefined; } private getLineNumber(): string | undefined { const activeTextEditor = vscode.window.activeTextEditor; if (activeTextEditor && activeTextEditor.selection) { return String(activeTextEditor.selection.start.line); } return undefined; } private getConfigurationValue(folderUri: vscode.Uri, suffix: string): string { const configuration = vscode.workspace.getConfiguration(undefined, folderUri); const configValue = configuration.get(suffix); return String(configValue); } private getWorkspaceFolderCount(): number { return vscode.workspace.workspaceFolders.length; } private getFolderUri(folderName: string): vscode.Uri { const folder = vscode.workspace.workspaceFolders.filter((f) => f.name === folderName).pop(); return folder ? folder.uri : undefined; } }