dap: Allow user to pass custom envs to adapter via project settings (#40490)

Jakub Konka created

It is now possible to configure logging level of CodeLLDB adapter via
envs specified in project settings like so:

```
{
    "dap": {
        "CodeLLDB": {
            "envs": {
                "RUST_LOG": "debug"
            }
        }
    }
}
```

Release Notes:

- N/A

Change summary

Cargo.lock                                                  |  1 
assets/settings/default.json                                |  5 +
crates/dap/src/adapters.rs                                  |  2 
crates/dap_adapters/src/codelldb.rs                         |  9 +--
crates/dap_adapters/src/gdb.rs                              |  6 +
crates/dap_adapters/src/go.rs                               |  3 
crates/dap_adapters/src/javascript.rs                       | 19 +++++-
crates/dap_adapters/src/python.rs                           | 18 +++++-
crates/debug_adapter_extension/Cargo.toml                   |  1 
crates/debug_adapter_extension/src/extension_dap_adapter.rs |  3 +
crates/project/src/debugger/dap_store.rs                    | 10 +++
crates/project/src/project_settings.rs                      |  2 
crates/settings/src/settings_content/project.rs             |  2 
13 files changed, 62 insertions(+), 19 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -4852,6 +4852,7 @@ version = "0.1.0"
 dependencies = [
  "anyhow",
  "async-trait",
+ "collections",
  "dap",
  "extension",
  "gpui",

assets/settings/default.json 🔗

@@ -1925,6 +1925,11 @@
   // DAP Specific settings.
   "dap": {
     // Specify the DAP name as a key here.
+    "CodeLLDB": {
+      "env": {
+        "RUST_LOG": "info"
+      }
+    }
   },
   // Common language server settings.
   "global_lsp_settings": {

crates/dap/src/adapters.rs 🔗

@@ -356,6 +356,7 @@ pub trait DebugAdapter: 'static + Send + Sync {
         config: &DebugTaskDefinition,
         user_installed_path: Option<PathBuf>,
         user_args: Option<Vec<String>>,
+        user_env: Option<HashMap<String, String>>,
         cx: &mut AsyncApp,
     ) -> Result<DebugAdapterBinary>;
 
@@ -455,6 +456,7 @@ impl DebugAdapter for FakeAdapter {
         task_definition: &DebugTaskDefinition,
         _: Option<PathBuf>,
         _: Option<Vec<String>>,
+        _: Option<HashMap<String, String>>,
         _: &mut AsyncApp,
     ) -> Result<DebugAdapterBinary> {
         let connection = task_definition

crates/dap_adapters/src/codelldb.rs 🔗

@@ -2,6 +2,7 @@ use std::{path::PathBuf, sync::OnceLock};
 
 use anyhow::{Context as _, Result};
 use async_trait::async_trait;
+use collections::HashMap;
 use dap::adapters::{DebugTaskDefinition, latest_github_release};
 use futures::StreamExt;
 use gpui::AsyncApp;
@@ -329,6 +330,7 @@ impl DebugAdapter for CodeLldbDebugAdapter {
         config: &DebugTaskDefinition,
         user_installed_path: Option<PathBuf>,
         user_args: Option<Vec<String>>,
+        user_env: Option<HashMap<String, String>>,
         _: &mut AsyncApp,
     ) -> Result<DebugAdapterBinary> {
         let mut command = user_installed_path
@@ -378,11 +380,6 @@ impl DebugAdapter for CodeLldbDebugAdapter {
         };
         let mut json_config = config.config.clone();
 
-        // Enable info level for CodeLLDB by default.
-        // Logs can then be viewed in our DAP logs.
-        let mut envs = collections::HashMap::default();
-        envs.insert("RUST_LOG".to_string(), "info".to_string());
-
         Ok(DebugAdapterBinary {
             command: Some(command.unwrap()),
             cwd: Some(delegate.worktree_root_path().to_path_buf()),
@@ -407,7 +404,7 @@ impl DebugAdapter for CodeLldbDebugAdapter {
             request_args: self
                 .request_args(delegate, json_config, &config.label)
                 .await?,
-            envs,
+            envs: user_env.unwrap_or_default(),
             connection: None,
         })
     }

crates/dap_adapters/src/gdb.rs 🔗

@@ -1,7 +1,8 @@
-use std::{collections::HashMap, ffi::OsStr};
+use std::ffi::OsStr;
 
 use anyhow::{Context as _, Result, bail};
 use async_trait::async_trait;
+use collections::HashMap;
 use dap::{StartDebuggingRequestArguments, adapters::DebugTaskDefinition};
 use gpui::AsyncApp;
 use task::{DebugScenario, ZedDebugConfig};
@@ -160,6 +161,7 @@ impl DebugAdapter for GdbDebugAdapter {
         config: &DebugTaskDefinition,
         user_installed_path: Option<std::path::PathBuf>,
         user_args: Option<Vec<String>>,
+        user_env: Option<HashMap<String, String>>,
         _: &mut AsyncApp,
     ) -> Result<DebugAdapterBinary> {
         let user_setting_path = user_installed_path
@@ -188,7 +190,7 @@ impl DebugAdapter for GdbDebugAdapter {
         Ok(DebugAdapterBinary {
             command: Some(gdb_path),
             arguments: user_args.unwrap_or_else(|| vec!["-i=dap".into()]),
-            envs: HashMap::default(),
+            envs: user_env.unwrap_or_default(),
             cwd: Some(delegate.worktree_root_path().to_path_buf()),
             connection: None,
             request_args: StartDebuggingRequestArguments {

crates/dap_adapters/src/go.rs 🔗

@@ -409,6 +409,7 @@ impl DebugAdapter for GoDebugAdapter {
         task_definition: &DebugTaskDefinition,
         user_installed_path: Option<PathBuf>,
         user_args: Option<Vec<String>>,
+        user_env: Option<HashMap<String, String>>,
         _cx: &mut AsyncApp,
     ) -> Result<DebugAdapterBinary> {
         let adapter_path = paths::debug_adapters_dir().join(&Self::ADAPTER_NAME);
@@ -460,7 +461,7 @@ impl DebugAdapter for GoDebugAdapter {
         let connection;
 
         let mut configuration = task_definition.config.clone();
-        let mut envs = HashMap::default();
+        let mut envs = user_env.unwrap_or_default();
 
         if let Some(configuration) = configuration.as_object_mut() {
             configuration

crates/dap_adapters/src/javascript.rs 🔗

@@ -52,12 +52,13 @@ impl JsDebugAdapter {
         task_definition: &DebugTaskDefinition,
         user_installed_path: Option<PathBuf>,
         user_args: Option<Vec<String>>,
+        user_env: Option<HashMap<String, String>>,
         _: &mut AsyncApp,
     ) -> Result<DebugAdapterBinary> {
         let tcp_connection = task_definition.tcp_connection.clone().unwrap_or_default();
         let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
 
-        let mut envs = HashMap::default();
+        let mut envs = user_env.unwrap_or_default();
 
         let mut configuration = task_definition.config.clone();
         if let Some(configuration) = configuration.as_object_mut() {
@@ -100,9 +101,9 @@ impl JsDebugAdapter {
             }
 
             if let Some(env) = configuration.get("env").cloned()
-                && let Ok(env) = serde_json::from_value(env)
+                && let Ok(env) = serde_json::from_value::<HashMap<String, String>>(env)
             {
-                envs = env;
+                envs.extend(env.into_iter());
             }
 
             configuration
@@ -504,6 +505,7 @@ impl DebugAdapter for JsDebugAdapter {
         config: &DebugTaskDefinition,
         user_installed_path: Option<PathBuf>,
         user_args: Option<Vec<String>>,
+        user_env: Option<HashMap<String, String>>,
         cx: &mut AsyncApp,
     ) -> Result<DebugAdapterBinary> {
         if self.checked.set(()).is_ok() {
@@ -521,8 +523,15 @@ impl DebugAdapter for JsDebugAdapter {
             }
         }
 
-        self.get_installed_binary(delegate, config, user_installed_path, user_args, cx)
-            .await
+        self.get_installed_binary(
+            delegate,
+            config,
+            user_installed_path,
+            user_args,
+            user_env,
+            cx,
+        )
+        .await
     }
 
     fn label_for_child_session(&self, args: &StartDebuggingRequestArguments) -> Option<String> {

crates/dap_adapters/src/python.rs 🔗

@@ -1,5 +1,6 @@
 use crate::*;
 use anyhow::{Context as _, bail};
+use collections::HashMap;
 use dap::{DebugRequest, StartDebuggingRequestArguments, adapters::DebugTaskDefinition};
 use fs::RemoveOptions;
 use futures::{StreamExt, TryStreamExt};
@@ -16,7 +17,6 @@ use std::ffi::OsString;
 use std::net::Ipv4Addr;
 use std::str::FromStr;
 use std::{
-    collections::HashMap,
     ffi::OsStr,
     path::{Path, PathBuf},
 };
@@ -312,6 +312,7 @@ impl PythonDebugAdapter {
         config: &DebugTaskDefinition,
         user_installed_path: Option<PathBuf>,
         user_args: Option<Vec<String>>,
+        user_env: Option<HashMap<String, String>>,
         python_from_toolchain: Option<String>,
     ) -> Result<DebugAdapterBinary> {
         let tcp_connection = config.tcp_connection.clone().unwrap_or_default();
@@ -349,7 +350,7 @@ impl PythonDebugAdapter {
                 timeout,
             }),
             cwd: Some(delegate.worktree_root_path().to_path_buf()),
-            envs: HashMap::default(),
+            envs: user_env.unwrap_or_default(),
             request_args: self.request_args(delegate, config).await?,
         })
     }
@@ -744,6 +745,7 @@ impl DebugAdapter for PythonDebugAdapter {
         config: &DebugTaskDefinition,
         user_installed_path: Option<PathBuf>,
         user_args: Option<Vec<String>>,
+        user_env: Option<HashMap<String, String>>,
         cx: &mut AsyncApp,
     ) -> Result<DebugAdapterBinary> {
         if let Some(local_path) = &user_installed_path {
@@ -752,7 +754,14 @@ impl DebugAdapter for PythonDebugAdapter {
                 local_path.display()
             );
             return self
-                .get_installed_binary(delegate, config, Some(local_path.clone()), user_args, None)
+                .get_installed_binary(
+                    delegate,
+                    config,
+                    Some(local_path.clone()),
+                    user_args,
+                    user_env,
+                    None,
+                )
                 .await;
         }
 
@@ -790,12 +799,13 @@ impl DebugAdapter for PythonDebugAdapter {
                     config,
                     None,
                     user_args,
+                    user_env,
                     Some(toolchain.path.to_string()),
                 )
                 .await;
         }
 
-        self.get_installed_binary(delegate, config, None, user_args, None)
+        self.get_installed_binary(delegate, config, None, user_args, user_env, None)
             .await
     }
 

crates/debug_adapter_extension/Cargo.toml 🔗

@@ -8,6 +8,7 @@ edition.workspace = true
 [dependencies]
 anyhow.workspace = true
 async-trait.workspace = true
+collections.workspace = true
 dap.workspace = true
 extension.workspace = true
 gpui.workspace = true

crates/debug_adapter_extension/src/extension_dap_adapter.rs 🔗

@@ -6,6 +6,7 @@ use std::{
 
 use anyhow::{Context, Result};
 use async_trait::async_trait;
+use collections::HashMap;
 use dap::{
     StartDebuggingRequestArgumentsRequest,
     adapters::{
@@ -91,6 +92,8 @@ impl DebugAdapter for ExtensionDapAdapter {
         user_installed_path: Option<PathBuf>,
         // TODO support user args in the extension API
         _user_args: Option<Vec<String>>,
+        // TODO support user env in the extension API
+        _user_env: Option<HashMap<String, String>>,
         _cx: &mut AsyncApp,
     ) -> Result<DebugAdapterBinary> {
         self.extension

crates/project/src/debugger/dap_store.rs 🔗

@@ -264,13 +264,21 @@ impl DapStore {
                     DapBinary::Custom(binary) => Some(PathBuf::from(binary)),
                 });
                 let user_args = dap_settings.map(|s| s.args.clone());
+                let user_env = dap_settings.map(|s| s.env.clone());
 
                 let delegate = self.delegate(worktree, console, cx);
                 let cwd: Arc<Path> = worktree.read(cx).abs_path().as_ref().into();
 
                 cx.spawn(async move |this, cx| {
                     let mut binary = adapter
-                        .get_binary(&delegate, &definition, user_installed_path, user_args, cx)
+                        .get_binary(
+                            &delegate,
+                            &definition,
+                            user_installed_path,
+                            user_args,
+                            user_env,
+                            cx,
+                        )
                         .await?;
 
                     let env = this

crates/project/src/project_settings.rs 🔗

@@ -1215,6 +1215,7 @@ pub fn local_settings_kind_to_proto(kind: LocalSettingsKind) -> proto::LocalSett
 pub struct DapSettings {
     pub binary: DapBinary,
     pub args: Vec<String>,
+    pub env: HashMap<String, String>,
 }
 
 impl From<DapSettingsContent> for DapSettings {
@@ -1224,6 +1225,7 @@ impl From<DapSettingsContent> for DapSettings {
                 .binary
                 .map_or_else(|| DapBinary::Default, |binary| DapBinary::Custom(binary)),
             args: content.args.unwrap_or_default(),
+            env: content.env.unwrap_or_default(),
         }
     }
 }

crates/settings/src/settings_content/project.rs 🔗

@@ -154,6 +154,8 @@ pub struct DapSettingsContent {
     pub binary: Option<String>,
     #[serde(default)]
     pub args: Option<Vec<String>>,
+    #[serde(default)]
+    pub env: Option<HashMap<String, String>>,
 }
 
 #[skip_serializing_none]