debugger: Filter out debug scenarios with invalid Adapters from debug picker (#35744)

Anthony Eid and Remco Smits created

I also removed a debug assertion that wasn't true when a debug session
was restarting through a request, because there wasn't a booting task
Zed needed to run before the session.

I renamed SessionState::Building to SessionState::Booting as well,
because building implies that we're building code while booting the
session covers more cases and is more accurate.

Release Notes:

- debugger: Filter out more invalid debug configurations from the debug
picker

Co-authored-by: Remco Smits <djsmits12@gmail.com>

Change summary

crates/dap/src/adapters.rs                        |  6 ++
crates/dap/src/registry.rs                        |  2 
crates/debugger_ui/src/debugger_panel.rs          |  2 
crates/debugger_ui/src/new_process_modal.rs       |  8 ++
crates/debugger_ui/src/session/running.rs         |  2 
crates/debugger_ui/src/tests/new_process_modal.rs |  2 
crates/project/src/debugger/session.rs            | 43 ++++++++--------
7 files changed, 38 insertions(+), 27 deletions(-)

Detailed changes

crates/dap/src/adapters.rs 🔗

@@ -74,6 +74,12 @@ impl Borrow<str> for DebugAdapterName {
     }
 }
 
+impl Borrow<SharedString> for DebugAdapterName {
+    fn borrow(&self) -> &SharedString {
+        &self.0
+    }
+}
+
 impl std::fmt::Display for DebugAdapterName {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         std::fmt::Display::fmt(&self.0, f)

crates/dap/src/registry.rs 🔗

@@ -87,7 +87,7 @@ impl DapRegistry {
         self.0.read().adapters.get(name).cloned()
     }
 
-    pub fn enumerate_adapters(&self) -> Vec<DebugAdapterName> {
+    pub fn enumerate_adapters<B: FromIterator<DebugAdapterName>>(&self) -> B {
         self.0.read().adapters.keys().cloned().collect()
     }
 }

crates/debugger_ui/src/debugger_panel.rs 🔗

@@ -300,7 +300,7 @@ impl DebugPanel {
         });
 
         session.update(cx, |session, _| match &mut session.mode {
-            SessionState::Building(state_task) => {
+            SessionState::Booting(state_task) => {
                 *state_task = Some(boot_task);
             }
             SessionState::Running(_) => {

crates/debugger_ui/src/new_process_modal.rs 🔗

@@ -1,5 +1,5 @@
 use anyhow::{Context as _, bail};
-use collections::{FxHashMap, HashMap};
+use collections::{FxHashMap, HashMap, HashSet};
 use language::LanguageRegistry;
 use std::{
     borrow::Cow,
@@ -450,7 +450,7 @@ impl NewProcessModal {
             .and_then(|buffer| buffer.read(cx).language())
             .cloned();
 
-        let mut available_adapters = workspace
+        let mut available_adapters: Vec<_> = workspace
             .update(cx, |_, cx| DapRegistry::global(cx).enumerate_adapters())
             .unwrap_or_default();
         if let Some(language) = active_buffer_language {
@@ -1054,6 +1054,9 @@ impl DebugDelegate {
                 })
             })
         });
+
+        let valid_adapters: HashSet<_> = cx.global::<DapRegistry>().enumerate_adapters();
+
         cx.spawn(async move |this, cx| {
             let (recent, scenarios) = if let Some(task) = task {
                 task.await
@@ -1094,6 +1097,7 @@ impl DebugDelegate {
                                 } => !(hide_vscode && dir.ends_with(".vscode")),
                                 _ => true,
                             })
+                            .filter(|(_, scenario)| valid_adapters.contains(&scenario.adapter))
                             .map(|(kind, scenario)| {
                                 let (language, scenario) =
                                     Self::get_scenario_kind(&languages, &dap_registry, scenario);

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

@@ -1651,7 +1651,7 @@ impl RunningState {
 
         let is_building = self.session.update(cx, |session, cx| {
             session.shutdown(cx).detach();
-            matches!(session.mode, session::SessionState::Building(_))
+            matches!(session.mode, session::SessionState::Booting(_))
         });
 
         if is_building {

crates/debugger_ui/src/tests/new_process_modal.rs 🔗

@@ -298,7 +298,7 @@ async fn test_dap_adapter_config_conversion_and_validation(cx: &mut TestAppConte
 
     let adapter_names = cx.update(|cx| {
         let registry = DapRegistry::global(cx);
-        registry.enumerate_adapters()
+        registry.enumerate_adapters::<Vec<_>>()
     });
 
     let zed_config = ZedDebugConfig {

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

@@ -56,7 +56,7 @@ use std::{
 };
 use task::TaskContext;
 use text::{PointUtf16, ToPointUtf16};
-use util::{ResultExt, maybe};
+use util::{ResultExt, debug_panic, maybe};
 use worktree::Worktree;
 
 #[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd, Ord, Eq)]
@@ -141,7 +141,10 @@ pub struct DataBreakpointState {
 }
 
 pub enum SessionState {
-    Building(Option<Task<Result<()>>>),
+    /// Represents a session that is building/initializing
+    /// even if a session doesn't have a pre build task this state
+    /// is used to run all the async tasks that are required to start the session
+    Booting(Option<Task<Result<()>>>),
     Running(RunningMode),
 }
 
@@ -574,7 +577,7 @@ impl SessionState {
     {
         match self {
             SessionState::Running(debug_adapter_client) => debug_adapter_client.request(request),
-            SessionState::Building(_) => Task::ready(Err(anyhow!(
+            SessionState::Booting(_) => Task::ready(Err(anyhow!(
                 "no adapter running to send request: {request:?}"
             ))),
         }
@@ -583,7 +586,7 @@ impl SessionState {
     /// Did this debug session stop at least once?
     pub(crate) fn has_ever_stopped(&self) -> bool {
         match self {
-            SessionState::Building(_) => false,
+            SessionState::Booting(_) => false,
             SessionState::Running(running_mode) => running_mode.has_ever_stopped,
         }
     }
@@ -839,7 +842,7 @@ impl Session {
             .detach();
 
             let this = Self {
-                mode: SessionState::Building(None),
+                mode: SessionState::Booting(None),
                 id: session_id,
                 child_session_ids: HashSet::default(),
                 parent_session,
@@ -879,7 +882,7 @@ impl Session {
 
     pub fn worktree(&self) -> Option<Entity<Worktree>> {
         match &self.mode {
-            SessionState::Building(_) => None,
+            SessionState::Booting(_) => None,
             SessionState::Running(local_mode) => local_mode.worktree.upgrade(),
         }
     }
@@ -940,14 +943,12 @@ impl Session {
             .await?;
             this.update(cx, |this, cx| {
                 match &mut this.mode {
-                    SessionState::Building(task) if task.is_some() => {
+                    SessionState::Booting(task) if task.is_some() => {
                         task.take().unwrap().detach_and_log_err(cx);
                     }
-                    _ => {
-                        debug_assert!(
-                            this.parent_session.is_some(),
-                            "Booting a root debug session without a boot task"
-                        );
+                    SessionState::Booting(_) => {}
+                    SessionState::Running(_) => {
+                        debug_panic!("Attempting to boot a session that is already running");
                     }
                 };
                 this.mode = SessionState::Running(mode);
@@ -1043,7 +1044,7 @@ impl Session {
 
     pub fn binary(&self) -> Option<&DebugAdapterBinary> {
         match &self.mode {
-            SessionState::Building(_) => None,
+            SessionState::Booting(_) => None,
             SessionState::Running(running_mode) => Some(&running_mode.binary),
         }
     }
@@ -1089,26 +1090,26 @@ impl Session {
 
     pub fn is_started(&self) -> bool {
         match &self.mode {
-            SessionState::Building(_) => false,
+            SessionState::Booting(_) => false,
             SessionState::Running(running) => running.is_started,
         }
     }
 
     pub fn is_building(&self) -> bool {
-        matches!(self.mode, SessionState::Building(_))
+        matches!(self.mode, SessionState::Booting(_))
     }
 
     pub fn as_running_mut(&mut self) -> Option<&mut RunningMode> {
         match &mut self.mode {
             SessionState::Running(local_mode) => Some(local_mode),
-            SessionState::Building(_) => None,
+            SessionState::Booting(_) => None,
         }
     }
 
     pub fn as_running(&self) -> Option<&RunningMode> {
         match &self.mode {
             SessionState::Running(local_mode) => Some(local_mode),
-            SessionState::Building(_) => None,
+            SessionState::Booting(_) => None,
         }
     }
 
@@ -1302,7 +1303,7 @@ impl Session {
             SessionState::Running(local_mode) => {
                 local_mode.initialize_sequence(&self.capabilities, initialize_rx, dap_store, cx)
             }
-            SessionState::Building(_) => {
+            SessionState::Booting(_) => {
                 Task::ready(Err(anyhow!("cannot initialize, still building")))
             }
         }
@@ -1339,7 +1340,7 @@ impl Session {
                 })
                 .detach();
             }
-            SessionState::Building(_) => {}
+            SessionState::Booting(_) => {}
         }
     }
 
@@ -2145,7 +2146,7 @@ impl Session {
                     )
                 }
             }
-            SessionState::Building(build_task) => {
+            SessionState::Booting(build_task) => {
                 build_task.take();
                 Task::ready(Some(()))
             }
@@ -2199,7 +2200,7 @@ impl Session {
     pub fn adapter_client(&self) -> Option<Arc<DebugAdapterClient>> {
         match self.mode {
             SessionState::Running(ref local) => Some(local.client.clone()),
-            SessionState::Building(_) => None,
+            SessionState::Booting(_) => None,
         }
     }