use ratatui::backend::TestBackend;
use ratatui::buffer::Buffer;
use ratatui::style::Style;
use ratatui::widgets::Widget;
use ratatui::{Frame, Terminal};
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct Run {
pub(crate) said: String,
pub(crate) style: Style,
}
impl Run {
pub(crate) fn new(said: &str, style: Style) -> Self {
Self {
said: said.to_string(),
style,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct Painted(Vec<Vec<Run>>);
impl Painted {
pub(crate) fn of<W: Widget>(widget: W, width: u16, height: u16) -> Self {
Self::drawn_by(width, height, |frame| {
frame.render_widget(widget, frame.area());
})
}
pub(crate) fn drawn_by(width: u16, height: u16, draw: impl FnOnce(&mut Frame)) -> Self {
let mut terminal = Terminal::new(TestBackend::new(width, height)).expect("a test backend");
terminal.draw(draw).expect("a draw into memory");
Self::read(terminal.backend().buffer())
}
pub(crate) fn read(buffer: &Buffer) -> Self {
Self(
(buffer.area.top()..buffer.area.bottom())
.map(|y| {
let mut runs: Vec<Run> = Vec::new();
for x in buffer.area.left()..buffer.area.right() {
let cell = &buffer[(x, y)];
match runs.last_mut() {
Some(run) if run.style == cell.style() => {
run.said.push_str(cell.symbol())
}
_ => runs.push(Run::new(cell.symbol(), cell.style())),
}
}
runs
})
.collect(),
)
}
pub(crate) fn rows(&self) -> Vec<String> {
self.0
.iter()
.map(|runs| runs.iter().map(|run| run.said.as_str()).collect())
.collect()
}
pub(crate) fn row(&self, y: usize) -> Vec<Run> {
self.0[y].clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
use ratatui::style::{Color, Modifier};
use ratatui::text::{Line, Span};
#[test]
fn a_run_ends_wherever_any_part_of_the_style_changes() {
let plain = Style::new().fg(Color::Red);
let painted = Painted::of(
Line::from(vec![
Span::styled("a", plain),
Span::styled("b", plain),
Span::styled("cd", plain.add_modifier(Modifier::BOLD)),
]),
4,
1,
);
let runs = painted.row(0);
assert_eq!(runs.len(), 2, "{runs:?}");
assert_eq!(runs[0].said, "ab", "one style, one run: {runs:?}");
assert_eq!(runs[1].said, "cd", "{runs:?}");
assert!(
!runs[0].style.add_modifier.contains(Modifier::BOLD),
"{runs:?}"
);
assert!(
runs[1].style.add_modifier.contains(Modifier::BOLD),
"{runs:?}"
);
}
}