on master help sign in
feat(debug): highlight current line (#5957)

Add new theme highlight keys, for setting the colour of the breakpoint character and the current line at which execution has been paused at. The two new keys are `ui.highlight.frameline` and `ui.debug.breakpoint`. Highlight according to those keys, both the line at which debugging is paused at and the breakpoint indicator. Add an indicator for the current line at which execution is paused at, themed by the `ui.debug.active` theme scope. Update various themes to showcase how the new functionality works. Better icons are dependent on #2869, and as such will be handled in the future, once it lands. Closes: #5952 Signed-off-by: Filip Dutescu <filip.dutescu@gmail.com>

commitd59b80514e15d26f280a9b0dbd18afac08578638 authorFilip Dutescu <filip.dutescu@gmail.com> dateWed 29 Mar 2023, 06:52 (4 years ago) parentbbcdcd0
15 files changed+98−53
book/src/themes.md+3 helix-dap/src/client.rs+6 helix-term/src/ui/editor.rs+18−34 helix-view/src/editor.rs+7 helix-view/src/gutter.rs+42−18 runtime/themes/acme.toml+2 runtime/themes/autumn.toml+2 runtime/themes/ayu_dark.toml+2 runtime/themes/ayu_light.toml+2 runtime/themes/ayu_mirage.toml+2 runtime/themes/dracula.toml+2 runtime/themes/dracula_at_night.toml+2 runtime/themes/onedark.toml+3 runtime/themes/onedarker.toml+2 theme.toml+3−1
book/src/themes.md+3
@@ -278,8 +278,11 @@ These scopes are used for theming the editor interface:
278278| `ui.cursor.primary.normal` | |
279279| `ui.cursor.primary.insert` | |
280280| `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 |
281283| `ui.gutter` | Gutter |
282284| `ui.gutter.selected` | Gutter for the line the cursor is on |
285+| `ui.highlight.frameline` | Line at which debugging execution is paused at |
283286| `ui.linenr` | Line numbers |
284287| `ui.linenr.selected` | Line number for the line the cursor is on |
285288| `ui.statusline` | Statusline |
helix-dap/src/client.rs+6
@@ -512,4 +512,10 @@ impl Client {
512512
513513 self.call::<requests::SetExceptionBreakpoints>(args)
514514 }
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+ }
515521}
helix-term/src/ui/editor.rs+18−34
@@ -93,40 +93,6 @@ impl EditorView {
9393 let mut line_decorations: Vec<Box<dyn LineDecoration>> = Vec::new();
9494 let mut translated_positions: Vec<TranslatedPosition> = Vec::new();
9595
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−
13096 if is_focused && config.cursorline {
13197 line_decorations.push(Self::cursorline_decorator(doc, view, theme))
13298 }
@@ -135,6 +101,23 @@ impl EditorView {
135101 Self::highlight_cursorcolumn(doc, view, surface, theme, inner, &text_annotations);
136102 }
137103
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+
138121 let mut highlights =
139122 Self::doc_syntax_highlights(doc, view.offset.anchor, inner.height, theme);
140123 let overlay_highlights = Self::overlay_syntax_highlights(
@@ -422,6 +405,7 @@ impl EditorView {
422405 let primary_selection_scope = theme
423406 .find_scope_index_exact("ui.selection.primary")
424407 .unwrap_or(selection_scope);
408+
425409 let base_cursor_scope = theme
426410 .find_scope_index_exact("ui.cursor")
427411 .unwrap_or(selection_scope);
helix-view/src/editor.rs+7
@@ -10,6 +10,7 @@ use crate::{
1010 view::ViewPosition,
1111 Align, Document, DocumentId, View, ViewId,
1212};
13+use dap::StackFrame;
1314use helix_vcs::DiffProviderRegistry;
1415
1516use futures_util::stream::select_all::SelectAll;
@@ -1652,6 +1653,12 @@ impl Editor {
16521653 doc.restore_cursor = false;
16531654 }
16541655 }
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+ }
16551662}
16561663
16571664fn try_restore_indent(doc: &mut Document, view: &mut View) {
helix-view/src/gutter.rs+42−18
@@ -2,7 +2,7 @@ use std::fmt::Write;
22
33use crate::{
44 editor::GutterType,
5− graphics::{Color, Style, UnderlineStyle},
5+ graphics::{Style, UnderlineStyle},
66 Document, Editor, Theme, View,
77};
88
@@ -245,9 +245,9 @@ pub fn breakpoints<'doc>(
245245 theme: &Theme,
246246 _is_focused: bool,
247247) -> GutterFn<'doc> {
248− let warning = theme.get("warning");
249248 let error = theme.get("error");
250249 let info = theme.get("info");
250+ let breakpoint_style = theme.get("ui.debug.breakpoint");
251251
252252 let breakpoints = doc.path().and_then(|path| editor.breakpoints.get(path));
253253
@@ -265,30 +265,52 @@ pub fn breakpoints<'doc>(
265265 .iter()
266266 .find(|breakpoint| breakpoint.line == line)?;
267267
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() {
269269 error.underline_style(UnderlineStyle::Line)
270270 } else if breakpoint.condition.is_some() {
271271 error
272272 } else if breakpoint.log_message.is_some() {
273273 info
274274 } else {
275− warning
275+ breakpoint_style
276276 };
277277
278− if !breakpoint.verified {
279− // Faded colors
280− style = if let Some(Color::Rgb(r, g, b)) = style.fg {
281− style.fg(Color::Rgb(
282− ((r as f32) * 0.4).floor() as u8,
283− ((g as f32) * 0.4).floor() as u8,
284− ((b as f32) * 0.4).floor() as u8,
285− ))
286− } else {
287− style.fg(Color::Gray)
288− }
289− };
278+ let sym = if breakpoint.verified { "●" } else { "◯" };
279+ write!(out, "{}", sym).unwrap();
280+ Some(style)
281+ },
282+ )
283+}
284+
285+fn execution_pause_indicator<'doc>(
286+ editor: &'doc Editor,
287+ doc: &'doc Document,
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;
311+ }
290312
291− let sym = if breakpoint.verified { "▲" } else { "⊚" };
313+ let sym = "▶";
292314 write!(out, "{}", sym).unwrap();
293315 Some(style)
294316 },
@@ -304,9 +326,11 @@ pub fn diagnostics_or_breakpoints<'doc>(
304326) -> GutterFn<'doc> {
305327 let mut diagnostics = diagnostic(editor, doc, view, theme, is_focused);
306328 let mut breakpoints = breakpoints(editor, doc, view, theme, is_focused);
329+ let mut execution_pause_indicator = execution_pause_indicator(editor, doc, theme, is_focused);
307330
308331 Box::new(move |line, selected, first_visual_line: bool, out| {
309− breakpoints(line, selected, first_visual_line, out)
332+ execution_pause_indicator(line, selected, first_visual_line, out)
333+ .or_else(|| breakpoints(line, selected, first_visual_line, out))
310334 .or_else(|| diagnostics(line, selected, first_visual_line, out))
311335 })
312336}
runtime/themes/acme.toml+2
@@ -12,6 +12,8 @@
1212"ui.virtual.ruler" = { bg = "acme_bar_bg" }
1313"ui.cursor.match" = {bg="acme_bar_bg"}
1414"ui.cursor" = {bg="cursor", fg="white"}
15+"ui.debug" = {fg="orange"}
16+"ui.highlight.frameline" = {bg="#da8581"}
1517"string" = "red"
1618"comment" = "green"
1719"ui.help" = {fg="black", bg="acme_bg"}
runtime/themes/autumn.toml+2
@@ -26,6 +26,8 @@
2626"ui.cursor.primary" = { fg = "my_white", modifiers = ["reversed"] }
2727"ui.cursorline.primary" = { bg = "my_black" }
2828"ui.cursorline.secondary" = { bg = "my_black" }
29+"ui.highlight.frameline" = { bg = "#8b6904" }
30+"ui.debug" = { fg = "my_yellow1", bg = "my_gray0" }
2931"ui.text" = "my_white"
3032"operator" = "my_white"
3133"ui.text.focus" = "my_white"
runtime/themes/ayu_dark.toml+2
@@ -61,6 +61,8 @@
6161"diagnostic.error"= { underline = { color = "red", style="curl"} }
6262"ui.bufferline" = { fg = "gray", bg = "background" }
6363"ui.bufferline.active" = { fg = "foreground", bg = "dark_gray" }
64+"ui.debug" = { fg = "orange", bg = "background" }
65+"ui.highlight.frameline" = { bg = "#0067a3" }
6466
6567"special" = "orange"
6668
runtime/themes/ayu_light.toml+2
@@ -61,6 +61,8 @@
6161"diagnostic.error"= { underline = { color = "red", style = "curl" } }
6262"ui.bufferline" = { fg = "gray", bg = "background" }
6363"ui.bufferline.active" = { fg = "foreground", bg = "dark_gray" }
64+"ui.debug" = { fg = "orange", bg = "background" }
65+"ui.highlight.frameline" = { bg = "#cfe0f2" }
6466
6567"special" = "orange"
6668
runtime/themes/ayu_mirage.toml+2
@@ -61,6 +61,8 @@
6161"diagnostic.error"= { underline = { color = "red", style = "curl" } }
6262"ui.bufferline" = { fg = "gray", bg = "background" }
6363"ui.bufferline.active" = { fg = "foreground", bg = "dark_gray" }
64+"ui.debug" = { fg = "orange", bg = "background" }
65+"ui.highlight.frameline" = { bg = "#0067a3" }
6466
6567"special" = "orange"
6668
runtime/themes/dracula.toml+2
@@ -25,6 +25,8 @@
2525"ui.cursor.primary" = { fg = "background", bg = "cyan", modifiers = ["dim"] }
2626"ui.cursorline.primary" = { bg = "background_dark" }
2727"ui.help" = { fg = "foreground", bg = "background_dark" }
28+"ui.debug" = { fg = "red" }
29+"ui.highlight.frameline" = { fg = "black", bg = "red" }
2830"ui.linenr" = { fg = "comment" }
2931"ui.linenr.selected" = { fg = "foreground" }
3032"ui.menu" = { fg = "foreground", bg = "background_dark" }
runtime/themes/dracula_at_night.toml+2
@@ -25,6 +25,8 @@
2525"ui.cursor.match" = { fg = "green", modifiers = ["underlined"] }
2626"ui.cursor.primary" = { fg = "background", bg = "cyan", modifiers = ["dim"] }
2727"ui.help" = { fg = "foreground", bg = "background_dark" }
28+"ui.debug" = { fg = "red" }
29+"ui.highlight.frameline" = { fg = "black", bg = "red" }
2830"ui.linenr" = { fg = "comment" }
2931"ui.linenr.selected" = { fg = "foreground" }
3032"ui.menu" = { fg = "foreground", bg = "background_dark" }
runtime/themes/onedark.toml+3
@@ -64,6 +64,7 @@
6464"ui.cursorline.primary" = { bg = "light-black" }
6565
6666"ui.highlight" = { bg = "gray" }
67+"ui.highlight.frameline" = { bg = "#97202a" }
6768
6869"ui.linenr" = { fg = "linenr" }
6970"ui.linenr.selected" = { fg = "white" }
@@ -84,6 +85,8 @@
8485"ui.menu.selected" = { fg = "black", bg = "blue" }
8586"ui.menu.scroll" = { fg = "white", bg = "light-gray" }
8687
88+"ui.debug" = { fg = "red" }
89+
8790[palette]
8891
8992yellow = "#E5C07B"
runtime/themes/onedarker.toml+2
@@ -78,6 +78,8 @@
7878"ui.text.focus" = { fg = "white", bg = "light-black", modifiers = ["bold"] }
7979
8080"ui.help" = { fg = "white", bg = "gray" }
81+"ui.debug" = { fg = "red" }
82+"ui.highlight.frameline" = { bg = "#97202a" }
8183"ui.popup" = { bg = "gray" }
8284"ui.window" = { fg = "gray" }
8385"ui.menu" = { fg = "white", bg = "gray" }
theme.toml+3−1
@@ -69,7 +69,9 @@ label = "honey"
6969"ui.cursor" = { modifiers = ["reversed"] }
7070"ui.cursorline.primary" = { bg = "bossanova" }
7171"ui.highlight" = { bg = "bossanova" }
72−
72+"ui.highlight.frameline" = { bg = "#634450" }
73+"ui.debug" = { fg = "#634450" }
74+"ui.debug.breakpoint" = { fg = "apricot" }
7375"ui.menu" = { fg = "lavender", bg = "revolver" }
7476"ui.menu.selected" = { fg = "revolver", bg = "white" }
7577"ui.menu.scroll" = { fg = "lavender", bg = "comet" }