debugger: Open debugger panel on session startup (#29186)

Now all debug sessions are routed through the debug panel and are
started synchronously instead of by a task that returns a session once
the initialization process is finished. A session is `Mode::Booting`
while it's starting the debug adapter process and then transitions to
`Mode::Running` once this is completed.

This PR also added new tests for the dap logger, reverse start debugging
request, and debugging over SSH.

Release Notes:

- N/A

---------

Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Anthony <anthony@zed.dev>
Co-authored-by: Cole Miller <m@cole-miller.net>
Co-authored-by: Cole Miller <cole@zed.dev>
Co-authored-by: Zed AI <ai@zed.dev>
Co-authored-by: Remco Smits <djsmits12@gmail.com>
This commit is contained in:
Conrad Irwin
2025-04-22 19:35:47 -04:00
committed by GitHub
co-authored by Anthony Eid Anthony Cole Miller Cole Miller Zed AI Remco Smits
parent 75ab8ff9a1
commit 6a009b447a
29 changed files with 1261 additions and 1021 deletions
+5 -5
View File
@@ -1,4 +1,4 @@
use crate::{attach_modal::Candidate, *};
use crate::{attach_modal::Candidate, tests::start_debug_session_with, *};
use attach_modal::AttachModal;
use dap::{FakeAdapter, client::SessionId};
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
@@ -26,8 +26,8 @@ async fn test_direct_attach_to_process(executor: BackgroundExecutor, cx: &mut Te
let workspace = init_test_workspace(&project, cx).await;
let cx = &mut VisualTestContext::from_window(*workspace, cx);
let session = debugger::test::start_debug_session_with(
&project,
let session = start_debug_session_with(
&workspace,
cx,
DebugTaskDefinition {
adapter: "fake-adapter".to_string(),
@@ -47,7 +47,6 @@ async fn test_direct_attach_to_process(executor: BackgroundExecutor, cx: &mut Te
});
},
)
.await
.unwrap();
cx.run_until_parked();
@@ -99,9 +98,10 @@ async fn test_show_attach_modal_and_select_process(
});
let attach_modal = workspace
.update(cx, |workspace, window, cx| {
let workspace_handle = cx.entity();
workspace.toggle_modal(window, cx, |window, cx| {
AttachModal::with_processes(
project.clone(),
workspace_handle,
DebugTaskDefinition {
adapter: FakeAdapter::ADAPTER_NAME.into(),
request: dap::DebugRequest::Attach(AttachRequest::default()),
+5 -4
View File
@@ -1,4 +1,7 @@
use crate::{tests::active_debug_session_panel, *};
use crate::{
tests::{active_debug_session_panel, start_debug_session},
*,
};
use dap::requests::StackTrace;
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
use project::{FakeFs, Project};
@@ -28,9 +31,7 @@ async fn test_handle_output_event(executor: BackgroundExecutor, cx: &mut TestApp
})
.unwrap();
let session = debugger::test::start_debug_session(&project, cx, |_| {})
.await
.unwrap();
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
client.on_request::<StackTrace, _>(move |_, _| {
+118
View File
@@ -0,0 +1,118 @@
use crate::tests::{init_test, init_test_workspace, start_debug_session};
use dap::requests::{StackTrace, Threads};
use debugger_tools::LogStore;
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
use project::Project;
use serde_json::json;
use std::cell::OnceCell;
#[gpui::test]
async fn test_dap_logger_captures_all_session_rpc_messages(
executor: BackgroundExecutor,
cx: &mut TestAppContext,
) {
let log_store_cell = std::rc::Rc::new(OnceCell::new());
cx.update(|cx| {
let log_store_cell = log_store_cell.clone();
cx.observe_new::<LogStore>(move |_, _, cx| {
log_store_cell.set(cx.entity()).unwrap();
})
.detach();
debugger_tools::init(cx);
});
init_test(cx);
let log_store = log_store_cell.get().unwrap().clone();
// Create a filesystem with a simple project
let fs = project::FakeFs::new(executor.clone());
fs.insert_tree(
"/project",
json!({
"main.rs": "fn main() {\n println!(\"Hello, world!\");\n}"
}),
)
.await;
assert!(
log_store.read_with(cx, |log_store, _| log_store
.contained_session_ids()
.is_empty()),
"log_store shouldn't contain any session IDs before any sessions were created"
);
let project = Project::test(fs, ["/project".as_ref()], cx).await;
let workspace = init_test_workspace(&project, cx).await;
let cx = &mut VisualTestContext::from_window(*workspace, cx);
// Start a debug session
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
let session_id = session.read_with(cx, |session, _| session.session_id());
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
assert_eq!(
log_store.read_with(cx, |log_store, _| log_store.contained_session_ids().len()),
1,
);
assert!(
log_store.read_with(cx, |log_store, _| log_store
.contained_session_ids()
.contains(&session_id)),
"log_store should contain the session IDs of the started session"
);
assert!(
!log_store.read_with(cx, |log_store, _| log_store
.rpc_messages_for_session_id(session_id)
.is_empty()),
"We should have the initialization sequence in the log store"
);
// Set up basic responses for common requests
client.on_request::<Threads, _>(move |_, _| {
Ok(dap::ThreadsResponse {
threads: vec![dap::Thread {
id: 1,
name: "Thread 1".into(),
}],
})
});
client.on_request::<StackTrace, _>(move |_, _| {
Ok(dap::StackTraceResponse {
stack_frames: Vec::default(),
total_frames: None,
})
});
// Run until all pending tasks are executed
cx.run_until_parked();
// Simulate a stopped event to generate more DAP messages
client
.fake_event(dap::messages::Events::Stopped(dap::StoppedEvent {
reason: dap::StoppedEventReason::Pause,
description: None,
thread_id: Some(1),
preserve_focus_hint: None,
text: None,
all_threads_stopped: None,
hit_breakpoint_ids: None,
}))
.await;
cx.run_until_parked();
// Shutdown the debug session
let shutdown_session = project.update(cx, |project, cx| {
project.dap_store().update(cx, |dap_store, cx| {
dap_store.shutdown_session(session.read(cx).session_id(), cx)
})
});
shutdown_session.await.unwrap();
cx.run_until_parked();
}
+94 -37
View File
@@ -1,4 +1,4 @@
use crate::*;
use crate::{tests::start_debug_session, *};
use dap::{
ErrorResponse, Message, RunInTerminalRequestArguments, SourceBreakpoint,
StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest,
@@ -48,9 +48,7 @@ async fn test_basic_show_debug_panel(executor: BackgroundExecutor, cx: &mut Test
let workspace = init_test_workspace(&project, cx).await;
let cx = &mut VisualTestContext::from_window(*workspace, cx);
let session = debugger::test::start_debug_session(&project, cx, |_| {})
.await
.unwrap();
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
client.on_request::<Threads, _>(move |_, _| {
@@ -187,9 +185,7 @@ async fn test_we_can_only_have_one_panel_per_debug_session(
let workspace = init_test_workspace(&project, cx).await;
let cx = &mut VisualTestContext::from_window(*workspace, cx);
let session = debugger::test::start_debug_session(&project, cx, |_| {})
.await
.unwrap();
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
client.on_request::<Threads, _>(move |_, _| {
@@ -354,9 +350,7 @@ async fn test_handle_successful_run_in_terminal_reverse_request(
let workspace = init_test_workspace(&project, cx).await;
let cx = &mut VisualTestContext::from_window(*workspace, cx);
let session = debugger::test::start_debug_session(&project, cx, |_| {})
.await
.unwrap();
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
client
@@ -419,6 +413,86 @@ async fn test_handle_successful_run_in_terminal_reverse_request(
shutdown_session.await.unwrap();
}
#[gpui::test]
async fn test_handle_start_debugging_request(
executor: BackgroundExecutor,
cx: &mut TestAppContext,
) {
init_test(cx);
let fs = FakeFs::new(executor.clone());
fs.insert_tree(
"/project",
json!({
"main.rs": "First line\nSecond line\nThird line\nFourth line",
}),
)
.await;
let project = Project::test(fs, ["/project".as_ref()], cx).await;
let workspace = init_test_workspace(&project, cx).await;
let cx = &mut VisualTestContext::from_window(*workspace, cx);
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
let fake_config = json!({"one": "two"});
let launched_with = Arc::new(parking_lot::Mutex::new(None));
let _subscription = project::debugger::test::intercept_debug_sessions(cx, {
let launched_with = launched_with.clone();
move |client| {
let launched_with = launched_with.clone();
client.on_request::<dap::requests::Launch, _>(move |_, args| {
launched_with.lock().replace(args.raw);
Ok(())
});
client.on_request::<dap::requests::Attach, _>(move |_, _| {
assert!(false, "should not get attach request");
Ok(())
});
}
});
client
.fake_reverse_request::<StartDebugging>(StartDebuggingRequestArguments {
request: StartDebuggingRequestArgumentsRequest::Launch,
configuration: fake_config.clone(),
})
.await;
cx.run_until_parked();
workspace
.update(cx, |workspace, _window, cx| {
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
let active_session = debug_panel
.read(cx)
.active_session()
.unwrap()
.read(cx)
.session(cx);
let parent_session = active_session.read(cx).parent_session().unwrap();
assert_eq!(
active_session.read(cx).definition(),
parent_session.read(cx).definition()
);
})
.unwrap();
assert_eq!(&fake_config, launched_with.lock().as_ref().unwrap());
let shutdown_session = project.update(cx, |project, cx| {
project.dap_store().update(cx, |dap_store, cx| {
dap_store.shutdown_session(session.read(cx).session_id(), cx)
})
});
shutdown_session.await.unwrap();
}
// // covers that we always send a response back, if something when wrong,
// // while spawning the terminal
#[gpui::test]
@@ -444,9 +518,7 @@ async fn test_handle_error_run_in_terminal_reverse_request(
let workspace = init_test_workspace(&project, cx).await;
let cx = &mut VisualTestContext::from_window(*workspace, cx);
let session = debugger::test::start_debug_session(&project, cx, |_| {})
.await
.unwrap();
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
client
@@ -522,9 +594,7 @@ async fn test_handle_start_debugging_reverse_request(
let workspace = init_test_workspace(&project, cx).await;
let cx = &mut VisualTestContext::from_window(*workspace, cx);
let session = debugger::test::start_debug_session(&project, cx, |_| {})
.await
.unwrap();
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
client.on_request::<dap::requests::Threads, _>(move |_, _| {
@@ -629,9 +699,7 @@ async fn test_shutdown_children_when_parent_session_shutdown(
let workspace = init_test_workspace(&project, cx).await;
let cx = &mut VisualTestContext::from_window(*workspace, cx);
let parent_session = debugger::test::start_debug_session(&project, cx, |_| {})
.await
.unwrap();
let parent_session = start_debug_session(&workspace, cx, |_| {}).unwrap();
let client = parent_session.update(cx, |session, _| session.adapter_client().unwrap());
client.on_request::<dap::requests::Threads, _>(move |_, _| {
@@ -737,9 +805,7 @@ async fn test_shutdown_parent_session_if_all_children_are_shutdown(
let workspace = init_test_workspace(&project, cx).await;
let cx = &mut VisualTestContext::from_window(*workspace, cx);
let parent_session = debugger::test::start_debug_session(&project, cx, |_| {})
.await
.unwrap();
let parent_session = start_debug_session(&workspace, cx, |_| {}).unwrap();
let client = parent_session.update(cx, |session, _| session.adapter_client().unwrap());
client.on_response::<StartDebugging, _>(move |_| {}).await;
@@ -858,7 +924,7 @@ async fn test_debug_panel_item_thread_status_reset_on_failure(
let workspace = init_test_workspace(&project, cx).await;
let cx = &mut VisualTestContext::from_window(*workspace, cx);
let session = debugger::test::start_debug_session(&project, cx, |client| {
let session = start_debug_session(&workspace, cx, |client| {
client.on_request::<dap::requests::Initialize, _>(move |_, _| {
Ok(dap::Capabilities {
supports_step_back: Some(true),
@@ -866,7 +932,6 @@ async fn test_debug_panel_item_thread_status_reset_on_failure(
})
});
})
.await
.unwrap();
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
@@ -1073,9 +1138,7 @@ async fn test_send_breakpoints_when_editor_has_been_saved(
.update(cx, |_, _, cx| worktree.read(cx).id())
.unwrap();
let session = debugger::test::start_debug_session(&project, cx, |_| {})
.await
.unwrap();
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
let buffer = project
@@ -1290,9 +1353,7 @@ async fn test_unsetting_breakpoints_on_clear_breakpoint_action(
editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
});
let session = debugger::test::start_debug_session(&project, cx, |_| {})
.await
.unwrap();
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
let called_set_breakpoints = Arc::new(AtomicBool::new(false));
@@ -1358,7 +1419,7 @@ async fn test_debug_session_is_shutdown_when_attach_and_launch_request_fails(
let workspace = init_test_workspace(&project, cx).await;
let cx = &mut VisualTestContext::from_window(*workspace, cx);
let task = project::debugger::test::start_debug_session(&project, cx, |client| {
start_debug_session(&workspace, cx, |client| {
client.on_request::<dap::requests::Initialize, _>(|_, _| {
Err(ErrorResponse {
error: Some(Message {
@@ -1372,12 +1433,8 @@ async fn test_debug_session_is_shutdown_when_attach_and_launch_request_fails(
}),
})
});
});
assert!(
task.await.is_err(),
"Session should failed to start if launch request fails"
);
})
.ok();
cx.run_until_parked();
+3 -7
View File
@@ -1,16 +1,13 @@
use crate::{
debugger_panel::DebugPanel,
tests::{active_debug_session_panel, init_test, init_test_workspace},
tests::{active_debug_session_panel, init_test, init_test_workspace, start_debug_session},
};
use dap::{
StoppedEvent,
requests::{Initialize, Modules},
};
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
use project::{
FakeFs, Project,
debugger::{self},
};
use project::{FakeFs, Project};
use std::sync::{
Arc,
atomic::{AtomicBool, AtomicI32, Ordering},
@@ -31,7 +28,7 @@ async fn test_module_list(executor: BackgroundExecutor, cx: &mut TestAppContext)
.unwrap();
let cx = &mut VisualTestContext::from_window(*workspace, cx);
let session = debugger::test::start_debug_session(&project, cx, |client| {
let session = start_debug_session(&workspace, cx, |client| {
client.on_request::<Initialize, _>(move |_, _| {
Ok(dap::Capabilities {
supports_modules_request: Some(true),
@@ -39,7 +36,6 @@ async fn test_module_list(executor: BackgroundExecutor, cx: &mut TestAppContext)
})
});
})
.await
.unwrap();
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
@@ -1,7 +1,7 @@
use crate::{
debugger_panel::DebugPanel,
session::running::stack_frame_list::StackFrameEntry,
tests::{active_debug_session_panel, init_test, init_test_workspace},
tests::{active_debug_session_panel, init_test, init_test_workspace, start_debug_session},
};
use dap::{
StackFrame,
@@ -9,7 +9,7 @@ use dap::{
};
use editor::{Editor, ToPoint as _};
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
use project::{FakeFs, Project, debugger};
use project::{FakeFs, Project};
use serde_json::json;
use std::sync::Arc;
use unindent::Unindent as _;
@@ -50,9 +50,7 @@ async fn test_fetch_initial_stack_frames_and_go_to_stack_frame(
let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
let workspace = init_test_workspace(&project, cx).await;
let cx = &mut VisualTestContext::from_window(*workspace, cx);
let session = debugger::test::start_debug_session(&project, cx, |_| {})
.await
.unwrap();
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
client.on_request::<Scopes, _>(move |_, _| Ok(dap::ScopesResponse { scopes: vec![] }));
@@ -229,9 +227,7 @@ async fn test_select_stack_frame(executor: BackgroundExecutor, cx: &mut TestAppC
});
let cx = &mut VisualTestContext::from_window(*workspace, cx);
let session = debugger::test::start_debug_session(&project, cx, |_| {})
.await
.unwrap();
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
client.on_request::<Threads, _>(move |_, _| {
@@ -495,9 +491,7 @@ async fn test_collapsed_entries(executor: BackgroundExecutor, cx: &mut TestAppCo
let workspace = init_test_workspace(&project, cx).await;
let cx = &mut VisualTestContext::from_window(*workspace, cx);
let session = debugger::test::start_debug_session(&project, cx, |_| {})
.await
.unwrap();
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
client.on_request::<Threads, _>(move |_, _| {
+7 -17
View File
@@ -6,7 +6,7 @@ use std::sync::{
use crate::{
DebugPanel,
session::running::variable_list::{CollapseSelectedEntry, ExpandSelectedEntry},
tests::{active_debug_session_panel, init_test, init_test_workspace},
tests::{active_debug_session_panel, init_test, init_test_workspace, start_debug_session},
};
use collections::HashMap;
use dap::{
@@ -15,7 +15,7 @@ use dap::{
};
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
use menu::{SelectFirst, SelectNext, SelectPrevious};
use project::{FakeFs, Project, debugger};
use project::{FakeFs, Project};
use serde_json::json;
use unindent::Unindent as _;
use util::path;
@@ -54,9 +54,7 @@ async fn test_basic_fetch_initial_scope_and_variables(
})
.unwrap();
let cx = &mut VisualTestContext::from_window(*workspace, cx);
let session = debugger::test::start_debug_session(&project, cx, |_| {})
.await
.unwrap();
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
client.on_request::<dap::requests::Threads, _>(move |_, _| {
@@ -266,9 +264,7 @@ async fn test_fetch_variables_for_multiple_scopes(
.unwrap();
let cx = &mut VisualTestContext::from_window(*workspace, cx);
let session = debugger::test::start_debug_session(&project, cx, |_| {})
.await
.unwrap();
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
client.on_request::<dap::requests::Threads, _>(move |_, _| {
@@ -528,9 +524,7 @@ async fn test_keyboard_navigation(executor: BackgroundExecutor, cx: &mut TestApp
})
.unwrap();
let cx = &mut VisualTestContext::from_window(*workspace, cx);
let session = debugger::test::start_debug_session(&project, cx, |_| {})
.await
.unwrap();
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
client.on_request::<dap::requests::Threads, _>(move |_, _| {
@@ -1313,9 +1307,7 @@ async fn test_variable_list_only_sends_requests_when_rendering(
let workspace = init_test_workspace(&project, cx).await;
let cx = &mut VisualTestContext::from_window(*workspace, cx);
let session = debugger::test::start_debug_session(&project, cx, |_| {})
.await
.unwrap();
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
client.on_request::<dap::requests::Threads, _>(move |_, _| {
@@ -1560,9 +1552,7 @@ async fn test_it_fetches_scopes_variables_when_you_select_a_stack_frame(
.unwrap();
let cx = &mut VisualTestContext::from_window(*workspace, cx);
let session = debugger::test::start_debug_session(&project, cx, |_| {})
.await
.unwrap();
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
client.on_request::<dap::requests::Threads, _>(move |_, _| {