Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'editors/code/src/config.ts')
| -rw-r--r-- | editors/code/src/config.ts | 123 |
1 files changed, 123 insertions, 0 deletions
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index 7e79eaab8e..bf4572fcf6 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -1,3 +1,4 @@ +import path = require('path'); import * as vscode from 'vscode'; import { Env } from './client'; import { log } from "./util"; @@ -210,3 +211,125 @@ export async function updateConfig(config: vscode.WorkspaceConfiguration) { } } } + +export function substituteVariablesInEnv(env: Env): Env { + const missingDeps = new Set<string>(); + // vscode uses `env:ENV_NAME` for env vars resolution, and it's easier + // to follow the same convention for our dependency tracking + const definedEnvKeys = new Set(Object.keys(env).map(key => `env:${key}`)); + const envWithDeps = Object.fromEntries(Object.entries(env).map(([key, value]) => { + const deps = new Set<string>(); + const depRe = new RegExp(/\${(?<depName>.+?)}/g); + let match = undefined; + while ((match = depRe.exec(value))) { + const depName = match.groups!.depName; + deps.add(depName); + // `depName` at this point can have a form of `expression` or + // `prefix:expression` + if (!definedEnvKeys.has(depName)) { + missingDeps.add(depName); + } + } + return [`env:${key}`, { deps: [...deps], value }]; + })); + + const resolved = new Set<string>(); + for (const dep of missingDeps) { + const match = /(?<prefix>.*?):(?<body>.+)/.exec(dep); + if (match) { + const { prefix, body } = match.groups!; + if (prefix === 'env') { + const envName = body; + envWithDeps[dep] = { + value: process.env[envName] ?? '', + deps: [] + }; + resolved.add(dep); + } else { + // we can't handle other prefixes at the moment + // leave values as is, but still mark them as resolved + envWithDeps[dep] = { + value: '${' + dep + '}', + deps: [] + }; + resolved.add(dep); + } + } else { + envWithDeps[dep] = { + value: computeVscodeVar(dep), + deps: [] + }; + } + } + const toResolve = new Set(Object.keys(envWithDeps)); + + let leftToResolveSize; + do { + leftToResolveSize = toResolve.size; + for (const key of toResolve) { + if (envWithDeps[key].deps.every(dep => resolved.has(dep))) { + envWithDeps[key].value = envWithDeps[key].value.replace( + /\${(?<depName>.+?)}/g, (_wholeMatch, depName) => { + return envWithDeps[depName].value; + }); + resolved.add(key); + toResolve.delete(key); + } + } + } while (toResolve.size > 0 && toResolve.size < leftToResolveSize); + + const resolvedEnv: Env = {}; + for (const key of Object.keys(env)) { + resolvedEnv[key] = envWithDeps[`env:${key}`].value; + } + return resolvedEnv; +} + +function computeVscodeVar(varName: string): string { + // https://code.visualstudio.com/docs/editor/variables-reference + const supportedVariables: { [k: string]: () => string } = { + workspaceFolder: () => { + const folders = vscode.workspace.workspaceFolders ?? []; + if (folders.length === 1) { + // TODO: support for remote workspaces? + return folders[0].uri.fsPath; + } else if (folders.length > 1) { + // could use currently opened document to detect the correct + // workspace. However, that would be determined by the document + // user has opened on Editor startup. Could lead to + // unpredictable workspace selection in practice. + // It's better to pick the first one + return folders[0].uri.fsPath; + } else { + // no workspace opened + return ''; + } + }, + + workspaceFolderBasename: () => { + const workspaceFolder = computeVscodeVar('workspaceFolder'); + if (workspaceFolder) { + return path.basename(workspaceFolder); + } else { + return ''; + } + }, + + cwd: () => process.cwd(), + + // see + // https://github.com/microsoft/vscode/blob/08ac1bb67ca2459496b272d8f4a908757f24f56f/src/vs/workbench/api/common/extHostVariableResolverService.ts#L81 + // or + // https://github.com/microsoft/vscode/blob/29eb316bb9f154b7870eb5204ec7f2e7cf649bec/src/vs/server/node/remoteTerminalChannel.ts#L56 + execPath: () => process.env.VSCODE_EXEC_PATH ?? process.execPath, + + pathSeparator: () => path.sep + }; + + if (varName in supportedVariables) { + return supportedVariables[varName](); + } else { + // can't resolve, keep the expression as is + return '${' + varName + '}'; + } +} |