From 507a1f8dd6a7f88b979a09532a14a10eda72867b Mon Sep 17 00:00:00 2001
From: Dmitry Sharshakov <d3dx12.xx@gmail.com>
Date: Mon, 6 Sep 2021 08:47:54 +0300
Subject: [PATCH] Get breakpoint reports from debugger

---
 helix-dap/src/client.rs        |  2 +
 helix-term/src/application.rs  | 52 ++++++++++++++++++++++++-
 helix-term/src/commands/dap.rs | 22 +++++++++--
 helix-term/src/ui/editor.rs    | 71 ++++++++++++++++++++++++++++------
 4 files changed, 130 insertions(+), 17 deletions(-)

diff --git a/helix-dap/src/client.rs b/helix-dap/src/client.rs
index e1791cd1..e5f8feb9 100644
--- a/helix-dap/src/client.rs
+++ b/helix-dap/src/client.rs
@@ -33,6 +33,7 @@ pub struct Client {
     pub thread_id: Option<isize>,
     /// Currently active frame for the current thread.
     pub active_frame: Option<usize>,
+    pub breakpoints: Vec<Breakpoint>,
 }
 
 impl Client {
@@ -80,6 +81,7 @@ impl Client {
             thread_states: HashMap::new(),
             thread_id: None,
             active_frame: None,
+            breakpoints: vec![],
         };
 
         tokio::spawn(Self::recv(server_rx, client_rx));
diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs
index ce6cc76b..7a573adc 100644
--- a/helix-term/src/application.rs
+++ b/helix-term/src/application.rs
@@ -7,7 +7,7 @@ use crate::{
     args::Args, commands::fetch_stack_trace, compositor::Compositor, config::Config, job::Jobs, ui,
 };
 
-use log::error;
+use log::{error, warn};
 use std::{
     io::{stdout, Write},
     sync::Arc,
@@ -320,6 +320,53 @@ impl Application {
                 Event::Thread(_) => {
                     // TODO: update thread_states, make threads request
                 }
+                Event::Breakpoint(events::Breakpoint { reason, breakpoint }) => match &reason[..] {
+                    "new" => {
+                        debugger.breakpoints.push(breakpoint);
+                    }
+                    "changed" => {
+                        match debugger
+                            .breakpoints
+                            .iter()
+                            .position(|b| b.id == breakpoint.id)
+                        {
+                            Some(i) => {
+                                let item = debugger.breakpoints.get_mut(i).unwrap();
+                                item.verified = breakpoint.verified;
+                                item.message = breakpoint.message.or_else(|| item.message.clone());
+                                item.source = breakpoint.source.or_else(|| item.source.clone());
+                                item.line = breakpoint.line.or(item.line);
+                                item.column = breakpoint.column.or(item.column);
+                                item.end_line = breakpoint.end_line.or(item.end_line);
+                                item.end_column = breakpoint.end_column.or(item.end_column);
+                                item.instruction_reference = breakpoint
+                                    .instruction_reference
+                                    .or_else(|| item.instruction_reference.clone());
+                                item.offset = breakpoint.offset.or(item.offset);
+                            }
+                            None => {
+                                warn!("Changed breakpoint with id {:?} not found", breakpoint.id);
+                            }
+                        }
+                    }
+                    "removed" => {
+                        match debugger
+                            .breakpoints
+                            .iter()
+                            .position(|b| b.id == breakpoint.id)
+                        {
+                            Some(i) => {
+                                debugger.breakpoints.remove(i);
+                            }
+                            None => {
+                                warn!("Removed breakpoint with id {:?} not found", breakpoint.id);
+                            }
+                        }
+                    }
+                    reason => {
+                        warn!("Unknown breakpoint event: {}", reason);
+                    }
+                },
                 Event::Output(events::Output {
                     category, output, ..
                 }) => {
@@ -340,9 +387,10 @@ impl Application {
                     // send existing breakpoints
                     for (path, breakpoints) in &self.editor.breakpoints {
                         // TODO: call futures in parallel, await all
-                        debugger
+                        debugger.breakpoints = debugger
                             .set_breakpoints(path.clone(), breakpoints.clone())
                             .await
+                            .unwrap()
                             .unwrap();
                     }
                     // TODO: fetch breakpoints (in case we're attaching)
diff --git a/helix-term/src/commands/dap.rs b/helix-term/src/commands/dap.rs
index 61b1f438..3fb990c2 100644
--- a/helix-term/src/commands/dap.rs
+++ b/helix-term/src/commands/dap.rs
@@ -336,10 +336,24 @@ pub fn dap_toggle_breakpoint(cx: &mut Context) {
         None => return,
     };
     let request = debugger.set_breakpoints(path, breakpoints);
-    if let Err(e) = block_on(request) {
-        cx.editor
-            .set_error(format!("Failed to set breakpoints: {:?}", e));
-    }
+    match block_on(request) {
+        Ok(Some(breakpoints)) => {
+            let old_breakpoints = debugger.breakpoints.clone();
+            debugger.breakpoints = breakpoints.clone();
+            for bp in breakpoints {
+                if !old_breakpoints.iter().any(|b| b.message == bp.message) {
+                    if let Some(msg) = &bp.message {
+                        cx.editor.set_status(format!("Breakpoint set: {}", msg));
+                        break;
+                    }
+                }
+            }
+        }
+        Err(e) => cx
+            .editor
+            .set_error(format!("Failed to set breakpoints: {:?}", e)),
+        _ => {}
+    };
 }
 
 pub fn dap_continue(cx: &mut Context) {
diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs
index 85b00481..63694d0b 100644
--- a/helix-term/src/ui/editor.rs
+++ b/helix-term/src/ui/editor.rs
@@ -16,11 +16,11 @@ use helix_core::{
     unicode::width::UnicodeWidthStr,
     LineEnding, Position, Range, Selection,
 };
-use helix_dap::{SourceBreakpoint, StackFrame};
+use helix_dap::{Breakpoint, SourceBreakpoint, StackFrame};
 use helix_view::{
     document::Mode,
     editor::LineNumber,
-    graphics::{CursorKind, Modifier, Rect, Style},
+    graphics::{Color, CursorKind, Modifier, Rect, Style},
     info::Info,
     input::KeyEvent,
     keyboard::{KeyCode, KeyModifiers},
@@ -76,7 +76,8 @@ impl EditorView {
         loader: &syntax::Loader,
         config: &helix_view::editor::Config,
         debugger: &Option<helix_dap::Client>,
-        breakpoints: &HashMap<PathBuf, Vec<helix_dap::SourceBreakpoint>>,
+        all_breakpoints: &HashMap<PathBuf, Vec<SourceBreakpoint>>,
+        dbg_breakpoints: &Option<Vec<Breakpoint>>,
     ) {
         let inner = view.inner_area();
         let area = view.area;
@@ -102,7 +103,8 @@ impl EditorView {
             is_focused,
             config,
             debugger,
-            breakpoints,
+            all_breakpoints,
+            dbg_breakpoints,
         );
 
         if is_focused {
@@ -122,7 +124,7 @@ impl EditorView {
             }
         }
 
-        self.render_diagnostics(doc, view, inner, surface, theme, breakpoints);
+        self.render_diagnostics(doc, view, inner, surface, theme, all_breakpoints);
 
         let statusline_area = view
             .area
@@ -426,7 +428,8 @@ impl EditorView {
         is_focused: bool,
         config: &helix_view::editor::Config,
         debugger: &Option<helix_dap::Client>,
-        all_breakpoints: &HashMap<PathBuf, Vec<helix_dap::SourceBreakpoint>>,
+        all_breakpoints: &HashMap<PathBuf, Vec<SourceBreakpoint>>,
+        dbg_breakpoints: &Option<Vec<Breakpoint>>,
     ) {
         let text = doc.text().slice(..);
         let last_line = view.last_line(doc);
@@ -500,10 +503,35 @@ impl EditorView {
 
             let selected = cursors.contains(&line);
 
-            if let Some(bps) = breakpoints.as_ref() {
-                if let Some(breakpoint) = bps.iter().find(|breakpoint| breakpoint.line - 1 == line)
+            if let Some(user) = breakpoints.as_ref() {
+                let debugger_breakpoint = if let Some(debugger) = dbg_breakpoints.as_ref() {
+                    debugger.iter().find(|breakpoint| {
+                        if breakpoint.source.is_some()
+                            && doc.path().is_some()
+                            && breakpoint.source.as_ref().unwrap().path == doc.path().cloned()
+                        {
+                            match (breakpoint.line, breakpoint.end_line) {
+                                #[allow(clippy::int_plus_one)]
+                                (Some(l), Some(el)) => l - 1 <= line && line <= el - 1,
+                                (Some(l), None) => l - 1 == line,
+                                _ => false,
+                            }
+                        } else {
+                            false
+                        }
+                    })
+                } else {
+                    None
+                };
+
+                if let Some(breakpoint) = user.iter().find(|breakpoint| breakpoint.line - 1 == line)
                 {
-                    let style =
+                    let unverified = match dbg_breakpoints {
+                        Some(_) => debugger_breakpoint.map(|b| !b.verified).unwrap_or(true),
+                        // We cannot mark breakpoint as unverified unless we have a debugger
+                        None => false,
+                    };
+                    let mut style =
                         if breakpoint.condition.is_some() && breakpoint.log_message.is_some() {
                             error.add_modifier(Modifier::UNDERLINED)
                         } else if breakpoint.condition.is_some() {
@@ -513,7 +541,26 @@ impl EditorView {
                         } else {
                             warning
                         };
+                    if unverified {
+                        // Faded colors
+                        style = if let Some(Color::Rgb(r, g, b)) = style.fg {
+                            style.fg(Color::Rgb(
+                                ((r as f32) * 0.4).floor() as u8,
+                                ((g as f32) * 0.4).floor() as u8,
+                                ((b as f32) * 0.4).floor() as u8,
+                            ))
+                        } else {
+                            style.fg(Color::Gray)
+                        }
+                    };
                     surface.set_stringn(viewport.x, viewport.y + i as u16, "▲", 1, style);
+                } else if let Some(breakpoint) = debugger_breakpoint {
+                    let style = if breakpoint.verified {
+                        info
+                    } else {
+                        info.fg(Color::Gray)
+                    };
+                    surface.set_stringn(viewport.x, viewport.y + i as u16, "⊚", 1, style);
                 }
             }
 
@@ -563,7 +610,7 @@ impl EditorView {
         viewport: Rect,
         surface: &mut Surface,
         theme: &Theme,
-        all_breakpoints: &HashMap<PathBuf, Vec<helix_dap::SourceBreakpoint>>,
+        all_breakpoints: &HashMap<PathBuf, Vec<SourceBreakpoint>>,
     ) {
         use helix_core::diagnostic::Severity;
         use tui::{
@@ -602,8 +649,8 @@ impl EditorView {
         }
 
         if let Some(path) = doc.path() {
+            let line = doc.text().char_to_line(cursor);
             if let Some(breakpoints) = all_breakpoints.get(path) {
-                let line = doc.text().char_to_line(cursor);
                 if let Some(breakpoint) = breakpoints
                     .iter()
                     .find(|breakpoint| breakpoint.line - 1 == line)
@@ -1272,6 +1319,7 @@ impl Component for EditorView {
         for (view, is_focused) in cx.editor.tree.views() {
             let doc = cx.editor.document(view.doc).unwrap();
             let loader = &cx.editor.syn_loader;
+            let dbg_breakpoints = cx.editor.debugger.as_ref().map(|d| d.breakpoints.clone());
             self.render_view(
                 doc,
                 view,
@@ -1283,6 +1331,7 @@ impl Component for EditorView {
                 &cx.editor.config,
                 &cx.editor.debugger,
                 &cx.editor.breakpoints,
+                &dbg_breakpoints,
             );
         }