dap: refactor frame handling

pull/574/head
Blaž Hrastnik 3 years ago
parent 986828e75c
commit 2c7b75475f

@ -29,10 +29,12 @@ pub struct Client {
pub caps: Option<DebuggerCapabilities>, pub caps: Option<DebuggerCapabilities>,
// //
pub breakpoints: HashMap<PathBuf, Vec<SourceBreakpoint>>, pub breakpoints: HashMap<PathBuf, Vec<SourceBreakpoint>>,
// TODO: multiple threads support // thread_id -> frames
pub stack_pointer: Option<StackFrame>, pub stack_frames: HashMap<usize, Vec<StackFrame>>,
pub thread_id: Option<usize>, pub thread_id: Option<usize>,
pub is_running: bool, /// Currently active frame for the current thread.
pub active_frame: Option<usize>,
pub is_running: bool, // TODO: track is_running per thread
} }
impl Client { impl Client {
@ -76,8 +78,9 @@ impl Client {
caps: None, caps: None,
// //
breakpoints: HashMap::new(), breakpoints: HashMap::new(),
stack_pointer: None, stack_frames: HashMap::new(),
thread_id: None, thread_id: None,
active_frame: None,
is_running: false, is_running: false,
}; };

@ -3,14 +3,7 @@ use helix_dap::Payload;
use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap}; use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap};
use helix_view::{theme, Editor}; use helix_view::{theme, Editor};
use crate::{ use crate::{args::Args, compositor::Compositor, config::Config, job::Jobs, ui};
args::Args,
commands::{align_view, Align},
compositor::Compositor,
config::Config,
job::Jobs,
ui,
};
use log::error; use log::error;
use std::{ use std::{
@ -263,6 +256,7 @@ impl Application {
} }
pub async fn handle_debugger_message(&mut self, payload: helix_dap::Payload) { pub async fn handle_debugger_message(&mut self, payload: helix_dap::Payload) {
use crate::commands::dap::select_thread_id;
use helix_dap::{events, Event}; use helix_dap::{events, Event};
let mut debugger = match self.editor.debugger.as_mut() { let mut debugger = match self.editor.debugger.as_mut() {
Some(debugger) => debugger, Some(debugger) => debugger,
@ -281,12 +275,9 @@ impl Application {
}) => { }) => {
debugger.is_running = false; debugger.is_running = false;
// whichever thread stops is made "current". // whichever thread stops is made "current" (if no previously selected thread).
debugger.thread_id = thread_id;
if let Some(thread_id) = thread_id { if let Some(thread_id) = thread_id {
let (bt, _) = debugger.stack_trace(thread_id).await.unwrap(); select_thread_id(&mut self.editor, thread_id, false).await;
debugger.stack_pointer = bt.get(0).cloned();
} }
let scope = match thread_id { let scope = match thread_id {
@ -305,52 +296,6 @@ impl Application {
status.push_str(" (all threads stopped)"); status.push_str(" (all threads stopped)");
} }
if let Some(helix_dap::StackFrame {
source:
Some(helix_dap::Source {
path: Some(ref path),
..
}),
line,
column,
end_line,
end_column,
..
}) = debugger.stack_pointer
{
let path = path.clone();
self.editor
.open(path, helix_view::editor::Action::Replace)
.unwrap(); // TODO: there should be no unwrapping!
let (view, doc) = current!(self.editor);
fn dap_pos_to_pos(
doc: &helix_core::Rope,
line: usize,
column: usize,
) -> Option<usize> {
// 1-indexing to 0 indexing
let line = doc.try_line_to_char(line - 1).ok()?;
let pos = line + column;
// TODO: this is probably utf-16 offsets
Some(pos)
}
let text_end = doc.text().len_chars().saturating_sub(1);
let start = dap_pos_to_pos(doc.text(), line, column).unwrap_or(0);
let selection = if let Some(end_line) = end_line {
let end = dap_pos_to_pos(doc.text(), end_line, end_column.unwrap_or(0))
.unwrap_or(0);
Selection::single(start.min(text_end), end.min(text_end))
} else {
Selection::point(start.min(text_end))
};
doc.set_selection(view.id, selection);
align_view(doc, view, Align::Center);
}
self.editor.set_status(status); self.editor.set_status(status);
} }
Event::Output(events::Output { Event::Output(events::Output {
@ -373,8 +318,9 @@ impl Application {
.set_status("Debugged application started".to_owned()); .set_status("Debugged application started".to_owned());
} }
Event::Continued(_) => { Event::Continued(_) => {
debugger.stack_pointer = None;
debugger.is_running = true; debugger.is_running = true;
debugger.active_frame = None;
debugger.thread_id = None;
} }
ev => { ev => {
log::warn!("Unhandled event {:?}", ev); log::warn!("Unhandled event {:?}", ev);

@ -1,4 +1,4 @@
mod dap; pub(crate) mod dap;
pub use dap::*; pub use dap::*;
@ -1955,8 +1955,17 @@ mod cmd {
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
use helix_lsp::block_on; use helix_lsp::block_on;
if let Some(debugger) = cx.editor.debugger.as_mut() { if let Some(debugger) = cx.editor.debugger.as_mut() {
let id = debugger.stack_pointer.clone().map(|x| x.id); let (frame, thread_id) = match (debugger.active_frame, debugger.thread_id) {
let response = block_on(debugger.eval(args.join(" "), id))?; (Some(frame), Some(thread_id)) => (frame, thread_id),
_ => {
bail!("Cannot find current stack frame to access variables")
}
};
// TODO: support no frame_id
let frame_id = debugger.stack_frames[&thread_id][frame].id;
let response = block_on(debugger.eval(args.join(" "), Some(frame_id)))?;
cx.editor.set_status(response.result); cx.editor.set_status(response.result);
} }
Ok(()) Ok(())
@ -1966,7 +1975,7 @@ mod cmd {
cx: &mut compositor::Context, cx: &mut compositor::Context,
condition: Option<String>, condition: Option<String>,
log_message: Option<String>, log_message: Option<String>,
) { ) -> anyhow::Result<()> {
use helix_lsp::block_on; use helix_lsp::block_on;
let (view, doc) = current!(cx.editor); let (view, doc) = current!(cx.editor);
@ -1981,38 +1990,29 @@ mod cmd {
let path = match doc.path() { let path = match doc.path() {
Some(path) => path.to_path_buf(), Some(path) => path.to_path_buf(),
None => { None => {
cx.editor bail!("Can't edit breakpoint: document has no path")
.set_error("Can't edit breakpoint: document has no path".to_string());
return;
} }
}; };
if let Some(debugger) = &mut cx.editor.debugger { if let Some(debugger) = &mut cx.editor.debugger {
if breakpoint.condition.is_some() if breakpoint.condition.is_some()
&& !debugger && !debugger
.caps .caps
.clone() .as_ref()
.unwrap() .unwrap()
.supports_conditional_breakpoints .supports_conditional_breakpoints
.unwrap_or_default() .unwrap_or_default()
{ {
cx.editor.set_error( bail!("Can't edit breakpoint: debugger does not support conditional breakpoints")
"Can't edit breakpoint: debugger does not support conditional breakpoints"
.to_string(),
);
return;
} }
if breakpoint.log_message.is_some() if breakpoint.log_message.is_some()
&& !debugger && !debugger
.caps .caps
.clone() .as_ref()
.unwrap() .unwrap()
.supports_log_points .supports_log_points
.unwrap_or_default() .unwrap_or_default()
{ {
cx.editor.set_error( bail!("Can't edit breakpoint: debugger does not support logpoints")
"Can't edit breakpoint: debugger does not support logpoints".to_string(),
);
return;
} }
let breakpoints = debugger.breakpoints.entry(path.clone()).or_default(); let breakpoints = debugger.breakpoints.entry(path.clone()).or_default();
@ -2024,11 +2024,11 @@ mod cmd {
let request = debugger.set_breakpoints(path, breakpoints); let request = debugger.set_breakpoints(path, breakpoints);
if let Err(e) = block_on(request) { if let Err(e) = block_on(request) {
cx.editor bail!("Failed to set breakpoints: {:?}", e)
.set_error(format!("Failed to set breakpoints: {:?}", e));
} }
} }
} }
Ok(())
} }
fn debug_start( fn debug_start(
@ -2076,8 +2076,7 @@ mod cmd {
Some(condition) Some(condition)
}; };
edit_breakpoint_impl(cx, condition, None); edit_breakpoint_impl(cx, condition, None)
Ok(())
} }
fn debug_set_logpoint( fn debug_set_logpoint(
@ -2092,8 +2091,7 @@ mod cmd {
Some(log_message) Some(log_message)
}; };
edit_breakpoint_impl(cx, None, log_message); edit_breakpoint_impl(cx, None, log_message)
Ok(())
} }
pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[

@ -1,5 +1,6 @@
use super::{Context, Editor}; use super::{align_view, Align, Context, Editor};
use crate::ui::Picker; use crate::ui::Picker;
use helix_core::Selection;
use helix_dap::Client; use helix_dap::Client;
use helix_lsp::block_on; use helix_lsp::block_on;
@ -8,6 +9,68 @@ use tokio_stream::wrappers::UnboundedReceiverStream;
use std::collections::HashMap; use std::collections::HashMap;
// general utils:
pub fn dap_pos_to_pos(doc: &helix_core::Rope, line: usize, column: usize) -> Option<usize> {
// 1-indexing to 0 indexing
let line = doc.try_line_to_char(line - 1).ok()?;
let pos = line + column;
// TODO: this is probably utf-16 offsets
Some(pos)
}
pub async fn select_thread_id(editor: &mut Editor, thread_id: usize, force: bool) {
let debugger = match &mut editor.debugger {
Some(debugger) => debugger,
None => return,
};
if !force && debugger.thread_id.is_some() {
return;
}
debugger.thread_id = Some(thread_id);
// fetch stack trace
// TODO: handle requesting more total frames
let (frames, _) = debugger.stack_trace(thread_id).await.unwrap();
debugger.stack_frames.insert(thread_id, frames);
debugger.active_frame = Some(0); // TODO: check how to determine this
let frame = debugger.stack_frames[&thread_id].get(0).cloned();
if let Some(frame) = &frame {
jump_to_stack_frame(editor, frame);
}
}
pub fn jump_to_stack_frame(editor: &mut Editor, frame: &helix_dap::StackFrame) {
let path = if let Some(helix_dap::Source {
path: Some(ref path),
..
}) = frame.source
{
path.clone()
} else {
return;
};
editor
.open(path, helix_view::editor::Action::Replace)
.unwrap(); // TODO: there should be no unwrapping!
let (view, doc) = current!(editor);
let text_end = doc.text().len_chars().saturating_sub(1);
let start = dap_pos_to_pos(doc.text(), frame.line, frame.column).unwrap_or(0);
let end = frame
.end_line
.and_then(|end_line| dap_pos_to_pos(doc.text(), end_line, frame.end_column.unwrap_or(0)))
.unwrap_or(start);
let selection = Selection::single(start.min(text_end), end.min(text_end));
doc.set_selection(view.id, selection);
align_view(doc, view, Align::Center);
}
// DAP // DAP
pub fn dap_start_impl( pub fn dap_start_impl(
editor: &mut Editor, editor: &mut Editor,
@ -222,13 +285,18 @@ pub fn dap_continue(cx: &mut Context) {
return; return;
} }
let request = debugger.continue_thread(debugger.thread_id.unwrap()); if let Some(thread_id) = debugger.thread_id {
if let Err(e) = block_on(request) { let request = debugger.continue_thread(debugger.thread_id.unwrap());
cx.editor.set_error(format!("Failed to continue: {:?}", e)); if let Err(e) = block_on(request) {
return; cx.editor.set_error(format!("Failed to continue: {:?}", e));
return;
}
debugger.is_running = true;
debugger.stack_frames.remove(&thread_id);
} else {
cx.editor
.set_error("Currently active thread is not stopped. Switch the thread.".into());
} }
debugger.is_running = true;
debugger.stack_pointer = None;
} }
} }
@ -299,13 +367,16 @@ pub fn dap_variables(cx: &mut Context) {
.set_status("Cannot access variables while target is running".to_owned()); .set_status("Cannot access variables while target is running".to_owned());
return; return;
} }
if debugger.stack_pointer.is_none() { let (frame, thread_id) = match (debugger.active_frame, debugger.thread_id) {
cx.editor (Some(frame), Some(thread_id)) => (frame, thread_id),
.set_status("Cannot find current stack pointer to access variables".to_owned()); _ => {
return; cx.editor
} .set_status("Cannot find current stack frame to access variables".to_owned());
return;
}
};
let frame_id = debugger.stack_pointer.clone().unwrap().id; let frame_id = debugger.stack_frames[&thread_id][frame].id;
let scopes = match block_on(debugger.scopes(frame_id)) { let scopes = match block_on(debugger.scopes(frame_id)) {
Ok(s) => s, Ok(s) => s,
Err(e) => { Err(e) => {
@ -366,10 +437,7 @@ pub fn dap_switch_thread(cx: &mut Context) {
threads, threads,
|thread| thread.name.clone().into(), |thread| thread.name.clone().into(),
|editor, thread, _action| { |editor, thread, _action| {
if let Some(debugger) = &mut editor.debugger { block_on(select_thread_id(editor, thread.id, true));
debugger.thread_id = Some(thread.id);
// TODO: probably need to refetch stack frames?
}
}, },
); );
cx.push_layer(Box::new(picker)) cx.push_layer(Box::new(picker))

@ -446,12 +446,16 @@ impl EditorView {
.map(|range| range.cursor_line(text)) .map(|range| range.cursor_line(text))
.collect(); .collect();
let mut breakpoints: Option<Vec<SourceBreakpoint>> = None; let mut breakpoints: Option<&Vec<SourceBreakpoint>> = None;
let mut stack_pointer: Option<StackFrame> = None; let mut stack_frame: Option<&StackFrame> = None;
if let Some(debugger) = debugger { if let Some(debugger) = debugger {
if let Some(path) = doc.path() { if let Some(path) = doc.path() {
breakpoints = debugger.breakpoints.get(path).cloned(); breakpoints = debugger.breakpoints.get(path);
stack_pointer = debugger.stack_pointer.clone()
if let (Some(frame), Some(thread_id)) = (debugger.active_frame, debugger.thread_id)
{
stack_frame = debugger.stack_frames[&thread_id].get(frame); // TODO: drop the clone..
};
} }
} }
@ -477,23 +481,24 @@ impl EditorView {
if let Some(bps) = breakpoints.as_ref() { if let Some(bps) = breakpoints.as_ref() {
if let Some(breakpoint) = bps.iter().find(|breakpoint| breakpoint.line - 1 == line) if let Some(breakpoint) = bps.iter().find(|breakpoint| breakpoint.line - 1 == line)
{ {
if breakpoint.condition.is_some() { let style = if breakpoint.condition.is_some() {
surface.set_stringn(viewport.x, viewport.y + i as u16, "▲", 1, error); error
} else if breakpoint.log_message.is_some() { } else if breakpoint.log_message.is_some() {
surface.set_stringn(viewport.x, viewport.y + i as u16, "▲", 1, info); info
} else { } else {
surface.set_stringn(viewport.x, viewport.y + i as u16, "▲", 1, warning); warning
} };
surface.set_stringn(viewport.x, viewport.y + i as u16, "▲", 1, style);
} }
} }
if let Some(sp) = stack_pointer.as_ref() { if let Some(frame) = stack_frame {
if let Some(src) = sp.source.as_ref() { if let Some(src) = &frame.source {
if doc if doc
.path() .path()
.map(|path| src.path == Some(path.clone())) .map(|path| src.path.as_ref() == Some(path))
.unwrap_or(false) .unwrap_or(false)
&& sp.line - 1 == line && frame.line - 1 == line
{ {
surface.set_style( surface.set_style(
Rect::new(viewport.x, viewport.y + i as u16, 6, 1), Rect::new(viewport.x, viewport.y + i as u16, 6, 1),

Loading…
Cancel
Save