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, /// Currently active frame for the current thread. pub active_frame: Option, + pub breakpoints: Vec, } 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, - breakpoints: &HashMap>, + all_breakpoints: &HashMap>, + dbg_breakpoints: &Option>, ) { 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, - all_breakpoints: &HashMap>, + all_breakpoints: &HashMap>, + dbg_breakpoints: &Option>, ) { 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>, + all_breakpoints: &HashMap>, ) { 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, ); }