Skip to content

Commit

Permalink
feat(debug): highlight current line
Browse files Browse the repository at this point in the history
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.breakpoint` and
`ui.highlight.currentline`. Highlight according to those keys, both the
line at which debugging is paused at and the breakpoint indicator.

Also add two new scopes, `ui.debug.cursor` and `ui.debug.selection`,
to cover the cursor and selection highlighting during debugging, since
it might look of using the default ones. Refactor logic to ensure
only ranges found inside the line at which execution is paused at are
overwritten.

Better icons are dependent on helix-editor#2869, and as such will be handled in the
future, once it lands.

Closes: helix-editor#5952
Signed-off-by: Filip Dutescu <[email protected]>
  • Loading branch information
filipdutescu committed Feb 20, 2023
1 parent 44729fb commit c8c3f07
Show file tree
Hide file tree
Showing 16 changed files with 344 additions and 112 deletions.
111 changes: 58 additions & 53 deletions book/src/themes.md

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions helix-dap/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -495,4 +495,14 @@ impl Client {

self.call::<requests::SetExceptionBreakpoints>(args)
}

pub fn current_stack_frame(&self) -> Option<&StackFrame> {
if let (Some(frame), Some(thread_id)) = (self.active_frame, self.thread_id) {
self.stack_frames
.get(&thread_id)
.and_then(|bt| bt.get(frame))
} else {
None
}
}
}
208 changes: 166 additions & 42 deletions helix-term/src/ui/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,18 +91,16 @@ impl EditorView {
let mut line_decorations: Vec<Box<dyn LineDecoration>> = Vec::new();
let mut translated_positions: Vec<TranslatedPosition> = Vec::new();

if is_focused && config.cursorline {
line_decorations.push(Self::cursorline_decorator(doc, view, theme))
}

if is_focused && config.cursorcolumn {
Self::highlight_cursorcolumn(doc, view, surface, theme, inner, &text_annotations);
}

// 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 {
let dap_line = if let Some(frame) = editor.current_stack_frame() {
if doc.path().is_some()
&& frame
.source
Expand All @@ -111,27 +109,27 @@ impl EditorView {
== doc.path()
{
let line = frame.line - 1; // convert to 0-indexing
let style = theme.get("ui.highlight");
let dap_current_style = theme.get("ui.debug.current");
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);

renderer.surface.set_style(
Rect::new(inner.x, inner.y + pos.visual_line, inner.width, 1),
dap_current_style,
);
};

// This adds the line background.
line_decorations.push(Box::new(line_decoration));
Some(line)
} else {
None
}
}

if is_focused && config.cursorline {
line_decorations.push(Self::cursorline_decorator(doc, view, theme))
}

if is_focused && config.cursorcolumn {
Self::highlight_cursorcolumn(doc, view, surface, theme, inner, &text_annotations);
}
} else {
None
};

let mut highlights =
Self::doc_syntax_highlights(doc, view.offset.anchor, inner.height, theme);
Expand All @@ -155,6 +153,23 @@ impl EditorView {
}

let highlights: Box<dyn Iterator<Item = HighlightEvent>> = if is_focused {
// This ensures foreground, cursor and selections are highlighted properly for DAP.
let (highlights, dap_range): (Box<dyn Iterator<Item = HighlightEvent>>, Option<Range>) =
if let Some(dap_line) = dap_line {
let dap_current_index = theme.find_scope_index("ui.debug.current").expect(
"Could not find scope `ui.debug.current` or any suitable fallback in theme!",
);
let text = doc.text();
let dap_line_start = text.line_to_char(dap_line);
let dap_line_end = text.line_to_char(dap_line + 1);
let highlights = Box::new(syntax::merge(
highlights,
vec![(dap_current_index, dap_line_start..dap_line_end)],
));
(highlights, Some(Range::new(dap_line_start, dap_line_end)))
} else {
(highlights, None)
};
let highlights = syntax::merge(
highlights,
Self::doc_selection_highlights(
Expand All @@ -163,6 +178,7 @@ impl EditorView {
view,
theme,
&config.cursor_shape,
&dap_range,
),
);
let focused_view_elements = Self::highlight_focused_view_elements(view, doc, theme);
Expand Down Expand Up @@ -406,6 +422,7 @@ impl EditorView {
view: &View,
theme: &Theme,
cursor_shape_config: &CursorShapeConfig,
dap_range: &Option<Range>,
) -> Vec<(usize, std::ops::Range<usize>)> {
let text = doc.text().slice(..);
let selection = doc.selection(view.id);
Expand All @@ -426,20 +443,18 @@ impl EditorView {
let base_primary_cursor_scope = theme
.find_scope_index("ui.cursor.primary")
.unwrap_or(base_cursor_scope);
let dap_selection_scope = theme
.find_scope_index_exact("ui.debug.selection")
.unwrap_or(selection_scope);
let dap_cursor_scope = theme
.find_scope_index_exact("ui.debug.cursor")
.unwrap_or(base_cursor_scope);

let cursor_scope = match mode {
Mode::Insert => theme.find_scope_index_exact("ui.cursor.insert"),
Mode::Select => theme.find_scope_index_exact("ui.cursor.select"),
Mode::Normal => theme.find_scope_index_exact("ui.cursor.normal"),
}
.unwrap_or(base_cursor_scope);
let cursor_scope = mode.cursor_scope(theme).unwrap_or(base_cursor_scope);

let primary_cursor_scope = match mode {
Mode::Insert => theme.find_scope_index_exact("ui.cursor.primary.insert"),
Mode::Select => theme.find_scope_index_exact("ui.cursor.primary.select"),
Mode::Normal => theme.find_scope_index_exact("ui.cursor.primary.normal"),
}
.unwrap_or(base_primary_cursor_scope);
let primary_cursor_scope = mode
.primary_cursor_scope(theme)
.unwrap_or(base_primary_cursor_scope);

let mut spans: Vec<(usize, std::ops::Range<usize>)> = Vec::new();
for (i, range) in selection.iter().enumerate() {
Expand Down Expand Up @@ -474,16 +489,39 @@ impl EditorView {
} else {
cursor_start
};
spans.push((selection_scope, range.anchor..selection_end));

let sel_range = Range::new(range.anchor, selection_end);
if Self::is_range_inside_dap_range(dap_range, &sel_range) {
let dap_range = dap_range.unwrap();
Self::set_dap_selection_highlights(
&mut spans,
selection_scope,
dap_selection_scope,
&dap_range,
&sel_range,
range.anchor,
selection_end,
);
} else {
spans.push((selection_scope, range.anchor..selection_end));
}
let cursor_range = Range::new(cursor_start, range.head);
if !selection_is_primary || cursor_is_block {
spans.push((cursor_scope, cursor_start..range.head));
if Self::is_range_inside_dap_range(dap_range, &cursor_range) {
let dap_range = dap_range.unwrap();
Self::set_dap_cursor_highlights(
&mut spans,
dap_cursor_scope,
&dap_range,
&cursor_range,
);
} else {
spans.push((cursor_scope, cursor_start..range.head));
}
}
} else {
// Reverse case.
let cursor_end = next_grapheme_boundary(text, range.head);
if !selection_is_primary || cursor_is_block {
spans.push((cursor_scope, range.head..cursor_end));
}
// non block cursors look like they exclude the cursor
let selection_start = if selection_is_primary
&& !cursor_is_block
Expand All @@ -493,13 +531,99 @@ impl EditorView {
} else {
cursor_end
};
spans.push((selection_scope, selection_start..range.anchor));

let cursor_range = Range::new(range.head, cursor_end);
if !selection_is_primary || cursor_is_block {
if Self::is_range_inside_dap_range(dap_range, &cursor_range) {
let dap_range = dap_range.unwrap();
Self::set_dap_cursor_highlights(
&mut spans,
dap_cursor_scope,
&dap_range,
&cursor_range,
);
} else {
spans.push((cursor_scope, range.head..cursor_end));
}
}
let sel_range = Range::new(selection_start, range.anchor);
if Self::is_range_inside_dap_range(dap_range, &sel_range) {
let dap_range = dap_range.unwrap();
Self::set_dap_selection_highlights(
&mut spans,
selection_scope,
dap_selection_scope,
&dap_range,
&sel_range,
selection_start,
range.anchor,
);
} else {
spans.push((selection_scope, selection_start..range.anchor));
}
}
}

spans
}

#[inline]
pub fn is_range_inside_dap_range(dap_range: &Option<Range>, range: &Range) -> bool {
let dap_range = match dap_range {
Some(dap_range) => dap_range,
None => {
return false;
}
};

(range.anchor > dap_range.anchor || range.head > dap_range.anchor)
&& (range.anchor < dap_range.head || range.head < dap_range.head)
}

fn set_dap_selection_highlights(
spans: &mut Vec<(usize, std::ops::Range<usize>)>,
selection_scope: usize,
dap_selection_scope: usize,
dap_range: &Range,
sel_range: &Range,
original_start: usize,
original_end: usize,
) {
let dap_sel_start = if dap_range.anchor > sel_range.anchor {
dap_range.anchor
} else {
sel_range.anchor
};
let dap_sel_end = if dap_range.head < sel_range.head {
dap_range.head
} else {
sel_range.head
};

spans.push((selection_scope, original_start..dap_sel_start));
spans.push((dap_selection_scope, dap_sel_start..dap_sel_end));
spans.push((selection_scope, dap_sel_end..original_end));
}

fn set_dap_cursor_highlights(
spans: &mut Vec<(usize, std::ops::Range<usize>)>,
dap_cursor_scope: usize,
dap_range: &Range,
cursor_range: &Range,
) {
let dap_cursor_start = if dap_range.anchor > cursor_range.anchor {
dap_range.anchor
} else {
cursor_range.anchor
};
let dap_cursor_end = if dap_range.head < cursor_range.head {
dap_range.head
} else {
cursor_range.head
};
spans.push((dap_cursor_scope, dap_cursor_start..dap_cursor_end));
}

/// Render brace match, etc (meant for the focused view only)
pub fn highlight_focused_view_elements(
view: &View,
Expand Down
18 changes: 18 additions & 0 deletions helix-view/src/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,24 @@ pub enum Mode {
Insert = 2,
}

impl Mode {
pub fn cursor_scope(&self, theme: &Theme) -> Option<usize> {
match self {
Mode::Insert => theme.find_scope_index_exact("ui.cursor.insert"),
Mode::Select => theme.find_scope_index_exact("ui.cursor.select"),
Mode::Normal => theme.find_scope_index_exact("ui.cursor.normal"),
}
}

pub fn primary_cursor_scope(&self, theme: &Theme) -> Option<usize> {
match self {
Mode::Insert => theme.find_scope_index_exact("ui.primary.cursor.insert"),
Mode::Select => theme.find_scope_index_exact("ui.primary.cursor.select"),
Mode::Normal => theme.find_scope_index_exact("ui.primary.cursor.normal"),
}
}
}

impl Display for Mode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Expand Down
12 changes: 12 additions & 0 deletions helix-view/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -1650,6 +1651,17 @@ impl Editor {
doc.restore_cursor = false;
}
}

pub fn current_stack_frame(&self) -> Option<&StackFrame> {
let debugger = match self.debugger.as_ref() {
Some(debugger) => debugger,
None => {
return None;
}
};

debugger.current_stack_frame()
}
}

fn try_restore_indent(doc: &mut Document, view: &mut View) {
Expand Down
Loading

0 comments on commit c8c3f07

Please sign in to comment.