on master help sign in
feat(dap): send Disconnect if Terminated event received (#5532)

Send a `Disconnect` DAP request if the `Terminated` event is received. According to the specification, if the debugging session was started by as `launch`, the debuggee should be terminated alongside the session. If instead the session was started as `attach`, it should not be disposed of. This default behaviour can be overriden if the `supportTerminateDebuggee` capability is supported by the adapter, through the `Disconnect` request `terminateDebuggee` argument, as described in [the specification][discon-spec]. This also implies saving the starting command for a debug sessions, in order to decide which behaviour should be used, as well as validating the capabilities of the adapter, in order to decide what the disconnect should do. An additional change made is handling of the `Exited` event, showing a message if the exit code is different than `0`, for the user to be aware off the termination failure. [discon-spec]: https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Disconnect Closes: #4674 Signed-off-by: Filip Dutescu <filip.dutescu@gmail.com>

commite3765ac6d20195d2434c8274850247fe0a044da0 authorFilip Dutescu <filip.dutescu@gmail.com> dateMon 20 Feb 2023, 06:00 (4 years ago) parent31b0c75
5 files changed+99−9
helix-dap/src/client.rs+23−5 helix-dap/src/lib.rs+1−1 helix-dap/src/types.rs+12−1 helix-term/src/commands/dap.rs+1−1 helix-view/src/handlers/dap.rs+62−1
helix-dap/src/client.rs+23−5
@@ -1,4 +1,5 @@
11use crate::{
2+ requests::DisconnectArguments,
23 transport::{Payload, Request, Response, Transport},
34 types::*,
45 Error, Result, ThreadId,
@@ -31,6 +32,7 @@ pub struct Client {
3132 _process: Option<Child>,
3233 server_tx: UnboundedSender<Payload>,
3334 request_counter: AtomicU64,
35+ connection_type: Option<ConnectionType>,
3436 pub caps: Option<DebuggerCapabilities>,
3537 // thread_id -> frames
3638 pub stack_frames: HashMap<ThreadId, Vec<StackFrame>>,
@@ -41,6 +43,12 @@ pub struct Client {
4143 pub quirks: DebuggerQuirks,
4244}
4345
46+#[derive(Clone, Copy, Debug)]
47+pub enum ConnectionType {
48+ Launch,
49+ Attach,
50+}
51+
4452impl Client {
4553 // Spawn a process and communicate with it by either TCP or stdio
4654 pub async fn process(
@@ -78,7 +86,7 @@ impl Client {
7886 server_tx,
7987 request_counter: AtomicU64::new(0),
8088 caps: None,
81− //
89+ connection_type: None,
8290 stack_frames: HashMap::new(),
8391 thread_states: HashMap::new(),
8492 thread_id: None,
@@ -207,6 +215,10 @@ impl Client {
207215 self.id
208216 }
209217
218+ pub fn connection_type(&self) -> Option<ConnectionType> {
219+ self.connection_type
220+ }
221+
210222 fn next_request_id(&self) -> u64 {
211223 self.request_counter.fetch_add(1, Ordering::Relaxed)
212224 }
@@ -334,15 +346,21 @@ impl Client {
334346 Ok(())
335347 }
336348
337− pub fn disconnect(&self) -> impl Future<Output = Result<Value>> {
338− self.call::<requests::Disconnect>(())
349+ pub fn disconnect(
350+ &mut self,
351+ args: Option<DisconnectArguments>,
352+ ) -> impl Future<Output = Result<Value>> {
353+ self.connection_type = None;
354+ self.call::<requests::Disconnect>(args)
339355 }
340356
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);
342359 self.call::<requests::Launch>(args)
343360 }
344361
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);
346364 self.call::<requests::Attach>(args)
347365 }
348366
helix-dap/src/lib.rs+1−1
@@ -2,7 +2,7 @@ mod client;
22mod transport;
33mod types;
44
5−pub use client::Client;
5+pub use client::{Client, ConnectionType};
66pub use events::Event;
77pub use transport::{Payload, Response, Transport};
88pub use types::*;
helix-dap/src/types.rs+12−1
@@ -391,11 +391,22 @@ pub mod requests {
391391 const COMMAND: &'static str = "attach";
392392 }
393393
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+
394405 #[derive(Debug)]
395406 pub enum Disconnect {}
396407
397408 impl Request for Disconnect {
398− type Arguments = ();
409+ type Arguments = Option<DisconnectArguments>;
399410 type Result = ();
400411 const COMMAND: &'static str = "disconnect";
401412 }
helix-term/src/commands/dap.rs+1−1
@@ -539,7 +539,7 @@ pub fn dap_variables(cx: &mut Context) {
539539pub fn dap_terminate(cx: &mut Context) {
540540 let debugger = debugger!(cx.editor);
541541
542− let request = debugger.disconnect();
542+ let request = debugger.disconnect(None);
543543 dap_callback(cx.jobs, request, |editor, _compositor, _response: ()| {
544544 // editor.set_error(format!("Failed to disconnect: {}", e));
545545 editor.debugger = None;
helix-view/src/handlers/dap.rs+62−1
@@ -1,7 +1,8 @@
11use crate::editor::{Action, Breakpoint};
22use crate::{align_view, Align, Editor};
3+use dap::requests::DisconnectArguments;
34use 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};
56use helix_lsp::block_on;
67use log::warn;
78use std::fmt::Write;
@@ -274,6 +275,66 @@ impl Editor {
274275 self.set_status("Debugged application started");
275276 }; // TODO: do we need to handle error?
276277 }
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+ }
277338 ev => {
278339 log::warn!("Unhandled event {:?}", ev);
279340 return false; // return early to skip render