From 28658836eee9860666a114756e13e1fdf7fd4637 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= <blaz@mxxn.io>
Date: Sun, 22 Aug 2021 16:05:12 +0900
Subject: [PATCH] Add more event types, simplify event decoding

---
 helix-dap/src/lib.rs          |   3 +-
 helix-dap/src/transport.rs    |  13 +---
 helix-dap/src/types.rs        | 133 ++++++++++++++++++++++++++++++++--
 helix-term/src/application.rs |  49 +++++++------
 4 files changed, 158 insertions(+), 40 deletions(-)

diff --git a/helix-dap/src/lib.rs b/helix-dap/src/lib.rs
index 64cc87cc..f60b102c 100644
--- a/helix-dap/src/lib.rs
+++ b/helix-dap/src/lib.rs
@@ -3,7 +3,8 @@ mod transport;
 mod types;
 
 pub use client::Client;
-pub use transport::{Event, Payload, Response, Transport};
+pub use events::Event;
+pub use transport::{Payload, Response, Transport};
 pub use types::*;
 
 use thiserror::Error;
diff --git a/helix-dap/src/transport.rs b/helix-dap/src/transport.rs
index 062e0447..afb7694d 100644
--- a/helix-dap/src/transport.rs
+++ b/helix-dap/src/transport.rs
@@ -1,4 +1,4 @@
-use crate::{Error, Result};
+use crate::{Error, Event, Result};
 use anyhow::Context;
 use log::{error, info, warn};
 use serde::{Deserialize, Serialize};
@@ -32,13 +32,6 @@ pub struct Response {
     pub body: Option<Value>,
 }
 
