use std::cell::RefCell;
use std::collections::BTreeMap;
use std::io::{self, Write};
thread_local! {
static FRAME: RefCell<Option<Vec<u8>>> = const { RefCell::new(None) };
static PREV: RefCell<Option<Screen>> = const { RefCell::new(None) };
static CLEAR_PENDING: RefCell<bool> = const { RefCell::new(false) };
}
const BEGIN_SYNC: &[u8] = b"\x1b[?2026h";
const END_SYNC: &[u8] = b"\x1b[?2026l";
const ERASE_DISPLAY: &[u8] = b"\x1b[2J";
#[derive(Default, PartialEq)]
struct Screen {
preamble: Vec<u8>,
rows: BTreeMap<usize, Vec<u8>>,
}
pub fn begin_frame() {
let clear = CLEAR_PENDING.with(|c| c.replace(false));
FRAME.with(|f| {
let mut slot = f.borrow_mut();
match slot.as_mut() {
Some(buf) => buf.clear(),
None => *slot = Some(Vec::with_capacity(64 * 1024)),
}
if clear {
slot.as_mut().unwrap().extend_from_slice(ERASE_DISPLAY);
}
});
if clear {
invalidate();
}
}
pub fn request_clear() {
CLEAR_PENDING.with(|c| *c.borrow_mut() = true);
}
pub struct FrameOut;
impl Write for FrameOut {
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
FRAME.with(|f| {
let mut slot = f.borrow_mut();
match slot.as_mut() {
Some(buf) => {
buf.extend_from_slice(data);
Ok(data.len())
}
None => io::stdout().lock().write(data),
}
})
}
fn flush(&mut self) -> io::Result<()> {
FRAME.with(|f| {
if f.borrow().is_none() {
io::stdout().lock().flush()
} else {
Ok(())
}
})
}
}
pub fn frame_out() -> FrameOut {
FrameOut
}
pub fn invalidate() {
PREV.with(|p| *p.borrow_mut() = None);
}
pub fn present() {
let mut out = io::stdout().lock();
if present_to(&mut out) {
let _ = out.flush();
}
}
fn present_to<W: Write>(out: &mut W) -> bool {
let taken = FRAME.with(|f| f.borrow_mut().take());
let Some(buf) = taken else {
return false;
};
if buf.is_empty() {
return false;
}
let (next, clear) = parse_frame(&buf);
let base = PREV.with(|p| p.borrow_mut().take());
let base = if clear { None } else { base };
let mut body: Vec<u8> = Vec::new();
if base.as_ref().map(|b| &b.preamble) != Some(&next.preamble) {
body.extend_from_slice(&next.preamble);
}
for (row, seg) in &next.rows {
if base.as_ref().and_then(|b| b.rows.get(row)) != Some(seg) {
body.extend_from_slice(seg);
}
}
if let Some(b) = base.as_ref() {
for row in b.rows.keys() {
if !next.rows.contains_key(row) {
let _ = write!(&mut body, "\x1b[{};1H\x1b[0m\x1b[K", row + 1);
}
}
}
PREV.with(|p| *p.borrow_mut() = Some(next));
if body.is_empty() {
return false; }
let _ = out.write_all(BEGIN_SYNC);
let _ = out.write_all(&body);
let _ = out.write_all(END_SYNC);
true
}
fn parse_frame(frame: &[u8]) -> (Screen, bool) {
let mut screen = Screen::default();
let mut clear = false;
let mut cur_row: Option<usize> = None;
let mut cur_sgr: Vec<u8> = Vec::new();
fn push(screen: &mut Screen, cur_row: Option<usize>, bytes: &[u8]) {
match cur_row {
None => screen.preamble.extend_from_slice(bytes),
Some(r) => {
if let Some(buf) = screen.rows.get_mut(&r) {
buf.extend_from_slice(bytes);
}
}
}
}
let n = frame.len();
let mut i = 0;
while i < n {
if frame[i] == 0x1b && i + 1 < n && frame[i + 1] == b'[' {
let start = i;
let mut j = i + 2;
while j < n && (0x20..=0x3f).contains(&frame[j]) {
j += 1;
}
if j >= n {
push(&mut screen, cur_row, &frame[start..]);
break;
}
let final_b = frame[j];
let params = &frame[i + 2..j];
let seq = &frame[start..=j];
match final_b {
b'H' | b'f' => {
let row = parse_first_param(params).saturating_sub(1);
cur_row = Some(row);
let bucket = screen.rows.entry(row).or_default();
bucket.extend_from_slice(&cur_sgr);
bucket.extend_from_slice(seq);
}
b'm' => {
if is_sgr_reset(params) {
cur_sgr.clear();
}
cur_sgr.extend_from_slice(seq);
push(&mut screen, cur_row, seq);
}
b'J' => {
if params == b"2" {
clear = true;
}
push(&mut screen, cur_row, seq);
}
_ => push(&mut screen, cur_row, seq),
}
i = j + 1;
} else {
push(&mut screen, cur_row, &frame[i..i + 1]);
i += 1;
}
}
(screen, clear)
}
fn parse_first_param(params: &[u8]) -> usize {
let mut n = 0usize;
let mut seen = false;
for &b in params {
if b.is_ascii_digit() {
n = n * 10 + (b - b'0') as usize;
seen = true;
} else {
break;
}
}
if seen {
n
} else {
1
}
}
fn is_sgr_reset(params: &[u8]) -> bool {
params.is_empty() || params == b"0" || params == b"00"
}
#[cfg(test)]
mod tests {
use super::*;
fn reset() {
FRAME.with(|f| *f.borrow_mut() = None);
PREV.with(|p| *p.borrow_mut() = None);
CLEAR_PENDING.with(|c| *c.borrow_mut() = false);
}
fn render(bytes: &[u8]) -> (Vec<u8>, bool) {
begin_frame();
frame_out().write_all(bytes).unwrap();
let mut sink = Vec::new();
let wrote = present_to(&mut sink);
(sink, wrote)
}
fn body(sink: &[u8]) -> Vec<u8> {
assert!(sink.starts_with(BEGIN_SYNC) && sink.ends_with(END_SYNC));
sink[BEGIN_SYNC.len()..sink.len() - END_SYNC.len()].to_vec()
}
#[test]
fn first_frame_emits_everything() {
reset();
let (sink, wrote) = render(b"\x1b[1;1HAAAA\x1b[2;1HBBBB");
assert!(wrote);
assert_eq!(body(&sink), b"\x1b[1;1HAAAA\x1b[2;1HBBBB");
assert_eq!(
sink.windows(BEGIN_SYNC.len())
.filter(|w| *w == BEGIN_SYNC)
.count(),
1
);
reset();
}
#[test]
fn identical_frame_writes_nothing() {
reset();
let f = b"\x1b[1;1HAAAA\x1b[2;1HBBBB";
assert!(render(f).1);
let (sink, wrote) = render(f);
assert!(!wrote, "unchanged frame must not emit");
assert!(sink.is_empty());
reset();
}
#[test]
fn only_changed_rows_are_emitted() {
reset();
assert!(render(b"\x1b[1;1HAAAA\x1b[2;1HBBBB\x1b[3;1HCCCC").1);
let (sink, wrote) = render(b"\x1b[1;1HAAAA\x1b[2;1HZZZZ\x1b[3;1HCCCC");
assert!(wrote);
assert_eq!(body(&sink), b"\x1b[2;1HZZZZ");
reset();
}
#[test]
fn changed_row_keeps_its_entry_sgr() {
reset();
assert!(render(b"\x1b[32m\x1b[1;1HG1\x1b[2;1HG2").1);
let (sink, _) = render(b"\x1b[32m\x1b[1;1HG1\x1b[2;1HXY");
assert_eq!(body(&sink), b"\x1b[32m\x1b[2;1HXY");
reset();
}
#[test]
fn toast_over_existing_row_keeps_background() {
let (s, _) = parse_frame(b"\x1b[2;1Hproc\x1b[1;1Hother\x1b[48;5;4m\x1b[2;1Htoast");
let seg = s.rows.get(&1).expect("row 2 present");
let want = b"\x1b[48;5;4m\x1b[2;1Htoast";
assert!(
seg.windows(want.len()).any(|w| w == want),
"row 2 segment missing bg before toast text: {:?}",
String::from_utf8_lossy(seg)
);
}
#[test]
fn clear_forces_full_redraw() {
reset();
let f = b"\x1b[1;1HAAAA\x1b[2;1HBBBB";
assert!(render(f).1);
let (sink, wrote) = render(b"\x1b[2J\x1b[1;1HAAAA\x1b[2;1HBBBB");
assert!(wrote);
assert!(body(&sink).windows(4).any(|w| w == b"AAAA"));
assert!(body(&sink).windows(4).any(|w| w == b"BBBB"));
reset();
}
#[test]
fn vanished_row_is_blanked() {
reset();
assert!(render(b"\x1b[1;1HAAAA\x1b[2;1HBBBB").1);
let (sink, wrote) = render(b"\x1b[1;1HAAAA");
assert!(wrote);
assert!(body(&sink).windows(6).any(|w| w == b"\x1b[2;1H"));
assert!(body(&sink).windows(3).any(|w| w == b"\x1b[K"));
reset();
}
#[test]
fn parse_splits_rows_by_cup() {
let (s, clear) = parse_frame(b"pre\x1b[3;1Hthird\x1b[1;5Hfirst");
assert!(!clear);
assert_eq!(s.preamble, b"pre");
assert_eq!(s.rows.get(&2).map(|v| &v[..]), Some(&b"\x1b[3;1Hthird"[..]));
assert_eq!(s.rows.get(&0).map(|v| &v[..]), Some(&b"\x1b[1;5Hfirst"[..]));
}
#[test]
fn parse_detects_clear() {
let (_, clear) = parse_frame(b"\x1b[2J\x1b[1;1Hx");
assert!(clear);
let (_, clear2) = parse_frame(b"\x1b[0J\x1b[1;1Hx");
assert!(!clear2);
}
#[test]
fn parse_first_param_defaults_to_one() {
assert_eq!(parse_first_param(b""), 1);
assert_eq!(parse_first_param(b"12;34"), 12);
assert_eq!(parse_first_param(b"7"), 7);
}
#[test]
fn sgr_reset_recognized() {
assert!(is_sgr_reset(b""));
assert!(is_sgr_reset(b"0"));
assert!(!is_sgr_reset(b"1"));
assert!(!is_sgr_reset(b"38;5;2"));
}
#[test]
fn truncated_csi_is_preserved() {
let (s, _) = parse_frame(b"\x1b[1;1Hx\x1b[3");
assert_eq!(
s.rows.get(&0).map(|v| &v[..]),
Some(&b"\x1b[1;1Hx\x1b[3"[..])
);
}
#[test]
fn begin_frame_buffers_and_clears() {
reset();
begin_frame();
frame_out().write_all(b"stale").unwrap();
begin_frame();
FRAME.with(|f| assert_eq!(f.borrow().as_deref(), Some(&b""[..])));
reset();
}
#[test]
fn request_clear_wipes_and_repaints_next_frame() {
reset();
let f = b"\x1b[1;1HAAAA\x1b[2;1HBBBB";
assert!(render(f).1);
request_clear();
let (sink, wrote) = render(f);
assert!(wrote, "resize recovery frame must emit");
let out = body(&sink);
assert!(
out.windows(4).any(|w| w == ERASE_DISPLAY),
"must erase display"
);
assert!(out.windows(4).any(|w| w == b"AAAA"), "row 1 re-emitted");
assert!(out.windows(4).any(|w| w == b"BBBB"), "row 2 re-emitted");
let (_, wrote2) = render(f);
assert!(!wrote2, "clear must not persist past one frame");
reset();
}
#[test]
fn present_without_frame_is_noop() {
reset();
let mut sink = Vec::new();
assert!(!present_to(&mut sink));
assert!(sink.is_empty());
reset();
}
}