debugger: Always show process list in attach (#28685)

Piotr Osiewicz created

Closes #ISSUE

Release Notes:

- N/A

Change summary

Cargo.lock                                   |   2 
crates/dap/Cargo.toml                        |   1 
crates/dap/src/adapters.rs                   |  15 --
crates/dap/src/registry.rs                   |   2 
crates/dap_adapters/Cargo.toml               |   1 
crates/dap_adapters/src/dap_adapters.rs      |   2 
crates/dap_adapters/src/javascript.rs        |  17 ---
crates/debugger_ui/src/attach_modal.rs       |  24 +---
crates/debugger_ui/src/new_session_modal.rs  | 103 ++++++++++-----------
crates/debugger_ui/src/tests/attach_modal.rs |  25 ++++-
10 files changed, 79 insertions(+), 113 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -4001,7 +4001,6 @@ dependencies = [
  "node_runtime",
  "parking_lot",
  "paths",
- "regex",
  "schemars",
  "serde",
  "serde_json",
@@ -4033,7 +4032,6 @@ dependencies = [
  "gpui",
  "language",
  "paths",
- "regex",
  "serde",
  "serde_json",
  "task",

crates/dap/Cargo.toml 🔗

@@ -39,7 +39,6 @@ log.workspace = true
 node_runtime.workspace = true
 parking_lot.workspace = true
 paths.workspace = true
-regex.workspace = true
 schemars.workspace = true
 serde.workspace = true
 serde_json.workspace = true

crates/dap/src/adapters.rs 🔗

@@ -20,7 +20,7 @@ use std::{
     net::Ipv4Addr,
     ops::Deref,
     path::PathBuf,
-    sync::{Arc, LazyLock},
+    sync::Arc,
 };
 use task::{DebugAdapterConfig, DebugTaskDefinition};
 use util::ResultExt;
@@ -291,14 +291,7 @@ pub trait DebugAdapter: 'static + Send + Sync {
 
     /// Should return base configuration to make the debug adapter work
     fn request_args(&self, config: &DebugTaskDefinition) -> Value;
-
-    fn attach_processes_filter(&self) -> regex::Regex {
-        EMPTY_REGEX.clone()
-    }
 }
-
-static EMPTY_REGEX: LazyLock<regex::Regex> =
-    LazyLock::new(|| regex::Regex::new("").expect("Regex compilation to succeed"));
 #[cfg(any(test, feature = "test-support"))]
 pub struct FakeAdapter {}
 
@@ -375,10 +368,4 @@ impl DebugAdapter for FakeAdapter {
             },
         })
     }
-
-    fn attach_processes_filter(&self) -> regex::Regex {
-        static REGEX: LazyLock<regex::Regex> =
-            LazyLock::new(|| regex::Regex::new("^fake-binary").unwrap());
-        REGEX.clone()
-    }
 }

crates/dap/src/registry.rs 🔗

@@ -8,7 +8,7 @@ struct DapRegistryState {
     adapters: BTreeMap<DebugAdapterName, Arc<dyn DebugAdapter>>,
 }
 
-#[derive(Default)]
+#[derive(Clone, Default)]
 /// Stores available debug adapters.
 pub struct DapRegistry(Arc<RwLock<DapRegistryState>>);
 

crates/dap_adapters/Cargo.toml 🔗

@@ -27,7 +27,6 @@ dap.workspace = true
 gpui.workspace = true
 language.workspace = true
 paths.workspace = true
-regex.workspace = true
 serde.workspace = true
 serde_json.workspace = true
 task.workspace = true

crates/dap_adapters/src/dap_adapters.rs 🔗

