debugger: Do not include Rust in default value for `sourceLanguages` (CodeLLDB config) (#33670)

Piotr Osiewicz and Anthony Eid created

- **debugger: Update exception breakpoints list on capability update**
- **Do not prefill codelldb sourcelanguages by default**

Release Notes:

- debugger: CodeLLDB no longer enables pretty-printers for Rust by
default. This fixes pretty-printers for C++. This is a breaking change
for user-defined debug scenarios from debug.json; in order to enable
Rust pretty printing when using CodeLLDB, add `"sourceLanguages":
["rust"]` to your debug configuration. This change does not affect
scenarios automatically inferred by Zed.

---------

Co-authored-by: Anthony Eid <anthony@zed.dev>

Change summary

crates/dap_adapters/src/codelldb.rs           | 35 ++++++++++++++------
crates/debugger_tools/src/dap_log.rs          | 11 ++++-
crates/debugger_ui/src/session/running.rs     | 10 ++++-
crates/project/src/debugger/locators/cargo.rs | 11 +++++-
crates/project/src/debugger/session.rs        | 22 +++++++++++++
docs/src/debugger.md                          | 22 ++++++++++++-
6 files changed, 90 insertions(+), 21 deletions(-)

Detailed changes

crates/dap_adapters/src/codelldb.rs 🔗

@@ -22,17 +22,16 @@ impl CodeLldbDebugAdapter {
     async fn request_args(
         &self,
         delegate: &Arc<dyn DapDelegate>,
-        task_definition: &DebugTaskDefinition,
+        mut configuration: Value,
+        label: &str,
     ) -> Result<dap::StartDebuggingRequestArguments> {
-        // CodeLLDB uses `name` for a terminal label.
-        let mut configuration = task_definition.config.clone();
-
         let obj = configuration
             .as_object_mut()
             .context("CodeLLDB is not a valid json object")?;
 
+        // CodeLLDB uses `name` for a terminal label.
         obj.entry("name")
-            .or_insert(Value::String(String::from(task_definition.label.as_ref())));
+            .or_insert(Value::String(String::from(label)));
 
         obj.entry("cwd")
             .or_insert(delegate.worktree_root_path().to_string_lossy().into());
@@ -361,17 +360,31 @@ impl DebugAdapter for CodeLldbDebugAdapter {
             self.path_to_codelldb.set(path.clone()).ok();
             command = Some(path);
         };
-
+        let mut json_config = config.config.clone();
         Ok(DebugAdapterBinary {
             command: Some(command.unwrap()),
             cwd: Some(delegate.worktree_root_path().to_path_buf()),
             arguments: user_args.unwrap_or_else(|| {
-                vec![
-                    "--settings".into(),
-                    json!({"sourceLanguages": ["cpp", "rust"]}).to_string(),
-                ]
+                if let Some(config) = json_config.as_object_mut()
+                    && let Some(source_languages) = config.get("sourceLanguages").filter(|value| {
+                        value
+                            .as_array()
+                            .map_or(false, |array| array.iter().all(Value::is_string))
+                    })
+                {
+                    let ret = vec![
+                        "--settings".into(),
+                        json!({"sourceLanguages": source_languages}).to_string(),
+                    ];
+                    config.remove("sourceLanguages");
+                    ret
+                } else {
+                    vec![]
+                }
             }),
-            request_args: self.request_args(delegate, &config).await?,
+            request_args: self
+                .request_args(delegate, json_config, &config.label)
+                .await?,
             envs: HashMap::default(),
             connection: None,
         })

crates/debugger_tools/src/dap_log.rs 🔗

@@ -434,9 +434,14 @@ impl LogStore {
 
     fn clean_sessions(&mut self, cx: &mut Context<Self>) {
         self.projects.values_mut().for_each(|project| {
-            project
-                .debug_sessions
-                .retain(|_, session| !session.is_terminated);
+            let mut allowed_terminated_sessions = 10u32;
+            project.debug_sessions.retain(|_, session| {
+                if !session.is_terminated {
+                    return true;
+                }
+                allowed_terminated_sessions = allowed_terminated_sessions.saturating_sub(1);
+                allowed_terminated_sessions > 0
+            });
         });
 
         cx.notify();

crates/debugger_ui/src/session/running.rs 🔗

@@ -900,7 +900,7 @@ impl RunningState {
 
 
             let config_is_valid = request_type.is_ok();
-
+            let mut extra_config = Value::Null;
             let build_output = if let Some(build) = build {
                 let (task_template, locator_name) = match build {
                     BuildTaskDefinition::Template {
@@ -930,6 +930,7 @@ impl RunningState {
                 };
 
                 let locator_name = if let Some(locator_name) = locator_name {
+                    extra_config = config.clone();
                     debug_assert!(!config_is_valid);
                     Some(locator_name)
                 } else if !config_is_valid {
@@ -945,6 +946,7 @@ impl RunningState {
                         });
                     if let Ok(t) = task {
                         t.await.and_then(|scenario| {
+                            extra_config = scenario.config;
                             match scenario.build {
                                 Some(BuildTaskDefinition::Template {
                                     locator_name, ..
@@ -1008,13 +1010,13 @@ impl RunningState {
                 if !exit_status.success() {
                     anyhow::bail!("Build failed");
                 }
-                Some((task.resolved.clone(), locator_name))
+                Some((task.resolved.clone(), locator_name, extra_config))
             } else {
                 None
             };
 
             if config_is_valid {
-            } else if let Some((task, locator_name)) = build_output {
+            } else if let Some((task, locator_name, extra_config)) = build_output {
                 let locator_name =
                     locator_name.with_context(|| {
                         format!("Could not find a valid locator for a build task and configure is invalid with error: {}", request_type.err()
@@ -1039,6 +1041,8 @@ impl RunningState {
                     .with_context(|| anyhow!("{}: is not a valid adapter name", &adapter))?.config_from_zed_format(zed_config)
 .await?;
                 config = scenario.config;
+                util::merge_non_null_json_value_into(extra_config, &mut config);
+
                 Self::substitute_variables_in_config(&mut config, &task_context);
             } else {
                 let Err(e) = request_type else {

crates/project/src/debugger/locators/cargo.rs 🔗

@@ -2,7 +2,7 @@ use anyhow::{Context as _, Result};
 use async_trait::async_trait;
 use dap::{DapLocator, DebugRequest, adapters::DebugAdapterName};
 use gpui::SharedString;
-use serde_json::Value;
+use serde_json::{Value, json};
 use smol::{
     io::AsyncReadExt,
     process::{Command, Stdio},
@@ -76,6 +76,13 @@ impl DapLocator for CargoLocator {
             _ => {}
         }
 
+        let config = if adapter.as_ref() == "CodeLLDB" {
+            json!({
+                "sourceLanguages": ["rust"]
+            })
+        } else {
+            Value::Null
+        };
         Some(DebugScenario {
             adapter: adapter.0.clone(),
             label: resolved_label.to_string().into(),
@@ -83,7 +90,7 @@ impl DapLocator for CargoLocator {
                 task_template,
                 locator_name: Some(self.name()),
             }),
-            config: serde_json::Value::Null,
+            config,
             tcp_connection: None,
         })
     }

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

@@ -1479,6 +1479,28 @@ impl Session {
             }
             Events::Capabilities(event) => {
                 self.capabilities = self.capabilities.merge(event.capabilities);
+
+                // The adapter might've enabled new exception breakpoints (or disabled existing ones).
+                let recent_filters = self
+                    .capabilities
+                    .exception_breakpoint_filters
+                    .iter()
+                    .flatten()
+                    .map(|filter| (filter.filter.clone(), filter.clone()))
+                    .collect::<BTreeMap<_, _>>();
+                for filter in recent_filters.values() {
+                    let default = filter.default.unwrap_or_default();
+                    self.exception_breakpoints
+                        .entry(filter.filter.clone())
+                        .or_insert_with(|| (filter.clone(), default));
+                }
+                self.exception_breakpoints
+                    .retain(|k, _| recent_filters.contains_key(k));
+                if self.is_started() {
+                    self.send_exception_breakpoints(cx);
+                }
+
+                // Remove the ones that no longer exist.
                 cx.notify();
             }
             Events::Memory(_) => {}

docs/src/debugger.md 🔗

@@ -214,6 +214,8 @@ requirements.txt
 
 #### Rust/C++/C
 
+> For CodeLLDB, you might want to set `sourceLanguages` in your launch configuration based on the source code language.
+
 ##### Using pre-built binary
 
 ```json
@@ -222,7 +224,7 @@ requirements.txt
     "label": "Debug native binary",
     "program": "$ZED_WORKTREE_ROOT/build/binary",
     "request": "launch",
-    "adapter": "CodeLLDB" // GDB is available on non arm macs as well as linux
+    "adapter": "CodeLLDB" // GDB is available on non-ARM Macs as well as Linux
   }
 ]
 ```
@@ -239,7 +241,23 @@ requirements.txt
     },
     "program": "$ZED_WORKTREE_ROOT/target/debug/binary",
     "request": "launch",
-    "adapter": "CodeLLDB" // GDB is available on non arm macs as well as linux
+    "adapter": "CodeLLDB" // GDB is available on non-ARM Macs as well as Linux
+  }
+]
+```
+
+##### Automatically locate a debug target based on build command
+
+```json
+[
+  {
+    "label": "Build & Debug native binary",
+    "adapter": "CodeLLDB" // GDB is available on non-ARM Macs as well as Linux
+    // Zed can infer the path to a debuggee based on the build command
+    "build": {
+      "command": "cargo",
+      "args": ["build"]
+    },
   }
 ]
 ```