mirror of
https://github.com/helix-editor/helix.git
synced 2025-10-06 00:13:28 +02:00
DAP: Support the startDebugging reverse request (#13403)
This commit is contained in:
@@ -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,
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user