1 // Code for creating styled buffers 2 3 use crate::snippet::{Style, StyledString}; 4 5 #[derive(Debug)] 6 pub struct StyledBuffer { 7 lines: Vec<Vec<StyledChar>>, 8 } 9 10 #[derive(Debug, Clone)] 11 struct StyledChar { 12 chr: char, 13 style: Style, 14 } 15 16 impl StyledChar { 17 const SPACE: Self = StyledChar::new(' ', Style::NoStyle); 18 new(chr: char, style: Style) -> Self19 const fn new(chr: char, style: Style) -> Self { 20 StyledChar { chr, style } 21 } 22 } 23 24 impl StyledBuffer { new() -> StyledBuffer25 pub fn new() -> StyledBuffer { 26 StyledBuffer { lines: vec![] } 27 } 28 29 /// Returns content of `StyledBuffer` split by lines and line styles render(&self) -> Vec<Vec<StyledString>>30 pub 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 ensure_lines(&mut self, line: usize)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. putc(&mut self, line: usize, col: usize, chr: char, style: Style)73 pub 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. puts(&mut self, line: usize, col: usize, string: &str, style: Style)84 pub 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 /// For given `line` inserts `string` with `style` before old content of that line, 93 /// adding lines if needed prepend(&mut self, line: usize, string: &str, style: Style)94 pub fn prepend(&mut self, line: usize, string: &str, style: Style) { 95 self.ensure_lines(line); 96 let string_len = string.chars().count(); 97 98 if !self.lines[line].is_empty() { 99 // Push the old content over to make room for new content 100 for _ in 0..string_len { 101 self.lines[line].insert(0, StyledChar::SPACE); 102 } 103 } 104 105 self.puts(line, 0, string, style); 106 } 107 108 /// For given `line` inserts `string` with `style` after old content of that line, 109 /// adding lines if needed append(&mut self, line: usize, string: &str, style: Style)110 pub fn append(&mut self, line: usize, string: &str, style: Style) { 111 if line >= self.lines.len() { 112 self.puts(line, 0, string, style); 113 } else { 114 let col = self.lines[line].len(); 115 self.puts(line, col, string, style); 116 } 117 } 118 num_lines(&self) -> usize119 pub fn num_lines(&self) -> usize { 120 self.lines.len() 121 } 122 123 /// Set `style` for `line`, `col_start..col_end` range if: 124 /// 1. That line and column range exist in `StyledBuffer` 125 /// 2. `overwrite` is `true` or existing style is `Style::NoStyle` or `Style::Quotation` set_style_range( &mut self, line: usize, col_start: usize, col_end: usize, style: Style, overwrite: bool, )126 pub fn set_style_range( 127 &mut self, 128 line: usize, 129 col_start: usize, 130 col_end: usize, 131 style: Style, 132 overwrite: bool, 133 ) { 134 for col in col_start..col_end { 135 self.set_style(line, col, style, overwrite); 136 } 137 } 138 139 /// Set `style` for `line`, `col` if: 140 /// 1. That line and column exist in `StyledBuffer` 141 /// 2. `overwrite` is `true` or existing style is `Style::NoStyle` or `Style::Quotation` set_style(&mut self, line: usize, col: usize, style: Style, overwrite: bool)142 pub fn set_style(&mut self, line: usize, col: usize, style: Style, overwrite: bool) { 143 if let Some(ref mut line) = self.lines.get_mut(line) { 144 if let Some(StyledChar { style: s, .. }) = line.get_mut(col) { 145 if overwrite || matches!(s, Style::NoStyle | Style::Quotation) { 146 *s = style; 147 } 148 } 149 } 150 } 151 } 152