mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-08-22 11:24:24 +00:00
Auto merge of #12215 - listochkin:Support-variable-substitution-in-vscode-settings, r=Veykril
feat: Support variable substitution in VSCode settings Currently support a subset of [variables provided by VSCode](https://code.visualstudio.com/docs/editor/variables-reference) in `server.extraEnv` section of Rust-Analyzer settings: * `workspaceFolder` * `workspaceFolderBasename` * `cwd` * `execPath` * `pathSeparator` Also, this PR adds support for general environment variables resolution. You can declare environment variables and reference them from other variables like this: ```JSON "rust-analyzer.server.extraEnv": { "RUSTFLAGS": "-L${env:OPEN_XR_SDK_PATH}", "OPEN_XR_SDK_PATH": "${workspaceFolder}\\..\\OpenXR-SDK\\build\\src\\loader\\Release" }, ``` The order of variable declaration doesn't matter, you can reference variables before defining them. If the variable is not present in `extraEnv` section, VSCode will search for them in your environment. Missing variables will be replaced with empty string. Circular references won't be resolved and will be passed to rust-analyzer server process as is. Closes #9626, but doesn't address use cases where people want to use values provided by `rustc` or `cargo`, such as `${targetTriple}` proposal #11649
This commit is contained in:
commit
927ef0ce7e
7 changed files with 225 additions and 9 deletions
|
@ -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 + '}';
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue