Skip to content

Commit

Permalink
fix(debug): text highlight, work on sel and cursor
Browse files Browse the repository at this point in the history
Signed-off-by: Filip Dutescu <filip.dutescu@gmail.com>
  • Loading branch information
filipdutescu committed Feb 15, 2023
1 parent 3df9d6a commit b26b98e
Show file tree
Hide file tree
Showing 2 changed files with 210 additions and 38 deletions.
230 changes: 192 additions & 38 deletions helix-term/src/ui/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,32 +99,6 @@ impl EditorView {
Self::highlight_cursorcolumn(doc, view, surface, theme, inner, &text_annotations);
}

// DAP: Highlight current stack frame position
if let Some(frame) = editor.current_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.debug.current");
let line_decoration = move |renderer: &mut TextRenderer, pos: LinePos| {
if pos.doc_line != 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(
Expand Down Expand Up @@ -167,6 +141,66 @@ impl EditorView {
Box::new(highlights)
};

// DAP: Highlight current stack frame position
// Add it last since it should superceed all other highlights
let highlights: Box<dyn Iterator<Item = HighlightEvent>> =
if let Some(frame) = editor.current_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.debug.current");
let line_decoration = move |renderer: &mut TextRenderer, pos: LinePos| {
if pos.doc_line != line {
return;
}

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

// This adds the line background.
line_decorations.push(Box::new(line_decoration));
// This ensures foreground, cursor and selections are highlighted properly.

let mut dap_range = Range::new(0, 0);
for (i, l) in doc.text().lines().enumerate() {
if i == line {
dap_range.head += l.len_chars();
break;
}
dap_range.anchor += l.len_chars();
dap_range.head += l.len_chars();
}
let current_scope = theme.find_scope_index("ui.debug.current").expect(
"No fallback defined for the `ui.debug.current` scope in the theme!",
);
let highlights = Box::new(syntax::merge(
highlights,
vec![(current_scope, dap_range.anchor..dap_range.head)],
));
let dap_highlights = Self::doc_dap_highlights(
editor.mode(),
doc,
view,
theme,
&config.cursor_shape,
dap_range,
);
Box::new(syntax::merge(highlights, dap_highlights))
} else {
Box::new(highlights)
}
} else {
Box::new(highlights)
};

Self::render_gutter(
editor,
doc,
Expand Down Expand Up @@ -419,19 +453,11 @@ impl EditorView {
.find_scope_index("ui.cursor.primary")
.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 @@ -492,6 +518,134 @@ impl EditorView {
spans
}

/// Highlight current debug line text (concretely, override all other highlights), and selection and colour cursor if any.
pub fn doc_dap_highlights(
mode: Mode,
doc: &Document,
view: &View,
theme: &Theme,
cursor_shape_config: &CursorShapeConfig,
dap_current_range: Range,
) -> Vec<(usize, std::ops::Range<usize>)> {
let mut spans: Vec<(usize, std::ops::Range<usize>)> = Vec::new();

let text = doc.text().slice(..);
let selection = doc.selection(view.id);
let primary_idx = selection.primary_index();

let cursorkind = cursor_shape_config.from_mode(mode);
let cursor_is_block = cursorkind == CursorKind::Block;

let selection_scope = theme
.find_scope_index_exact("ui.selection")
.expect("could not find `ui.selection` scope in the theme!");
let dap_selection_scope = theme
.find_scope_index_exact("ui.debug.selection")
.unwrap_or(selection_scope);
let base_cursor_scope = theme
.find_scope_index_exact("ui.cursor")
.unwrap_or(selection_scope);
let base_primary_cursor_scope = theme
.find_scope_index("ui.cursor.primary")
.unwrap_or(base_cursor_scope);

let cursor_scope = mode.cursor_scope(theme).unwrap_or(base_cursor_scope);

let primary_cursor_scope = mode
.primary_cursor_scope(theme)
.unwrap_or(base_primary_cursor_scope);

for (i, range) in selection.iter().enumerate() {
if Self::is_selection_outside_dap_line(&dap_current_range, &range) {
continue;
}

let selection_is_primary = i == primary_idx;
let (cursor_scope, selection_scope) = if selection_is_primary {
(primary_cursor_scope, dap_selection_scope)
} else {
(cursor_scope, dap_selection_scope)
};

// Special-case: cursor at end of the rope.
if range.head == range.anchor && range.head == text.len_chars() {
if !selection_is_primary || cursor_is_block {
// Bar and underline cursors are drawn by the terminal
// BUG: If the editor area loses focus while having a bar or
// underline cursor (eg. when a regex prompt has focus) then
// the primary cursor will be invisible. This doesn't happen
// with block cursors since we manually draw *all* cursors.
spans.push((cursor_scope, range.head..range.head + 1));
}
continue;
}

let range = range.min_width_1(text);
if range.head > range.anchor {
// Standard case.
let dap_anchor = if range.anchor < dap_current_range.anchor {
dap_current_range.anchor
} else {
range.anchor
};
let dap_head = if range.head > dap_current_range.head {
dap_current_range.head
} else {
range.head
};
let range = Range::new(dap_anchor, dap_head);

let cursor_start = prev_grapheme_boundary(text, range.head);
// non block cursors look like they exclude the cursor
let selection_end =
if selection_is_primary && !cursor_is_block && mode != Mode::Insert {
range.head
} else {
cursor_start
};
spans.push((selection_scope, range.anchor..selection_end));
if !selection_is_primary || cursor_is_block {
spans.push((cursor_scope, cursor_start..range.head));
}
} else {
// Reverse case.
let dap_anchor = if range.anchor < dap_current_range.head {
dap_current_range.head
} else {
range.anchor
};
let dap_head = if range.head > dap_current_range.anchor {
dap_current_range.anchor
} else {
range.head
};
let range = Range::new(dap_anchor, dap_head);

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
&& !(mode == Mode::Insert && cursor_end == range.anchor)
{
range.head
} else {
cursor_end
};
spans.push((selection_scope, selection_start..range.anchor));
}
}

spans
}

pub fn is_selection_outside_dap_line(dap_range: &Range, selection_range: &Range) -> bool {
(selection_range.anchor < dap_range.anchor && selection_range.head < dap_range.anchor)
|| (selection_range.anchor > dap_range.head && selection_range.head > dap_range.head)
}

/// 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 @@ -48,6 +48,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

0 comments on commit b26b98e

Please sign in to comment.