dap: Rewrite breakpoints so that there's a single set maintained
This commit is contained in:
parent
3633f85b38
commit
8ffafb826f
6 changed files with 163 additions and 181 deletions
|
@ -35,7 +35,6 @@ pub struct Client {
|
|||
pub thread_id: Option<ThreadId>,
|
||||
/// Currently active frame for the current thread.
|
||||
pub active_frame: Option<usize>,
|
||||
pub breakpoints: Vec<Breakpoint>,
|
||||
pub quirks: DebuggerQuirks,
|
||||
}
|
||||
|
||||
|
@ -81,7 +80,6 @@ impl Client {
|
|||
thread_states: HashMap::new(),
|
||||
thread_id: None,
|
||||
active_frame: None,
|
||||
breakpoints: vec![],
|
||||
quirks: DebuggerQuirks::default(),
|
||||
};
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use helix_core::{merge_toml_values, syntax};
|
||||
use helix_dap::Payload;
|
||||
use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap};
|
||||
use helix_view::{theme, Editor};
|
||||
use helix_view::{editor::Breakpoint, theme, Editor};
|
||||
|
||||
use crate::{
|
||||
args::Args, commands::fetch_stack_trace, compositor::Compositor, config::Config, job::Jobs, ui,
|
||||
|
@ -319,7 +319,7 @@ impl Application {
|
|||
}
|
||||
|
||||
pub async fn handle_debugger_message(&mut self, payload: helix_dap::Payload) {
|
||||
use crate::commands::dap::{resume_application, select_thread_id};
|
||||
use crate::commands::dap::{breakpoints_changed, resume_application, select_thread_id};
|
||||
use helix_dap::{events, Event};
|
||||
let debugger = match self.editor.debugger.as_mut() {
|
||||
Some(debugger) => debugger,
|
||||
|
@ -388,46 +388,35 @@ impl Application {
|
|||
}
|
||||
Event::Breakpoint(events::Breakpoint { reason, breakpoint }) => match &reason[..] {
|
||||
"new" => {
|
||||
debugger.breakpoints.push(breakpoint);
|
||||
self.editor
|
||||
.breakpoints
|
||||
.entry(breakpoint.source.unwrap().path.unwrap()) // TODO: no unwraps
|
||||
.or_default()
|
||||
.push(Breakpoint {
|
||||
id: breakpoint.id,
|
||||
verified: breakpoint.verified,
|
||||
message: breakpoint.message,
|
||||
line: breakpoint.line.unwrap().saturating_sub(1), // TODO: no unwrap
|
||||
column: breakpoint.column,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
"changed" => {
|
||||
match debugger
|
||||
.breakpoints
|
||||
.iter()
|
||||
.position(|b| b.id == breakpoint.id)
|
||||
for breakpoints in self.editor.breakpoints.values_mut() {
|
||||
if let Some(i) = breakpoints.iter().position(|b| b.id == breakpoint.id)
|
||||
{
|
||||
Some(i) => {
|
||||
debugger.breakpoints[i] = breakpoint;
|
||||
// let item = debugger.breakpoints.get_mut(i).unwrap();
|
||||
// item.verified = breakpoint.verified;
|
||||
// // TODO: wasteful clones
|
||||
// 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);
|
||||
breakpoints[i].verified = breakpoint.verified;
|
||||
breakpoints[i].message = breakpoint.message.clone();
|
||||
breakpoints[i].line = breakpoint.line.unwrap().saturating_sub(1); // TODO: no unwrap
|
||||
breakpoints[i].column = breakpoint.column;
|
||||
}
|
||||
}
|
||||
}
|
||||
"removed" => {
|
||||
match debugger
|
||||
.breakpoints
|
||||
.iter()
|
||||
.position(|b| b.id == breakpoint.id)
|
||||
for breakpoints in self.editor.breakpoints.values_mut() {
|
||||
if let Some(i) = breakpoints.iter().position(|b| b.id == breakpoint.id)
|
||||
{
|
||||
Some(i) => {
|
||||
debugger.breakpoints.remove(i);
|
||||
}
|
||||
None => {
|
||||
warn!("Removed breakpoint with id {:?} not found", breakpoint.id);
|
||||
breakpoints.remove(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -453,13 +442,9 @@ impl Application {
|
|||
}
|
||||
Event::Initialized => {
|
||||
// send existing breakpoints
|
||||
for (path, breakpoints) in &self.editor.breakpoints {
|
||||
for (path, breakpoints) in &mut self.editor.breakpoints {
|
||||
// TODO: call futures in parallel, await all
|
||||
debugger.breakpoints = debugger
|
||||
.set_breakpoints(path.clone(), breakpoints.clone())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let _ = breakpoints_changed(debugger, path.clone(), breakpoints);
|
||||
}
|
||||
// TODO: fetch breakpoints (in case we're attaching)
|
||||
|
||||
|
|
|
@ -1897,8 +1897,8 @@ fn append_mode(cx: &mut Context) {
|
|||
mod cmd {
|
||||
use super::*;
|
||||
|
||||
use helix_dap::SourceBreakpoint;
|
||||
use helix_view::editor::Action;
|
||||
use helix_view::editor::Breakpoint;
|
||||
use ui::completers::{self, Completer};
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -2563,9 +2563,7 @@ mod cmd {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_breakpoint_at_current_line(
|
||||
editor: &mut Editor,
|
||||
) -> Option<(usize, SourceBreakpoint)> {
|
||||
pub fn get_breakpoint_at_current_line(editor: &mut Editor) -> Option<(usize, Breakpoint)> {
|
||||
let (view, doc) = current!(editor);
|
||||
let text = doc.text().slice(..);
|
||||
|
||||
|
|
|
@ -11,11 +11,13 @@ use helix_core::{
|
|||
};
|
||||
use helix_dap::{self as dap, Client, ThreadId};
|
||||
use helix_lsp::block_on;
|
||||
use helix_view::editor::Breakpoint;
|
||||
|
||||
use serde_json::{to_value, Value};
|
||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
// general utils:
|
||||
pub fn dap_pos_to_pos(doc: &helix_core::Rope, line: usize, column: usize) -> Option<usize> {
|
||||
|
@ -406,40 +408,90 @@ pub fn dap_toggle_breakpoint(cx: &mut Context) {
|
|||
dap_toggle_breakpoint_impl(cx, path, line);
|
||||
}
|
||||
|
||||
pub fn dap_toggle_breakpoint_impl(cx: &mut Context, path: std::path::PathBuf, line: usize) {
|
||||
let breakpoint = helix_dap::SourceBreakpoint {
|
||||
line: line + 1, // convert from 0-indexing to 1-indexing (TODO: could set debugger to 0-indexing on init)
|
||||
pub fn breakpoints_changed(
|
||||
debugger: &mut dap::Client,
|
||||
path: PathBuf,
|
||||
breakpoints: &mut [Breakpoint],
|
||||
) -> Result<(), anyhow::Error> {
|
||||
// TODO: handle capabilities correctly again, by filterin breakpoints when emitting
|
||||
// if breakpoint.condition.is_some()
|
||||
// && !debugger
|
||||
// .caps
|
||||
// .as_ref()
|
||||
// .unwrap()
|
||||
// .supports_conditional_breakpoints
|
||||
// .unwrap_or_default()
|
||||
// {
|
||||
// bail!(
|
||||
// "Can't edit breakpoint: debugger does not support conditional breakpoints"
|
||||
// )
|
||||
// }
|
||||
// if breakpoint.log_message.is_some()
|
||||
// && !debugger
|
||||
// .caps
|
||||
// .as_ref()
|
||||
// .unwrap()
|
||||
// .supports_log_points
|
||||
// .unwrap_or_default()
|
||||
// {
|
||||
// bail!("Can't edit breakpoint: debugger does not support logpoints")
|
||||
// }
|
||||
let source_breakpoints = breakpoints
|
||||
.iter()
|
||||
.map(|breakpoint| helix_dap::SourceBreakpoint {
|
||||
line: breakpoint.line + 1, // convert from 0-indexing to 1-indexing (TODO: could set debugger to 0-indexing on init)
|
||||
..Default::default()
|
||||
};
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let request = debugger.set_breakpoints(path, source_breakpoints);
|
||||
match block_on(request) {
|
||||
Ok(Some(dap_breakpoints)) => {
|
||||
for (breakpoint, dap_breakpoint) in breakpoints.iter_mut().zip(dap_breakpoints) {
|
||||
breakpoint.id = dap_breakpoint.id;
|
||||
breakpoint.verified = dap_breakpoint.verified;
|
||||
breakpoint.message = dap_breakpoint.message;
|
||||
// TODO: handle breakpoint.message
|
||||
// TODO: verify source matches
|
||||
breakpoint.line = dap_breakpoint.line.unwrap_or(0).saturating_sub(1); // convert to 0-indexing
|
||||
// TODO: no unwrap
|
||||
breakpoint.column = dap_breakpoint.column;
|
||||
// TODO: verify end_linef/col instruction reference, offset
|
||||
}
|
||||
}
|
||||
Err(e) => anyhow::bail!("Failed to set breakpoints: {}", e),
|
||||
_ => {}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn dap_toggle_breakpoint_impl(cx: &mut Context, path: PathBuf, line: usize) {
|
||||
// TODO: need to map breakpoints over edits and update them?
|
||||
// we shouldn't really allow editing while debug is running though
|
||||
|
||||
let breakpoints = cx.editor.breakpoints.entry(path.clone()).or_default();
|
||||
// TODO: always keep breakpoints sorted and use binary search
|
||||
if let Some(pos) = breakpoints.iter().position(|b| b.line == breakpoint.line) {
|
||||
// TODO: always keep breakpoints sorted and use binary search to determine insertion point
|
||||
if let Some(pos) = breakpoints
|
||||
.iter()
|
||||
.position(|breakpoint| breakpoint.line == line)
|
||||
{
|
||||
breakpoints.remove(pos);
|
||||
} else {
|
||||
breakpoints.push(breakpoint);
|
||||
breakpoints.push(Breakpoint {
|
||||
line,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
let breakpoints = breakpoints.clone();
|
||||
|
||||
let debugger = match &mut cx.editor.debugger {
|
||||
Some(debugger) => debugger,
|
||||
None => return,
|
||||
};
|
||||
let request = debugger.set_breakpoints(path, breakpoints);
|
||||
match block_on(request) {
|
||||
Ok(Some(breakpoints)) => {
|
||||
// TODO: handle breakpoint.message
|
||||
debugger.breakpoints = breakpoints;
|
||||
|
||||
if let Err(e) = breakpoints_changed(debugger, path, breakpoints) {
|
||||
cx.editor
|
||||
.set_error(format!("Failed to set breakpoints: {}", e));
|
||||
}
|
||||
Err(e) => cx
|
||||
.editor
|
||||
.set_error(format!("Failed to set breakpoints: {}", e)),
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn dap_continue(cx: &mut Context) {
|
||||
|
@ -651,36 +703,15 @@ pub fn dap_edit_condition(cx: &mut Context) {
|
|||
input => Some(input.to_owned()),
|
||||
};
|
||||
|
||||
if let Some(debugger) = &mut cx.editor.debugger {
|
||||
// TODO: handle capabilities correctly again, by filterin breakpoints when emitting
|
||||
// if breakpoint.condition.is_some()
|
||||
// && !debugger
|
||||
// .caps
|
||||
// .as_ref()
|
||||
// .unwrap()
|
||||
// .supports_conditional_breakpoints
|
||||
// .unwrap_or_default()
|
||||
// {
|
||||
// bail!(
|
||||
// "Can't edit breakpoint: debugger does not support conditional breakpoints"
|
||||
// )
|
||||
// }
|
||||
// if breakpoint.log_message.is_some()
|
||||
// && !debugger
|
||||
// .caps
|
||||
// .as_ref()
|
||||
// .unwrap()
|
||||
// .supports_log_points
|
||||
// .unwrap_or_default()
|
||||
// {
|
||||
// bail!("Can't edit breakpoint: debugger does not support logpoints")
|
||||
// }
|
||||
let request =
|
||||
debugger.set_breakpoints(path.clone(), breakpoints.clone());
|
||||
if let Err(e) = block_on(request) {
|
||||
let debugger = match &mut cx.editor.debugger {
|
||||
Some(debugger) => debugger,
|
||||
None => return,
|
||||
};
|
||||
|
||||
if let Err(e) = breakpoints_changed(debugger, path.clone(), breakpoints)
|
||||
{
|
||||
cx.editor
|
||||
.set_status(format!("Failed to set breakpoints: {}", e))
|
||||
}
|
||||
.set_error(format!("Failed to set breakpoints: {}", e));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
@ -719,36 +750,14 @@ pub fn dap_edit_log(cx: &mut Context) {
|
|||
input => Some(input.to_owned()),
|
||||
};
|
||||
|
||||
if let Some(debugger) = &mut cx.editor.debugger {
|
||||
// TODO: handle capabilities correctly again, by filterin breakpoints when emitting
|
||||
// if breakpoint.condition.is_some()
|
||||
// && !debugger
|
||||
// .caps
|
||||
// .as_ref()
|
||||
// .unwrap()
|
||||
// .supports_conditional_breakpoints
|
||||
// .unwrap_or_default()
|
||||
// {
|
||||
// bail!(
|
||||
// "Can't edit breakpoint: debugger does not support conditional breakpoints"
|
||||
// )
|
||||
// }
|
||||
// if breakpoint.log_message.is_some()
|
||||
// && !debugger
|
||||
// .caps
|
||||
// .as_ref()
|
||||
// .unwrap()
|
||||
// .supports_log_points
|
||||
// .unwrap_or_default()
|
||||
// {
|
||||
// bail!("Can't edit breakpoint: debugger does not support logpoints")
|
||||
// }
|
||||
let request =
|
||||
debugger.set_breakpoints(path.clone(), breakpoints.clone());
|
||||
if let Err(e) = block_on(request) {
|
||||
let debugger = match &mut cx.editor.debugger {
|
||||
Some(debugger) => debugger,
|
||||
None => return,
|
||||
};
|
||||
if let Err(e) = breakpoints_changed(debugger, path.clone(), breakpoints)
|
||||
{
|
||||
cx.editor
|
||||
.set_status(format!("Failed to set breakpoints: {}", e))
|
||||
}
|
||||
.set_error(format!("Failed to set breakpoints: {}", e));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
|
|
@ -15,7 +15,6 @@ use helix_core::{
|
|||
unicode::width::UnicodeWidthStr,
|
||||
LineEnding, Position, Range, Selection,
|
||||
};
|
||||
use helix_dap::{Breakpoint, SourceBreakpoint, StackFrame};
|
||||
use helix_view::{
|
||||
document::{Mode, SCRATCH_BUFFER_NAME},
|
||||
graphics::{Color, CursorKind, Modifier, Rect, Style},
|
||||
|
@ -24,7 +23,7 @@ use helix_view::{
|
|||
keyboard::{KeyCode, KeyModifiers},
|
||||
Document, Editor, Theme, View,
|
||||
};
|
||||
use std::{borrow::Cow, collections::HashMap, path::PathBuf};
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crossterm::event::{Event, MouseButton, MouseEvent, MouseEventKind};
|
||||
use tui::buffer::Buffer as Surface;
|
||||
|
@ -74,7 +73,6 @@ impl EditorView {
|
|||
let inner = view.inner_area();
|
||||
let area = view.area;
|
||||
let theme = &editor.theme;
|
||||
let all_breakpoints = HashMap::new();
|
||||
|
||||
let highlights = Self::doc_syntax_highlights(doc, view.offset, inner.height, theme, loader);
|
||||
let highlights = syntax::merge(highlights, Self::doc_diagnostics_highlights(doc, theme));
|
||||
|
@ -107,7 +105,7 @@ impl EditorView {
|
|||
}
|
||||
}
|
||||
|
||||
self.render_diagnostics(doc, view, inner, surface, theme, &all_breakpoints);
|
||||
self.render_diagnostics(doc, view, inner, surface, theme);
|
||||
|
||||
let statusline_area = view
|
||||
.area
|
||||
|
@ -436,39 +434,21 @@ impl EditorView {
|
|||
let error = theme.get("error");
|
||||
let info = theme.get("info");
|
||||
|
||||
let all_breakpoints = &editor.breakpoints;
|
||||
|
||||
// TODO: debugger breakpoints should keep editor level breakpoints in sync
|
||||
|
||||
let breakpoints = doc
|
||||
.path()
|
||||
.and_then(|path| all_breakpoints.get(path))
|
||||
.and_then(|path| editor.breakpoints.get(path))
|
||||
.unwrap();
|
||||
|
||||
Box::new(move |line: usize, _selected: bool, out: &mut String| {
|
||||
// TODO: debugger should translate received breakpoints to 0-indexing
|
||||
let breakpoint = breakpoints.iter().find(|breakpoint| {
|
||||
false
|
||||
// if breakpoint.source == doc.path() {
|
||||
// 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
|
||||
// }
|
||||
});
|
||||
let breakpoint = breakpoints
|
||||
.iter()
|
||||
.find(|breakpoint| breakpoint.line == line);
|
||||
|
||||
let breakpoint = match breakpoint {
|
||||
Some(b) => b,
|
||||
None => return None,
|
||||
};
|
||||
|
||||
// let verified = breakpoint.verified;
|
||||
let verified = false;
|
||||
|
||||
let mut style =
|
||||
if breakpoint.condition.is_some() && breakpoint.log_message.is_some() {
|
||||
error.add_modifier(Modifier::UNDERLINED)
|
||||
|
@ -480,7 +460,7 @@ impl EditorView {
|
|||
warning
|
||||
};
|
||||
|
||||
if !verified {
|
||||
if !breakpoint.verified {
|
||||
// Faded colors
|
||||
style = if let Some(Color::Rgb(r, g, b)) = style.fg {
|
||||
style.fg(Color::Rgb(
|
||||
|
@ -495,7 +475,7 @@ impl EditorView {
|
|||
|
||||
// TODO: also handle breakpoints only present in the user struct
|
||||
use std::fmt::Write;
|
||||
let sym = if verified { "▲" } else { "⊚" };
|
||||
let sym = if breakpoint.verified { "▲" } else { "⊚" };
|
||||
write!(out, "{}", sym).unwrap();
|
||||
Some(style)
|
||||
})
|
||||
|
@ -567,7 +547,6 @@ impl EditorView {
|
|||
viewport: Rect,
|
||||
surface: &mut Surface,
|
||||
theme: &Theme,
|
||||
all_breakpoints: &HashMap<PathBuf, Vec<SourceBreakpoint>>,
|
||||
) {
|
||||
use helix_core::diagnostic::Severity;
|
||||
use tui::{
|
||||
|
@ -605,28 +584,28 @@ impl EditorView {
|
|||
lines.extend(text.lines);
|
||||
}
|
||||
|
||||
if let Some(path) = doc.path() {
|
||||
let line = doc.text().char_to_line(cursor);
|
||||
if let Some(breakpoints) = all_breakpoints.get(path) {
|
||||
if let Some(breakpoint) = breakpoints
|
||||
.iter()
|
||||
.find(|breakpoint| breakpoint.line - 1 == line)
|
||||
{
|
||||
if let Some(condition) = &breakpoint.condition {
|
||||
lines.extend(
|
||||
Text::styled(condition, warning.add_modifier(Modifier::UNDERLINED))
|
||||
.lines,
|
||||
);
|
||||
}
|
||||
if let Some(log_message) = &breakpoint.log_message {
|
||||
lines.extend(
|
||||
Text::styled(log_message, info.add_modifier(Modifier::UNDERLINED))
|
||||
.lines,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// let line = doc.text().char_to_line(cursor);
|
||||
// let breakpoint = doc
|
||||
// .path()
|
||||
// .and_then(|path| all_breakpoints.get(path))
|
||||
// .and_then(|breakpoints| {
|
||||
// breakpoints
|
||||
// .iter()
|
||||
// .find(|breakpoint| breakpoint.line == line)
|
||||
// });
|
||||
|
||||
// if let Some(breakpoint) = breakpoint {
|
||||
// if let Some(condition) = &breakpoint.condition {
|
||||
// lines.extend(
|
||||
// Text::styled(condition, warning.add_modifier(Modifier::UNDERLINED)).lines,
|
||||
// );
|
||||
// }
|
||||
// if let Some(log_message) = &breakpoint.log_message {
|
||||
// lines.extend(
|
||||
// Text::styled(log_message, info.add_modifier(Modifier::UNDERLINED)).lines,
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
let paragraph = Paragraph::new(lines)
|
||||
.alignment(Alignment::Right)
|
||||
|
|
|
@ -156,6 +156,19 @@ impl std::fmt::Debug for Motion {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Breakpoint {
|
||||
pub id: Option<usize>,
|
||||
pub verified: bool,
|
||||
pub message: Option<String>,
|
||||
|
||||
pub line: usize,
|
||||
pub column: Option<usize>,
|
||||
pub condition: Option<String>,
|
||||
pub hit_condition: Option<String>,
|
||||
pub log_message: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Editor {
|
||||
pub tree: Tree,
|
||||
|
@ -169,7 +182,7 @@ pub struct Editor {
|
|||
|
||||
pub debugger: Option<dap::Client>,
|
||||
pub debugger_events: SelectAll<UnboundedReceiverStream<dap::Payload>>,
|
||||
pub breakpoints: HashMap<PathBuf, Vec<dap::SourceBreakpoint>>,
|
||||
pub breakpoints: HashMap<PathBuf, Vec<Breakpoint>>,
|
||||
|
||||
pub clipboard_provider: Box<dyn ClipboardProvider>,
|
||||
|
||||
|
|
Loading…
Reference in a new issue