use std::fmt::Write;
use ratatui::{
prelude::{Line, Span},
backend::TestBackend,
buffer::{Buffer, Cell},
layout::{Alignment, Constraint},
style::{Color, Modifier, Style},
widgets::{Block, Borders, BorderType, Padding, Row, Table},
Frame, Terminal,
};
use serde_json::Value;
pub fn buffer_to_ansi(buffer: &Buffer) -> String {
let mut out = String::new();
let mut last_fg = Color::Reset;
let mut last_bg = Color::Reset;
let mut last_modifier = Modifier::empty();
for y in buffer.area.top()..buffer.area.bottom() {
for x in buffer.area.left()..buffer.area.right() {
let cell = &buffer[(x, y)];
if cell.fg != last_fg || cell.bg != last_bg || cell.modifier != last_modifier {
write!(out, "\x1b[0m").unwrap(); apply_style(&mut out, cell);
last_fg = cell.fg;
last_bg = cell.bg;
last_modifier = cell.modifier;
}
out.push_str(cell.symbol());
}
writeln!(out, "\x1b[0m").unwrap();
last_fg = Color::Reset;
last_bg = Color::Reset;
last_modifier = Modifier::empty();
}
out
}
fn apply_style(out: &mut String, cell: &Cell) {
if cell.modifier.contains(Modifier::BOLD) { write!(out, "\x1b[1m").unwrap(); }
if cell.modifier.contains(Modifier::DIM) { write!(out, "\x1b[2m").unwrap(); }
if cell.modifier.contains(Modifier::ITALIC) { write!(out, "\x1b[3m").unwrap(); }
if cell.modifier.contains(Modifier::UNDERLINED) { write!(out, "\x1b[4m").unwrap(); }
if cell.modifier.contains(Modifier::REVERSED) { write!(out, "\x1b[7m").unwrap(); }
write_color(out, cell.fg, false);
write_color(out, cell.bg, true);
}
fn write_color(out: &mut String, color: Color, is_bg: std::primitive::bool) {
let prefix = if is_bg { "48" } else { "38" };
match color {
Color::Reset => {}
Color::Black => write!(out, "\x1b[{}m", if is_bg { 40 } else { 30 }).unwrap(),
Color::Red => write!(out, "\x1b[{}m", if is_bg { 41 } else { 31 }).unwrap(),
Color::Green => write!(out, "\x1b[{}m", if is_bg { 42 } else { 32 }).unwrap(),
Color::Yellow => write!(out, "\x1b[{}m", if is_bg { 43 } else { 33 }).unwrap(),
Color::Blue => write!(out, "\x1b[{}m", if is_bg { 44 } else { 34 }).unwrap(),
Color::Magenta => write!(out, "\x1b[{}m", if is_bg { 45 } else { 35 }).unwrap(),
Color::Cyan => write!(out, "\x1b[{}m", if is_bg { 46 } else { 36 }).unwrap(),
Color::Gray => write!(out, "\x1b[{}m", if is_bg { 100 } else { 90 }).unwrap(),
Color::DarkGray => write!(out, "\x1b[{}m", if is_bg { 100 } else { 90 }).unwrap(),
Color::LightRed => write!(out, "\x1b[{}m", if is_bg { 101 } else { 91 }).unwrap(),
Color::LightGreen => write!(out, "\x1b[{}m", if is_bg { 102 } else { 92 }).unwrap(),
Color::LightYellow => write!(out, "\x1b[{}m", if is_bg { 103 } else { 93 }).unwrap(),
Color::LightBlue => write!(out, "\x1b[{}m", if is_bg { 104 } else { 94 }).unwrap(),
Color::LightMagenta => write!(out, "\x1b[{}m", if is_bg { 105 } else { 95 }).unwrap(),
Color::LightCyan => write!(out, "\x1b[{}m", if is_bg { 106 } else { 96 }).unwrap(),
Color::White => write!(out, "\x1b[{}m", if is_bg { 107 } else { 97 }).unwrap(),
Color::Indexed(i) => write!(out, "\x1b[{};5;{}m", prefix, i).unwrap(),
Color::Rgb(r, g, b) => write!(out, "\x1b[{};2;{};{};{}m", prefix, r, g, b).unwrap(),
}
}
pub fn render<F>(width: u16, height: u16, render_callback: F) -> String
where
F: FnOnce(&mut Frame<'_>),
{
let backend = TestBackend::new(width, height);
let mut terminal = Terminal::new(backend).unwrap();
terminal.draw(render_callback).unwrap();
let buffer = terminal.backend().buffer();
buffer_to_ansi(buffer)
}
pub fn terminal_size() -> (u16, u16) {
crossterm::terminal::size().unwrap()
}
fn make_row<'a>(data: &Value, fields: &[&str]) -> Row<'a> {
let mut col = vec![];
for field in fields {
col.push(data[field].as_str().unwrap().to_owned());
}
Row::new(col)
}
pub fn make_table<'a>(data: &[Value], fields: &[&'a str]) -> Table<'a> {
let rows: Vec<_> = data.iter()
.map(|row| make_row(row, fields))
.collect();
let widths = Constraint::from_fills(vec![1; fields.len()]);
let header = Row::new(fields.to_owned())
.style(Style::new().bold())
.bottom_margin(1);
Table::<'a>::new(rows, widths)
.column_spacing(1)
.header(header)
}
pub fn make_block<'a, T: Into<Line<'a>>>(title: T) -> Block<'a> {
let mut title: Line<'a> = title.into();
let space = Span::raw(" ");
title.spans.insert(0, space.clone());
title.spans.push(space);
Block::default()
.title_alignment(Alignment::Center)
.title(title)
.borders(Borders::ALL)
.border_type(BorderType::Double)
.padding(Padding::symmetric(2, 1))
}