diff --git a/CHANGELOG.md b/CHANGELOG.md index c25e3d3..96f4115 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added +- configuration option `pass_environment` which specifies a list of env var names to be passed from ra-multiplex client proxy to the spawned language server (rust-analyzer) + + ## [v0.2.4] - 2024-05-15 ### Fixed diff --git a/defaults.toml b/defaults.toml index e2230ed..e715602 100644 --- a/defaults.toml +++ b/defaults.toml @@ -3,3 +3,4 @@ gc_interval = 10 listen = ["127.0.0.1", 27631] connect = ["127.0.0.1", 27631] log_filters = "info" +pass_environment = [] diff --git a/src/client.rs b/src/client.rs index 186f850..3b5d1e2 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,3 +1,4 @@ +use std::collections::BTreeMap; use std::io::ErrorKind; use std::sync::Arc; @@ -62,11 +63,16 @@ pub async fn process( debug!(?options, "lspmux initialization"); match options.method { - ext::Request::Connect { server, args, cwd } => { + ext::Request::Connect { + server, + args, + env, + cwd, + } => { connect( port, instance_map, - (server, args, cwd), + (server, args, env, cwd), req, init_params, reader, @@ -164,7 +170,12 @@ async fn reload( async fn connect( port: u16, instance_map: Arc>, - (server, args, cwd): (String, Vec, Option), + (server, args, env, cwd): ( + String, + Vec, + BTreeMap, + Option, + ), req: Request, init_params: InitializeParams, mut reader: LspReader>, @@ -178,6 +189,7 @@ async fn connect( let key = InstanceKey { server, args, + env, workspace_root, }; let instance = instance::get_or_spawn(instance_map, key, init_params).await?; diff --git a/src/config.rs b/src/config.rs index bcd2e9c..2bb9f93 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,5 +1,6 @@ use std::fs; use std::net::{IpAddr, Ipv4Addr}; +use std::collections::BTreeSet; use anyhow::{Context, Result}; use directories::ProjectDirs; @@ -32,6 +33,10 @@ mod default { pub fn log_filters() -> String { "info".to_owned() } + + pub fn pass_environment() -> BTreeSet { + BTreeSet::new() + } } mod de { @@ -96,6 +101,9 @@ pub struct Config { #[serde(default = "default::log_filters")] pub log_filters: String, + + #[serde(default = "default::pass_environment")] + pub pass_environment: BTreeSet, } #[cfg(test)] @@ -121,6 +129,7 @@ impl Default for Config { listen: default::listen(), connect: default::connect(), log_filters: default::log_filters(), + pass_environment: default::pass_environment(), } } } diff --git a/src/ext.rs b/src/ext.rs index 0601761..330de3f 100644 --- a/src/ext.rs +++ b/src/ext.rs @@ -86,6 +86,12 @@ pub async fn status(config: &Config, json: bool) -> Result<()> { println!("- Instance"); println!(" pid: {}", instance.pid); println!(" server: {:?} {:?}", instance.server, instance.args); + if !instance.env.is_empty() { + println!(" server env:"); + for (key, val) in instance.env { + println!(" {key} = {val}"); + } + } println!(" path: {:?}", instance.workspace_root); let now = time::OffsetDateTime::now_utc().unix_timestamp(); println!(" last used: {}s ago", now - instance.last_used); diff --git a/src/instance.rs b/src/instance.rs index b2de494..24288dd 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -1,5 +1,5 @@ use std::collections::hash_map::Entry; -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeMap, HashMap, HashSet}; use std::io::ErrorKind; use std::ops::Deref; use std::path::Path; @@ -31,6 +31,7 @@ use crate::lsp::{self, ext}; pub struct InstanceKey { pub server: String, pub args: Vec, + pub env: BTreeMap, pub workspace_root: String, } @@ -310,6 +311,7 @@ impl Instance { pid: self.pid, server: self.key.server.clone(), args: self.key.args.clone(), + env: self.key.env.clone(), workspace_root: self.key.workspace_root.clone(), last_used: self.last_used.load(Ordering::Relaxed), clients, @@ -422,6 +424,7 @@ async fn spawn( ) -> Result> { let mut child = Command::new(&key.server) .args(&key.args) + .envs(&key.env) .current_dir(&key.workspace_root) .stdin(Stdio::piped()) .stdout(Stdio::piped()) diff --git a/src/lsp/ext.rs b/src/lsp/ext.rs index 2e4a8fe..fd05bc6 100644 --- a/src/lsp/ext.rs +++ b/src/lsp/ext.rs @@ -1,5 +1,6 @@ //! LSP-mux (ra-multiplex) specific protocol extensions +use std::collections::BTreeMap; use std::str::FromStr; use anyhow::{bail, Context, Result}; @@ -123,10 +124,15 @@ pub enum Request { server: String, /// Arguments which will be passed to the language server, defaults to an - /// empty list if omited. + /// empty list if omitted. #[serde(default = "Vec::new")] args: Vec, + /// Environment variables which will be set for the language server, + /// defaults to an empty set if omitted. + #[serde(default = "BTreeMap::new", skip_serializing_if = "BTreeMap::is_empty")] + env: BTreeMap, + /// Current working directory of the proxy command. This is only used as /// fallback if the client doesn't provide any workspace root. #[serde(skip_serializing_if = "Option::is_none")] @@ -159,6 +165,7 @@ pub struct Instance { pub pid: u32, pub server: String, pub args: Vec, + pub env: BTreeMap, pub workspace_root: String, pub registered_dyn_capabilities: Vec, pub last_used: i64, diff --git a/src/proxy.rs b/src/proxy.rs index fbc26a8..a1dc9dc 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -1,3 +1,4 @@ +use std::collections::BTreeMap; use std::env; use std::pin::Pin; use std::task::{Context, Poll}; @@ -58,6 +59,13 @@ pub async fn run(config: &Config, server: String, args: Vec) -> Result<( .ok() .and_then(|path| path.to_str().map(String::from)); + let mut env = BTreeMap::new(); + for key in &config.pass_environment { + if let Ok(val) = std::env::var(key) { + env.insert(key.clone(), val); + } + } + let mut stream = TcpStream::connect(config.connect) .await .context("connect")?; @@ -79,7 +87,7 @@ pub async fn run(config: &Config, server: String, args: Vec) -> Result<( .lsp_mux .get_or_insert_with(|| LspMuxOptions { version: LspMuxOptions::PROTOCOL_VERSION.to_owned(), - method: Request::Connect { server, args, cwd }, + method: Request::Connect { server, args, env, cwd }, }); req.params = serde_json::to_value(params).expect("BUG: invalid data");