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>
This commit is contained in:
Filip Dutescu 2023-03-29 06:52:19 +03:00 committed by GitHub
parent bbcdcd04a5
commit d59b80514e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 98 additions and 53 deletions

View file

@ -278,8 +278,11 @@ These scopes are used for theming the editor interface:
| `ui.cursor.primary.normal` | | | `ui.cursor.primary.normal` | |
| `ui.cursor.primary.insert` | | | `ui.cursor.primary.insert` | |
| `ui.cursor.primary.select` | | | `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` | Gutter |
| `ui.gutter.selected` | Gutter for the line the cursor is on | | `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` | Line numbers |
| `ui.linenr.selected` | Line number for the line the cursor is on | | `ui.linenr.selected` | Line number for the line the cursor is on |
| `ui.statusline` | Statusline | | `ui.statusline` | Statusline |

View file

@ -512,4 +512,10 @@ impl Client {
self.call::<requests::SetExceptionBreakpoints>(args) self.call::<requests::SetExceptionBreakpoints>(args)
} }
pub fn current_stack_frame(&self) -> Option<&StackFrame> {
self.stack_frames
.get(&self.thread_id?)?
.get(self.active_frame?)
}
} }

View file

@ -93,40 +93,6 @@ impl EditorView {
let mut line_decorations: Vec<Box<dyn LineDecoration>> = Vec::new(); let mut line_decorations: Vec<Box<dyn LineDecoration>> = Vec::new();
let mut translated_positions: Vec<TranslatedPosition> = Vec::new(); let mut translated_positions: Vec<TranslatedPosition> = 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 { if is_focused && config.cursorline {
line_decorations.push(Self::cursorline_decorator(doc, view, theme)) 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); 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 = let mut highlights =
Self::doc_syntax_highlights(doc, view.offset.anchor, inner.height, theme); Self::doc_syntax_highlights(doc, view.offset.anchor, inner.height, theme);
let overlay_highlights = Self::overlay_syntax_highlights( let overlay_highlights = Self::overlay_syntax_highlights(
@ -422,6 +405,7 @@ impl EditorView {
let primary_selection_scope = theme let primary_selection_scope = theme
.find_scope_index_exact("ui.selection.primary") .find_scope_index_exact("ui.selection.primary")
.unwrap_or(selection_scope); .unwrap_or(selection_scope);
let base_cursor_scope = theme let base_cursor_scope = theme
.find_scope_index_exact("ui.cursor") .find_scope_index_exact("ui.cursor")
.unwrap_or(selection_scope); .unwrap_or(selection_scope);

View file

@ -10,6 +10,7 @@ use crate::{
view::ViewPosition, view::ViewPosition,
Align, Document, DocumentId, View, ViewId, Align, Document, DocumentId, View, ViewId,
}; };
use dap::StackFrame;
use helix_vcs::DiffProviderRegistry; use helix_vcs::DiffProviderRegistry;
use futures_util::stream::select_all::SelectAll; use futures_util::stream::select_all::SelectAll;
@ -1652,6 +1653,12 @@ impl Editor {
doc.restore_cursor = false; 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) { fn try_restore_indent(doc: &mut Document, view: &mut View) {

View file

@ -2,7 +2,7 @@ use std::fmt::Write;
use crate::{ use crate::{
editor::GutterType, editor::GutterType,
graphics::{Color, Style, UnderlineStyle}, graphics::{Style, UnderlineStyle},
Document, Editor, Theme, View, Document, Editor, Theme, View,
}; };
@ -245,9 +245,9 @@ pub fn breakpoints<'doc>(
theme: &Theme, theme: &Theme,
_is_focused: bool, _is_focused: bool,
) -> GutterFn<'doc> { ) -> GutterFn<'doc> {
let warning = theme.get("warning");
let error = theme.get("error"); let error = theme.get("error");
let info = theme.get("info"); let info = theme.get("info");
let breakpoint_style = theme.get("ui.debug.breakpoint");
let breakpoints = doc.path().and_then(|path| editor.breakpoints.get(path)); let breakpoints = doc.path().and_then(|path| editor.breakpoints.get(path));
@ -265,30 +265,52 @@ pub fn breakpoints<'doc>(
.iter() .iter()
.find(|breakpoint| breakpoint.line == line)?; .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) error.underline_style(UnderlineStyle::Line)
} else if breakpoint.condition.is_some() { } else if breakpoint.condition.is_some() {
error error
} else if breakpoint.log_message.is_some() { } else if breakpoint.log_message.is_some() {
info info
} else { } else {
warning breakpoint_style
}; };
if !breakpoint.verified { let sym = if breakpoint.verified { "" } else { "" };
// Faded colors write!(out, "{}", sym).unwrap();
style = if let Some(Color::Rgb(r, g, b)) = style.fg { Some(style)
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,
)) fn execution_pause_indicator<'doc>(
} else { editor: &'doc Editor,
style.fg(Color::Gray) 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(); write!(out, "{}", sym).unwrap();
Some(style) Some(style)
}, },
@ -304,9 +326,11 @@ pub fn diagnostics_or_breakpoints<'doc>(
) -> GutterFn<'doc> { ) -> GutterFn<'doc> {
let mut diagnostics = diagnostic(editor, doc, view, theme, is_focused); let mut diagnostics = diagnostic(editor, doc, view, theme, is_focused);
let mut breakpoints = breakpoints(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| { 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)) .or_else(|| diagnostics(line, selected, first_visual_line, out))
}) })
} }

