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

Release Notes:

- Debugger Beta: added the `dev: copy debug adapter arguments` action to
help troubleshoot debug configurations.
This commit is contained in:
Cole Miller 2025-06-12 21:38:25 -04:00 committed by GitHub
parent 1078f929aa
commit f227c2ff0c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 52 additions and 20 deletions

View file

@ -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| {

View file

@ -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>,

View file

@ -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,

View file

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

View file

@ -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();

View file

@ -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
);
})

View file

@ -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 {