rustc_errors/
styled_buffer.rs

1// Code for creating styled buffers
2
3use crate::snippet::{Style, StyledString};
4
5#[derive(Debug)]
6pub(crate) struct StyledBuffer {
7    lines: Vec<Vec<StyledChar>>,
8}
9
10#[derive(Debug, Clone)]
11struct StyledChar {
12    chr: char,
13    style: Style,
14}
15
16impl StyledChar {
17    const SPACE: Self = StyledChar::new(' ', Style::NoStyle);
18
19    const fn new(chr: char, style: Style) -> Self {
20        StyledChar { chr, style }
21    }
22}
23
24impl StyledBuffer {
25    pub(crate) fn new() -> StyledBuffer {
26        StyledBuffer { lines: vec![] }
27    }
28
29    /// Returns content of `StyledBuffer` split by lines and line styles
30    pub(crate) fn render(&self) -> Vec<Vec<StyledString>> {
31        // Tabs are assumed to have been replaced by spaces in calling code.
32        debug_assert!(self.lines.iter().all(|r| !r.iter().any(|sc| sc.chr == '\t')));
33
34        let mut output: Vec<Vec<StyledString>> = vec![];
35        let mut styled_vec: Vec<StyledString> = vec![];
36
37        for styled_line in &self.lines {
38            let mut current_style = Style::NoStyle;
39            let mut current_text = String::new();
40
41            for sc in styled_line {
42                if sc.style != current_style {
43                    if !current_text.is_empty() {
44                        styled_vec.push(StyledString { text: current_text, style: current_style });
45                    }
46                    current_style = sc.style;
47                    current_text = String::new();
48                }
49                current_text.push(sc.chr);
50            }
51            if !current_text.is_empty() {
52                styled_vec.push(StyledString { text: current_text, style: current_style });
53            }
54
55            // We're done with the row, push and keep going
56            output.push(styled_vec);
57
58            styled_vec = vec![];
59        }
60
61        output
62    }
63
64    fn ensure_lines(&mut self, line: usize) {
65        if line >= self.lines.len() {
66            self.lines.resize(line + 1, Vec::new());
67        }
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: Style) {
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: Style) {
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    pub(crate) fn replace(&mut self, line: usize, start: usize, end: usize, string: &str) {
93        if start == end {
94            return;
95        }
96        if start > self.lines[line].len() || end > self.lines[line].len() {
97            return;
98        }
99        let _ = self.lines[line].drain(start..(end - string.chars().count()));
100        for (i, c) in string.chars().enumerate() {
101            self.lines[line][start + i] = StyledChar::new(c, Style::LineNumber);
102        }
103    }
104
105    /// For given `line` inserts `string` with `style` before old content of that line,
106    /// adding lines if needed
107    pub(crate) fn prepend(&mut self, line: usize, string: &str, style: Style) {
108        self.ensure_lines(line);
109        let string_len = string.chars().count();
110
111        if !self.lines[line].is_empty() {
112            // Push the old content over to make room for new content
113            for _ in 0..string_len {
114                self.lines[line].insert(0, StyledChar::SPACE);
115            }
116        }
117
118        self.puts(line, 0, string, style);
119    }
120
121    /// For given `line` inserts `string` with `style` after old content of that line,
122    /// adding lines if needed
123    pub(crate) fn append(&mut self, line: usize, string: &str, style: Style) {
124        if line >= self.lines.len() {
125            self.puts(line, 0, string, style);
126        } else {
127            let col = self.lines[line].len();
128            self.puts(line, col, string, style);
129        }
130    }
131
132    pub(crate) fn num_lines(&self) -> usize {
133        self.lines.len()
134    }
135
136    /// Set `style` for `line`, `col_start..col_end` range if:
137    /// 1. That line and column range exist in `StyledBuffer`
138    /// 2. `overwrite` is `true` or existing style is `Style::NoStyle` or `Style::Quotation`
139    pub(crate) fn set_style_range(
140        &mut self,
141        line: usize,
142        col_start: usize,
143        col_end: usize,
144        style: Style,
145        overwrite: bool,
146    ) {
147        for col in col_start..col_end {
148            self.set_style(line, col, style, overwrite);
149        }
150    }
151
152    /// Set `style` for `line`, `col` if:
153    /// 1. That line and column exist in `StyledBuffer`
154    /// 2. `overwrite` is `true` or existing style is `Style::NoStyle` or `Style::Quotation`
155    fn set_style(&mut self, line: usize, col: usize, style: Style, overwrite: bool) {
156        if let Some(ref mut line) = self.lines.get_mut(line) {
157            if let Some(StyledChar { style: s, .. }) = line.get_mut(col) {
158                if overwrite || matches!(s, Style::NoStyle | Style::Quotation) {
159                    *s = style;
160                }
161            }
162        }
163    }
164}