on master help sign in
dap: refactor frame handling
commit2c7b75475f9534ca330cccea4778a3878fed6b4c authorBlaž Hrastnik <blaz@mxxn.io> dateMon 30 Aug 2021, 10:58 (5 years ago) parent986828e
5 files changed+138−118
helix-dap/src/client.rs+7−4 helix-term/src/application.rs+6−60 helix-term/src/commands.rs+22−24 helix-term/src/commands/dap.rs+85−17 helix-term/src/ui/editor.rs+18−13
helix-dap/src/client.rs+7−4
@@ -29,10 +29,12 @@ pub struct Client {
2929 pub caps: Option<DebuggerCapabilities>,
3030 //
3131 pub breakpoints: HashMap<PathBuf, Vec<SourceBreakpoint>>,
32− // TODO: multiple threads support
33− pub stack_pointer: Option<StackFrame>,
32+ // thread_id -> frames
33+ pub stack_frames: HashMap<usize, Vec<StackFrame>>,
3434 pub thread_id: Option<usize>,
35− pub is_running: bool,
35+ /// Currently active frame for the current thread.
36+ pub active_frame: Option<usize>,
37+ pub is_running: bool, // TODO: track is_running per thread
3638}
3739
3840impl Client {
@@ -76,8 +78,9 @@ impl Client {
7678 caps: None,
7779 //
7880 breakpoints: HashMap::new(),
79− stack_pointer: None,
81+ stack_frames: HashMap::new(),
8082 thread_id: None,
83+ active_frame: None,
8184 is_running: false,
8285 };
8386
helix-term/src/application.rs+6−60
@@ -3,14 +3,7 @@ use helix_dap::Payload;
33use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap};
44use helix_view::{theme, Editor};
55
6−use crate::{
7− args::Args,
8− commands::{align_view, Align},
9− compositor::Compositor,
10− config::Config,
11− job::Jobs,
12− ui,
13−};
6+use crate::{args::Args, compositor::Compositor, config::Config, job::Jobs, ui};
147
158use log::error;
169use std::{
@@ -263,6 +256,7 @@ impl Application {
263256 }
264257
265258 pub async fn handle_debugger_message(&mut self, payload: helix_dap::Payload) {
259+ use crate::commands::dap::select_thread_id;
266260 use helix_dap::{events, Event};
267261 let mut debugger = match self.editor.debugger.as_mut() {
268262 Some(debugger) => debugger,
@@ -281,12 +275,9 @@ impl Application {
281275 }) => {
282276 debugger.is_running = false;
283277
284− // whichever thread stops is made "current".
285− debugger.thread_id = thread_id;
286−
278+ // whichever thread stops is made "current" (if no previously selected thread).
287279 if let Some(thread_id) = thread_id {
288− let (bt, _) = debugger.stack_trace(thread_id).await.unwrap();
289− debugger.stack_pointer = bt.get(0).cloned();
280+ select_thread_id(&mut self.editor, thread_id, false).await;
290281 }
291282
292283 let scope = match thread_id {
@@ -305,52 +296,6 @@ impl Application {
305296 status.push_str(" (all threads stopped)");
306297 }
307298
308− if let Some(helix_dap::StackFrame {
309− source:
310− Some(helix_dap::Source {
311− path: Some(ref path),
312− ..
313− }),
314− line,
315− column,
316− end_line,
317− end_column,
318− ..
319− }) = debugger.stack_pointer
320− {
321− let path = path.clone();
322− self.editor
323− .open(path, helix_view::editor::Action::Replace)
324− .unwrap(); // TODO: there should be no unwrapping!
325−
326− let (view, doc) = current!(self.editor);
327−
328− fn dap_pos_to_pos(
329− doc: &helix_core::Rope,
330− line: usize,
331− column: usize,
332− ) -> Option<usize> {
333− // 1-indexing to 0 indexing
334− let line = doc.try_line_to_char(line - 1).ok()?;
335− let pos = line + column;
336− // TODO: this is probably utf-16 offsets
337− Some(pos)
338− }
339−
340− let text_end = doc.text().len_chars().saturating_sub(1);
341− let start = dap_pos_to_pos(doc.text(), line, column).unwrap_or(0);
342−
343− let selection = if let Some(end_line) = end_line {
344− let end = dap_pos_to_pos(doc.text(), end_line, end_column.unwrap_or(0))
345− .unwrap_or(0);
346−
347− Selection::single(start.min(text_end), end.min(text_end))
348− } else {
349− Selection::point(start.min(text_end))
350− };
351− doc.set_selection(view.id, selection);
352− align_view(doc, view, Align::Center);
353− }
354299 self.editor.set_status(status);
355300 }
356301 Event::Output(events::Output {
@@ -373,8 +318,9 @@ impl Application {
373318 .set_status("Debugged application started".to_owned());
374319 }
375320 Event::Continued(_) => {
376− debugger.stack_pointer = None;
377321 debugger.is_running = true;
322+ debugger.active_frame = None;
323+ debugger.thread_id = None;
378324 }
379325 ev => {
380326 log::warn!("Unhandled event {:?}", ev);
helix-term/src/commands.rs+22−24
@@ -1,4 +1,4 @@
1−mod dap;
1+pub(crate) mod dap;
22
33pub use dap::*;
44
@@ -1955,8 +1955,17 @@ mod cmd {
19551955 ) -> anyhow::Result<()> {
19561956 use helix_lsp::block_on;
19571957 if let Some(debugger) = cx.editor.debugger.as_mut() {
1958− let id = debugger.stack_pointer.clone().map(|x| x.id);
1959− let response = block_on(debugger.eval(args.join(" "), id))?;
1958+ let (frame, thread_id) = match (debugger.active_frame, debugger.thread_id) {
1959+ (Some(frame), Some(thread_id)) => (frame, thread_id),
1960+ _ => {
1961+ bail!("Cannot find current stack frame to access variables")
1962+ }
1963+ };
1964+
1965+ // TODO: support no frame_id
1966+
1967+ let frame_id = debugger.stack_frames[&thread_id][frame].id;
1968+ let response = block_on(debugger.eval(args.join(" "), Some(frame_id)))?;
19601969 cx.editor.set_status(response.result);
19611970 }
19621971 Ok(())
@@ -1966,7 +1975,7 @@ mod cmd {
19661975 cx: &mut compositor::Context,
19671976 condition: Option<String>,
19681977 log_message: Option<String>,
1969− ) {
1978+ ) -> anyhow::Result<()> {
19701979 use helix_lsp::block_on;
19711980
19721981 let (view, doc) = current!(cx.editor);
@@ -1981,38 +1990,29 @@ mod cmd {
19811990 let path = match doc.path() {
19821991 Some(path) => path.to_path_buf(),
19831992 None => {
1984− cx.editor
1985− .set_error("Can't edit breakpoint: document has no path".to_string());
1986− return;
1993+ bail!("Can't edit breakpoint: document has no path")
19871994 }
19881995 };
19891996 if let Some(debugger) = &mut cx.editor.debugger {
19901997 if breakpoint.condition.is_some()
19911998 && !debugger
19921999 .caps
1993− .clone()
2000+ .as_ref()
19942001 .unwrap()
19952002 .supports_conditional_breakpoints
19962003 .unwrap_or_default()
19972004 {
1998− cx.editor.set_error(
1999− "Can't edit breakpoint: debugger does not support conditional breakpoints"
2000− .to_string(),
2001− );
2002− return;
2005+ bail!("Can't edit breakpoint: debugger does not support conditional breakpoints")
20032006 }
20042007 if breakpoint.log_message.is_some()
20052008 && !debugger
20062009 .caps
2007− .clone()
2010+ .as_ref()
20082011 .unwrap()
20092012 .supports_log_points
20102013 .unwrap_or_default()
20112014 {
2012− cx.editor.set_error(
2013− "Can't edit breakpoint: debugger does not support logpoints".to_string(),
2014− );
2015− return;
2015+ bail!("Can't edit breakpoint: debugger does not support logpoints")
20162016 }
20172017
20182018 let breakpoints = debugger.breakpoints.entry(path.clone()).or_default();
@@ -2024,11 +2024,11 @@ mod cmd {
20242024
20252025 let request = debugger.set_breakpoints(path, breakpoints);
20262026 if let Err(e) = block_on(request) {
2027− cx.editor
2028− .set_error(format!("Failed to set breakpoints: {:?}", e));
2027+ bail!("Failed to set breakpoints: {:?}", e)
20292028 }
20302029 }
20312030 }
2031+ Ok(())
20322032 }
20332033
20342034 fn debug_start(
@@ -2076,8 +2076,7 @@ mod cmd {
20762076 Some(condition)
20772077 };
20782078
2079− edit_breakpoint_impl(cx, condition, None);
2080− Ok(())
2079+ edit_breakpoint_impl(cx, condition, None)
20812080 }
20822081
20832082 fn debug_set_logpoint(
@@ -2092,8 +2091,7 @@ mod cmd {
20922091 Some(log_message)
20932092 };
20942093
2095− edit_breakpoint_impl(cx, None, log_message);
2096− Ok(())
2094+ edit_breakpoint_impl(cx, None, log_message)
20972095 }
20982096
20992097 pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
helix-term/src/commands/dap.rs+85−17
@@ -1,5 +1,6 @@
1−use super::{Context, Editor};
1+use super::{align_view, Align, Context, Editor};
22use crate::ui::Picker;
3+use helix_core::Selection;
34use helix_dap::Client;
45use helix_lsp::block_on;
56
@@ -8,6 +9,68 @@ use tokio_stream::wrappers::UnboundedReceiverStream;
89
910use std::collections::HashMap;
1011
12+// general utils:
13+pub fn dap_pos_to_pos(doc: &helix_core::Rope, line: usize, column: usize) -> Option<usize> {
14+ // 1-indexing to 0 indexing
15+ let line = doc.try_line_to_char(line - 1).ok()?;
16+ let pos = line + column;
17+ // TODO: this is probably utf-16 offsets
18+ Some(pos)
19+}
20+
21+pub async fn select_thread_id(editor: &mut Editor, thread_id: usize, force: bool) {
22+ let debugger = match &mut editor.debugger {
23+ Some(debugger) => debugger,
24+ None => return,
25+ };
26+
27+ if !force && debugger.thread_id.is_some() {
28+ return;
29+ }
30+
31+ debugger.thread_id = Some(thread_id);
32+
33+ // fetch stack trace
34+ // TODO: handle requesting more total frames
35+ let (frames, _) = debugger.stack_trace(thread_id).await.unwrap();
36+ debugger.stack_frames.insert(thread_id, frames);
37+ debugger.active_frame = Some(0); // TODO: check how to determine this
38+
39+ let frame = debugger.stack_frames[&thread_id].get(0).cloned();
40+ if let Some(frame) = &frame {
41+ jump_to_stack_frame(editor, frame);
42+ }
43+}
44+
45+pub fn jump_to_stack_frame(editor: &mut Editor, frame: &helix_dap::StackFrame) {
46+ let path = if let Some(helix_dap::Source {
47+ path: Some(ref path),
48+ ..
49+ }) = frame.source
50+ {
51+ path.clone()
52+ } else {
53+ return;
54+ };
55+
56+ editor
57+ .open(path, helix_view::editor::Action::Replace)
58+ .unwrap(); // TODO: there should be no unwrapping!
59+
60+ let (view, doc) = current!(editor);
61+
62+ let text_end = doc.text().len_chars().saturating_sub(1);
63+ let start = dap_pos_to_pos(doc.text(), frame.line, frame.column).unwrap_or(0);
64+ let end = frame
65+ .end_line
66+ .and_then(|end_line| dap_pos_to_pos(doc.text(), end_line, frame.end_column.unwrap_or(0)))
67+ .unwrap_or(start);
68+
69+ let selection = Selection::single(start.min(text_end), end.min(text_end));
70+ doc.set_selection(view.id, selection);
71+ align_view(doc, view, Align::Center);
72+}
73+
1174// DAP
1275pub fn dap_start_impl(
1376 editor: &mut Editor,
@@ -222,13 +285,18 @@ pub fn dap_continue(cx: &mut Context) {
222285 return;
223286 }
224287
225− let request = debugger.continue_thread(debugger.thread_id.unwrap());
226− if let Err(e) = block_on(request) {
227− cx.editor.set_error(format!("Failed to continue: {:?}", e));
228− return;
288+ if let Some(thread_id) = debugger.thread_id {
289+ let request = debugger.continue_thread(debugger.thread_id.unwrap());
290+ if let Err(e) = block_on(request) {
291+ cx.editor.set_error(format!("Failed to continue: {:?}", e));
292+ return;
293+ }
294+ debugger.is_running = true;
295+ debugger.stack_frames.remove(&thread_id);
296+ } else {
297+ cx.editor
298+ .set_error("Currently active thread is not stopped. Switch the thread.".into());
229299 }
230− debugger.is_running = true;
231− debugger.stack_pointer = None;
232300 }
233301}
234302
@@ -299,13 +367,16 @@ pub fn dap_variables(cx: &mut Context) {
299367 .set_status("Cannot access variables while target is running".to_owned());
300368 return;
301369 }
302− if debugger.stack_pointer.is_none() {
303− cx.editor
304− .set_status("Cannot find current stack pointer to access variables".to_owned());
305− return;
306− }
370+ let (frame, thread_id) = match (debugger.active_frame, debugger.thread_id) {
371+ (Some(frame), Some(thread_id)) => (frame, thread_id),
372+ _ => {
373+ cx.editor
374+ .set_status("Cannot find current stack frame to access variables".to_owned());
375+ return;
376+ }
377+ };
307378
308− let frame_id = debugger.stack_pointer.clone().unwrap().id;
379+ let frame_id = debugger.stack_frames[&thread_id][frame].id;
309380 let scopes = match block_on(debugger.scopes(frame_id)) {
310381 Ok(s) => s,
311382 Err(e) => {
@@ -366,10 +437,7 @@ pub fn dap_switch_thread(cx: &mut Context) {
366437 threads,
367438 |thread| thread.name.clone().into(),
368439 |editor, thread, _action| {
369− if let Some(debugger) = &mut editor.debugger {
370− debugger.thread_id = Some(thread.id);
371− // TODO: probably need to refetch stack frames?
372− }
440+ block_on(select_thread_id(editor, thread.id, true));
373441 },
374442 );
375443 cx.push_layer(Box::new(picker))
helix-term/src/ui/editor.rs+18−13
@@ -446,12 +446,16 @@ impl EditorView {
446446 .map(|range| range.cursor_line(text))
447447 .collect();
448448
449− let mut breakpoints: Option<Vec<SourceBreakpoint>> = None;
450− let mut stack_pointer: Option<StackFrame> = None;
449+ let mut breakpoints: Option<&Vec<SourceBreakpoint>> = None;
450+ let mut stack_frame: Option<&StackFrame> = None;
451451 if let Some(debugger) = debugger {
452452 if let Some(path) = doc.path() {
453− breakpoints = debugger.breakpoints.get(path).cloned();
454− stack_pointer = debugger.stack_pointer.clone()
453+ breakpoints = debugger.breakpoints.get(path);
454+
455+ if let (Some(frame), Some(thread_id)) = (debugger.active_frame, debugger.thread_id)
456+ {
457+ stack_frame = debugger.stack_frames[&thread_id].get(frame); // TODO: drop the clone..
458+ };
455459 }
456460 }
457461
@@ -477,23 +481,24 @@ impl EditorView {
477481 if let Some(bps) = breakpoints.as_ref() {
478482 if let Some(breakpoint) = bps.iter().find(|breakpoint| breakpoint.line - 1 == line)
479483 {
480− if breakpoint.condition.is_some() {
481− surface.set_stringn(viewport.x, viewport.y + i as u16, "▲", 1, error);
484+ let style = if breakpoint.condition.is_some() {
485+ error
482486 } else if breakpoint.log_message.is_some() {
483− surface.set_stringn(viewport.x, viewport.y + i as u16, "▲", 1, info);
487+ info
484488 } else {
485− surface.set_stringn(viewport.x, viewport.y + i as u16, "▲", 1, warning);
486− }
489+ warning
490+ };
491+ surface.set_stringn(viewport.x, viewport.y + i as u16, "▲", 1, style);
487492 }
488493 }
489494
490− if let Some(sp) = stack_pointer.as_ref() {
491− if let Some(src) = sp.source.as_ref() {
495+ if let Some(frame) = stack_frame {
496+ if let Some(src) = &frame.source {
492497 if doc
493498 .path()
494− .map(|path| src.path == Some(path.clone()))
499+ .map(|path| src.path.as_ref() == Some(path))
495500 .unwrap_or(false)
496− && sp.line - 1 == line
501+ && frame.line - 1 == line
497502 {
498503 surface.set_style(
499504 Rect::new(viewport.x, viewport.y + i as u16, 6, 1),