Skip to main content

annotate_snippets/renderer/
styled_buffer.rs

1//! Adapted from [styled_buffer]
2//!
3//! [styled_buffer]: https://github.com/rust-lang/rust/blob/894f7a4ba6554d3797404bbf550d9919df060b97/compiler/rustc_errors/src/styled_buffer.rs
4
5use alloc::string::String;
6use alloc::{vec, vec::Vec};
7use core::fmt::{self, Write};
8
9use crate::Level;
10use crate::renderer::ElementStyle;
11use crate::renderer::stylesheet::Stylesheet;
12
13#[derive(Debug)]
14pub(crate) struct StyledBuffer {
15    lines: Vec<Vec<StyledChar>>,
16}
17
18#[derive(Clone, Copy, Debug, PartialEq)]
19pub(crate) struct StyledChar {
20    ch: char,
21    style: ElementStyle,
22}
23
24impl StyledChar {
25    pub(crate) const SPACE: Self = Self::new(' ', ElementStyle::NoStyle);
26
27    pub(crate) const fn new(ch: char, style: ElementStyle) -> Self {
28        Self { ch, style }
29    }
30}
31
32impl StyledBuffer {
33    pub(crate) fn new() -> Self {
34        Self { lines: vec![] }
35    }
36
37    fn ensure_lines(&mut self, line: usize) {
38        if line >= self.lines.len() {
39            self.lines.resize(line + 1, Vec::new());
40        }
41    }
42
43    pub(crate) fn render(
44        &self,
45        level: &Level<'_>,
46        stylesheet: &Stylesheet,
47        str: &mut String,
48    ) -> Result<(), fmt::Error> {
49        let capacity = self.lines.iter().map(|line| line.len()).sum();
50        str.reserve(capacity);
51
52        for (i, line) in self.lines.iter().enumerate() {
53            let mut current_style = stylesheet.none;
54            for StyledChar { ch, style } in line {
55                let ch_style = style.color_spec(level, stylesheet);
56                if ch_style != current_style {
57                    if !line.is_empty() {
58                        write!(str, "{current_style:#}")?;
59                    }
60                    current_style = ch_style;
61                    write!(str, "{current_style}")?;
62                }
63                str.push(*ch);
64            }
65            write!(str, "{current_style:#}")?;
66            if i != self.lines.len() - 1 {
67                str.push('\n');
68            }
69        }
70        Ok(())
71    }
72
73    /// Sets `chr` with `style` for given `line`, `col`.
74    /// If `line` does not exist in our buffer, adds empty lines up to the given
75    /// and fills the last line with unstyled whitespace.
76    pub(crate) fn putc(&mut self, line: usize, col: usize, chr: char, style: ElementStyle) {
77        self.ensure_lines(line);
78        if col >= self.lines[line].len() {
79            self.lines[line].resize(col + 1, StyledChar::SPACE);
80        }
81        self.lines[line][col] = StyledChar::new(chr, style);
82    }
83
84    /// Sets `string` with `style` for given `line`, starting from `col`.
85    /// If `line` does not exist in our buffer, adds empty lines up to the given
86    /// and fills the last line with unstyled whitespace.
87    pub(crate) fn puts(&mut self, line: usize, col: usize, string: &str, style: ElementStyle) {
88        if string.is_empty() {
89            // don't add trailing whitespace (from column offset) for blank strings
90            return;
91        }
92
93        self.ensure_lines(line);
94        let line = &mut self.lines[line];
95
96        let new_len = col + string.chars().count();
97        if new_len > line.len() {
98            line.resize(new_len, StyledChar::SPACE);
99        }
100
101        for (offset, chr) in string.chars().enumerate() {
102            let col = col + offset;
103            line[col] = StyledChar::new(chr, style);
104        }
105    }
106
107    /// For given `line` inserts `string` with `style` after old content of that line,
108    /// adding lines if needed
109    pub(crate) fn append(&mut self, line: usize, string: &str, style: ElementStyle) {
110        if line >= self.lines.len() {
111            self.puts(line, 0, string, style);
112        } else {
113            let col = self.lines[line].len();
114            self.puts(line, col, string, style);
115        }
116    }
117
118    pub(crate) fn replace(&mut self, line: usize, start: usize, end: usize, string: &str) {
119        if start == end {
120            return;
121        }
122        // If the replacement range would be out of bounds, do nothing, as we
123        // can't replace things that don't exist.
124        if start > self.lines[line].len() || end > self.lines[line].len() {
125            return;
126        };
127        self.lines[line].splice(
128            start..end,
129            string
130                .chars()
131                .map(|c| StyledChar::new(c, ElementStyle::LineNumber)),
132        );
133    }
134
135    pub(crate) fn num_lines(&self) -> usize {
136        self.lines.len()
137    }
138
139    /// Set `style` for `line`, `col_start..col_end` range if:
140    /// 1. That line and column range exist in `StyledBuffer`
141    /// 2. `overwrite` is `true` or existing style is `Style::NoStyle` or `Style::Quotation`
142    pub(crate) fn set_style_range(
143        &mut self,
144        line: usize,
145        col_start: usize,
146        col_end: usize,
147        style: ElementStyle,
148        overwrite: bool,
149    ) {
150        for col in col_start..col_end {
151            self.set_style(line, col, style, overwrite);
152        }
153    }
154
155    /// Set `style` for `line`, `col` if:
156    /// 1. That line and column exist in `StyledBuffer`
157    /// 2. `overwrite` is `true` or existing style is `Style::NoStyle` or `Style::Quotation`
158    pub(crate) fn set_style(
159        &mut self,
160        line: usize,
161        col: usize,
162        style: ElementStyle,
163        overwrite: bool,
164    ) {
165        if let Some(ref mut line) = self.lines.get_mut(line)
166            && let Some(StyledChar { style: s, .. }) = line.get_mut(col)
167            && (overwrite || matches!(s, ElementStyle::NoStyle | ElementStyle::Quotation))
168        {
169            *s = style;
170        }
171    }
172}