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 | 574 |
1 files changed, 0 insertions, 574 deletions
diff --git a/helix-term/src/ui/document.rs b/helix-term/src/ui/document.rs deleted file mode 100644 index de85268a..00000000 --- a/helix-term/src/ui/document.rs +++ /dev/null @@ -1,574 +0,0 @@ -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::text_annotations::TextAnnotations; -use helix_core::{visual_offset_from_block, Position, RopeSlice}; -use helix_stdx::rope::RopeSliceExt; -use helix_view::editor::{WhitespaceConfig, WhitespaceRenderValue}; -use helix_view::graphics::Rect; -use helix_view::theme::Style; -use helix_view::view::ViewPosition; -use helix_view::{Document, Theme}; -use tui::buffer::Buffer as Surface; - -use crate::ui::text_decorations::DecorationManager; - -#[derive(Debug, PartialEq, Eq, Copy, Clone)] -pub struct LinePos { - /// Indicates whether the given visual line - /// is the first visual line of the given document line - pub first_visual_line: bool, - /// The line index of the document line that contains the given visual line - pub doc_line: usize, - /// Vertical offset from the top of the inner view area - pub visual_line: u16, -} - -#[allow(clippy::too_many_arguments)] -pub fn render_document( - surface: &mut Surface, - viewport: Rect, - doc: &Document, - offset: ViewPosition, - doc_annotations: &TextAnnotations, - syntax_highlighter: Option<Highlighter<'_>>, - overlay_highlights: Vec<syntax::OverlayHighlights>, - theme: &Theme, - decorations: DecorationManager, -) { - let mut renderer = TextRenderer::new( - surface, - doc, - theme, - Position::new(offset.vertical_offset, offset.horizontal_offset), - viewport, - ); - render_text( - &mut renderer, - doc.text().slice(..), - offset.anchor, - &doc.text_format(viewport.width, Some(theme)), - doc_annotations, - syntax_highlighter, - overlay_highlights, - theme, - decorations, - ) -} - -#[allow(clippy::too_many_arguments)] -pub fn render_text( - renderer: &mut TextRenderer, - text: RopeSlice<'_>, - anchor: usize, - text_fmt: &TextFormat, - text_annotations: &TextAnnotations, - syntax_highlighter: Option<Highlighter<'_>>, - overlay_highlights: Vec<syntax::OverlayHighlights>, - theme: &Theme, - mut decorations: DecorationManager, -) { - let row_off = visual_offset_from_block(text, anchor, anchor, text_fmt, text_annotations) - .0 - .row; - - 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 last_line_pos = LinePos { - first_visual_line: false, - doc_line: usize::MAX, - visual_line: u16::MAX, - }; - let mut last_line_end = 0; - let mut is_in_indent_area = true; - let mut last_line_indent_level = 0; - let mut reached_view_top = false; - - loop { - let Some(mut grapheme) = formatter.next() else { - break; - }; - - // skip any graphemes on visual lines before the block start - if grapheme.visual_pos.row < row_off { - continue; - } - grapheme.visual_pos.row -= row_off; - if !reached_view_top { - decorations.prepare_for_rendering(grapheme.char_idx); - reached_view_top = true; - } - - // if the end of the viewport is reached stop rendering - if grapheme.visual_pos.row as u16 >= renderer.viewport.height + renderer.offset.row as u16 { - break; - } - - // apply decorations before rendering a new line - if grapheme.visual_pos.row as u16 != last_line_pos.visual_line { - // we initiate doc_line with usize::MAX because no file - // can reach that size (memory allocations are limited to isize::MAX) - // initially there is no "previous" line (so doc_line is set to usize::MAX) - // in that case we don't need to draw indent guides/virtual text - if last_line_pos.doc_line != usize::MAX { - // draw indent guides for the last line - renderer.draw_indent_guides(last_line_indent_level, last_line_pos.visual_line); - is_in_indent_area = true; - decorations.render_virtual_lines(renderer, last_line_pos, last_line_end) - } - last_line_pos = LinePos { - first_visual_line: grapheme.line_idx != last_line_pos.doc_line, - doc_line: grapheme.line_idx, - visual_line: grapheme.visual_pos.row as u16, - }; - decorations.decorate_line(renderer, last_line_pos); - } - - // acquire the correct grapheme style - while grapheme.char_idx >= syntax_highlighter.pos { - syntax_highlighter.advance(); - } - while grapheme.char_idx >= overlay_highlighter.pos { - overlay_highlighter.advance(); - } - - 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)); - } - GraphemeStyle { - syntax_style: style, - overlay_style: Style::default(), - } - } else { - GraphemeStyle { - syntax_style: syntax_highlighter.style, - overlay_style: overlay_highlighter.style, - } - }; - decorations.decorate_grapheme(renderer, &grapheme); - - let virt = grapheme.is_virtual(); - let grapheme_width = renderer.draw_grapheme( - grapheme.raw, - grapheme_style, - virt, - &mut last_line_indent_level, - &mut is_in_indent_area, - grapheme.visual_pos, - ); - last_line_end = grapheme.visual_pos.col + grapheme_width; - } - - renderer.draw_indent_guides(last_line_indent_level, last_line_pos.visual_line); - decorations.render_virtual_lines(renderer, last_line_pos, last_line_end) -} - -#[derive(Debug)] -pub struct TextRenderer<'a> { - surface: &'a mut Surface, - pub text_style: Style, - pub whitespace_style: Style, - pub indent_guide_char: String, - pub indent_guide_style: Style, - pub newline: String, - pub nbsp: String, - pub nnbsp: String, - pub space: String, - pub tab: String, - pub virtual_tab: String, - pub indent_width: u16, - pub starting_indent: usize, - pub draw_indent_guides: bool, - pub viewport: Rect, - pub offset: Position, -} - -pub struct GraphemeStyle { - syntax_style: Style, - overlay_style: Style, -} - -impl<'a> TextRenderer<'a> { - pub fn new( - surface: &'a mut Surface, - doc: &Document, - theme: &Theme, - offset: Position, - viewport: Rect, - ) -> TextRenderer<'a> { - let editor_config = doc.config.load(); - let WhitespaceConfig { - render: ws_render, - characters: ws_chars, - } = &editor_config.whitespace; - - 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)) - .collect() - } else { - " ".repeat(tab_width) - }; - let virtual_tab = " ".repeat(tab_width); - let newline = if ws_render.newline() == WhitespaceRenderValue::All { - ws_chars.newline.into() - } else { - " ".to_owned() - }; - - let space = if ws_render.space() == WhitespaceRenderValue::All { - ws_chars.space.into() - } else { - " ".to_owned() - }; - let nbsp = if ws_render.nbsp() == WhitespaceRenderValue::All { - ws_chars.nbsp.into() - } else { - " ".to_owned() - }; - let nnbsp = if ws_render.nnbsp() == WhitespaceRenderValue::All { - ws_chars.nnbsp.into() - } else { - " ".to_owned() - }; - - let text_style = theme.get("ui.text"); - - let indent_width = doc.indent_style.indent_width(tab_width) as u16; - - TextRenderer { - surface, - indent_guide_char: editor_config.indent_guides.character.into(), - newline, - nbsp, - nnbsp, - space, - tab, - virtual_tab, - whitespace_style: theme.get("ui.virtual.whitespace"), - indent_width, - starting_indent: offset.col / indent_width as usize - + (offset.col % indent_width as usize != 0) as usize - + editor_config.indent_guides.skip_levels as usize, - indent_guide_style: text_style.patch( - theme - .try_get("ui.virtual.indent-guide") - .unwrap_or_else(|| theme.get("ui.virtual.whitespace")), - ), - text_style, - draw_indent_guides: editor_config.indent_guides.render, - viewport, - offset, - } - } - /// Draws a single `grapheme` at the current render position with a specified `style`. - pub fn draw_decoration_grapheme( - &mut self, - grapheme: Grapheme, - mut style: Style, - mut row: u16, - col: u16, - ) -> bool { - if (row as usize) < self.offset.row - || row >= self.viewport.height - || col >= self.viewport.width - { - return false; - } - row -= self.offset.row as u16; - // TODO is it correct to apply the whitspace style to all unicode white spaces? - if grapheme.is_whitespace() { - style = style.patch(self.whitespace_style); - } - - let grapheme = match grapheme { - Grapheme::Tab { width } => { - let grapheme_tab_width = char_to_byte_idx(&self.virtual_tab, width); - &self.virtual_tab[..grapheme_tab_width] - } - Grapheme::Other { ref g } if g == "\u{00A0}" => " ", - Grapheme::Other { ref g } => g, - Grapheme::Newline => " ", - }; - - self.surface.set_string( - self.viewport.x + col, - self.viewport.y + row, - grapheme, - style, - ); - true - } - - /// Draws a single `grapheme` at the current render position with a specified `style`. - pub fn draw_grapheme( - &mut self, - grapheme: Grapheme, - grapheme_style: GraphemeStyle, - is_virtual: bool, - last_indent_level: &mut usize, - is_in_indent_area: &mut bool, - mut position: Position, - ) -> usize { - if position.row < self.offset.row { - return 0; - } - position.row -= self.offset.row; - let cut_off_start = self.offset.col.saturating_sub(position.col); - let is_whitespace = grapheme.is_whitespace(); - - // TODO is it correct to apply the whitespace style to all unicode white spaces? - let mut style = grapheme_style.syntax_style; - if is_whitespace { - style = style.patch(self.whitespace_style); - } - style = style.patch(grapheme_style.overlay_style); - - let width = grapheme.width(); - let space = if is_virtual { " " } else { &self.space }; - let nbsp = if is_virtual { " " } else { &self.nbsp }; - let nnbsp = if is_virtual { " " } else { &self.nnbsp }; - let tab = if is_virtual { - &self.virtual_tab - } else { - &self.tab - }; - let grapheme = match grapheme { - Grapheme::Tab { width } => { - let grapheme_tab_width = char_to_byte_idx(tab, width); - &tab[..grapheme_tab_width] - } - // TODO special rendering for other whitespaces? - Grapheme::Other { ref g } if g == " " => space, - Grapheme::Other { ref g } if g == "\u{00A0}" => nbsp, - Grapheme::Other { ref g } if g == "\u{202F}" => nnbsp, - Grapheme::Other { ref g } => g, - Grapheme::Newline => &self.newline, - }; - - let in_bounds = self.column_in_bounds(position.col, width); - - if in_bounds { - self.surface.set_string( - self.viewport.x + (position.col - self.offset.col) as u16, - self.viewport.y + position.row as u16, - grapheme, - style, - ); - } else if cut_off_start != 0 && cut_off_start < width { - // partially on screen - let rect = Rect::new( - self.viewport.x, - self.viewport.y + position.row as u16, - (width - cut_off_start) as u16, - 1, - ); - self.surface.set_style(rect, style); - } - if *is_in_indent_area && !is_whitespace { - *last_indent_level = position.col; - *is_in_indent_area = false; - } - - 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 - } - - /// Overlay indentation guides ontop of a rendered line - /// The indentation level is computed in `draw_lines`. - /// Therefore this function must always be called afterwards. - pub fn draw_indent_guides(&mut self, indent_level: usize, mut row: u16) { - if !self.draw_indent_guides || self.offset.row > row as usize { - return; - } - row -= self.offset.row as u16; - - // Don't draw indent guides outside of view - let end_indent = min( - indent_level, - // Add indent_width - 1 to round up, since the first visible - // indent might be a bit after offset.col - self.offset.col + self.viewport.width as usize + (self.indent_width as usize - 1), - ) / self.indent_width as usize; - - for i in self.starting_indent..end_indent { - let x = (self.viewport.x as usize + (i * self.indent_width as usize) - self.offset.col) - as u16; - let y = self.viewport.y + row; - debug_assert!(self.surface.in_bounds(x, y)); - self.surface - .set_string(x, y, &self.indent_guide_char, self.indent_guide_style); - } - } - - pub fn set_string(&mut self, x: u16, y: u16, string: impl AsRef<str>, style: Style) { - if (y as usize) < self.offset.row { - return; - } - self.surface - .set_string(x, y + self.viewport.y, string, style) - } - - pub fn set_stringn( - &mut self, - x: u16, - y: u16, - string: impl AsRef<str>, - width: usize, - style: Style, - ) { - if (y as usize) < self.offset.row { - return; - } - self.surface - .set_stringn(x, y + self.viewport.y, string, width, style); - } - - /// Sets the style of an area **within the text viewport* this accounts - /// both for the renderers vertical offset and its viewport - pub fn set_style(&mut self, mut area: Rect, style: Style) { - area = area.clip_top(self.offset.row as u16); - area.y += self.viewport.y; - self.surface.set_style(area, style); - } - - #[allow(clippy::too_many_arguments)] - pub fn set_string_truncated( - &mut self, - x: u16, - y: u16, - string: &str, - width: usize, - style: impl Fn(usize) -> Style, // Map a grapheme's string offset to a style - ellipsis: bool, - truncate_start: bool, - ) -> (u16, u16) { - if (y as usize) < self.offset.row { - return (x, y); - } - self.surface.set_string_truncated( - x, - y + self.viewport.y, - string, - width, - style, - ellipsis, - truncate_start, - ) - } -} - -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(); - } -} |