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::renderer::stylesheet::Stylesheet;
10use crate::renderer::ElementStyle;
11use crate::Level;
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 = StyledChar::new(' ', ElementStyle::NoStyle);
26
27    pub(crate) const fn new(ch: char, style: ElementStyle) -> StyledChar {
28        StyledChar { ch, style }
29    }
30}
31
32impl StyledBuffer {
33    pub(crate) fn new() -> StyledBuffer {
34        StyledBuffer { 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        for (i, line) in self.lines.iter().enumerate() {
50            let mut current_style = stylesheet.none;
51            for StyledChar { ch, style } in line {
52                let ch_style = style.color_spec(level, stylesheet);
53                if ch_style != current_style {
54                    if !line.is_empty() {
55                        write!(str, "{current_style:#}")?;
56                    }
57                    current_style = ch_style;
58                    write!(str, "{current_style}")?;
59                }
60                write!(str, "{ch}")?;
61            }
62            write!(str, "{current_style:#}")?;
63            if i != self.lines.len() - 1 {
64                writeln!(str)?;
65            }
66        }
67        Ok(())
68    }
69
70    /// Sets `chr` with `style` for given `line`, `col`.
71    /// If `line` does not exist in our buffer, adds empty lines up to the given
72    /// and fills the last line with unstyled whitespace.
73    pub(crate) fn putc(&mut self, line: usize, col: usize, chr: char, style: ElementStyle) {
74        self.ensure_lines(line);
75        if col >= self.lines[line].len() {
76            self.lines[line].resize(col + 1, StyledChar::SPACE);
77        }
78        self.lines[line][col] = StyledChar::new(chr, style);
79    }
80
81    /// Sets `string` with `style` for given `line`, starting from `col`.
82    /// If `line` does not exist in our buffer, adds empty lines up to the given
83    /// and fills the last line with unstyled whitespace.
84    pub(crate) fn puts(&mut self, line: usize, col: usize, string: &str, style: ElementStyle) {
85        let mut n = col;
86        for c in string.chars() {
87            self.putc(line, n, c, style);
88            n += 1;
89        }
90    }
91
92    /// For given `line` inserts `string` with `style` after old content of that line,
93    /// adding lines if needed
94    pub(crate) fn append(&mut self, line: usize, string: &str, style: ElementStyle) {
95        if line >= self.lines.len() {
96            self.puts(line, 0, string, style);
97        } else {
98            let col = self.lines[line].len();
99            self.puts(line, col, string, style);
100        }
101    }
102
103    pub(crate) fn replace(&mut self, line: usize, start: usize, end: usize, string: &str) {
104        if start == end {
105            return;
106        }
107        // If the replacement range would be out of bounds, do nothing, as we
108        // can't replace things that don't exist.
109        if start > self.lines[line].len() || end > self.lines[line].len() {
110            return;
111        }
112        let _ = self.lines[line].drain(start..(end - string.chars().count()));
113        for (i, c) in string.chars().enumerate() {
114            self.lines[line][start + i] = StyledChar::new(c, ElementStyle::LineNumber);
115        }
116    }
117
118    /// For given `line` inserts `string` with `style` before old content of that line,
119    /// adding lines if needed
120    pub(crate) fn prepend(&mut self, line: usize, string: &str, style: ElementStyle) {
121        self.ensure_lines(line);
122        let string_len = string.chars().count();
123
124        if !self.lines[line].is_empty() {
125            // Push the old content over to make room for new content
126            for _ in 0..string_len {
127                self.lines[line].insert(0, StyledChar::SPACE);
128            }
129        }
130
131        self.puts(line, 0, string, style);
132    }
133
134    pub(crate) fn num_lines(&self) -> usize {
135        self.lines.len()
136    }
137
138    /// Set `style` for `line`, `col_start..col_end` range if:
139    /// 1. That line and column range exist in `StyledBuffer`
140    /// 2. `overwrite` is `true` or existing style is `Style::NoStyle` or `Style::Quotation`
141    pub(crate) fn set_style_range(
142        &mut self,
143        line: usize,
144        col_start: usize,
145        col_end: usize,
146        style: ElementStyle,
147        overwrite: bool,
148    ) {
149        for col in col_start..col_end {
150            self.set_style(line, col, style, overwrite);
151        }
152    }
153
154    /// Set `style` for `line`, `col` if:
155    /// 1. That line and column exist in `StyledBuffer`
156    /// 2. `overwrite` is `true` or existing style is `Style::NoStyle` or `Style::Quotation`
157    pub(crate) fn set_style(
158        &mut self,
159        line: usize,
160        col: usize,
161        style: ElementStyle,
162        overwrite: bool,
163    ) {
164        if let Some(ref mut line) = self.lines.get_mut(line) {
165            if let Some(StyledChar { style: s, .. }) = line.get_mut(col) {
166                if overwrite || matches!(s, ElementStyle::NoStyle | ElementStyle::Quotation) {
167                    *s = style;
168                }
169            }
170        }
171    }
172}