// Code for creating styled buffers use crate::snippet::{Style, StyledString}; #[derive(Debug)] pub struct StyledBuffer { lines: Vec>, } #[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 fn new() -> StyledBuffer { StyledBuffer { lines: vec![] } } /// Returns content of `StyledBuffer` split by lines and line styles pub fn render(&self) -> Vec> { // 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![]; let mut styled_vec: Vec = 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 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 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; } } /// For given `line` inserts `string` with `style` before old content of that line, /// adding lines if needed pub 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 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 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 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` pub fn set_style(&mut self, line: usize, col: usize, style: Style, overwrite: bool) { if let Some(ref mut line) = self.lines.get_mut(line) { if let Some(StyledChar { style: s, .. }) = line.get_mut(col) { if overwrite || *s == Style::NoStyle || *s == Style::Quotation { *s = style; } } } } }