annotate_snippets/renderer/
styled_buffer.rs1use 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 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 pub(crate) fn puts(&mut self, line: usize, col: usize, string: &str, style: ElementStyle) {
88 if string.is_empty() {
89 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 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 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 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 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}