Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-term/src/ui/document.rs')
| -rw-r--r-- | helix-term/src/ui/document.rs | 224 |
1 files changed, 101 insertions, 123 deletions
diff --git a/helix-term/src/ui/document.rs b/helix-term/src/ui/document.rs index de85268a..79145ba0 100644 --- a/helix-term/src/ui/document.rs +++ b/helix-term/src/ui/document.rs @@ -3,7 +3,8 @@ use std::cmp::min; use helix_core::doc_formatter::{DocumentFormatter, GraphemeSource, TextFormat}; use helix_core::graphemes::Grapheme; use helix_core::str_utils::char_to_byte_idx; -use helix_core::syntax::{self, HighlightEvent, Highlighter, OverlayHighlights}; +use helix_core::syntax::Highlight; +use helix_core::syntax::HighlightEvent; use helix_core::text_annotations::TextAnnotations; use helix_core::{visual_offset_from_block, Position, RopeSlice}; use helix_stdx::rope::RopeSliceExt; @@ -16,6 +17,59 @@ use tui::buffer::Buffer as Surface; use crate::ui::text_decorations::DecorationManager; +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +enum StyleIterKind { + /// base highlights (usually emitted by TS), byte indices (potentially not codepoint aligned) + BaseHighlights, + /// overlay highlights (emitted by custom code from selections), char indices + Overlay, +} + +/// A wrapper around a HighlightIterator +/// that merges the layered highlights to create the final text style +/// and yields the active text style and the char_idx where the active +/// style will have to be recomputed. +/// +/// TODO(ropey2): hopefully one day helix and ropey will operate entirely +/// on byte ranges and we can remove this +struct StyleIter<'a, H: Iterator<Item = HighlightEvent>> { + text_style: Style, + active_highlights: Vec<Highlight>, + highlight_iter: H, + kind: StyleIterKind, + text: RopeSlice<'a>, + theme: &'a Theme, +} + +impl<H: Iterator<Item = HighlightEvent>> Iterator for StyleIter<'_, H> { + type Item = (Style, usize); + fn next(&mut self) -> Option<(Style, usize)> { + while let Some(event) = self.highlight_iter.next() { + match event { + HighlightEvent::HighlightStart(highlights) => { + self.active_highlights.push(highlights) + } + HighlightEvent::HighlightEnd => { + self.active_highlights.pop(); + } + HighlightEvent::Source { mut end, .. } => { + let style = self + .active_highlights + .iter() + .fold(self.text_style, |acc, span| { + acc.patch(self.theme.highlight(span.0)) + }); + if self.kind == StyleIterKind::BaseHighlights { + end = self.text.byte_to_next_char(end); + } + return Some((style, end)); + } + } + } + None + } +} + #[derive(Debug, PartialEq, Eq, Copy, Clone)] pub struct LinePos { /// Indicates whether the given visual line @@ -34,8 +88,8 @@ pub fn render_document( doc: &Document, offset: ViewPosition, doc_annotations: &TextAnnotations, - syntax_highlighter: Option<Highlighter<'_>>, - overlay_highlights: Vec<syntax::OverlayHighlights>, + syntax_highlight_iter: impl Iterator<Item = HighlightEvent>, + overlay_highlight_iter: impl Iterator<Item = HighlightEvent>, theme: &Theme, decorations: DecorationManager, ) { @@ -52,8 +106,8 @@ pub fn render_document( offset.anchor, &doc.text_format(viewport.width, Some(theme)), doc_annotations, - syntax_highlighter, - overlay_highlights, + syntax_highlight_iter, + overlay_highlight_iter, theme, decorations, ) @@ -66,8 +120,8 @@ pub fn render_text( anchor: usize, text_fmt: &TextFormat, text_annotations: &TextAnnotations, - syntax_highlighter: Option<Highlighter<'_>>, - overlay_highlights: Vec<syntax::OverlayHighlights>, + syntax_highlight_iter: impl Iterator<Item = HighlightEvent>, + overlay_highlight_iter: impl Iterator<Item = HighlightEvent>, theme: &Theme, mut decorations: DecorationManager, ) { @@ -77,9 +131,22 @@ pub fn render_text( let mut formatter = DocumentFormatter::new_at_prev_checkpoint(text, text_fmt, text_annotations, anchor); - let mut syntax_highlighter = - SyntaxHighlighter::new(syntax_highlighter, text, theme, renderer.text_style); - let mut overlay_highlighter = OverlayHighlighter::new(overlay_highlights, theme); + let mut syntax_styles = StyleIter { + text_style: renderer.text_style, + active_highlights: Vec::with_capacity(64), + highlight_iter: syntax_highlight_iter, + kind: StyleIterKind::BaseHighlights, + theme, + text, + }; + let mut overlay_styles = StyleIter { + text_style: Style::default(), + active_highlights: Vec::with_capacity(64), + highlight_iter: overlay_highlight_iter, + kind: StyleIterKind::Overlay, + theme, + text, + }; let mut last_line_pos = LinePos { first_visual_line: false, @@ -89,6 +156,12 @@ pub fn render_text( let mut last_line_end = 0; let mut is_in_indent_area = true; let mut last_line_indent_level = 0; + let mut syntax_style_span = syntax_styles + .next() + .unwrap_or_else(|| (Style::default(), usize::MAX)); + let mut overlay_style_span = overlay_styles + .next() + .unwrap_or_else(|| (Style::default(), usize::MAX)); let mut reached_view_top = false; loop { @@ -132,17 +205,21 @@ pub fn render_text( } // acquire the correct grapheme style - while grapheme.char_idx >= syntax_highlighter.pos { - syntax_highlighter.advance(); + while grapheme.char_idx >= syntax_style_span.1 { + syntax_style_span = syntax_styles + .next() + .unwrap_or((Style::default(), usize::MAX)); } - while grapheme.char_idx >= overlay_highlighter.pos { - overlay_highlighter.advance(); + while grapheme.char_idx >= overlay_style_span.1 { + overlay_style_span = overlay_styles + .next() + .unwrap_or((Style::default(), usize::MAX)); } let grapheme_style = if let GraphemeSource::VirtualText { highlight } = grapheme.source { let mut style = renderer.text_style; if let Some(highlight) = highlight { - style = style.patch(theme.highlight(highlight)); + style = style.patch(theme.highlight(highlight.0)); } GraphemeStyle { syntax_style: style, @@ -150,8 +227,8 @@ pub fn render_text( } } else { GraphemeStyle { - syntax_style: syntax_highlighter.style, - overlay_style: overlay_highlighter.style, + syntax_style: syntax_style_span.0, + overlay_style: overlay_style_span.0, } }; decorations.decorate_grapheme(renderer, &grapheme); @@ -214,7 +291,7 @@ impl<'a> TextRenderer<'a> { let tab_width = doc.tab_width(); let tab = if ws_render.tab() == WhitespaceRenderValue::All { std::iter::once(ws_chars.tab) - .chain(std::iter::repeat_n(ws_chars.tabpad, tab_width - 1)) + .chain(std::iter::repeat(ws_chars.tabpad).take(tab_width - 1)) .collect() } else { " ".repeat(tab_width) @@ -356,7 +433,7 @@ impl<'a> TextRenderer<'a> { Grapheme::Newline => &self.newline, }; - let in_bounds = self.column_in_bounds(position.col, width); + let in_bounds = self.column_in_bounds(position.col + width - 1); if in_bounds { self.surface.set_string( @@ -375,6 +452,7 @@ impl<'a> TextRenderer<'a> { ); self.surface.set_style(rect, style); } + if *is_in_indent_area && !is_whitespace { *last_indent_level = position.col; *is_in_indent_area = false; @@ -383,8 +461,8 @@ impl<'a> TextRenderer<'a> { width } - pub fn column_in_bounds(&self, colum: usize, width: usize) -> bool { - self.offset.col <= colum && colum + width <= self.offset.col + self.viewport.width as usize + pub fn column_in_bounds(&self, colum: usize) -> bool { + self.offset.col <= colum && colum < self.viewport.width as usize + self.offset.col } /// Overlay indentation guides ontop of a rendered line @@ -445,6 +523,8 @@ impl<'a> TextRenderer<'a> { self.surface.set_style(area, style); } + /// Sets the style of an area **within the text viewport* this accounts + /// both for the renderers vertical offset and its viewport #[allow(clippy::too_many_arguments)] pub fn set_string_truncated( &mut self, @@ -470,105 +550,3 @@ impl<'a> TextRenderer<'a> { ) } } - -struct SyntaxHighlighter<'h, 'r, 't> { - inner: Option<Highlighter<'h>>, - text: RopeSlice<'r>, - /// The character index of the next highlight event, or `usize::MAX` if the highlighter is - /// finished. - pos: usize, - theme: &'t Theme, - text_style: Style, - style: Style, -} - -impl<'h, 'r, 't> SyntaxHighlighter<'h, 'r, 't> { - fn new( - inner: Option<Highlighter<'h>>, - text: RopeSlice<'r>, - theme: &'t Theme, - text_style: Style, - ) -> Self { - let mut highlighter = Self { - inner, - text, - pos: 0, - theme, - style: text_style, - text_style, - }; - highlighter.update_pos(); - highlighter - } - - fn update_pos(&mut self) { - self.pos = self - .inner - .as_ref() - .and_then(|highlighter| { - let next_byte_idx = highlighter.next_event_offset(); - (next_byte_idx != u32::MAX).then(|| { - // Move the byte index to the nearest character boundary (rounding up) and - // convert it to a character index. - self.text - .byte_to_char(self.text.ceil_char_boundary(next_byte_idx as usize)) - }) - }) - .unwrap_or(usize::MAX); - } - - fn advance(&mut self) { - let Some(highlighter) = self.inner.as_mut() else { - return; - }; - - let (event, highlights) = highlighter.advance(); - let base = match event { - HighlightEvent::Refresh => self.text_style, - HighlightEvent::Push => self.style, - }; - - self.style = highlights.fold(base, |acc, highlight| { - acc.patch(self.theme.highlight(highlight)) - }); - self.update_pos(); - } -} - -struct OverlayHighlighter<'t> { - inner: syntax::OverlayHighlighter, - pos: usize, - theme: &'t Theme, - style: Style, -} - -impl<'t> OverlayHighlighter<'t> { - fn new(overlays: Vec<OverlayHighlights>, theme: &'t Theme) -> Self { - let inner = syntax::OverlayHighlighter::new(overlays); - let mut highlighter = Self { - inner, - pos: 0, - theme, - style: Style::default(), - }; - highlighter.update_pos(); - highlighter - } - - fn update_pos(&mut self) { - self.pos = self.inner.next_event_offset(); - } - - fn advance(&mut self) { - let (event, highlights) = self.inner.advance(); - let base = match event { - HighlightEvent::Refresh => Style::default(), - HighlightEvent::Push => self.style, - }; - - self.style = highlights.fold(base, |acc, highlight| { - acc.patch(self.theme.highlight(highlight)) - }); - self.update_pos(); - } -} |