DAP: Support the startDebugging reverse request (#13403)

This commit is contained in:
Jason Williams
2025-06-23 15:48:05 +01:00
committed by GitHub
parent 58dfa158c2
commit 2338b44909
15 changed files with 417 additions and 129 deletions

View File

@@ -14,7 +14,6 @@ use crate::{
tree::{self, Tree},
Document, DocumentId, View, ViewId,
};
use dap::StackFrame;
use helix_event::dispatch;
use helix_vcs::DiffProviderRegistry;
@@ -52,7 +51,7 @@ use helix_core::{
},
Change, LineEnding, Position, Range, Selection, Uri, NATIVE_LINE_ENDING,
};
use helix_dap as dap;
use helix_dap::{self as dap, registry::DebugAdapterId};
use helix_lsp::lsp;
use helix_stdx::path::canonicalize;
@@ -1083,8 +1082,7 @@ pub struct Editor {
pub diagnostics: Diagnostics,
pub diff_providers: DiffProviderRegistry,
pub debugger: Option<dap::Client>,
pub debugger_events: SelectAll<UnboundedReceiverStream<dap::Payload>>,
pub debug_adapters: dap::registry::Registry,
pub breakpoints: HashMap<PathBuf, Vec<Breakpoint>>,
pub syn_loader: Arc<ArcSwap<syntax::Loader>>,
@@ -1142,7 +1140,7 @@ pub enum EditorEvent {
DocumentSaved(DocumentSavedEventResult),
ConfigEvent(ConfigEvent),
LanguageServerMessage((LanguageServerId, Call)),
DebuggerEvent(dap::Payload),
DebuggerEvent((DebugAdapterId, dap::Payload)),
IdleTimer,
Redraw,
}
@@ -1229,8 +1227,7 @@ impl Editor {
language_servers,
diagnostics: Diagnostics::new(),
diff_providers: DiffProviderRegistry::default(),
debugger: None,
debugger_events: SelectAll::new(),
debug_adapters: dap::registry::Registry::new(),
breakpoints: HashMap::new(),
syn_loader,
theme_loader,
@@ -2154,7 +2151,7 @@ impl Editor {
Some(message) = self.language_servers.incoming.next() => {
return EditorEvent::LanguageServerMessage(message)
}
Some(event) = self.debugger_events.next() => {
Some(event) = self.debug_adapters.incoming.next() => {
return EditorEvent::DebuggerEvent(event)
}
@@ -2230,10 +2227,8 @@ impl Editor {
}
}
pub fn current_stack_frame(&self) -> Option<&StackFrame> {
self.debugger
.as_ref()
.and_then(|debugger| debugger.current_stack_frame())
pub fn current_stack_frame(&self) -> Option<&dap::StackFrame> {
self.debug_adapters.current_stack_frame()
}
/// Returns the id of a view that this doc contains a selection for,

View File

@@ -2,20 +2,22 @@ use crate::editor::{Action, Breakpoint};
use crate::{align_view, Align, Editor};
use dap::requests::DisconnectArguments;
use helix_core::Selection;
use helix_dap::{self as dap, Client, ConnectionType, Payload, Request, ThreadId};
use helix_dap::{
self as dap, registry::DebugAdapterId, Client, ConnectionType, Payload, Request, ThreadId,
};
use helix_lsp::block_on;
use log::warn;
use serde_json::json;
use log::{error, warn};
use serde_json::{json, Value};
use std::fmt::Write;
use std::path::PathBuf;
#[macro_export]
macro_rules! debugger {
($editor:expr) => {{
match &mut $editor.debugger {
Some(debugger) => debugger,
None => return,
}
let Some(debugger) = $editor.debug_adapters.get_active_client_mut() else {
return;
};
debugger
}};
}
@@ -141,13 +143,13 @@ pub fn breakpoints_changed(
}
impl Editor {
pub async fn handle_debugger_message(&mut self, payload: helix_dap::Payload) -> bool {
pub async fn handle_debugger_message(
&mut self,
id: DebugAdapterId,
payload: helix_dap::Payload,
) -> bool {
use helix_dap::{events, Event};
let debugger = match self.debugger.as_mut() {
Some(debugger) => debugger,
None => return false,
};
match payload {
Payload::Event(event) => {
let event = match Event::parse(&event.event, event.body) {
@@ -170,6 +172,11 @@ impl Editor {
all_threads_stopped,
..
}) => {
let debugger = match self.debug_adapters.get_client_mut(id) {
Some(debugger) => debugger,
None => return false,
};
let all_threads_stopped = all_threads_stopped.unwrap_or_default();
if all_threads_stopped {
@@ -184,6 +191,7 @@ impl Editor {
} else if let Some(thread_id) = thread_id {
debugger.thread_states.insert(thread_id, reason.clone()); // TODO: dap uses "type" || "reason" here
fetch_stack_trace(debugger, thread_id).await;
// whichever thread stops is made "current" (if no previously selected thread).
select_thread_id(self, thread_id, false).await;
}
@@ -205,8 +213,14 @@ impl Editor {
}
self.set_status(status);
self.debug_adapters.set_active_client(id);
}
Event::Continued(events::ContinuedBody { thread_id, .. }) => {
let debugger = match self.debug_adapters.get_client_mut(id) {
Some(debugger) => debugger,
None => return false,
};
debugger
.thread_states
.insert(thread_id, "running".to_owned());
@@ -214,8 +228,15 @@ impl Editor {
debugger.resume_application();
}
}
Event::Thread(_) => {
// TODO: update thread_states, make threads request
Event::Thread(thread) => {
self.set_status(format!("Thread {}: {}", thread.thread_id, thread.reason));
let debugger = match self.debug_adapters.get_client_mut(id) {
Some(debugger) => debugger,
None => return false,
};
debugger.thread_id = Some(thread.thread_id);
// set the stack frame for the thread
}
Event::Breakpoint(events::BreakpointBody { reason, breakpoint }) => {
match &reason[..] {
@@ -284,6 +305,12 @@ impl Editor {
self.set_status(format!("{} {}", prefix, output));
}
Event::Initialized(_) => {
self.set_status("Debugger initialized...");
let debugger = match self.debug_adapters.get_client_mut(id) {
Some(debugger) => debugger,
None => return false,
};
// send existing breakpoints
for (path, breakpoints) in &mut self.breakpoints {
// TODO: call futures in parallel, await all
@@ -296,14 +323,23 @@ impl Editor {
}; // TODO: do we need to handle error?
}
Event::Terminated(terminated) => {
let restart_args = if let Some(terminated) = terminated {
let debugger = match self.debug_adapters.get_client_mut(id) {
Some(debugger) => debugger,
None => return false,
};
let restart_arg = if let Some(terminated) = terminated {
terminated.restart
} else {
None
};
let restart_bool = restart_arg
.as_ref()
.and_then(|v| v.as_bool())
.unwrap_or(false);
let disconnect_args = Some(DisconnectArguments {
restart: Some(restart_args.is_some()),
restart: Some(restart_bool),
terminate_debuggee: None,
suspend_debuggee: None,
});
@@ -316,8 +352,23 @@ impl Editor {
return false;
}
match restart_args {
Some(restart_args) => {
match restart_arg {
Some(Value::Bool(false)) | None => {
self.debug_adapters.remove_client(id);
self.debug_adapters.unset_active_client();
self.set_status(
"Terminated debugging session and disconnected debugger.",
);
// Go through all breakpoints and set verfified to false
// this should update the UI to show the breakpoints are no longer connected
for breakpoints in self.breakpoints.values_mut() {
for breakpoint in breakpoints.iter_mut() {
breakpoint.verified = false;
}
}
}
Some(val) => {
log::info!("Attempting to restart debug session.");
let connection_type = match debugger.connection_type() {
Some(connection_type) => connection_type,
@@ -329,9 +380,9 @@ impl Editor {
let relaunch_resp = if let ConnectionType::Launch = connection_type
{
debugger.launch(restart_args).await
debugger.launch(val).await
} else {
debugger.attach(restart_args).await
debugger.attach(val).await
};
if let Err(err) = relaunch_resp {
@@ -341,12 +392,6 @@ impl Editor {
));
}
}
None => {
self.debugger = None;
self.set_status(
"Terminated debugging session and disconnected debugger.",
);
}
}
}
Event::Exited(resp) => {
@@ -393,10 +438,70 @@ impl Editor {
shell_process_id: None,
}))
}
Ok(Request::StartDebugging(arguments)) => {
let debugger = match self.debug_adapters.get_client_mut(id) {
Some(debugger) => debugger,
None => {
self.set_error("No active debugger found.");
return true;
}
};
// Currently we only support starting a child debugger if the parent is using the TCP transport
let socket = match debugger.socket {
Some(socket) => socket,
None => {
self.set_error("Child debugger can only be started if the parent debugger is using TCP transport.");
return true;
}
};
let config = match debugger.config.clone() {
Some(config) => config,
None => {
error!("No configuration found for the debugger.");
return true;
}
};
let result = self.debug_adapters.start_client(Some(socket), &config);
let client_id = match result {
Ok(child) => child,
Err(err) => {
self.set_error(format!(
"Failed to create child debugger: {:?}",
err
));
return true;
}
};
let client = match self.debug_adapters.get_client_mut(client_id) {
Some(child) => child,
None => {
self.set_error("Failed to get child debugger.");
return true;
}
};
let relaunch_resp = if let ConnectionType::Launch = arguments.request {
client.launch(arguments.configuration).await
} else {
client.attach(arguments.configuration).await
};
if let Err(err) = relaunch_resp {
self.set_error(format!("Failed to start debugging session: {:?}", err));
return true;
}
Ok(json!({
"success": true,
}))
}
Err(err) => Err(err),
};
if let Some(debugger) = self.debugger.as_mut() {
if let Some(debugger) = self.debug_adapters.get_client_mut(id) {
debugger
.reply(request.seq, &request.command, reply)
.await