|  | // Code for creating styled buffers | 
|  |  | 
|  | use crate::snippet::{Style, StyledString}; | 
|  |  | 
|  | #[derive(Debug)] | 
|  | pub(crate) struct StyledBuffer { | 
|  | lines: Vec<Vec<StyledChar>>, | 
|  | } | 
|  |  | 
|  | #[derive(Debug, Clone)] | 
|  | struct StyledChar { | 
|  | chr: char, | 
|  | style: Style, | 
|  | } | 
|  |  | 
|  | impl StyledChar { | 
|  | const SPACE: Self = StyledChar::new(' ', Style::NoStyle); | 
|  |  | 
|  | const fn new(chr: char, style: Style) -> Self { | 
|  | StyledChar { chr, style } | 
|  | } | 
|  | } | 
|  |  | 
|  | impl StyledBuffer { | 
|  | pub(crate) fn new() -> StyledBuffer { | 
|  | StyledBuffer { lines: vec![] } | 
|  | } | 
|  |  | 
|  | /// Returns content of `StyledBuffer` split by lines and line styles | 
|  | pub(crate) fn render(&self) -> Vec<Vec<StyledString>> { | 
|  | // Tabs are assumed to have been replaced by spaces in calling code. | 
|  | debug_assert!(self.lines.iter().all(|r| !r.iter().any(|sc| sc.chr == '\t'))); | 
|  |  | 
|  | let mut output: Vec<Vec<StyledString>> = vec![]; | 
|  | let mut styled_vec: Vec<StyledString> = vec![]; | 
|  |  | 
|  | for styled_line in &self.lines { | 
|  | let mut current_style = Style::NoStyle; | 
|  | let mut current_text = String::new(); | 
|  |  | 
|  | for sc in styled_line { | 
|  | if sc.style != current_style { | 
|  | if !current_text.is_empty() { | 
|  | styled_vec.push(StyledString { text: current_text, style: current_style }); | 
|  | } | 
|  | current_style = sc.style; | 
|  | current_text = String::new(); | 
|  | } | 
|  | current_text.push(sc.chr); | 
|  | } | 
|  | if !current_text.is_empty() { | 
|  | styled_vec.push(StyledString { text: current_text, style: current_style }); | 
|  | } | 
|  |  | 
|  | // We're done with the row, push and keep going | 
|  | output.push(styled_vec); | 
|  |  | 
|  | styled_vec = vec![]; | 
|  | } | 
|  |  | 
|  | output | 
|  | } | 
|  |  | 
|  | fn ensure_lines(&mut self, line: usize) { | 
|  | if line >= self.lines.len() { | 
|  | self.lines.resize(line + 1, Vec::new()); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Sets `chr` with `style` for given `line`, `col`. | 
|  | /// If `line` does not exist in our buffer, adds empty lines up to the given | 
|  | /// and fills the last line with unstyled whitespace. | 
|  | pub(crate) fn putc(&mut self, line: usize, col: usize, chr: char, style: Style) { | 
|  | self.ensure_lines(line); | 
|  | if col >= self.lines[line].len() { | 
|  | self.lines[line].resize(col + 1, StyledChar::SPACE); | 
|  | } | 
|  | self.lines[line][col] = StyledChar::new(chr, style); | 
|  | } | 
|  |  | 
|  | /// Sets `string` with `style` for given `line`, starting from `col`. | 
|  | /// If `line` does not exist in our buffer, adds empty lines up to the given | 
|  | /// and fills the last line with unstyled whitespace. | 
|  | pub(crate) fn puts(&mut self, line: usize, col: usize, string: &str, style: Style) { | 
|  | let mut n = col; | 
|  | for c in string.chars() { | 
|  | self.putc(line, n, c, style); | 
|  | n += 1; | 
|  | } | 
|  | } | 
|  |  | 
|  | pub(crate) fn replace(&mut self, line: usize, start: usize, end: usize, string: &str) { | 
|  | if start == end { | 
|  | return; | 
|  | } | 
|  | if start > self.lines[line].len() || end > self.lines[line].len() { | 
|  | return; | 
|  | } | 
|  | let _ = self.lines[line].drain(start..(end - string.chars().count())); | 
|  | for (i, c) in string.chars().enumerate() { | 
|  | self.lines[line][start + i] = StyledChar::new(c, Style::LineNumber); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// For given `line` inserts `string` with `style` before old content of that line, | 
|  | /// adding lines if needed | 
|  | pub(crate) fn prepend(&mut self, line: usize, string: &str, style: Style) { | 
|  | self.ensure_lines(line); | 
|  | let string_len = string.chars().count(); | 
|  |  | 
|  | if !self.lines[line].is_empty() { | 
|  | // Push the old content over to make room for new content | 
|  | for _ in 0..string_len { | 
|  | self.lines[line].insert(0, StyledChar::SPACE); | 
|  | } | 
|  | } | 
|  |  | 
|  | self.puts(line, 0, string, style); | 
|  | } | 
|  |  | 
|  | /// For given `line` inserts `string` with `style` after old content of that line, | 
|  | /// adding lines if needed | 
|  | pub(crate) fn append(&mut self, line: usize, string: &str, style: Style) { | 
|  | if line >= self.lines.len() { | 
|  | self.puts(line, 0, string, style); | 
|  | } else { | 
|  | let col = self.lines[line].len(); | 
|  | self.puts(line, col, string, style); | 
|  | } | 
|  | } | 
|  |  | 
|  | pub(crate) fn num_lines(&self) -> usize { | 
|  | self.lines.len() | 
|  | } | 
|  |  | 
|  | /// Set `style` for `line`, `col_start..col_end` range if: | 
|  | /// 1. That line and column range exist in `StyledBuffer` | 
|  | /// 2. `overwrite` is `true` or existing style is `Style::NoStyle` or `Style::Quotation` | 
|  | pub(crate) fn set_style_range( | 
|  | &mut self, | 
|  | line: usize, | 
|  | col_start: usize, | 
|  | col_end: usize, | 
|  | style: Style, | 
|  | overwrite: bool, | 
|  | ) { | 
|  | for col in col_start..col_end { | 
|  | self.set_style(line, col, style, overwrite); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Set `style` for `line`, `col` if: | 
|  | /// 1. That line and column exist in `StyledBuffer` | 
|  | /// 2. `overwrite` is `true` or existing style is `Style::NoStyle` or `Style::Quotation` | 
|  | fn set_style(&mut self, line: usize, col: usize, style: Style, overwrite: bool) { | 
|  | if let Some(ref mut line) = self.lines.get_mut(line) | 
|  | && let Some(StyledChar { style: s, .. }) = line.get_mut(col) | 
|  | && (overwrite || matches!(s, Style::NoStyle | Style::Quotation)) | 
|  | { | 
|  | *s = style; | 
|  | } | 
|  | } | 
|  | } |