on master help sign in
feat(debug): highlight current line (#5957)
d59b805Filip Dutescu
book/src/themes.md+3
@@ -278,8 +278,11 @@ These scopes are used for theming the editor interface:
278| `ui.cursor.primary.normal` | |
278| `ui.cursor.primary.normal` | |
279| `ui.cursor.primary.insert` | |
279| `ui.cursor.primary.insert` | |
280| `ui.cursor.primary.select` | |
280| `ui.cursor.primary.select` | |
281+| `ui.debug.breakpoint` | Breakpoint indicator, found in the gutter |
282+| `ui.debug.active` | Indicator for the line at which debugging execution is paused at, found in the gutter |
281| `ui.gutter` | Gutter |
283| `ui.gutter` | Gutter |
282| `ui.gutter.selected` | Gutter for the line the cursor is on |
284| `ui.gutter.selected` | Gutter for the line the cursor is on |
285+| `ui.highlight.frameline` | Line at which debugging execution is paused at |
283| `ui.linenr` | Line numbers |
286| `ui.linenr` | Line numbers |
284| `ui.linenr.selected` | Line number for the line the cursor is on |
287| `ui.linenr.selected` | Line number for the line the cursor is on |
285| `ui.statusline` | Statusline |
288| `ui.statusline` | Statusline |
helix-dap/src/client.rs+6
@@ -512,4 +512,10 @@ impl Client {
512
512
513 self.call::<requests::SetExceptionBreakpoints>(args)
513 self.call::<requests::SetExceptionBreakpoints>(args)
514 }
514 }
515+
516+ pub fn current_stack_frame(&self) -> Option<&StackFrame> {
517+ self.stack_frames
518+ .get(&self.thread_id?)?
519+ .get(self.active_frame?)
520+ }
515}
521}
helix-term/src/ui/editor.rs+18−34
@@ -93,40 +93,6 @@ impl EditorView {
93 let mut line_decorations: Vec<Box<dyn LineDecoration>> = Vec::new();
93 let mut line_decorations: Vec<Box<dyn LineDecoration>> = Vec::new();
94 let mut translated_positions: Vec<TranslatedPosition> = Vec::new();
94 let mut translated_positions: Vec<TranslatedPosition> = Vec::new();
95
95
96− // DAP: Highlight current stack frame position
97− let stack_frame = editor.debugger.as_ref().and_then(|debugger| {
98− if let (Some(frame), Some(thread_id)) = (debugger.active_frame, debugger.thread_id) {
99− debugger
100− .stack_frames
101− .get(&thread_id)
102− .and_then(|bt| bt.get(frame))
103− } else {
104− None
105− }
106− });
107− if let Some(frame) = stack_frame {
108− if doc.path().is_some()
109− && frame
110− .source
111− .as_ref()
112− .and_then(|source| source.path.as_ref())
113− == doc.path()
114− {
115− let line = frame.line - 1; // convert to 0-indexing
116− let style = theme.get("ui.highlight");
117− let line_decoration = move |renderer: &mut TextRenderer, pos: LinePos| {
118− if pos.doc_line != line {
119− return;
120− }
121− renderer
122− .surface
123− .set_style(Rect::new(area.x, pos.visual_line, area.width, 1), style);
124− };
125−
126− line_decorations.push(Box::new(line_decoration));
127− }
128− }
129−
130 if is_focused && config.cursorline {
96 if is_focused && config.cursorline {
131 line_decorations.push(Self::cursorline_decorator(doc, view, theme))
97 line_decorations.push(Self::cursorline_decorator(doc, view, theme))
132 }
98 }
@@ -135,6 +101,23 @@ impl EditorView {
135 Self::highlight_cursorcolumn(doc, view, surface, theme, inner, &text_annotations);
101 Self::highlight_cursorcolumn(doc, view, surface, theme, inner, &text_annotations);
136 }
102 }
137
103
104+ // Set DAP highlights, if needed.
105+ if let Some(frame) = editor.current_stack_frame() {
106+ let dap_line = frame.line.saturating_sub(1) as usize;
107+ let style = theme.get("ui.highlight.frameline");
108+ let line_decoration = move |renderer: &mut TextRenderer, pos: LinePos| {
109+ if pos.doc_line != dap_line {
110+ return;
111+ }
112+ renderer.surface.set_style(
113+ Rect::new(inner.x, inner.y + pos.visual_line, inner.width, 1),
114+ style,
115+ );
116+ };
117+
118+ line_decorations.push(Box::new(line_decoration));
119+ }
120+
138 let mut highlights =
121 let mut highlights =
139 Self::doc_syntax_highlights(doc, view.offset.anchor, inner.height, theme);
122 Self::doc_syntax_highlights(doc, view.offset.anchor, inner.height, theme);
140 let overlay_highlights = Self::overlay_syntax_highlights(
123 let overlay_highlights = Self::overlay_syntax_highlights(
@@ -422,6 +405,7 @@ impl EditorView {
422 let primary_selection_scope = theme
405 let primary_selection_scope = theme
423 .find_scope_index_exact("ui.selection.primary")
406 .find_scope_index_exact("ui.selection.primary")
424 .unwrap_or(selection_scope);
407 .unwrap_or(selection_scope);
408+
425 let base_cursor_scope = theme
409 let base_cursor_scope = theme
426 .find_scope_index_exact("ui.cursor")
410 .find_scope_index_exact("ui.cursor")
427 .unwrap_or(selection_scope);
411 .unwrap_or(selection_scope);
helix-view/src/editor.rs+7
@@ -10,6 +10,7 @@ use crate::{
10 view::ViewPosition,
10 view::ViewPosition,
11 Align, Document, DocumentId, View, ViewId,
11 Align, Document, DocumentId, View, ViewId,
12};
12};
13+use dap::StackFrame;
13use helix_vcs::DiffProviderRegistry;
14use helix_vcs::DiffProviderRegistry;
14
15
15use futures_util::stream::select_all::SelectAll;
16use futures_util::stream::select_all::SelectAll;
@@ -1652,6 +1653,12 @@ impl Editor {
1652 doc.restore_cursor = false;
1653 doc.restore_cursor = false;
1653 }
1654 }
1654 }
1655 }
1656+
1657+ pub fn current_stack_frame(&self) -> Option<&StackFrame> {
1658+ self.debugger
1659+ .as_ref()
1660+ .and_then(|debugger| debugger.current_stack_frame())
1661+ }
1655}
1662}
1656
1663
1657fn try_restore_indent(doc: &mut Document, view: &mut View) {
1664fn try_restore_indent(doc: &mut Document, view: &mut View) {
helix-view/src/gutter.rs+42−18
@@ -2,7 +2,7 @@ use std::fmt::Write;
2
2
3use crate::{
3use crate::{
4 editor::GutterType,
4 editor::GutterType,
5− graphics::{Color, Style, UnderlineStyle},
5+ graphics::{Style, UnderlineStyle},
6 Document, Editor, Theme, View,
6 Document, Editor, Theme, View,
7};
7};
8
8
@@ -245,9 +245,9 @@ pub fn breakpoints<'doc>(
245 theme: &Theme,
245 theme: &Theme,
246 _is_focused: bool,
246 _is_focused: bool,
247) -> GutterFn<'doc> {
247) -> GutterFn<'doc> {
248− let warning = theme.get("warning");
249 let error = theme.get("error");
248 let error = theme.get("error");
250 let info = theme.get("info");
249 let info = theme.get("info");
250+ let breakpoint_style = theme.get("ui.debug.breakpoint");
251
251
252 let breakpoints = doc.path().and_then(|path| editor.breakpoints.get(path));
252 let breakpoints = doc.path().and_then(|path| editor.breakpoints.get(path));
253
253
@@ -265,30 +265,52 @@ pub fn breakpoints<'doc>(
265 .iter()
265 .iter()
266 .find(|breakpoint| breakpoint.line == line)?;
266 .find(|breakpoint| breakpoint.line == line)?;
267
267
268− let mut style = if breakpoint.condition.is_some() && breakpoint.log_message.is_some() {
268+ let style = if breakpoint.condition.is_some() && breakpoint.log_message.is_some() {
269 error.underline_style(UnderlineStyle::Line)
269 error.underline_style(UnderlineStyle::Line)
270 } else if breakpoint.condition.is_some() {
270 } else if breakpoint.condition.is_some() {
271 error
271 error
272 } else if breakpoint.log_message.is_some() {
272 } else if breakpoint.log_message.is_some() {
273 info
273 info
274 } else {
274 } else {
275− warning
275+ breakpoint_style
276 };
276 };
277
277
278− if !breakpoint.verified {
278+ let sym = if breakpoint.verified { "●" } else { "◯" };
279− // Faded colors
279+ write!(out, "{}", sym).unwrap();
280− style = if let Some(Color::Rgb(r, g, b)) = style.fg {
280+ Some(style)
281− style.fg(Color::Rgb(
281+ },
282− ((r as f32) * 0.4).floor() as u8,
282+ )
283− ((g as f32) * 0.4).floor() as u8,
283+}
284− ((b as f32) * 0.4).floor() as u8,
284+
285− ))
285+fn execution_pause_indicator<'doc>(
286− } else {
286+ editor: &'doc Editor,
287− style.fg(Color::Gray)
287+ doc: &'doc Document,
288− }
288+ theme: &Theme,
289+ is_focused: bool,
290+) -> GutterFn<'doc> {
291+ let style = theme.get("ui.debug.active");
292+ let current_stack_frame = editor.current_stack_frame();
293+ let frame_line = current_stack_frame.map(|frame| frame.line - 1);
294+ let frame_source_path = current_stack_frame.map(|frame| {
295+ frame
296+ .source
297+ .as_ref()
298+ .and_then(|source| source.path.as_ref())
299+ });
300+ let should_display_for_current_doc =
301+ doc.path().is_some() && frame_source_path.unwrap_or(None) == doc.path();
302+
303+ Box::new(
304+ move |line: usize, _selected: bool, first_visual_line: bool, out: &mut String| {
305+ if !first_visual_line
306+ || !is_focused
307+ || line != frame_line?
308+ || !should_display_for_current_doc
309+ {
310+ return None;
289− };
311+ }
290
312
291− let sym = if breakpoint.verified { "▲" } else { "⊚" };
313+ let sym = "▶";
292 write!(out, "{}", sym).unwrap();
314 write!(out, "{}", sym).unwrap();
293 Some(style)
315 Some(style)
294 },
316 },
@@ -304,9 +326,11 @@ pub fn diagnostics_or_breakpoints<'doc>(
304) -> GutterFn<'doc> {
326) -> GutterFn<'doc> {
305 let mut diagnostics = diagnostic(editor, doc, view, theme, is_focused);
327 let mut diagnostics = diagnostic(editor, doc, view, theme, is_focused);
306 let mut breakpoints = breakpoints(editor, doc, view, theme, is_focused);
328 let mut breakpoints = breakpoints(editor, doc, view, theme, is_focused);
329+ let mut execution_pause_indicator = execution_pause_indicator(editor, doc, theme, is_focused);
307
330
308 Box::new(move |line, selected, first_visual_line: bool, out| {
331 Box::new(move |line, selected, first_visual_line: bool, out| {
332+ execution_pause_indicator(line, selected, first_visual_line, out)
309− breakpoints(line, selected, first_visual_line, out)
333+ .or_else(|| breakpoints(line, selected, first_visual_line, out))
310 .or_else(|| diagnostics(line, selected, first_visual_line, out))
334 .or_else(|| diagnostics(line, selected, first_visual_line, out))
311 })
335 })
312}
336}
runtime/themes/acme.toml+2
@@ -12,6 +12,8 @@
12"ui.virtual.ruler" = { bg = "acme_bar_bg" }
12"ui.virtual.ruler" = { bg = "acme_bar_bg" }
13"ui.cursor.match" = {bg="acme_bar_bg"}
13"ui.cursor.match" = {bg="acme_bar_bg"}
14"ui.cursor" = {bg="cursor", fg="white"}
14"ui.cursor" = {bg="cursor", fg="white"}
15+"ui.debug" = {fg="orange"}
16+"ui.highlight.frameline" = {bg="#da8581"}
15"string" = "red"
17"string" = "red"
16"comment" = "green"
18"comment" = "green"
17"ui.help" = {fg="black", bg="acme_bg"}
19"ui.help" = {fg="black", bg="acme_bg"}
runtime/themes/autumn.toml+2
@@ -26,6 +26,8 @@
26"ui.cursor.primary" = { fg = "my_white", modifiers = ["reversed"] }
26"ui.cursor.primary" = { fg = "my_white", modifiers = ["reversed"] }
27"ui.cursorline.primary" = { bg = "my_black" }
27"ui.cursorline.primary" = { bg = "my_black" }
28"ui.cursorline.secondary" = { bg = "my_black" }
28"ui.cursorline.secondary" = { bg = "my_black" }
29+"ui.highlight.frameline" = { bg = "#8b6904" }
30+"ui.debug" = { fg = "my_yellow1", bg = "my_gray0" }
29"ui.text" = "my_white"
31"ui.text" = "my_white"
30"operator" = "my_white"
32"operator" = "my_white"
31"ui.text.focus" = "my_white"
33"ui.text.focus" = "my_white"
runtime/themes/ayu_dark.toml+2
@@ -61,6 +61,8 @@
61"diagnostic.error"= { underline = { color = "red", style="curl"} }
61"diagnostic.error"= { underline = { color = "red", style="curl"} }
62"ui.bufferline" = { fg = "gray", bg = "background" }
62"ui.bufferline" = { fg = "gray", bg = "background" }
63"ui.bufferline.active" = { fg = "foreground", bg = "dark_gray" }
63"ui.bufferline.active" = { fg = "foreground", bg = "dark_gray" }
64+"ui.debug" = { fg = "orange", bg = "background" }
65+"ui.highlight.frameline" = { bg = "#0067a3" }
64
66
65"special" = "orange"
67"special" = "orange"
66
68
runtime/themes/ayu_light.toml+2
@@ -61,6 +61,8 @@
61"diagnostic.error"= { underline = { color = "red", style = "curl" } }
61"diagnostic.error"= { underline = { color = "red", style = "curl" } }
62"ui.bufferline" = { fg = "gray", bg = "background" }
62"ui.bufferline" = { fg = "gray", bg = "background" }
63"ui.bufferline.active" = { fg = "foreground", bg = "dark_gray" }
63"ui.bufferline.active" = { fg = "foreground", bg = "dark_gray" }
64+"ui.debug" = { fg = "orange", bg = "background" }
65+"ui.highlight.frameline" = { bg = "#cfe0f2" }
64
66
65"special" = "orange"
67"special" = "orange"
66
68
runtime/themes/ayu_mirage.toml+2
@@ -61,6 +61,8 @@
61"diagnostic.error"= { underline = { color = "red", style = "curl" } }
61"diagnostic.error"= { underline = { color = "red", style = "curl" } }
62"ui.bufferline" = { fg = "gray", bg = "background" }
62"ui.bufferline" = { fg = "gray", bg = "background" }
63"ui.bufferline.active" = { fg = "foreground", bg = "dark_gray" }
63"ui.bufferline.active" = { fg = "foreground", bg = "dark_gray" }
64+"ui.debug" = { fg = "orange", bg = "background" }
65+"ui.highlight.frameline" = { bg = "#0067a3" }
64
66
65"special" = "orange"
67"special" = "orange"
66
68
runtime/themes/dracula.toml+2
@@ -25,6 +25,8 @@
25"ui.cursor.primary" = { fg = "background", bg = "cyan", modifiers = ["dim"] }
25"ui.cursor.primary" = { fg = "background", bg = "cyan", modifiers = ["dim"] }
26"ui.cursorline.primary" = { bg = "background_dark" }
26"ui.cursorline.primary" = { bg = "background_dark" }
27"ui.help" = { fg = "foreground", bg = "background_dark" }
27"ui.help" = { fg = "foreground", bg = "background_dark" }
28+"ui.debug" = { fg = "red" }
29+"ui.highlight.frameline" = { fg = "black", bg = "red" }
28"ui.linenr" = { fg = "comment" }
30"ui.linenr" = { fg = "comment" }
29"ui.linenr.selected" = { fg = "foreground" }
31"ui.linenr.selected" = { fg = "foreground" }
30"ui.menu" = { fg = "foreground", bg = "background_dark" }
32"ui.menu" = { fg = "foreground", bg = "background_dark" }
runtime/themes/dracula_at_night.toml+2
@@ -25,6 +25,8 @@
25"ui.cursor.match" = { fg = "green", modifiers = ["underlined"] }
25"ui.cursor.match" = { fg = "green", modifiers = ["underlined"] }
26"ui.cursor.primary" = { fg = "background", bg = "cyan", modifiers = ["dim"] }
26"ui.cursor.primary" = { fg = "background", bg = "cyan", modifiers = ["dim"] }
27"ui.help" = { fg = "foreground", bg = "background_dark" }
27"ui.help" = { fg = "foreground", bg = "background_dark" }
28+"ui.debug" = { fg = "red" }
29+"ui.highlight.frameline" = { fg = "black", bg = "red" }
28"ui.linenr" = { fg = "comment" }
30"ui.linenr" = { fg = "comment" }
29"ui.linenr.selected" = { fg = "foreground" }
31"ui.linenr.selected" = { fg = "foreground" }
30"ui.menu" = { fg = "foreground", bg = "background_dark" }
32"ui.menu" = { fg = "foreground", bg = "background_dark" }
runtime/themes/onedark.toml+3
@@ -64,6 +64,7 @@
64"ui.cursorline.primary" = { bg = "light-black" }
64"ui.cursorline.primary" = { bg = "light-black" }
65
65
66"ui.highlight" = { bg = "gray" }
66"ui.highlight" = { bg = "gray" }
67+"ui.highlight.frameline" = { bg = "#97202a" }
67
68
68"ui.linenr" = { fg = "linenr" }
69"ui.linenr" = { fg = "linenr" }
69"ui.linenr.selected" = { fg = "white" }
70"ui.linenr.selected" = { fg = "white" }
@@ -84,6 +85,8 @@
84"ui.menu.selected" = { fg = "black", bg = "blue" }
85"ui.menu.selected" = { fg = "black", bg = "blue" }
85"ui.menu.scroll" = { fg = "white", bg = "light-gray" }
86"ui.menu.scroll" = { fg = "white", bg = "light-gray" }
86
87
88+"ui.debug" = { fg = "red" }
89+
87[palette]
90[palette]
88
91
89yellow = "#E5C07B"
92yellow = "#E5C07B"
runtime/themes/onedarker.toml+2
@@ -78,6 +78,8 @@
78"ui.text.focus" = { fg = "white", bg = "light-black", modifiers = ["bold"] }
78"ui.text.focus" = { fg = "white", bg = "light-black", modifiers = ["bold"] }
79
79
80"ui.help" = { fg = "white", bg = "gray" }
80"ui.help" = { fg = "white", bg = "gray" }
81+"ui.debug" = { fg = "red" }
82+"ui.highlight.frameline" = { bg = "#97202a" }
81"ui.popup" = { bg = "gray" }
83"ui.popup" = { bg = "gray" }
82"ui.window" = { fg = "gray" }
84"ui.window" = { fg = "gray" }
83"ui.menu" = { fg = "white", bg = "gray" }
85"ui.menu" = { fg = "white", bg = "gray" }
theme.toml+3−1
@@ -69,7 +69,9 @@ label = "honey"
69"ui.cursor" = { modifiers = ["reversed"] }
69"ui.cursor" = { modifiers = ["reversed"] }
70"ui.cursorline.primary" = { bg = "bossanova" }
70"ui.cursorline.primary" = { bg = "bossanova" }
71"ui.highlight" = { bg = "bossanova" }
71"ui.highlight" = { bg = "bossanova" }
72−
72+"ui.highlight.frameline" = { bg = "#634450" }
73+"ui.debug" = { fg = "#634450" }
74+"ui.debug.breakpoint" = { fg = "apricot" }
73"ui.menu" = { fg = "lavender", bg = "revolver" }
75"ui.menu" = { fg = "lavender", bg = "revolver" }
74"ui.menu.selected" = { fg = "revolver", bg = "white" }
76"ui.menu.selected" = { fg = "revolver", bg = "white" }
75"ui.menu.scroll" = { fg = "lavender", bg = "comet" }
77"ui.menu.scroll" = { fg = "lavender", bg = "comet" }