debugger: Add an action to copy debuggee info and initialization args (#32647)

Cole Miller created

Release Notes:

- Debugger Beta: added the `dev: copy debug adapter arguments` action to
help troubleshoot debug configurations.

Change summary

crates/collab/src/tests/remote_editing_collaboration_tests.rs |  2 
crates/dap/src/adapters.rs                                    |  4 
crates/debugger_ui/src/debugger_panel.rs                      | 43 +++-
crates/debugger_ui/src/debugger_ui.rs                         |  2 
crates/debugger_ui/src/session/running.rs                     |  2 
crates/debugger_ui/src/tests/debugger_panel.rs                |  9 
crates/project/src/debugger/session.rs                        | 10 
7 files changed, 52 insertions(+), 20 deletions(-)

Detailed changes

crates/collab/src/tests/remote_editing_collaboration_tests.rs 🔗

@@ -671,7 +671,7 @@ async fn test_remote_server_debugger(
     });
 
     session.update(cx_a, |session, _| {
-        assert_eq!(session.binary().command.as_deref(), Some("ssh"));
+        assert_eq!(session.binary().unwrap().command.as_deref(), Some("ssh"));
     });
 
     let shutdown_session = workspace.update(cx_a, |workspace, cx| {

crates/dap/src/adapters.rs 🔗

@@ -93,7 +93,7 @@ impl<'a> From<&'a str> for DebugAdapterName {
     }
 }
 
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Serialize)]
 pub struct TcpArguments {
     pub host: Ipv4Addr,
     pub port: u16,
@@ -179,7 +179,7 @@ impl DebugTaskDefinition {
 }
 
 /// Created from a [DebugTaskDefinition], this struct describes how to spawn the debugger to create a previously-configured debug session.
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, PartialEq, Serialize)]
 pub struct DebugAdapterBinary {
     pub command: Option<String>,
     pub arguments: Vec<String>,

crates/debugger_ui/src/debugger_panel.rs 🔗

@@ -2,10 +2,10 @@ use crate::persistence::DebuggerPaneItem;
 use crate::session::DebugSession;
 use crate::session::running::RunningState;
 use crate::{
-    ClearAllBreakpoints, Continue, Detach, FocusBreakpointList, FocusConsole, FocusFrames,
-    FocusLoadedSources, FocusModules, FocusTerminal, FocusVariables, NewProcessModal,
-    NewProcessMode, Pause, Restart, StepInto, StepOut, StepOver, Stop, ToggleExpandItem,
-    ToggleSessionPicker, ToggleThreadPicker, persistence, spawn_task_or_modal,
+    ClearAllBreakpoints, Continue, CopyDebugAdapterArguments, Detach, FocusBreakpointList,
+    FocusConsole, FocusFrames, FocusLoadedSources, FocusModules, FocusTerminal, FocusVariables,
+    NewProcessModal, NewProcessMode, Pause, Restart, StepInto, StepOut, StepOver, Stop,
+    ToggleExpandItem, ToggleSessionPicker, ToggleThreadPicker, persistence, spawn_task_or_modal,
 };
 use anyhow::Result;
 use dap::adapters::DebugAdapterName;
@@ -16,9 +16,9 @@ use dap::{
 };
 use dap::{DapRegistry, StartDebuggingRequestArguments};
 use gpui::{
-    Action, App, AsyncWindowContext, Context, DismissEvent, Entity, EntityId, EventEmitter,
-    FocusHandle, Focusable, MouseButton, MouseDownEvent, Point, Subscription, Task, WeakEntity,
-    actions, anchored, deferred,
+    Action, App, AsyncWindowContext, ClipboardItem, Context, DismissEvent, Entity, EntityId,
+    EventEmitter, FocusHandle, Focusable, MouseButton, MouseDownEvent, Point, Subscription, Task,
+    WeakEntity, actions, anchored, deferred,
 };
 
 use language::Buffer;
@@ -30,6 +30,7 @@ use settings::Settings;
 use std::sync::Arc;
 use task::{DebugScenario, TaskContext};
 use ui::{ContextMenu, Divider, PopoverMenuHandle, Tooltip, prelude::*};
+use util::maybe;
 use workspace::SplitDirection;
 use workspace::{
     Pane, Workspace,
@@ -335,7 +336,7 @@ impl DebugPanel {
         let dap_store_handle = self.project.read(cx).dap_store().clone();
         let label = curr_session.read(cx).label().clone();
         let adapter = curr_session.read(cx).adapter().clone();
-        let binary = curr_session.read(cx).binary().clone();
+        let binary = curr_session.read(cx).binary().cloned().unwrap();
         let task = curr_session.update(cx, |session, cx| session.shutdown(cx));
 
         cx.spawn_in(window, async move |this, cx| {
@@ -388,7 +389,10 @@ impl DebugPanel {
         let dap_store_handle = self.project.read(cx).dap_store().clone();
         let label = self.label_for_child_session(&parent_session, request, cx);
         let adapter = parent_session.read(cx).adapter().clone();
-        let mut binary = parent_session.read(cx).binary().clone();
+        let Some(mut binary) = parent_session.read(cx).binary().cloned() else {
+            log::error!("Attempted to start a child-session without a binary");
+            return;
+        };
         binary.request_args = request.clone();
         cx.spawn_in(window, async move |this, cx| {
             let (session, task) = dap_store_handle.update(cx, |dap_store, cx| {
@@ -517,6 +521,26 @@ impl DebugPanel {
         }
     }
 
+    fn copy_debug_adapter_arguments(
+        &mut self,
+        _: &CopyDebugAdapterArguments,
+        _window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        let content = maybe!({
+            let mut session = self.active_session()?.read(cx).session(cx);
+            while let Some(parent) = session.read(cx).parent_session().cloned() {
+                session = parent;
+            }
+            let binary = session.read(cx).binary()?;
+            let content = serde_json::to_string_pretty(&binary).ok()?;
+            Some(content)
+        });
+        if let Some(content) = content {
+            cx.write_to_clipboard(ClipboardItem::new_string(content));
+        }
+    }
+
     pub(crate) fn top_controls_strip(
         &mut self,
         window: &mut Window,
@@ -1348,6 +1372,7 @@ impl Render for DebugPanel {
                 });
                 cx.notify();
             }))
+            .on_action(cx.listener(Self::copy_debug_adapter_arguments))
             .when(self.active_session.is_some(), |this| {
                 this.on_mouse_down(
                     MouseButton::Right,

crates/debugger_ui/src/debugger_ui.rs 🔗

@@ -56,6 +56,8 @@ actions!(
     ]
 );
 
+actions!(dev, [CopyDebugAdapterArguments]);
+
 pub fn init(cx: &mut App) {
     DebuggerSettings::register(cx);
     workspace::FollowableViewRegistry::register::<DebugSession>(cx);

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

@@ -993,7 +993,7 @@ impl RunningState {
         let cwd = Some(&request.cwd)
             .filter(|cwd| cwd.len() > 0)
             .map(PathBuf::from)
-            .or_else(|| session.binary().cwd.clone());
+            .or_else(|| session.binary().unwrap().cwd.clone());
 
         let mut args = request.args.clone();
 

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

@@ -469,14 +469,19 @@ async fn test_handle_start_debugging_request(
 
             // We should preserve the original binary (params to spawn process etc.) except for launch params
             // (as they come from reverse spawn request).
-            let mut original_binary = parent_session.read(cx).binary().clone();
+            let mut original_binary = parent_session.read(cx).binary().cloned().unwrap();
             original_binary.request_args = StartDebuggingRequestArguments {
                 request: StartDebuggingRequestArgumentsRequest::Launch,
                 configuration: fake_config.clone(),
             };
 
             assert_eq!(
-                current_sessions[1].read(cx).session(cx).read(cx).binary(),
+                current_sessions[1]
+                    .read(cx)
+                    .session(cx)
+                    .read(cx)
+                    .binary()
+                    .unwrap(),
                 &original_binary
             );
         })

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

@@ -892,11 +892,11 @@ impl Session {
         &self.capabilities
     }
 
-    pub fn binary(&self) -> &DebugAdapterBinary {
-        let Mode::Running(local_mode) = &self.mode else {
-            panic!("Session is not running");
-        };
-        &local_mode.binary
+    pub fn binary(&self) -> Option<&DebugAdapterBinary> {
+        match &self.mode {
+            Mode::Building => None,
+            Mode::Running(running_mode) => Some(&running_mode.binary),
+        }
     }
 
     pub fn adapter(&self) -> DebugAdapterName {