Skip to content

Commit

Permalink
Add support for highlighting enclosing brackets
Browse files Browse the repository at this point in the history
Highlight enclosing brackets when `highlight_matching_brackets` is enabled.

Draw lines delineating the enclosing scope when `highlight_scope_lines` is enabled.
  • Loading branch information
riverbl committed Sep 19, 2023
1 parent 83ca73b commit 67c0921
Show file tree
Hide file tree
Showing 5 changed files with 311 additions and 33 deletions.
31 changes: 25 additions & 6 deletions lapce-app/src/doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ use lapce_core::{
selection::{InsertDrift, Selection},
style::line_styles,
syntax::{edit::SyntaxEdit, Syntax},
word::WordCursor,
};
use lapce_rpc::{
buffer::BufferId,
Expand Down Expand Up @@ -166,7 +167,7 @@ impl std::fmt::Debug for Document {
}

/// A single document that can be viewed by multiple [`EditorData`]'s
/// [`EditorViewData`]s and [`EditorView]s.
/// [`EditorViewData`]s and [`EditorView]s.
#[derive(Clone)]
pub struct Document {
pub scope: Scope,
Expand All @@ -181,7 +182,7 @@ pub struct Document {
semantic_styles: RwSignal<Option<Spans<Style>>>,
/// Inlay hints for the document
pub inlay_hints: RwSignal<Option<Spans<InlayHint>>>,
/// Current completion lens text, if any.
/// Current completion lens text, if any.
/// This will be displayed even on views that are not focused.
pub completion_lens: RwSignal<Option<String>>,
/// (line, col)
Expand Down Expand Up @@ -537,7 +538,7 @@ impl Document {
}
}

/// Update the styles after an edit, so the highlights are at the correct positions.
/// Update the styles after an edit, so the highlights are at the correct positions.
/// This does not do a reparse of the document itself.
fn update_styles(&self, delta: &RopeDelta) {
self.semantic_styles.update(|styles| {
Expand Down Expand Up @@ -627,7 +628,7 @@ impl Document {
}
}

/// Get the style information for the particular line from semantic/syntax highlighting.
/// Get the style information for the particular line from semantic/syntax highlighting.
/// This caches the result if possible.
pub fn line_style(&self, line: usize) -> Arc<Vec<LineStyle>> {
if self.line_styles.borrow().get(&line).is_none() {
Expand Down Expand Up @@ -1283,7 +1284,7 @@ impl Document {
Some(rendered_whitespaces)
}

/// Create a new text layout for the given line.
/// Create a new text layout for the given line.
/// Typically you should use [`Document::get_text_layout`] instead.
fn new_text_layout(&self, line: usize, _font_size: usize) -> TextLayoutLine {
let config = self.common.config.get_untracked();
Expand Down Expand Up @@ -1490,7 +1491,7 @@ impl Document {
}
}

/// Get the text layout for the given line.
/// Get the text layout for the given line.
/// If the text layout is not cached, it will be created and cached.
pub fn get_text_layout(
&self,
Expand Down Expand Up @@ -1564,4 +1565,22 @@ impl Document {
})
}
}

/// Returns the offsets of the brackets enclosing the given offset.
/// Uses a language aware algorithm if syntax support is available for the current language,
/// else falls back to a language unaware algorithm.
pub fn find_enclosing_brackets(&self, offset: usize) -> Option<(usize, usize)> {
self.syntax
.with_untracked(|syntax| {
(!syntax.text.is_empty()).then(|| syntax.find_enclosing_pair(offset))
})
// If syntax.text is empty, either the buffer is empty or we don't have syntax support
// for the current language.
// Try a language unaware search for enclosing brackets in case it is the latter.
.unwrap_or_else(|| {
self.buffer.with_untracked(|buffer| {
WordCursor::new(buffer.text(), offset).find_enclosing_pair()
})
})
}

Check warning on line 1585 in lapce-app/src/doc.rs

View check run for this annotation

Codecov / codecov/patch

lapce-app/src/doc.rs#L1572-L1585

Added lines #L1572 - L1585 were not covered by tests
}
207 changes: 206 additions & 1 deletion lapce-app/src/editor/view.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{collections::HashMap, rc::Rc, sync::Arc};
use std::{cmp, collections::HashMap, rc::Rc, sync::Arc};

use floem::{
action::{set_ime_allowed, set_ime_cursor_area},
Expand Down Expand Up @@ -843,6 +843,210 @@ impl EditorView {
cx.fill(&rect, color, 0.0);
}
}

/// Calculate the `x` coordinate of the left edge of the given column on the given line.
/// If `before_cursor` is `true`, the calculated position will be to the right of any inlay
/// hints before and adjacent to the given column. Else, the calculated position will be to the
/// left of any such inlay hints.
fn calculate_col_x(
view: &EditorViewData,
line: usize,
col: usize,
before_cursor: bool,
) -> f64 {
const FONT_SIZE: usize = 12;

let phantom_text = view.line_phantom_text(line);
let col = phantom_text.col_after(col, before_cursor);
view.line_point_of_line_col(line, col, FONT_SIZE).x
}

Check warning on line 862 in lapce-app/src/editor/view.rs

View check run for this annotation

Codecov / codecov/patch

lapce-app/src/editor/view.rs#L851-L862

Added lines #L851 - L862 were not covered by tests

/// Paint a highlight around the characters at the given positions.
fn paint_char_highlights(
&self,
cx: &mut PaintCx,
screen_lines: &ScreenLines,
highlight_line_cols: impl Iterator<Item = (usize, usize)>,
) {
let view = &self.editor.view;
let config = self.editor.common.config.get_untracked();
let line_height = config.editor.line_height() as f64;

Check warning on line 873 in lapce-app/src/editor/view.rs

View check run for this annotation

Codecov / codecov/patch

lapce-app/src/editor/view.rs#L865-L873

Added lines #L865 - L873 were not covered by tests

for (line, col) in highlight_line_cols {

Check warning on line 875 in lapce-app/src/editor/view.rs

View check run for this annotation

Codecov / codecov/patch

lapce-app/src/editor/view.rs#L875

Added line #L875 was not covered by tests
// Is the given line on screen?
if let Some(line_info) = screen_lines.info.get(&line) {
let x0 = Self::calculate_col_x(view, line, col, true);
let x1 = Self::calculate_col_x(view, line, col + 1, false);

let y0 = line_info.y as f64;
let y1 = y0 + line_height;

let rect = Rect::new(x0, y0, x1, y1);

cx.stroke(
&rect,
config.get_color(LapceColor::EDITOR_FOREGROUND),
1.0,
);
}

Check warning on line 891 in lapce-app/src/editor/view.rs

View check run for this annotation

Codecov / codecov/patch

lapce-app/src/editor/view.rs#L877-L891

Added lines #L877 - L891 were not covered by tests
}
}

Check warning on line 893 in lapce-app/src/editor/view.rs

View check run for this annotation

Codecov / codecov/patch

lapce-app/src/editor/view.rs#L893

Added line #L893 was not covered by tests

/// Paint scope lines between `(start_line, start_col)` and `(end_line, end_col)`.
fn paint_scope_lines(
&self,
cx: &mut PaintCx,
viewport: Rect,
screen_lines: &ScreenLines,
(start_line, start_col): (usize, usize),
(end_line, end_col): (usize, usize),
) {
let view = &self.editor.view;
let doc = view.doc.get_untracked();
let config = self.editor.common.config.get_untracked();
let line_height = config.editor.line_height() as f64;
let brush = config.get_color(LapceColor::EDITOR_FOREGROUND);

if start_line == end_line {
if let Some(line_info) = screen_lines.info.get(&start_line) {
let x0 =
Self::calculate_col_x(view, start_line, start_col + 1, false);
let x1 = Self::calculate_col_x(view, end_line, end_col, true);

if x0 < x1 {
let y = line_info.y as f64 + line_height;

let p0 = Point::new(x0, y);
let p1 = Point::new(x1, y);
let line = Line::new(p0, p1);

cx.stroke(&line, brush, 1.0);
}
}

Check warning on line 925 in lapce-app/src/editor/view.rs

View check run for this annotation

Codecov / codecov/patch

lapce-app/src/editor/view.rs#L896-L925

Added lines #L896 - L925 were not covered by tests
} else {
// Are start_line and end_line on screen?
let start_line_y = screen_lines
.info
.get(&start_line)
.map(|line_info| line_info.y as f64 + line_height);
let end_line_y = screen_lines
.info
.get(&end_line)
.map(|line_info| line_info.y as f64 + line_height);

// We only need to draw anything if start_line is on or before the visible section and
// end_line is on or after the visible section.
let y0 = start_line_y.or_else(|| {
(start_line < *screen_lines.lines.first().unwrap())
.then(|| viewport.min_y())
});
let y1 = end_line_y.or_else(|| {
(end_line > *screen_lines.lines.last().unwrap())
.then(|| viewport.max_y())
});

Check warning on line 946 in lapce-app/src/editor/view.rs

View check run for this annotation

Codecov / codecov/patch

lapce-app/src/editor/view.rs#L928-L946

Added lines #L928 - L946 were not covered by tests

if let [Some(y0), Some(y1)] = [y0, y1] {
let start_x =
Self::calculate_col_x(view, start_line, start_col + 1, false);
let end_x = Self::calculate_col_x(view, end_line, end_col, true);

// The vertical line should be drawn to the left of any non-whitespace characters
// in the enclosed section.
let min_text_x = doc.buffer.with_untracked(|buffer| {
((start_line + 1)..=end_line)
.filter(|&line| !buffer.is_line_whitespace(line))
.map(|line| {
let non_blank_offset =
buffer.first_non_blank_character_on_line(line);
let (_, col) = view.offset_to_line_col(non_blank_offset);

Self::calculate_col_x(view, line, col, true)
})
.min_by(f64::total_cmp)
});

let min_x = min_text_x.map_or(start_x, |min_text_x| {
cmp::min_by(min_text_x, start_x, f64::total_cmp)
});

Check warning on line 970 in lapce-app/src/editor/view.rs

View check run for this annotation

Codecov / codecov/patch

lapce-app/src/editor/view.rs#L948-L970

Added lines #L948 - L970 were not covered by tests

// Is start_line on screen, and is the vertical line to the left of the opening
// bracket?
if let Some(y) = start_line_y.filter(|_| start_x > min_x) {
let p0 = Point::new(min_x, y);
let p1 = Point::new(start_x, y);
let line = Line::new(p0, p1);

cx.stroke(&line, brush, 1.0);
}

Check warning on line 980 in lapce-app/src/editor/view.rs

View check run for this annotation

Codecov / codecov/patch

lapce-app/src/editor/view.rs#L974-L980

Added lines #L974 - L980 were not covered by tests

// Is end_line on screen, and is the vertical line to the left of the closing
// bracket?
if let Some(y) = end_line_y.filter(|_| end_x > min_x) {
let p0 = Point::new(min_x, y);
let p1 = Point::new(end_x, y);
let line = Line::new(p0, p1);

cx.stroke(&line, brush, 1.0);
}

Check warning on line 990 in lapce-app/src/editor/view.rs

View check run for this annotation

Codecov / codecov/patch

lapce-app/src/editor/view.rs#L984-L990

Added lines #L984 - L990 were not covered by tests

let p0 = Point::new(min_x, y0);
let p1 = Point::new(min_x, y1);
let line = Line::new(p0, p1);

cx.stroke(&line, brush, 1.0);
}

Check warning on line 997 in lapce-app/src/editor/view.rs

View check run for this annotation

Codecov / codecov/patch

lapce-app/src/editor/view.rs#L992-L997

Added lines #L992 - L997 were not covered by tests
}
}

Check warning on line 999 in lapce-app/src/editor/view.rs

View check run for this annotation

Codecov / codecov/patch

lapce-app/src/editor/view.rs#L999

Added line #L999 was not covered by tests

/// Paint enclosing bracket highlights and scope lines if the corresponding settings are
/// enabled.
fn paint_bracket_highlights_scope_lines(
&self,
cx: &mut PaintCx,
viewport: Rect,
screen_lines: &ScreenLines,
) {
let config = self.editor.common.config.get_untracked();

if config.editor.highlight_matching_brackets
|| config.editor.highlight_scope_lines

Check warning on line 1012 in lapce-app/src/editor/view.rs

View check run for this annotation

Codecov / codecov/patch

lapce-app/src/editor/view.rs#L1003-L1012

Added lines #L1003 - L1012 were not covered by tests
{
let view = &self.editor.view;
let offset = self
.editor
.cursor
.with_untracked(|cursor| cursor.mode.offset());

let bracket_offsets = view
.doc
.with_untracked(|doc| doc.find_enclosing_brackets(offset))
.map(|(start, end)| [start, end]);

let bracket_line_cols = bracket_offsets.map(|bracket_offsets| {
bracket_offsets.map(|offset| view.offset_to_line_col(offset))
});

if config.editor.highlight_matching_brackets {
self.paint_char_highlights(
cx,
screen_lines,
bracket_line_cols.into_iter().flatten(),
);
}

Check warning on line 1035 in lapce-app/src/editor/view.rs

View check run for this annotation

Codecov / codecov/patch

lapce-app/src/editor/view.rs#L1014-L1035

Added lines #L1014 - L1035 were not covered by tests

if config.editor.highlight_scope_lines {
if let Some([start_line_col, end_line_col]) = bracket_line_cols {
self.paint_scope_lines(
cx,
viewport,
screen_lines,
start_line_col,
end_line_col,
);
}
}
}
}

Check warning on line 1049 in lapce-app/src/editor/view.rs

View check run for this annotation

Codecov / codecov/patch

lapce-app/src/editor/view.rs#L1037-L1049

Added lines #L1037 - L1049 were not covered by tests
}

impl View for EditorView {
Expand Down Expand Up @@ -945,6 +1149,7 @@ impl View for EditorView {
self.paint_cursor(cx, is_local, &screen_lines);
self.paint_diff_sections(cx, viewport, &screen_lines, &config);
self.paint_find(cx, &screen_lines);
self.paint_bracket_highlights_scope_lines(cx, viewport, &screen_lines);

Check warning on line 1152 in lapce-app/src/editor/view.rs

View check run for this annotation

Codecov / codecov/patch

lapce-app/src/editor/view.rs#L1152

Added line #L1152 was not covered by tests
self.paint_text(cx, viewport, &screen_lines);
self.paint_sticky_headers(cx, viewport);
self.paint_scroll_bar(cx, viewport, is_local, config);
Expand Down
Loading

0 comments on commit 67c0921

Please sign in to comment.