@@ -31,7 +31,7 @@ pub fn init(registry: Arc<DapRegistry>) {
     registry.add_adapter(Arc::from(CodeLldbDebugAdapter::default()));
     registry.add_adapter(Arc::from(PythonDebugAdapter));
     registry.add_adapter(Arc::from(PhpDebugAdapter));
-    registry.add_adapter(Arc::from(JsDebugAdapter::default()));
+    registry.add_adapter(Arc::from(JsDebugAdapter));
     registry.add_adapter(Arc::from(LldbDebugAdapter));
     registry.add_adapter(Arc::from(GoDebugAdapter));
     registry.add_adapter(Arc::from(GdbDebugAdapter));

crates/dap_adapters/src/javascript.rs 🔗

@@ -1,24 +1,13 @@
 use adapters::latest_github_release;
 use gpui::AsyncApp;
-use regex::Regex;
 use std::path::PathBuf;
 use task::{DebugRequestType, DebugTaskDefinition};
 
 use crate::*;
 
 #[derive(Debug)]
-pub(crate) struct JsDebugAdapter {
-    attach_processes: Regex,
-}
+pub(crate) struct JsDebugAdapter;
 
-impl Default for JsDebugAdapter {
-    fn default() -> Self {
-        Self {
-            attach_processes: Regex::new(r"(?i)^(?:node|bun|iojs)(?:$|\b)")
-                .expect("Regex compilation to succeed"),
-        }
-    }
-}
 impl JsDebugAdapter {
     const ADAPTER_NAME: &'static str = "JavaScript";
     const ADAPTER_NPM_NAME: &'static str = "vscode-js-debug";
@@ -149,8 +138,4 @@ impl DebugAdapter for JsDebugAdapter {
         }
         args
     }
-
-    fn attach_processes_filter(&self) -> Regex {
-        self.attach_processes.clone()
-    }
 }

crates/debugger_ui/src/attach_modal.rs 🔗

@@ -4,7 +4,6 @@ use gpui::Subscription;
 use gpui::{DismissEvent, Entity, EventEmitter, Focusable, Render};
 use picker::{Picker, PickerDelegate};
 
-use std::cell::LazyCell;
 use std::sync::Arc;
 use sysinfo::System;
 use ui::{Context, Tooltip, prelude::*};
@@ -24,7 +23,7 @@ pub(crate) struct AttachModalDelegate {
     matches: Vec<StringMatch>,
     placeholder_text: Arc<str>,
     project: Entity<project::Project>,
-    debug_config: task::DebugTaskDefinition,
+    pub(crate) debug_config: task::DebugTaskDefinition,
     candidates: Arc<[Candidate]>,
 }
 
