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