Skip to content

Commit

Permalink
Add initial text annotations support
Browse files Browse the repository at this point in the history
Currently supports putting annotations at EOL and overlaying.
  • Loading branch information
sudormrfbin committed Jul 5, 2021
1 parent ebccc96 commit 4fe1e11
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 6 deletions.
49 changes: 44 additions & 5 deletions helix-term/src/ui/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use helix_core::{
coords_at_pos,
graphemes::ensure_grapheme_boundary,
syntax::{self, HighlightEvent},
unicode::segmentation::UnicodeSegmentation,
unicode::width::UnicodeWidthStr,
LineEnding, Position, Range,
};
use helix_view::{
Expand Down Expand Up @@ -111,6 +113,12 @@ impl EditorView {

let last_line = view.last_line(doc);

let text_annotations = doc
.text_annotations()
.iter()
.filter(|v| (view.first_line..last_line).contains(&v.line))
.collect::<Vec<_>>();

let range = {
// calculate viewport byte ranges
let start = text.line_to_byte(view.first_line);
Expand Down Expand Up @@ -223,6 +231,27 @@ impl EditorView {
.collect(),
));

let out_of_bounds = |visual_x: u16| {
visual_x < view.first_col as u16 || visual_x >= viewport.width + view.first_col as u16
};

use crate::helix_view::decorations::TextAnnotation;
let render_annotation = |annot: &TextAnnotation, line: u16, pos: u16, surface: &mut Surface| {
let mut visual_x = pos;
for grapheme in annot.text.graphemes(true) {
if out_of_bounds(visual_x) {
break;
}
surface.set_string(
viewport.x + visual_x - view.first_col as u16,
viewport.y + line,
grapheme,
annot.style,
);
visual_x = visual_x.saturating_add(grapheme.width() as u16);
}
};

'outer: for event in highlights {
match event {
HighlightEvent::HighlightStart(span) => {
Expand All @@ -242,18 +271,22 @@ impl EditorView {
});

for grapheme in RopeGraphemes::new(text) {
let out_of_bounds = visual_x < view.first_col as u16
|| visual_x >= viewport.width + view.first_col as u16;

if LineEnding::from_rope_slice(&grapheme).is_some() {
if !out_of_bounds {
if !out_of_bounds(visual_x) {
// we still want to render an empty cell with the style
surface.set_string(
viewport.x + visual_x - view.first_col as u16,
viewport.y + line,
" ",
style,
);
visual_x += 1;
}

if let Some(annot) = text_annotations.iter().find(|t| {
t.text_type.is_eol() && t.line == view.first_line + line as usize
}) {
render_annotation(annot, line, visual_x, surface);
}

visual_x = 0;
Expand All @@ -276,7 +309,7 @@ impl EditorView {
(grapheme.as_ref(), width)
};

if !out_of_bounds {
if !out_of_bounds(visual_x) {
// if we're offscreen just keep going until we hit a new line
surface.set_string(
viewport.x + visual_x - view.first_col as u16,
Expand All @@ -293,6 +326,12 @@ impl EditorView {
}
}

for &annot in text_annotations.iter().filter(|t| t.text_type.is_overlay()) {
let visual_x = annot.offset.unwrap() as u16;
let line = (annot.line - view.first_line) as u16;
render_annotation(annot, line, visual_x, surface);
}

// render gutters

let linenr: Style = theme.get("ui.linenr");
Expand Down
30 changes: 30 additions & 0 deletions helix-view/src/decorations.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use crate::graphics::Style;

#[derive(Clone, Copy, PartialEq)]
pub enum TextAnnotationType {
/// Add to end of line
Eol,
/// Replace actual text or arbitary cells with annotations
Overlay,
}

impl TextAnnotationType {
pub fn is_eol(&self) -> bool {
*self == Self::Eol
}

pub fn is_overlay(&self) -> bool {
*self == Self::Overlay
}
}

pub struct TextAnnotation {
/// String used to namespace and identify similar annotations
pub scope: String,
pub text: String,
pub style: Style,
pub line: usize,
/// Render at this column, None for Eol
pub offset: Option<usize>,
pub text_type: TextAnnotationType,
}
20 changes: 19 additions & 1 deletion helix-view/src/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use helix_core::{
};
use helix_lsp::util::LspFormatting;

use crate::{DocumentId, Theme, ViewId};
use crate::{DocumentId, Theme, ViewId, decorations::TextAnnotation};

const BUF_SIZE: usize = 8192;

Expand Down Expand Up @@ -105,6 +105,7 @@ pub struct Document {
version: i32, // should be usize?

diagnostics: Vec<Diagnostic>,
text_annotations: Vec<TextAnnotation>,
language_server: Option<Arc<helix_lsp::Client>>,
}

Expand Down Expand Up @@ -438,6 +439,7 @@ impl Document {
language: None,
changes,
old_state,
text_annotations: vec![],
diagnostics: Vec::new(),
version: 0,
history: Cell::new(History::default()),
Expand Down Expand Up @@ -1060,6 +1062,22 @@ impl Document {
})
}

pub fn text_annotations(&self) -> &[TextAnnotation] {
&self.text_annotations
}

pub fn push_text_annotations(&mut self, annots: Vec<TextAnnotation>) {
self.text_annotations.extend(annots)
}

/// Remove annotations that return true for the given predicate
pub fn remove_text_annotations<F>(&mut self, f: F)
where
F: Fn(&TextAnnotation) -> bool,
{
self.text_annotations.retain(|t| !f(t))
}

// pub fn slice<R>(&self, range: R) -> RopeSlice where R: RangeBounds {
// self.state.doc.slice
// }
Expand Down
1 change: 1 addition & 0 deletions helix-view/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pub mod macros;

pub mod clipboard;
pub mod decorations;
pub mod document;
pub mod editor;
pub mod graphics;
Expand Down

0 comments on commit 4fe1e11

Please sign in to comment.