@@ -58,7 +57,7 @@ impl AttachModal {
         window: &mut Window,
         cx: &mut Context<Self>,
     ) -> Self {
-        let mut processes: Vec<_> = System::new_all()
+        let mut processes: Box<[_]> = System::new_all()
             .processes()
             .values()
             .map(|process| {
@@ -75,30 +74,18 @@ impl AttachModal {
             })
             .collect();
         processes.sort_by_key(|k| k.name.clone());
+        let processes = processes.into_iter().collect();
         Self::with_processes(project, debug_config, processes, modal, window, cx)
     }
 
     pub(super) fn with_processes(
         project: Entity<project::Project>,
         debug_config: task::DebugTaskDefinition,
-        processes: Vec<Candidate>,
+        processes: Arc<[Candidate]>,
         modal: bool,
         window: &mut Window,
         cx: &mut Context<Self>,
     ) -> Self {
-        let adapter = project
-            .read(cx)
-            .debug_adapters()
-            .adapter(&debug_config.adapter);
-        let filter = LazyCell::new(|| adapter.map(|adapter| adapter.attach_processes_filter()));
-        let processes = processes
-            .into_iter()
-            .filter(|process| {
-                filter
-                    .as_ref()
-                    .map_or(false, |filter| filter.is_match(&process.name))
-            })
-            .collect();
         let picker = cx.new(|cx| {
             Picker::uniform_list(
                 AttachModalDelegate::new(project, debug_config, processes),
@@ -117,9 +104,10 @@ impl AttachModal {
 }
 
 impl Render for AttachModal {
-    fn render(&mut self, _window: &mut Window, _: &mut Context<Self>) -> impl ui::IntoElement {
+    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl ui::IntoElement {
         v_flex()
             .key_context("AttachModal")
+            .track_focus(&self.focus_handle(cx))
             .w(rems(34.))
             .child(self.picker.clone())
     }

crates/debugger_ui/src/new_session_modal.rs 🔗

@@ -11,6 +11,7 @@ use gpui::{
     App, AppContext, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Render, TextStyle,
     WeakEntity,
 };
+use project::Project;
 use settings::Settings;
 use task::{DebugTaskDefinition, LaunchConfig};
 use theme::ThemeSettings;
@@ -59,7 +60,7 @@ impl NewSessionModal {
         debug_panel: WeakEntity<DebugPanel>,
         workspace: WeakEntity<Workspace>,
         window: &mut Window,
-        cx: &mut App,
+        cx: &mut Context<Self>,
     ) -> Self {
         let debugger = past_debug_definition
             .as_ref()
@@ -171,25 +172,13 @@ impl NewSessionModal {
         attach.update(cx, |this, cx| {
             if selected_debugger != this.debug_definition.adapter {
                 this.debug_definition.adapter = selected_debugger.into();
-                if let Some(project) = this
-                    .workspace
-                    .read_with(cx, |workspace, _| workspace.project().clone())
-                    .ok()
-                {
-                    this.attach_picker = Some(cx.new(|cx| {
-                        let modal = AttachModal::new(
-                            project,
-                            this.debug_definition.clone(),
-                            false,
-                            window,
-                            cx,
-                        );
-
-                        window.focus(&modal.focus_handle(cx));
-
-                        modal
-                    }));
-                }
+
+                this.attach_picker.update(cx, |this, cx| {
+                    this.picker.update(cx, |this, cx| {
+                        this.delegate.debug_config.adapter = selected_debugger.into();
+                        this.focus(window, cx);
+                    })
+                });
             }
 
             cx.notify();
@@ -256,7 +245,6 @@ impl NewSessionModal {
             ContextMenu::build(window, cx, move |mut menu, _, cx| {
                 let setter_for_name = |task: DebugTaskDefinition| {
                     let weak = weak.clone();
-                    let workspace = workspace.clone();
                     move |window: &mut Window, cx: &mut App| {
                         weak.update(cx, |this, cx| {
                             this.last_selected_profile_name = Some(SharedString::from(&task.label));
@@ -271,12 +259,19 @@ impl NewSessionModal {
                                     );
                                 }
                                 DebugRequestType::Attach(_) => {
+                                    let Ok(project) = this
+                                        .workspace
+                                        .read_with(cx, |this, _| this.project().clone())
+                                    else {
+                                        return;
+                                    };
                                     this.mode = NewSessionMode::attach(
                                         this.debugger.clone(),
-                                        workspace.clone(),
+                                        project,
                                         window,
                                         cx,
                                     );
+                                    this.mode.focus_handle(cx).focus(window);
                                     if let Some((debugger, attach)) =
                                         this.debugger.as_ref().zip(this.mode.as_attach())
                                     {
@@ -365,18 +360,16 @@ impl LaunchMode {
 
 #[derive(Clone)]
 struct AttachMode {
-    workspace: WeakEntity<Workspace>,
     debug_definition: DebugTaskDefinition,
-    attach_picker: Option<Entity<AttachModal>>,
-    focus_handle: FocusHandle,
+    attach_picker: Entity<AttachModal>,
 }
 
 impl AttachMode {
     fn new(
         debugger: Option<SharedString>,
-        workspace: WeakEntity<Workspace>,
+        project: Entity<Project>,
         window: &mut Window,
-        cx: &mut App,
+        cx: &mut Context<NewSessionModal>,
     ) -> Entity<Self> {
         let debug_definition = DebugTaskDefinition {
             label: "Attach New Session Setup".into(),
@@ -387,27 +380,15 @@ impl AttachMode {
             initialize_args: None,
             stop_on_entry: Some(false),
         };
+        let attach_picker = cx.new(|cx| {
+            let modal = AttachModal::new(project, debug_definition.clone(), false, window, cx);
+            window.focus(&modal.focus_handle(cx));
 
-        let attach_picker = if let Some(project) = debugger.and(
-            workspace
-                .read_with(cx, |workspace, _| workspace.project().clone())
-                .ok(),
-        ) {
-            Some(cx.new(|cx| {
-                let modal = AttachModal::new(project, debug_definition.clone(), false, window, cx);
-                window.focus(&modal.focus_handle(cx));
-
-                modal
-            }))
-        } else {
-            None
-        };
-
-        cx.new(|cx| Self {
-            workspace,
+            modal
+        });
+        cx.new(|_| Self {
             debug_definition,
             attach_picker,
-            focus_handle: cx.focus_handle(),
         })
     }
     fn debug_task(&self) -> task::AttachConfig {
@@ -444,7 +425,7 @@ impl Focusable for NewSessionMode {
     fn focus_handle(&self, cx: &App) -> FocusHandle {
         match &self {
             NewSessionMode::Launch(entity) => entity.read(cx).program.focus_handle(cx),
-            NewSessionMode::Attach(entity) => entity.read(cx).focus_handle.clone(),
+            NewSessionMode::Attach(entity) => entity.read(cx).attach_picker.focus_handle(cx),
         }
     }
 }
@@ -476,8 +457,11 @@ impl RenderOnce for LaunchMode {
 }
 
 impl RenderOnce for AttachMode {
-    fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
-        v_flex().w_full().children(self.attach_picker.clone())
+    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
+        v_flex()
+            .w_full()
+            .track_focus(&self.attach_picker.focus_handle(cx))
+            .child(self.attach_picker.clone())
     }
 }
 
@@ -497,13 +481,17 @@ impl RenderOnce for NewSessionMode {
 impl NewSessionMode {
     fn attach(
         debugger: Option<SharedString>,
-        workspace: WeakEntity<Workspace>,
+        project: Entity<Project>,
         window: &mut Window,
-        cx: &mut App,
+        cx: &mut Context<NewSessionModal>,
     ) -> Self {
-        Self::Attach(AttachMode::new(debugger, workspace, window, cx))
+        Self::Attach(AttachMode::new(debugger, project, window, cx))
     }
-    fn launch(past_launch_config: Option<LaunchConfig>, window: &mut Window, cx: &mut App) -> Self {
+    fn launch(
+        past_launch_config: Option<LaunchConfig>,
+        window: &mut Window,
+        cx: &mut Context<NewSessionModal>,
+    ) -> Self {
         Self::Launch(LaunchMode::new(past_launch_config, window, cx))
     }
 }
@@ -592,18 +580,25 @@ impl Render for NewSessionModal {
                                 .toggle_state(matches!(self.mode, NewSessionMode::Attach(_)))
                                 .style(ui::ButtonStyle::Subtle)
                                 .on_click(cx.listener(|this, _, window, cx| {
+                                    let Ok(project) = this
+                                        .workspace
+                                        .read_with(cx, |this, _| this.project().clone())
+                                    else {
+                                        return;
+                                    };
                                     this.mode = NewSessionMode::attach(
                                         this.debugger.clone(),
-                                        this.workspace.clone(),
+                                        project,
                                         window,
                                         cx,
                                     );
+                                    this.mode.focus_handle(cx).focus(window);
                                     if let Some((debugger, attach)) =
                                         this.debugger.as_ref().zip(this.mode.as_attach())
                                     {
                                         Self::update_attach_picker(&attach, &debugger, window, cx);
                                     }
-                                    this.mode.focus_handle(cx).focus(window);
+
                                     cx.notify();
                                 }))
                                 .last(),

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

@@ -100,7 +100,7 @@ async fn test_show_attach_modal_and_select_process(
                         },
                         Candidate {
                             pid: 3,
-                            name: "non-fake-binary-1".into(),
+                            name: "real-binary-1".into(),
                             command: vec![],
                         },
                         Candidate {
@@ -108,7 +108,9 @@ async fn test_show_attach_modal_and_select_process(
                             name: "fake-binary-2".into(),
                             command: vec![],
                         },
-                    ],
+                    ]
+                    .into_iter()
+                    .collect(),
                     true,
                     window,
                     cx,
@@ -121,17 +123,30 @@ async fn test_show_attach_modal_and_select_process(
 
     cx.run_until_parked();
 
+    // assert we got the expected processes
+    workspace
+        .update(cx, |_, window, cx| {
+            let names =
+                attach_modal.update(cx, |modal, cx| attach_modal::_process_names(&modal, cx));
+            // Initially all processes are visible.
+            assert_eq!(3, names.len());
+            attach_modal.update(cx, |this, cx| {
+                this.picker.update(cx, |this, cx| {
+                    this.set_query("fakb", window, cx);
+                })
+            })
+        })
+        .unwrap();
+    cx.run_until_parked();
     // assert we got the expected processes
     workspace
         .update(cx, |_, _, cx| {
             let names =
                 attach_modal.update(cx, |modal, cx| attach_modal::_process_names(&modal, cx));
-
-            // we filtered out all processes that are not starting with `fake-binary`
+            // Initially all processes are visible.
             assert_eq!(2, names.len());
         })
         .unwrap();
-
     // select the only existing process
     cx.dispatch_action(Confirm);