View file

@ -12,6 +12,8 @@
"ui.virtual.ruler" = { bg = "acme_bar_bg" } "ui.virtual.ruler" = { bg = "acme_bar_bg" }
"ui.cursor.match" = {bg="acme_bar_bg"} "ui.cursor.match" = {bg="acme_bar_bg"}
"ui.cursor" = {bg="cursor", fg="white"} "ui.cursor" = {bg="cursor", fg="white"}
"ui.debug" = {fg="orange"}
"ui.highlight.frameline" = {bg="#da8581"}
"string" = "red" "string" = "red"
"comment" = "green" "comment" = "green"
"ui.help" = {fg="black", bg="acme_bg"} "ui.help" = {fg="black", bg="acme_bg"}

View file

@ -26,6 +26,8 @@
"ui.cursor.primary" = { fg = "my_white", modifiers = ["reversed"] } "ui.cursor.primary" = { fg = "my_white", modifiers = ["reversed"] }
"ui.cursorline.primary" = { bg = "my_black" } "ui.cursorline.primary" = { bg = "my_black" }
"ui.cursorline.secondary" = { 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" "ui.text" = "my_white"
"operator" = "my_white" "operator" = "my_white"
"ui.text.focus" = "my_white" "ui.text.focus" = "my_white"

View file

@ -61,6 +61,8 @@
"diagnostic.error"= { underline = { color = "red", style="curl"} } "diagnostic.error"= { underline = { color = "red", style="curl"} }
"ui.bufferline" = { fg = "gray", bg = "background" } "ui.bufferline" = { fg = "gray", bg = "background" }
"ui.bufferline.active" = { fg = "foreground", bg = "dark_gray" } "ui.bufferline.active" = { fg = "foreground", bg = "dark_gray" }
"ui.debug" = { fg = "orange", bg = "background" }
"ui.highlight.frameline" = { bg = "#0067a3" }
"special" = "orange" "special" = "orange"

View file

@ -61,6 +61,8 @@
"diagnostic.error"= { underline = { color = "red", style = "curl" } } "diagnostic.error"= { underline = { color = "red", style = "curl" } }
"ui.bufferline" = { fg = "gray", bg = "background" } "ui.bufferline" = { fg = "gray", bg = "background" }
"ui.bufferline.active" = { fg = "foreground", bg = "dark_gray" } "ui.bufferline.active" = { fg = "foreground", bg = "dark_gray" }
"ui.debug" = { fg = "orange", bg = "background" }
"ui.highlight.frameline" = { bg = "#cfe0f2" }
"special" = "orange" "special" = "orange"

View file

@ -61,6 +61,8 @@
"diagnostic.error"= { underline = { color = "red", style = "curl" } } "diagnostic.error"= { underline = { color = "red", style = "curl" } }
"ui.bufferline" = { fg = "gray", bg = "background" } "ui.bufferline" = { fg = "gray", bg = "background" }
"ui.bufferline.active" = { fg = "foreground", bg = "dark_gray" } "ui.bufferline.active" = { fg = "foreground", bg = "dark_gray" }
"ui.debug" = { fg = "orange", bg = "background" }
"ui.highlight.frameline" = { bg = "#0067a3" }
"special" = "orange" "special" = "orange"

View file

@ -25,6 +25,8 @@
"ui.cursor.primary" = { fg = "background", bg = "cyan", modifiers = ["dim"] } "ui.cursor.primary" = { fg = "background", bg = "cyan", modifiers = ["dim"] }
"ui.cursorline.primary" = { bg = "background_dark" } "ui.cursorline.primary" = { bg = "background_dark" }
"ui.help" = { fg = "foreground", 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" = { fg = "comment" }
"ui.linenr.selected" = { fg = "foreground" } "ui.linenr.selected" = { fg = "foreground" }
"ui.menu" = { fg = "foreground", bg = "background_dark" } "ui.menu" = { fg = "foreground", bg = "background_dark" }

View file

@ -25,6 +25,8 @@
"ui.cursor.match" = { fg = "green", modifiers = ["underlined"] } "ui.cursor.match" = { fg = "green", modifiers = ["underlined"] }
"ui.cursor.primary" = { fg = "background", bg = "cyan", modifiers = ["dim"] } "ui.cursor.primary" = { fg = "background", bg = "cyan", modifiers = ["dim"] }
"ui.help" = { fg = "foreground", 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" = { fg = "comment" }
"ui.linenr.selected" = { fg = "foreground" } "ui.linenr.selected" = { fg = "foreground" }
"ui.menu" = { fg = "foreground", bg = "background_dark" } "ui.menu" = { fg = "foreground", bg = "background_dark" }

View file

@ -64,6 +64,7 @@
"ui.cursorline.primary" = { bg = "light-black" } "ui.cursorline.primary" = { bg = "light-black" }
"ui.highlight" = { bg = "gray" } "ui.highlight" = { bg = "gray" }
"ui.highlight.frameline" = { bg = "#97202a" }
"ui.linenr" = { fg = "linenr" } "ui.linenr" = { fg = "linenr" }
"ui.linenr.selected" = { fg = "white" } "ui.linenr.selected" = { fg = "white" }
@ -84,6 +85,8 @@
"ui.menu.selected" = { fg = "black", bg = "blue" } "ui.menu.selected" = { fg = "black", bg = "blue" }
"ui.menu.scroll" = { fg = "white", bg = "light-gray" } "ui.menu.scroll" = { fg = "white", bg = "light-gray" }
"ui.debug" = { fg = "red" }
[palette] [palette]
yellow = "#E5C07B" yellow = "#E5C07B"

View file

@ -78,6 +78,8 @@
"ui.text.focus" = { fg = "white", bg = "light-black", modifiers = ["bold"] } "ui.text.focus" = { fg = "white", bg = "light-black", modifiers = ["bold"] }
"ui.help" = { fg = "white", bg = "gray" } "ui.help" = { fg = "white", bg = "gray" }
"ui.debug" = { fg = "red" }
"ui.highlight.frameline" = { bg = "#97202a" }
"ui.popup" = { bg = "gray" } "ui.popup" = { bg = "gray" }
"ui.window" = { fg = "gray" } "ui.window" = { fg = "gray" }
"ui.menu" = { fg = "white", bg = "gray" } "ui.menu" = { fg = "white", bg = "gray" }

View file

@ -69,7 +69,9 @@ label = "honey"
"ui.cursor" = { modifiers = ["reversed"] } "ui.cursor" = { modifiers = ["reversed"] }
"ui.cursorline.primary" = { bg = "bossanova" } "ui.cursorline.primary" = { bg = "bossanova" }
"ui.highlight" = { 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" = { fg = "lavender", bg = "revolver" }
"ui.menu.selected" = { fg = "revolver", bg = "white" } "ui.menu.selected" = { fg = "revolver", bg = "white" }
"ui.menu.scroll" = { fg = "lavender", bg = "comet" } "ui.menu.scroll" = { fg = "lavender", bg = "comet" }