use std::io::Write;
use ratatui::buffer::Buffer;
use ratatui::style::Color;
fn set(rgb: (u8, u8, u8)) {
let (r, g, b) = rgb;
let _ = write!(std::io::stdout(), "\x1b]11;#{r:02x}{g:02x}{b:02x}\x07");
let _ = std::io::stdout().flush();
}
pub(crate) fn reset() {
let _ = write!(std::io::stdout(), "\x1b]111\x07");
let _ = std::io::stdout().flush();
}
pub(crate) fn wanted(bg: Color) -> Option<(u8, u8, u8)> {
match bg {
Color::Rgb(r, g, b) => Some((r, g, b)),
_ => None,
}
}
pub(crate) fn bottom_row_bg(buf: &Buffer) -> Color {
let area = buf.area();
if area.height == 0 || area.width == 0 {
return Color::Reset;
}
let y = area.bottom() - 1;
let mut best = Color::Reset;
let mut best_count = 0usize;
for x in area.left()..area.right() {
let bg = buf[(x, y)].bg;
let count = (area.left()..area.right())
.filter(|&i| buf[(i, y)].bg == bg)
.count();
if count > best_count {
best = bg;
best_count = count;
}
}
best
}
pub(crate) fn sync(bg: Color, applied: &mut Option<(u8, u8, u8)>) {
if let Some(rgb) = next(bg, *applied) {
set(rgb);
*applied = Some(rgb);
}
}
fn next(bg: Color, applied: Option<(u8, u8, u8)>) -> Option<(u8, u8, u8)> {
wanted(bg).filter(|rgb| applied != Some(*rgb))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn only_a_real_colour_is_worth_telling_the_terminal_about() {
assert_eq!(wanted(Color::Rgb(0x1e, 0x1e, 0x2e)), Some((30, 30, 46)));
assert_eq!(wanted(Color::Reset), None);
assert_eq!(wanted(Color::Indexed(4)), None);
assert_eq!(wanted(Color::Blue), None);
}
#[test]
fn the_colour_matched_is_the_one_on_the_last_row() {
use ratatui::layout::Rect;
let panel = Color::Rgb(31, 35, 40);
let mut buf = Buffer::empty(Rect::new(0, 0, 10, 3));
for x in 0..10 {
buf[(x, 2)].set_bg(panel);
}
buf[(4, 2)].set_bg(Color::Rgb(1, 1, 1));
buf[(5, 2)].set_bg(Color::Rgb(1, 1, 1));
assert_eq!(bottom_row_bg(&buf), panel);
for x in 0..10 {
buf[(x, 0)].set_bg(Color::Rgb(9, 9, 9));
}
assert_eq!(bottom_row_bg(&buf), panel);
assert_eq!(
bottom_row_bg(&Buffer::empty(Rect::new(0, 0, 0, 0))),
Color::Reset
);
}
#[test]
fn the_colour_must_be_read_from_the_frame_not_from_the_terminal_after() {
use ratatui::widgets::Block;
use ratatui::{Terminal, backend::TestBackend};
let panel = Color::Rgb(31, 35, 40);
let mut term = Terminal::new(TestBackend::new(20, 5)).unwrap();
let mut inside = Color::Reset;
term.draw(|f| {
f.render_widget(
Block::default().style(ratatui::style::Style::default().bg(panel)),
f.area(),
);
inside = bottom_row_bg(f.buffer_mut());
})
.unwrap();
assert_eq!(inside, panel, "the frame's own bottom row was not read");
assert_eq!(
bottom_row_bg(term.current_buffer_mut()),
Color::Reset,
"the post-draw buffer has stopped being blank -- the comment above \
(and the reason for reading inside the closure) needs revisiting"
);
}
#[test]
fn the_terminal_is_told_once_per_change_and_not_once_per_frame() {
assert_eq!(next(Color::Rgb(1, 2, 3), None), Some((1, 2, 3)));
assert_eq!(
next(Color::Rgb(1, 2, 3), Some((1, 2, 3))),
None,
"an unchanged theme wrote an escape sequence anyway"
);
assert_eq!(
next(Color::Rgb(9, 9, 9), Some((1, 2, 3))),
Some((9, 9, 9)),
"a theme change was not passed on"
);
assert_eq!(next(Color::Reset, Some((9, 9, 9))), None);
}
}