on master help sign in
dap: refactor frame handling
2c7b754Blaž Hrastnik
helix-dap/src/client.rs+7−4
@@ -29,10 +29,12 @@ pub struct Client {
29 pub caps: Option<DebuggerCapabilities>,
29 pub caps: Option<DebuggerCapabilities>,
30 //
30 //
31 pub breakpoints: HashMap<PathBuf, Vec<SourceBreakpoint>>,
31 pub breakpoints: HashMap<PathBuf, Vec<SourceBreakpoint>>,
32− // TODO: multiple threads support
32+ // thread_id -> frames
33− pub stack_pointer: Option<StackFrame>,
33+ pub stack_frames: HashMap<usize, Vec<StackFrame>>,
34 pub thread_id: Option<usize>,
34 pub thread_id: Option<usize>,
35+ /// Currently active frame for the current thread.
36+ pub active_frame: Option<usize>,
35− pub is_running: bool,
37+ pub is_running: bool, // TODO: track is_running per thread
36}
38}
37
39
38impl Client {
40impl Client {
@@ -76,8 +78,9 @@ impl Client {
76 caps: None,
78 caps: None,
77 //
79 //
78 breakpoints: HashMap::new(),
80 breakpoints: HashMap::new(),
79− stack_pointer: None,
81+ stack_frames: HashMap::new(),
80 thread_id: None,
82 thread_id: None,
83+ active_frame: None,
81 is_running: false,
84 is_running: false,
82 };
85 };
83
86
helix-term/src/application.rs+6−60
@@ -3,14 +3,7 @@ use helix_dap::Payload;
3use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap};
3use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap};
4use helix_view::{theme, Editor};
4use helix_view::{theme, Editor};
5
5
6−use crate::{
7− args::Args,
8− commands::{align_view, Align},
9− compositor::Compositor,
6+use crate::{args::Args, compositor::Compositor, config::Config, job::Jobs, ui};
10− config::Config,
11− job::Jobs,
12− ui,
13−};
14
7
15use log::error;
8use log::error;
16use std::{
9use std::{
@@ -263,6 +256,7 @@ impl Application {
263 }
256 }
264
257
265 pub async fn handle_debugger_message(&mut self, payload: helix_dap::Payload) {
258 pub async fn handle_debugger_message(&mut self, payload: helix_dap::Payload) {
259+ use crate::commands::dap::select_thread_id;
266 use helix_dap::{events, Event};
260 use helix_dap::{events, Event};
267 let mut debugger = match self.editor.debugger.as_mut() {
261 let mut debugger = match self.editor.debugger.as_mut() {
268 Some(debugger) => debugger,
262 Some(debugger) => debugger,
@@ -281,12 +275,9 @@ impl Application {
281 }) => {
275 }) => {
282 debugger.is_running = false;
276 debugger.is_running = false;
283
277
284− // whichever thread stops is made "current".
278+ // whichever thread stops is made "current" (if no previously selected thread).
285− debugger.thread_id = thread_id;
286−
287 if let Some(thread_id) = thread_id {
279 if let Some(thread_id) = thread_id {
288− let (bt, _) = debugger.stack_trace(thread_id).await.unwrap();
280+ select_thread_id(&mut self.editor, thread_id, false).await;
289− debugger.stack_pointer = bt.get(0).cloned();
290 }
281 }
291
282
292 let scope = match thread_id {
283 let scope = match thread_id {
@@ -305,52 +296,6 @@ impl Application {
305 status.push_str(" (all threads stopped)");
296 status.push_str(" (all threads stopped)");
306 }
297 }
307
298
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− }
354 self.editor.set_status(status);
299 self.editor.set_status(status);
355 }
300 }
356 Event::Output(events::Output {
301 Event::Output(events::Output {
@@ -373,8 +318,9 @@ impl Application {
373 .set_status("Debugged application started".to_owned());
318 .set_status("Debugged application started".to_owned());
374 }
319 }
375 Event::Continued(_) => {
320 Event::Continued(_) => {
376− debugger.stack_pointer = None;
377 debugger.is_running = true;
321 debugger.is_running = true;
322+ debugger.active_frame = None;
323+ debugger.thread_id = None;
378 }
324 }
379 ev => {
325 ev => {
380 log::warn!("Unhandled event {:?}", ev);
326 log::warn!("Unhandled event {:?}", ev);
helix-term/src/commands.rs+22−24
@@ -1,4 +1,4 @@
1−mod dap;
1+pub(crate) mod dap;
2
2
3pub use dap::*;
3pub use dap::*;
4
4
@@ -1955,8 +1955,17 @@ mod cmd {
1955 ) -> anyhow::Result<()> {
1955 ) -> anyhow::Result<()> {
1956 use helix_lsp::block_on;
1956 use helix_lsp::block_on;
1957 if let Some(debugger) = cx.editor.debugger.as_mut() {
1957 if let Some(debugger) = cx.editor.debugger.as_mut() {
1958− let id = debugger.stack_pointer.clone().map(|x| x.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;
1959− let response = block_on(debugger.eval(args.join(" "), id))?;
1968+ let response = block_on(debugger.eval(args.join(" "), Some(frame_id)))?;
1960 cx.editor.set_status(response.result);
1969 cx.editor.set_status(response.result);
1961 }
1970 }
1962 Ok(())
1971 Ok(())
@@ -1966,7 +1975,7 @@ mod cmd {
1966 cx: &mut compositor::Context,
1975 cx: &mut compositor::Context,
1967 condition: Option<String>,
1976 condition: Option<String>,
1968 log_message: Option<String>,
1977 log_message: Option<String>,
1969− ) {
1978+ ) -> anyhow::Result<()> {
1970 use helix_lsp::block_on;
1979 use helix_lsp::block_on;
1971
1980
1972 let (view, doc) = current!(cx.editor);
1981 let (view, doc) = current!(cx.editor);
@@ -1981,38 +1990,29 @@ mod cmd {
1981 let path = match doc.path() {
1990 let path = match doc.path() {
1982 Some(path) => path.to_path_buf(),
1991 Some(path) => path.to_path_buf(),
1983 None => {
1992 None => {
1984− cx.editor
1985− .set_error("Can't edit breakpoint: document has no path".to_string());
1993+ bail!("Can't edit breakpoint: document has no path")
1986− return;
1987 }
1994 }
1988 };
1995 };
1989 if let Some(debugger) = &mut cx.editor.debugger {
1996 if let Some(debugger) = &mut cx.editor.debugger {
1990 if breakpoint.condition.is_some()
1997 if breakpoint.condition.is_some()
1991 && !debugger
1998 && !debugger
1992 .caps
1999 .caps
1993− .clone()
2000+ .as_ref()
1994 .unwrap()
2001 .unwrap()
1995 .supports_conditional_breakpoints
2002 .supports_conditional_breakpoints
1996 .unwrap_or_default()
2003 .unwrap_or_default()
1997 {
2004 {
1998− cx.editor.set_error(
1999− "Can't edit breakpoint: debugger does not support conditional breakpoints"
2005+ bail!("Can't edit breakpoint: debugger does not support conditional breakpoints")
2000− .to_string(),
2001− );
2002− return;
2003 }
2006 }
2004 if breakpoint.log_message.is_some()
2007 if breakpoint.log_message.is_some()
2005 && !debugger
2008 && !debugger
2006 .caps
2009 .caps
2007− .clone()
2010+ .as_ref()
2008 .unwrap()
2011 .unwrap()
2009 .supports_log_points
2012 .supports_log_points
2010 .unwrap_or_default()
2013 .unwrap_or_default()
2011 {
2014 {
2012− cx.editor.set_error(
2013− "Can't edit breakpoint: debugger does not support logpoints".to_string(),
2015+ bail!("Can't edit breakpoint: debugger does not support logpoints")
2014− );
2015− return;
2016 }
2016 }
2017
2017
2018 let breakpoints = debugger.breakpoints.entry(path.clone()).or_default();
2018 let breakpoints = debugger.breakpoints.entry(path.clone()).or_default();
@@ -2024,11 +2024,11 @@ mod cmd {
2024
2024
2025 let request = debugger.set_breakpoints(path, breakpoints);
2025 let request = debugger.set_breakpoints(path, breakpoints);
2026 if let Err(e) = block_on(request) {
2026 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)
2029 }
2028 }
2030 }
2029 }
2031 }
2030 }
2031+ Ok(())
2032 }
2032 }
2033
2033
2034 fn debug_start(
2034 fn debug_start(
@@ -2076,8 +2076,7 @@ mod cmd {
2076 Some(condition)
2076 Some(condition)
2077 };
2077 };
2078
2078
2079− edit_breakpoint_impl(cx, condition, None);
2079+ edit_breakpoint_impl(cx, condition, None)
2080− Ok(())
2081 }
2080 }
2082
2081
2083 fn debug_set_logpoint(
2082 fn debug_set_logpoint(
@@ -2092,8 +2091,7 @@ mod cmd {
2092 Some(log_message)
2091 Some(log_message)
2093 };
2092 };
2094
2093
2095− edit_breakpoint_impl(cx, None, log_message);
2094+ edit_breakpoint_impl(cx, None, log_message)
2096− Ok(())
2097 }
2095 }
2098
2096
2099 pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
2097 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};
2use crate::ui::Picker;
2use crate::ui::Picker;
3+use helix_core::Selection;
3use helix_dap::Client;
4use helix_dap::Client;
4use helix_lsp::block_on;
5use helix_lsp::block_on;
5
6
@@ -8,6 +9,68 @@ use tokio_stream::wrappers::UnboundedReceiverStream;
8
9
9use std::collections::HashMap;
10use std::collections::HashMap;
10
11
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+
11// DAP
74// DAP
12pub fn dap_start_impl(
75pub fn dap_start_impl(
13 editor: &mut Editor,
76 editor: &mut Editor,
@@ -222,13 +285,18 @@ pub fn dap_continue(cx: &mut Context) {
222 return;
285 return;
223 }
286 }
224
287
288+ if let Some(thread_id) = debugger.thread_id {
225− let request = debugger.continue_thread(debugger.thread_id.unwrap());
289+ let request = debugger.continue_thread(debugger.thread_id.unwrap());
226− if let Err(e) = block_on(request) {
290+ if let Err(e) = block_on(request) {
227− cx.editor.set_error(format!("Failed to continue: {:?}", e));
291+ cx.editor.set_error(format!("Failed to continue: {:?}", e));
228− return;
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());
229 }
299 }
230− debugger.is_running = true;
231− debugger.stack_pointer = None;
232 }
300 }
233}
301}
234
302
@@ -299,13 +367,16 @@ pub fn dap_variables(cx: &mut Context) {
299 .set_status("Cannot access variables while target is running".to_owned());
367 .set_status("Cannot access variables while target is running".to_owned());
300 return;
368 return;
301 }
369 }
302− if debugger.stack_pointer.is_none() {
370+ let (frame, thread_id) = match (debugger.active_frame, debugger.thread_id) {
371+ (Some(frame), Some(thread_id)) => (frame, thread_id),
372+ _ => {
303− cx.editor
373+ cx.editor
304− .set_status("Cannot find current stack pointer to access variables".to_owned());
374+ .set_status("Cannot find current stack frame to access variables".to_owned());
305− return;
375+ return;
376+ }
306− }
377+ };
307
378
308− let frame_id = debugger.stack_pointer.clone().unwrap().id;
379+ let frame_id = debugger.stack_frames[&thread_id][frame].id;
309 let scopes = match block_on(debugger.scopes(frame_id)) {
380 let scopes = match block_on(debugger.scopes(frame_id)) {
310 Ok(s) => s,
381 Ok(s) => s,
311 Err(e) => {
382 Err(e) => {
@@ -366,10 +437,7 @@ pub fn dap_switch_thread(cx: &mut Context) {
366 threads,
437 threads,
367 |thread| thread.name.clone().into(),
438 |thread| thread.name.clone().into(),
368 |editor, thread, _action| {
439 |editor, thread, _action| {
369− if let Some(debugger) = &mut editor.debugger {
440+ block_on(select_thread_id(editor, thread.id, true));
370− debugger.thread_id = Some(thread.id);
371− // TODO: probably need to refetch stack frames?
372− }
373 },
441 },
374 );
442 );
375 cx.push_layer(Box::new(picker))
443 cx.push_layer(Box::new(picker))
helix-term/src/ui/editor.rs+18−13
@@ -446,12 +446,16 @@ impl EditorView {
446 .map(|range| range.cursor_line(text))
446 .map(|range| range.cursor_line(text))
447 .collect();
447 .collect();
448
448
449− let mut breakpoints: Option<Vec<SourceBreakpoint>> = None;
449+ let mut breakpoints: Option<&Vec<SourceBreakpoint>> = None;
450− let mut stack_pointer: Option<StackFrame> = None;
450+ let mut stack_frame: Option<&StackFrame> = None;
451 if let Some(debugger) = debugger {
451 if let Some(debugger) = debugger {
452 if let Some(path) = doc.path() {
452 if let Some(path) = doc.path() {
453− breakpoints = debugger.breakpoints.get(path).cloned();
453+ breakpoints = debugger.breakpoints.get(path);
454− stack_pointer = debugger.stack_pointer.clone()
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+ };
455 }
459 }
456 }
460 }
457
461
@@ -477,23 +481,24 @@ impl EditorView {
477 if let Some(bps) = breakpoints.as_ref() {
481 if let Some(bps) = breakpoints.as_ref() {
478 if let Some(breakpoint) = bps.iter().find(|breakpoint| breakpoint.line - 1 == line)
482 if let Some(breakpoint) = bps.iter().find(|breakpoint| breakpoint.line - 1 == line)
479 {
483 {
480− if breakpoint.condition.is_some() {
484+ let style = if breakpoint.condition.is_some() {
481− surface.set_stringn(viewport.x, viewport.y + i as u16, "▲", 1, error);
485+ error
482 } else if breakpoint.log_message.is_some() {
486 } else if breakpoint.log_message.is_some() {
483− surface.set_stringn(viewport.x, viewport.y + i as u16, "▲", 1, info);
487+ info
484 } else {
488 } else {
489+ warning
490+ };
485− surface.set_stringn(viewport.x, viewport.y + i as u16, "▲", 1, warning);
491+ surface.set_stringn(viewport.x, viewport.y + i as u16, "▲", 1, style);
486− }
487 }
492 }
488 }
493 }
489
494
490− if let Some(sp) = stack_pointer.as_ref() {
495+ if let Some(frame) = stack_frame {
491− if let Some(src) = sp.source.as_ref() {
496+ if let Some(src) = &frame.source {
492 if doc
497 if doc
493 .path()
498 .path()
494− .map(|path| src.path == Some(path.clone()))
499+ .map(|path| src.path.as_ref() == Some(path))
495 .unwrap_or(false)
500 .unwrap_or(false)
496− && sp.line - 1 == line
501+ && frame.line - 1 == line
497 {
502 {
498 surface.set_style(
503 surface.set_style(
499 Rect::new(viewport.x, viewport.y + i as u16, 6, 1),
504 Rect::new(viewport.x, viewport.y + i as u16, 6, 1),