on master help sign in
feat(dap): send Disconnect if Terminated event received (#5532)
e3765acFilip Dutescu
helix-dap/src/client.rs+23−5
@@ -1,4 +1,5 @@
1use crate::{
1use crate::{
2+ requests::DisconnectArguments,
2 transport::{Payload, Request, Response, Transport},
3 transport::{Payload, Request, Response, Transport},
3 types::*,
4 types::*,
4 Error, Result, ThreadId,
5 Error, Result, ThreadId,
@@ -31,6 +32,7 @@ pub struct Client {
31 _process: Option<Child>,
32 _process: Option<Child>,
32 server_tx: UnboundedSender<Payload>,
33 server_tx: UnboundedSender<Payload>,
33 request_counter: AtomicU64,
34 request_counter: AtomicU64,
35+ connection_type: Option<ConnectionType>,
34 pub caps: Option<DebuggerCapabilities>,
36 pub caps: Option<DebuggerCapabilities>,
35 // thread_id -> frames
37 // thread_id -> frames
36 pub stack_frames: HashMap<ThreadId, Vec<StackFrame>>,
38 pub stack_frames: HashMap<ThreadId, Vec<StackFrame>>,
@@ -41,6 +43,12 @@ pub struct Client {
41 pub quirks: DebuggerQuirks,
43 pub quirks: DebuggerQuirks,
42}
44}
43
45
46+#[derive(Clone, Copy, Debug)]
47+pub enum ConnectionType {
48+ Launch,
49+ Attach,
50+}
51+
44impl Client {
52impl Client {
45 // Spawn a process and communicate with it by either TCP or stdio
53 // Spawn a process and communicate with it by either TCP or stdio
46 pub async fn process(
54 pub async fn process(
@@ -78,7 +86,7 @@ impl Client {
78 server_tx,
86 server_tx,
79 request_counter: AtomicU64::new(0),
87 request_counter: AtomicU64::new(0),
80 caps: None,
88 caps: None,
81− //
89+ connection_type: None,
82 stack_frames: HashMap::new(),
90 stack_frames: HashMap::new(),
83 thread_states: HashMap::new(),
91 thread_states: HashMap::new(),
84 thread_id: None,
92 thread_id: None,
@@ -207,6 +215,10 @@ impl Client {
207 self.id
215 self.id
208 }
216 }
209
217
218+ pub fn connection_type(&self) -> Option<ConnectionType> {
219+ self.connection_type
220+ }
221+
210 fn next_request_id(&self) -> u64 {
222 fn next_request_id(&self) -> u64 {
211 self.request_counter.fetch_add(1, Ordering::Relaxed)
223 self.request_counter.fetch_add(1, Ordering::Relaxed)
212 }
224 }
@@ -334,15 +346,21 @@ impl Client {
334 Ok(())
346 Ok(())
335 }
347 }
336
348
349+ pub fn disconnect(
350+ &mut self,
351+ args: Option<DisconnectArguments>,
337− pub fn disconnect(&self) -> impl Future<Output = Result<Value>> {
352+ ) -> impl Future<Output = Result<Value>> {
353+ self.connection_type = None;
338− self.call::<requests::Disconnect>(())
354+ self.call::<requests::Disconnect>(args)
339 }
355 }
340
356
341− pub fn launch(&self, args: serde_json::Value) -> impl Future<Output = Result<Value>> {
357+ pub fn launch(&mut self, args: serde_json::Value) -> impl Future<Output = Result<Value>> {
358+ self.connection_type = Some(ConnectionType::Launch);
342 self.call::<requests::Launch>(args)
359 self.call::<requests::Launch>(args)
343 }
360 }
344
361
345− pub fn attach(&self, args: serde_json::Value) -> impl Future<Output = Result<Value>> {
362+ pub fn attach(&mut self, args: serde_json::Value) -> impl Future<Output = Result<Value>> {
363+ self.connection_type = Some(ConnectionType::Attach);
346 self.call::<requests::Attach>(args)
364 self.call::<requests::Attach>(args)
347 }
365 }
348
366
helix-dap/src/lib.rs+1−1
@@ -2,7 +2,7 @@ mod client;
2mod transport;
2mod transport;
3mod types;
3mod types;
4
4
5−pub use client::Client;
5+pub use client::{Client, ConnectionType};
6pub use events::Event;
6pub use events::Event;
7pub use transport::{Payload, Response, Transport};
7pub use transport::{Payload, Response, Transport};
8pub use types::*;
8pub use types::*;
helix-dap/src/types.rs+12−1
@@ -391,11 +391,22 @@ pub mod requests {
391 const COMMAND: &'static str = "attach";
391 const COMMAND: &'static str = "attach";
392 }
392 }
393
393
394+ #[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)]
395+ #[serde(rename_all = "camelCase")]
396+ pub struct DisconnectArguments {
397+ #[serde(skip_serializing_if = "Option::is_none")]
398+ pub restart: Option<bool>,
399+ #[serde(skip_serializing_if = "Option::is_none")]
400+ pub terminate_debuggee: Option<bool>,
401+ #[serde(skip_serializing_if = "Option::is_none")]
402+ pub suspend_debuggee: Option<bool>,
403+ }
404+
394 #[derive(Debug)]
405 #[derive(Debug)]
395 pub enum Disconnect {}
406 pub enum Disconnect {}
396
407
397 impl Request for Disconnect {
408 impl Request for Disconnect {
398− type Arguments = ();
409+ type Arguments = Option<DisconnectArguments>;
399 type Result = ();
410 type Result = ();
400 const COMMAND: &'static str = "disconnect";
411 const COMMAND: &'static str = "disconnect";
401 }
412 }
helix-term/src/commands/dap.rs+1−1
@@ -539,7 +539,7 @@ pub fn dap_variables(cx: &mut Context) {
539pub fn dap_terminate(cx: &mut Context) {
539pub fn dap_terminate(cx: &mut Context) {
540 let debugger = debugger!(cx.editor);
540 let debugger = debugger!(cx.editor);
541
541
542− let request = debugger.disconnect();
542+ let request = debugger.disconnect(None);
543 dap_callback(cx.jobs, request, |editor, _compositor, _response: ()| {
543 dap_callback(cx.jobs, request, |editor, _compositor, _response: ()| {
544 // editor.set_error(format!("Failed to disconnect: {}", e));
544 // editor.set_error(format!("Failed to disconnect: {}", e));
545 editor.debugger = None;
545 editor.debugger = None;
helix-view/src/handlers/dap.rs+62−1
@@ -1,7 +1,8 @@
1use crate::editor::{Action, Breakpoint};
1use crate::editor::{Action, Breakpoint};
2use crate::{align_view, Align, Editor};
2use crate::{align_view, Align, Editor};
3+use dap::requests::DisconnectArguments;
3use helix_core::Selection;
4use helix_core::Selection;
4−use helix_dap::{self as dap, Client, Payload, Request, ThreadId};
5+use helix_dap::{self as dap, Client, ConnectionType, Payload, Request, ThreadId};
5use helix_lsp::block_on;
6use helix_lsp::block_on;
6use log::warn;
7use log::warn;
7use std::fmt::Write;
8use std::fmt::Write;
@@ -274,6 +275,66 @@ impl Editor {
274 self.set_status("Debugged application started");
275 self.set_status("Debugged application started");
275 }; // TODO: do we need to handle error?
276 }; // TODO: do we need to handle error?
276 }
277 }
278+ Event::Terminated(terminated) => {
279+ let restart_args = if let Some(terminated) = terminated {
280+ terminated.restart
281+ } else {
282+ None
283+ };
284+
285+ let disconnect_args = Some(DisconnectArguments {
286+ restart: Some(restart_args.is_some()),
287+ terminate_debuggee: None,
288+ suspend_debuggee: None,
289+ });
290+
291+ if let Err(err) = debugger.disconnect(disconnect_args).await {
292+ self.set_error(format!(
293+ "Cannot disconnect debugger upon terminated event receival {:?}",
294+ err
295+ ));
296+ return false;
297+ }
298+
299+ match restart_args {
300+ Some(restart_args) => {
301+ log::info!("Attempting to restart debug session.");
302+ let connection_type = match debugger.connection_type() {
303+ Some(connection_type) => connection_type,
304+ None => {
305+ self.set_error("No starting request found, to be used in restarting the debugging session.");
306+ return false;
307+ }
308+ };
309+
310+ let relaunch_resp = if let ConnectionType::Launch = connection_type {
311+ debugger.launch(restart_args).await
312+ } else {
313+ debugger.attach(restart_args).await
314+ };
315+
316+ if let Err(err) = relaunch_resp {
317+ self.set_error(format!(
318+ "Failed to restart debugging session: {:?}",
319+ err
320+ ));
321+ }
322+ }
323+ None => {
324+ self.set_status(
325+ "Terminated debugging session and disconnected debugger.",
326+ );
327+ }
328+ }
329+ }
330+ Event::Exited(resp) => {
331+ let exit_code = resp.exit_code;
332+ if exit_code != 0 {
333+ self.set_error(format!(
334+ "Debuggee failed to exit successfully (exit code: {exit_code})."
335+ ));
336+ }
337+ }
277 ev => {
338 ev => {
278 log::warn!("Unhandled event {:?}", ev);
339 log::warn!("Unhandled event {:?}", ev);
279 return false; // return early to skip render
340 return false; // return early to skip render