-#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
-pub struct Event {
-    // seq is omitted as unused and is not sent by some implementations
-    pub event: String,
-    pub body: Option<Value>,
-}
-
 #[derive(Debug, Clone, Deserialize, Serialize)]
 #[serde(tag = "type", rename_all = "camelCase")]
 pub enum Payload {
@@ -208,8 +201,8 @@ impl Transport {
                 client_tx.send(msg).expect("Failed to send");
                 Ok(())
             }
-            Payload::Event(Event { ref event, .. }) => {
-                info!("<- DAP event {}", event);
+            Payload::Event(ref event) => {
+                info!("<- DAP event {:?}", event);
                 client_tx.send(msg).expect("Failed to send");
                 Ok(())
             }
diff --git a/helix-dap/src/types.rs b/helix-dap/src/types.rs
index 3fd858de..fcbbf184 100644
--- a/helix-dap/src/types.rs
+++ b/helix-dap/src/types.rs
@@ -206,6 +206,21 @@ pub struct Variable {
     pub memory_reference: Option<String>,
 }
 
+#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct Module {
+    pub id: String, // TODO: || number
+    pub name: String,
+    pub path: Option<PathBuf>,
+    pub is_optimized: Option<bool>,
+    pub is_user_code: Option<bool>,
+    pub version: Option<String>,
+    pub symbol_status: Option<String>,
+    pub symbol_file_path: Option<String>,
+    pub date_time_stamp: Option<String>,
+    pub address_range: Option<String>,
+}
+
 pub mod requests {
     use super::*;
     #[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize)]
@@ -411,6 +426,69 @@ pub mod requests {
 
 pub mod events {
     use super::*;
+
+    #[derive(Debug, Clone, Serialize, Deserialize)]
+    #[serde(rename_all = "camelCase")]
+    #[serde(tag = "event", content = "body")]
+    // seq is omitted as unused and is not sent by some implementations
+    pub enum Event {
+        Initialized,
+        Stopped(Stopped),
+        Continued(Continued),
+        Exited(Exited),
+        Terminated(Terminated),
+        Thread(Thread),
+        Output(Output),
+        Breakpoint(Breakpoint),
+        Module(Module),
+        LoadedSource(LoadedSource),
+        Process(Process),
+        Capabilities(Capabilities),
+        // ProgressStart(),
+        // ProgressUpdate(),
+        // ProgressEnd(),
+        // Invalidated(),
+        Memory(Memory),
+    }
+
+    #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+    #[serde(rename_all = "camelCase")]
+    pub struct Stopped {
+        pub reason: String,
+        pub description: Option<String>,
+        pub thread_id: Option<usize>,
+        pub preserve_focus_hint: Option<bool>,
+        pub text: Option<String>,
+        pub all_threads_stopped: Option<bool>,
+        pub hit_breakpoint_ids: Option<Vec<usize>>,
+    }
+
+    #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+    #[serde(rename_all = "camelCase")]
+    pub struct Continued {
+        pub thread_id: usize,
+        pub all_threads_continued: Option<bool>,
+    }
+
+    #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+    #[serde(rename_all = "camelCase")]
+    pub struct Exited {
+        pub exit_code: usize,
+    }
+
+    #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+    #[serde(rename_all = "camelCase")]
+    pub struct Terminated {
+        pub restart: Option<Value>,
+    }
+
+    #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+    #[serde(rename_all = "camelCase")]
+    pub struct Thread {
+        pub reason: String,
+        pub thread_id: usize,
+    }
+
     #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
     #[serde(rename_all = "camelCase")]
     pub struct Output {
@@ -426,13 +504,54 @@ pub mod events {
 
     #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
     #[serde(rename_all = "camelCase")]
-    pub struct Stopped {
+    pub struct Breakpoint {
         pub reason: String,
-        pub description: Option<String>,
-        pub thread_id: Option<usize>,
-        pub preserve_focus_hint: Option<bool>,
-        pub text: Option<String>,
-        pub all_threads_stopped: Option<bool>,
-        pub hit_breakpoint_ids: Option<Vec<usize>>,
+        pub breakpoint: super::Breakpoint,
+    }
+
+    #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+    #[serde(rename_all = "camelCase")]
+    pub struct Module {
+        pub reason: String,
+        pub module: super::Module,
+    }
+
+    #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+    #[serde(rename_all = "camelCase")]
+    pub struct LoadedSource {
+        pub reason: String,
+        pub source: super::Source,
+    }
+
+    #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+    #[serde(rename_all = "camelCase")]
+    pub struct Process {
+        pub name: String,
+        pub system_process_id: Option<usize>,
+        pub is_local_process: Option<bool>,
+        pub start_method: String, // TODO: use enum
+        pub pointer_size: Option<usize>,
+    }
+
+    #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+    #[serde(rename_all = "camelCase")]
+    pub struct Capabilities {
+        pub module: super::DebuggerCapabilities,
+    }
+
+    // #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+    // #[serde(rename_all = "camelCase")]
+    // pub struct Invalidated {
+    // pub areas: Vec<InvalidatedArea>,
+    // pub thread_id: Option<usize>,
+    // pub stack_frame_id: Option<usize>,
+    // }
+
+    #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
+    #[serde(rename_all = "camelCase")]
+    pub struct Memory {
+        pub memory_reference: String,
+        pub offset: usize,
+        pub count: usize,
     }
 }
diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs
index c9be81e2..0f0a9ae5 100644
--- a/helix-term/src/application.rs
+++ b/helix-term/src/application.rs
@@ -2,7 +2,6 @@ use helix_core::syntax;
 use helix_dap::Payload;
 use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap};
 use helix_view::{theme, Editor};
-use serde_json::from_value;
 
 use crate::{args::Args, compositor::Compositor, config::Config, job::Jobs, ui};
 
@@ -251,40 +250,47 @@ impl Application {
     }
 
     pub async fn handle_debugger_message(&mut self, payload: helix_dap::Payload) {
+        use helix_dap::{events, Event};
         let mut debugger = match self.editor.debugger.as_mut() {
             Some(debugger) => debugger,
             None => return,
         };
 
         match payload {
-            Payload::Event(ev) => match &ev.event[..] {
-                "stopped" => {
+            Payload::Event(ev) => match ev {
+                Event::Stopped(events::Stopped {
+                    thread_id,
+                    description,
+                    text,
+                    reason,
+                    all_threads_stopped,
+                    ..
+                }) => {
                     debugger.is_running = false;
                     let main = debugger
                         .threads()
                         .await
                         .ok()
                         .and_then(|threads| threads.get(0).cloned());
+
                     if let Some(main) = main {
                         let (bt, _) = debugger.stack_trace(main.id).await.unwrap();
                         debugger.stack_pointer = bt.get(0).cloned();
                     }
 
-                    let body: helix_dap::events::Stopped =
-                        from_value(ev.body.expect("`stopped` event must have a body")).unwrap();
-                    let scope = match body.thread_id {
+                    let scope = match thread_id {
                         Some(id) => format!("Thread {}", id),
                         None => "Target".to_owned(),
                     };
 
-                    let mut status = format!("{} stopped because of {}", scope, body.reason);
-                    if let Some(desc) = body.description {
+                    let mut status = format!("{} stopped because of {}", scope, reason);
+                    if let Some(desc) = description {
                         status.push_str(&format!(" {}", desc));
                     }
-                    if let Some(text) = body.text {
+                    if let Some(text) = text {
                         status.push_str(&format!(" {}", text));
                     }
-                    if body.all_threads_stopped == Some(true) {
+                    if all_threads_stopped.unwrap_or_default() {
                         status.push_str(" (all threads stopped)");
                     }
 
@@ -302,31 +308,30 @@ impl Application {
                             .unwrap();
                     }
                     self.editor.set_status(status);
-                    self.render();
                 }
-                "output" => {
-                    let body: helix_dap::events::Output =
-                        from_value(ev.body.expect("`output` event must have a body")).unwrap();
-
-                    let prefix = match body.category {
+                Event::Output(events::Output {
+                    category, output, ..
+                }) => {
+                    let prefix = match category {
                         Some(category) => format!("Debug ({}):", category),
                         None => "Debug:".to_owned(),
                     };
 
-                    self.editor
-                        .set_status(format!("{} {}", prefix, body.output));
-                    self.render();
+                    self.editor.set_status(format!("{} {}", prefix, output));
                 }
-                "initialized" => {
+                Event::Initialized => {
                     self.editor
                         .set_status("Debugged application started".to_owned());
-                    self.render();
                 }
-                _ => {}
+                ev => {
+                    log::warn!("Unhandled event {:?}", ev);
+                    return; // return early to skip render
+                }
             },
             Payload::Response(_) => unreachable!(),
             Payload::Request(_) => todo!(),
         }
+        self.render();
     }
 
     pub async fn handle_language_server_message(