From 686f76636bb84ebe0b083a2c5cf02d43909d2fd3 Mon Sep 17 00:00:00 2001 From: Filip Dutescu Date: Wed, 29 Mar 2023 06:52:19 +0300 Subject: [PATCH] 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 --- book/src/themes.md | 3 ++ helix-dap/src/client.rs | 6 +++ helix-term/src/ui/editor.rs | 52 +++++++++--------------- helix-view/src/editor.rs | 7 ++++ helix-view/src/gutter.rs | 60 +++++++++++++++++++--------- 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 | 4 +- 15 files changed, 98 insertions(+), 53 deletions(-) diff --git a/book/src/themes.md b/book/src/themes.md index 7accb67f0d055..56d0372ca8888 100644 --- a/book/src/themes.md +++ b/book/src/themes.md @@ -278,8 +278,11 @@ These scopes are used for theming the editor interface: | `ui.cursor.primary.normal` | | | `ui.cursor.primary.insert` | | | `ui.cursor.primary.select` | | +| `ui.debug.breakpoint` | Breakpoint indicator, found in the gutter | +| `ui.debug.active` | Indicator for the line at which debugging execution is paused at, found in the gutter | | `ui.gutter` | Gutter | | `ui.gutter.selected` | Gutter for the line the cursor is on | +| `ui.highlight.frameline` | Line at which debugging execution is paused at | | `ui.linenr` | Line numbers | | `ui.linenr.selected` | Line number for the line the cursor is on | | `ui.statusline` | Statusline | diff --git a/helix-dap/src/client.rs b/helix-dap/src/client.rs index ff727d00abe22..7efb72d80fc9c 100644 --- a/helix-dap/src/client.rs +++ b/helix-dap/src/client.rs @@ -512,4 +512,10 @@ impl Client { self.call::(args) } + + pub fn current_stack_frame(&self) -> Option<&StackFrame> { + self.stack_frames + .get(&self.thread_id?)? + .get(self.active_frame?) + } } diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 4cac0fa8b2d10..d4b141a041af6 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -93,40 +93,6 @@ impl EditorView { let mut line_decorations: Vec> = Vec::new(); let mut translated_positions: Vec = Vec::new(); - // DAP: Highlight current stack frame position - let stack_frame = editor.debugger.as_ref().and_then(|debugger| { - if let (Some(frame), Some(thread_id)) = (debugger.active_frame, debugger.thread_id) { - debugger - .stack_frames - .get(&thread_id) - .and_then(|bt| bt.get(frame)) - } else { - None - } - }); - if let Some(frame) = stack_frame { - if doc.path().is_some() - && frame - .source - .as_ref() - .and_then(|source| source.path.as_ref()) - == doc.path() - { - let line = frame.line - 1; // convert to 0-indexing - let style = theme.get("ui.highlight"); - let line_decoration = move |renderer: &mut TextRenderer, pos: LinePos| { - if pos.doc_line != line { - return; - } - renderer - .surface - .set_style(Rect::new(area.x, pos.visual_line, area.width, 1), style); - }; - - line_decorations.push(Box::new(line_decoration)); - } - } - if is_focused && config.cursorline { line_decorations.push(Self::cursorline_decorator(doc, view, theme)) } @@ -135,6 +101,23 @@ impl EditorView { Self::highlight_cursorcolumn(doc, view, surface, theme, inner, &text_annotations); } + // Set DAP highlights, if needed. + if let Some(frame) = editor.current_stack_frame() { + let dap_line = frame.line.saturating_sub(1) as usize; + let style = theme.get("ui.highlight.frameline"); + let line_decoration = move |renderer: &mut TextRenderer, pos: LinePos| { + if pos.doc_line != dap_line { + return; + } + renderer.surface.set_style( + Rect::new(inner.x, inner.y + pos.visual_line, inner.width, 1), + style, + ); + }; + + line_decorations.push(Box::new(line_decoration)); + } + let mut highlights = Self::doc_syntax_highlights(doc, view.offset.anchor, inner.height, theme); let overlay_highlights = Self::overlay_syntax_highlights( @@ -422,6 +405,7 @@ impl EditorView { let primary_selection_scope = theme .find_scope_index_exact("ui.selection.primary") .unwrap_or(selection_scope); + let base_cursor_scope = theme .find_scope_index_exact("ui.cursor") .unwrap_or(selection_scope); diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index ee535b5c01a50..c939aa5cd23ef 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -10,6 +10,7 @@ use crate::{ view::ViewPosition, Align, Document, DocumentId, View, ViewId, }; +use dap::StackFrame; use helix_vcs::DiffProviderRegistry; use futures_util::stream::select_all::SelectAll; @@ -1652,6 +1653,12 @@ impl Editor { doc.restore_cursor = false; } } + + pub fn current_stack_frame(&self) -> Option<&StackFrame> { + self.debugger + .as_ref() + .and_then(|debugger| debugger.current_stack_frame()) + } } fn try_restore_indent(doc: &mut Document, view: &mut View) { diff --git a/helix-view/src/gutter.rs b/helix-view/src/gutter.rs index 36e8e16a436ef..3ecae91954fa0 100644 --- a/helix-view/src/gutter.rs +++ b/helix-view/src/gutter.rs @@ -2,7 +2,7 @@ use std::fmt::Write; use crate::{ editor::GutterType, - graphics::{Color, Style, UnderlineStyle}, + graphics::{Style, UnderlineStyle}, Document, Editor, Theme, View, }; @@ -245,9 +245,9 @@ pub fn breakpoints<'doc>( theme: &Theme, _is_focused: bool, ) -> GutterFn<'doc> { - let warning = theme.get("warning"); let error = theme.get("error"); let info = theme.get("info"); + let breakpoint_style = theme.get("ui.debug.breakpoint"); let breakpoints = doc.path().and_then(|path| editor.breakpoints.get(path)); @@ -265,30 +265,52 @@ pub fn breakpoints<'doc>( .iter() .find(|breakpoint| breakpoint.line == line)?; - let mut style = if breakpoint.condition.is_some() && breakpoint.log_message.is_some() { + let style = if breakpoint.condition.is_some() && breakpoint.log_message.is_some() { error.underline_style(UnderlineStyle::Line) } else if breakpoint.condition.is_some() { error } else if breakpoint.log_message.is_some() { info } else { - warning + breakpoint_style }; - if !breakpoint.verified { - // Faded colors - style = if let Some(Color::Rgb(r, g, b)) = style.fg { - style.fg(Color::Rgb( - ((r as f32) * 0.4).floor() as u8, - ((g as f32) * 0.4).floor() as u8, - ((b as f32) * 0.4).floor() as u8, - )) - } else { - style.fg(Color::Gray) - } - }; + let sym = if breakpoint.verified { "●" } else { "◯" }; + write!(out, "{}", sym).unwrap(); + Some(style) + }, + ) +} + +fn execution_pause_indicator<'doc>( + editor: &'doc Editor, + doc: &'doc Document, + theme: &Theme, + is_focused: bool, +) -> GutterFn<'doc> { + let style = theme.get("ui.debug.active"); + let current_stack_frame = editor.current_stack_frame(); + let frame_line = current_stack_frame.map(|frame| frame.line - 1); + let frame_source_path = current_stack_frame.map(|frame| { + frame + .source + .as_ref() + .and_then(|source| source.path.as_ref()) + }); + let should_display_for_current_doc = + doc.path().is_some() && frame_source_path.unwrap_or(None) == doc.path(); + + Box::new( + move |line: usize, _selected: bool, first_visual_line: bool, out: &mut String| { + if !first_visual_line + || !is_focused + || line != frame_line? + || !should_display_for_current_doc + { + return None; + } - let sym = if breakpoint.verified { "▲" } else { "⊚" }; + let sym = "▶"; write!(out, "{}", sym).unwrap(); Some(style) }, @@ -304,9 +326,11 @@ pub fn diagnostics_or_breakpoints<'doc>( ) -> GutterFn<'doc> { let mut diagnostics = diagnostic(editor, doc, view, theme, is_focused); let mut breakpoints = breakpoints(editor, doc, view, theme, is_focused); + let mut execution_pause_indicator = execution_pause_indicator(editor, doc, theme, is_focused); Box::new(move |line, selected, first_visual_line: bool, out| { - breakpoints(line, selected, first_visual_line, out) + execution_pause_indicator(line, selected, first_visual_line, out) + .or_else(|| breakpoints(line, selected, first_visual_line, out)) .or_else(|| diagnostics(line, selected, first_visual_line, out)) }) } diff --git a/runtime/themes/acme.toml b/runtime/themes/acme.toml index e1d66ff8f2dae..650924741db99 100644 --- a/runtime/themes/acme.toml +++ b/runtime/themes/acme.toml @@ -12,6 +12,8 @@ "ui.virtual.ruler" = { bg = "acme_bar_bg" } "ui.cursor.match" = {bg="acme_bar_bg"} "ui.cursor" = {bg="cursor", fg="white"} +"ui.debug" = {fg="orange"} +"ui.highlight.frameline" = {bg="#da8581"} "string" = "red" "comment" = "green" "ui.help" = {fg="black", bg="acme_bg"} diff --git a/runtime/themes/autumn.toml b/runtime/themes/autumn.toml index 1430e0a8ef93c..4474b0d417b1a 100644 --- a/runtime/themes/autumn.toml +++ b/runtime/themes/autumn.toml @@ -26,6 +26,8 @@ "ui.cursor.primary" = { fg = "my_white", modifiers = ["reversed"] } "ui.cursorline.primary" = { bg = "my_black" } "ui.cursorline.secondary" = { bg = "my_black" } +"ui.highlight.frameline" = { bg = "#8b6904" } +"ui.debug" = { fg = "my_yellow1", bg = "my_gray0" } "ui.text" = "my_white" "operator" = "my_white" "ui.text.focus" = "my_white" diff --git a/runtime/themes/ayu_dark.toml b/runtime/themes/ayu_dark.toml index 37060a24404d2..211d423f4e431 100644 --- a/runtime/themes/ayu_dark.toml +++ b/runtime/themes/ayu_dark.toml @@ -61,6 +61,8 @@ "diagnostic.error"= { underline = { color = "red", style="curl"} } "ui.bufferline" = { fg = "gray", bg = "background" } "ui.bufferline.active" = { fg = "foreground", bg = "dark_gray" } +"ui.debug" = { fg = "orange", bg = "background" } +"ui.highlight.frameline" = { bg = "#0067a3" } "special" = "orange" diff --git a/runtime/themes/ayu_light.toml b/runtime/themes/ayu_light.toml index 58b25484c8f31..4b0ba1db76bd3 100644 --- a/runtime/themes/ayu_light.toml +++ b/runtime/themes/ayu_light.toml @@ -61,6 +61,8 @@ "diagnostic.error"= { underline = { color = "red", style = "curl" } } "ui.bufferline" = { fg = "gray", bg = "background" } "ui.bufferline.active" = { fg = "foreground", bg = "dark_gray" } +"ui.debug" = { fg = "orange", bg = "background" } +"ui.highlight.frameline" = { bg = "#cfe0f2" } "special" = "orange" diff --git a/runtime/themes/ayu_mirage.toml b/runtime/themes/ayu_mirage.toml index 4c1f8fa654313..5afe0acd91acc 100644 --- a/runtime/themes/ayu_mirage.toml +++ b/runtime/themes/ayu_mirage.toml @@ -61,6 +61,8 @@ "diagnostic.error"= { underline = { color = "red", style = "curl" } } "ui.bufferline" = { fg = "gray", bg = "background" } "ui.bufferline.active" = { fg = "foreground", bg = "dark_gray" } +"ui.debug" = { fg = "orange", bg = "background" } +"ui.highlight.frameline" = { bg = "#0067a3" } "special" = "orange" diff --git a/runtime/themes/dracula.toml b/runtime/themes/dracula.toml index 8bde4708de2fc..b08357dbe574a 100644 --- a/runtime/themes/dracula.toml +++ b/runtime/themes/dracula.toml @@ -25,6 +25,8 @@ "ui.cursor.primary" = { fg = "background", bg = "cyan", modifiers = ["dim"] } "ui.cursorline.primary" = { bg = "background_dark" } "ui.help" = { fg = "foreground", bg = "background_dark" } +"ui.debug" = { fg = "red" } +"ui.highlight.frameline" = { fg = "black", bg = "red" } "ui.linenr" = { fg = "comment" } "ui.linenr.selected" = { fg = "foreground" } "ui.menu" = { fg = "foreground", bg = "background_dark" } diff --git a/runtime/themes/dracula_at_night.toml b/runtime/themes/dracula_at_night.toml index 9f10ec90f378a..b2e3b9a9fc110 100644 --- a/runtime/themes/dracula_at_night.toml +++ b/runtime/themes/dracula_at_night.toml @@ -25,6 +25,8 @@ "ui.cursor.match" = { fg = "green", modifiers = ["underlined"] } "ui.cursor.primary" = { fg = "background", bg = "cyan", modifiers = ["dim"] } "ui.help" = { fg = "foreground", bg = "background_dark" } +"ui.debug" = { fg = "red" } +"ui.highlight.frameline" = { fg = "black", bg = "red" } "ui.linenr" = { fg = "comment" } "ui.linenr.selected" = { fg = "foreground" } "ui.menu" = { fg = "foreground", bg = "background_dark" } diff --git a/runtime/themes/onedark.toml b/runtime/themes/onedark.toml index 81ca046301916..6df5f797b35ef 100644 --- a/runtime/themes/onedark.toml +++ b/runtime/themes/onedark.toml @@ -64,6 +64,7 @@ "ui.cursorline.primary" = { bg = "light-black" } "ui.highlight" = { bg = "gray" } +"ui.highlight.frameline" = { bg = "#97202a" } "ui.linenr" = { fg = "linenr" } "ui.linenr.selected" = { fg = "white" } @@ -84,6 +85,8 @@ "ui.menu.selected" = { fg = "black", bg = "blue" } "ui.menu.scroll" = { fg = "white", bg = "light-gray" } +"ui.debug" = { fg = "red" } + [palette] yellow = "#E5C07B" diff --git a/runtime/themes/onedarker.toml b/runtime/themes/onedarker.toml index 33f900cc0356d..7169fd025e720 100644 --- a/runtime/themes/onedarker.toml +++ b/runtime/themes/onedarker.toml @@ -78,6 +78,8 @@ "ui.text.focus" = { fg = "white", bg = "light-black", modifiers = ["bold"] } "ui.help" = { fg = "white", bg = "gray" } +"ui.debug" = { fg = "red" } +"ui.highlight.frameline" = { bg = "#97202a" } "ui.popup" = { bg = "gray" } "ui.window" = { fg = "gray" } "ui.menu" = { fg = "white", bg = "gray" } diff --git a/theme.toml b/theme.toml index b67eaecc35733..dd1a5d889ac1d 100644 --- a/theme.toml +++ b/theme.toml @@ -69,7 +69,9 @@ label = "honey" "ui.cursor" = { modifiers = ["reversed"] } "ui.cursorline.primary" = { bg = "bossanova" } "ui.highlight" = { bg = "bossanova" } - +"ui.highlight.frameline" = { bg = "#634450" } +"ui.debug" = { fg = "#634450" } +"ui.debug.breakpoint" = { fg = "apricot" } "ui.menu" = { fg = "lavender", bg = "revolver" } "ui.menu.selected" = { fg = "revolver", bg = "white" } "ui.menu.scroll" = { fg = "lavender", bg = "comet" }