use std::io::{self, Write};
use super::{Color, Modifier};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct SgrState {
pub fg: Color,
pub bg: Color,
pub modifier: Modifier,
}
impl SgrState {
pub const RESET: SgrState = SgrState {
fg: Color::Reset,
bg: Color::Reset,
modifier: Modifier::empty(),
};
}
pub fn emit_sgr_transition<W: Write>(
w: &mut W,
prev: SgrState,
new: SgrState,
) -> io::Result<SgrState> {
if prev == new {
return Ok(new);
}
let mod_to_remove = prev.modifier.difference(new.modifier);
let mod_to_add = new.modifier.difference(prev.modifier);
for code in modifier_off_codes(mod_to_remove) {
write_sgr(w, code)?;
}
for code in modifier_on_codes(mod_to_add) {
write_sgr(w, code)?;
}
if prev.fg != new.fg {
emit_fg(w, new.fg)?;
}
if prev.bg != new.bg {
emit_bg(w, new.bg)?;
}
Ok(new)
}
pub fn emit_reset<W: Write>(w: &mut W) -> io::Result<()> {
w.write_all(b"\x1b[0m")
}
fn write_sgr<W: Write>(w: &mut W, code: u16) -> io::Result<()> {
write!(w, "\x1b[{}m", code)
}
fn modifier_on_codes(bits: Modifier) -> impl Iterator<Item = u16> {
let mut out = Vec::new();
if bits.contains(Modifier::BOLD) {
out.push(1);
}
if bits.contains(Modifier::ITALIC) {
out.push(3);
}
if bits.contains(Modifier::UNDERLINED) {
out.push(4);
}
if bits.contains(Modifier::SLOW_BLINK) {
out.push(5);
}
if bits.contains(Modifier::RAPID_BLINK) {
out.push(6);
}
if bits.contains(Modifier::HIDDEN) {
out.push(8);
}
if bits.contains(Modifier::CROSSED_OUT) {
out.push(9);
}
out.into_iter()
}
fn modifier_off_codes(bits: Modifier) -> impl Iterator<Item = u16> {
let mut out = Vec::new();
if bits.contains(Modifier::BOLD) {
out.push(22);
}
if bits.contains(Modifier::ITALIC) {
out.push(23);
}
if bits.contains(Modifier::UNDERLINED) {
out.push(24);
}
if bits.intersects(Modifier::SLOW_BLINK | Modifier::RAPID_BLINK) {
out.push(25);
}
if bits.contains(Modifier::HIDDEN) {
out.push(28);
}
if bits.contains(Modifier::CROSSED_OUT) {
out.push(29);
}
out.into_iter()
}
fn emit_fg<W: Write>(w: &mut W, color: Color) -> io::Result<()> {
match color {
Color::Reset => write!(w, "\x1b[39m"),
Color::Indexed(n) => write!(w, "\x1b[38;5;{}m", n),
Color::Rgb(r, g, b) => write!(w, "\x1b[38;2;{};{};{}m", r, g, b),
}
}
fn emit_bg<W: Write>(w: &mut W, color: Color) -> io::Result<()> {
match color {
Color::Reset => write!(w, "\x1b[49m"),
Color::Indexed(n) => write!(w, "\x1b[48;5;{}m", n),
Color::Rgb(r, g, b) => write!(w, "\x1b[48;2;{};{};{}m", r, g, b),
}
}
pub fn emit_cup<W: Write>(w: &mut W, x: u16, y: u16) -> io::Result<()> {
write!(w, "\x1b[{};{}H", y.saturating_add(1), x.saturating_add(1))
}
#[cfg(test)]
mod tests {
use super::*;
fn emit_diff(prev: SgrState, new: SgrState) -> Vec<u8> {
let mut buf = Vec::new();
emit_sgr_transition(&mut buf, prev, new).unwrap();
buf
}
#[test]
fn fg_rgb_pure_red_is_truecolor() {
let buf = emit_diff(
SgrState::default(),
SgrState {
fg: Color::Rgb(255, 0, 0),
..SgrState::default()
},
);
assert_eq!(buf, b"\x1b[38;2;255;0;0m");
}
#[test]
fn fg_rgb_lightcoral_is_truecolor() {
let buf = emit_diff(
SgrState::default(),
SgrState {
fg: Color::Rgb(240, 128, 128),
..SgrState::default()
},
);
assert_eq!(buf, b"\x1b[38;2;240;128;128m");
}
#[test]
fn fg_indexed() {
let buf = emit_diff(
SgrState::default(),
SgrState {
fg: Color::Indexed(204),
..SgrState::default()
},
);
assert_eq!(buf, b"\x1b[38;5;204m");
}
#[test]
fn fg_rgb_truecolor() {
let buf = emit_diff(
SgrState::default(),
SgrState {
fg: Color::Rgb(61, 144, 206),
..SgrState::default()
},
);
assert_eq!(buf, b"\x1b[38;2;61;144;206m");
}
#[test]
fn fg_reset() {
let buf = emit_diff(
SgrState {
fg: Color::Rgb(255, 0, 0),
..SgrState::default()
},
SgrState::default(),
);
assert_eq!(buf, b"\x1b[39m");
}
#[test]
fn bg_rgb_pure_blue_is_truecolor() {
let buf = emit_diff(
SgrState::default(),
SgrState {
bg: Color::Rgb(0, 0, 255),
..SgrState::default()
},
);
assert_eq!(buf, b"\x1b[48;2;0;0;255m");
}
#[test]
fn bg_rgb_truecolor() {
let buf = emit_diff(
SgrState::default(),
SgrState {
bg: Color::Rgb(10, 20, 30),
..SgrState::default()
},
);
assert_eq!(buf, b"\x1b[48;2;10;20;30m");
}
#[test]
fn bold_on() {
let buf = emit_diff(
SgrState::default(),
SgrState {
modifier: Modifier::BOLD,
..SgrState::default()
},
);
assert_eq!(buf, b"\x1b[1m");
}
#[test]
fn multiple_modifiers() {
let buf = emit_diff(
SgrState::default(),
SgrState {
modifier: Modifier::BOLD | Modifier::ITALIC | Modifier::UNDERLINED,
..SgrState::default()
},
);
assert_eq!(buf, b"\x1b[1m\x1b[3m\x1b[4m");
}
#[test]
fn bold_off_uses_sgr22() {
let buf = emit_diff(
SgrState {
modifier: Modifier::BOLD,
..SgrState::default()
},
SgrState::default(),
);
assert_eq!(buf, b"\x1b[22m");
}
#[test]
fn italic_off_uses_sgr23() {
let buf = emit_diff(
SgrState {
modifier: Modifier::ITALIC,
..SgrState::default()
},
SgrState::default(),
);
assert_eq!(buf, b"\x1b[23m");
}
#[test]
fn underline_off_uses_sgr24() {
let buf = emit_diff(
SgrState {
modifier: Modifier::UNDERLINED,
..SgrState::default()
},
SgrState::default(),
);
assert_eq!(buf, b"\x1b[24m");
}
#[test]
fn same_state_emits_nothing() {
let state = SgrState {
fg: Color::Rgb(255, 0, 0),
bg: Color::Rgb(0, 0, 0),
modifier: Modifier::BOLD,
};
assert_eq!(emit_diff(state, state), b"");
}
#[test]
fn only_fg_changed() {
let prev = SgrState {
fg: Color::Rgb(255, 0, 0),
bg: Color::Rgb(0, 0, 0),
modifier: Modifier::BOLD,
};
let new = SgrState {
fg: Color::Rgb(0, 128, 0),
..prev
};
assert_eq!(emit_diff(prev, new), b"\x1b[38;2;0;128;0m");
}
#[test]
fn add_bold_change_fg_and_bg() {
let prev = SgrState::default();
let new = SgrState {
fg: Color::Rgb(255, 0, 0),
bg: Color::Rgb(0, 0, 0),
modifier: Modifier::BOLD,
};
let buf = emit_diff(prev, new);
assert_eq!(buf, b"\x1b[1m\x1b[38;2;255;0;0m\x1b[48;2;0;0;0m");
}
#[test]
fn remove_one_bit_add_another() {
let prev = SgrState {
modifier: Modifier::BOLD,
..SgrState::default()
};
let new = SgrState {
modifier: Modifier::ITALIC,
..SgrState::default()
};
assert_eq!(emit_diff(prev, new), b"\x1b[22m\x1b[3m");
}
#[test]
fn cup_emits_one_indexed_row_col() {
let mut buf = Vec::new();
emit_cup(&mut buf, 5, 10).unwrap();
assert_eq!(buf, b"\x1b[11;6H");
}
#[test]
fn cup_origin_is_1_1() {
let mut buf = Vec::new();
emit_cup(&mut buf, 0, 0).unwrap();
assert_eq!(buf, b"\x1b[1;1H");
}
#[test]
fn emit_reset_produces_sgr0() {
let mut buf = Vec::new();
emit_reset(&mut buf).unwrap();
assert_eq!(buf, b"\x1b[0m");
}
}