use crate::ported::init::SHTTY;
use crate::ported::utils::write_loop;
use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::Mutex;
static BUF: Mutex<Vec<u8>> = Mutex::new(Vec::new());
static DEPTH: AtomicI32 = AtomicI32::new(0);
pub fn begin() {
DEPTH.fetch_add(1, Ordering::SeqCst);
}
pub fn end() {
if DEPTH.fetch_sub(1, Ordering::SeqCst) <= 1 {
DEPTH.store(0, Ordering::SeqCst);
flush();
}
}
pub fn flush() {
let pending = {
let mut buf = match BUF.lock() {
Ok(b) => b,
Err(poisoned) => poisoned.into_inner(),
};
if buf.is_empty() {
return;
}
std::mem::take(&mut *buf)
};
let fd = SHTTY.load(Ordering::Relaxed);
let _ = write_loop(if fd >= 0 { fd } else { 1 }, &pending);
}
pub fn write(bytes: &[u8]) {
if bytes.is_empty() {
return;
}
if DEPTH.load(Ordering::SeqCst) > 0 {
if let Ok(mut buf) = BUF.lock() {
buf.extend_from_slice(bytes);
return;
}
}
let fd = SHTTY.load(Ordering::Relaxed);
let _ = write_loop(if fd >= 0 { fd } else { 1 }, bytes);
}
thread_local! {
static TPUTS_SINK: std::cell::RefCell<Vec<u8>> = const { std::cell::RefCell::new(Vec::new()) };
}
extern "C" fn tputs_collect(c: libc::c_int) -> libc::c_int {
TPUTS_SINK.with(|s| s.borrow_mut().push(c as u8));
0
}
pub fn tputs(s: &str) -> Vec<u8> {
if !s.contains("$<") {
return s.as_bytes().to_vec();
}
crate::tparm::tputs(s.as_bytes(), 1, &pad_info())
}
fn pad_info() -> crate::tparm::PadInfo {
if let Ok(g) = pad_cache().lock() {
if let Some(p) = *g {
return p;
}
}
let computed = compute_pad_info();
if let Ok(mut g) = pad_cache().lock() {
*g = Some(computed);
}
computed
}
fn pad_cache() -> &'static std::sync::Mutex<Option<crate::tparm::PadInfo>> {
static C: std::sync::OnceLock<std::sync::Mutex<Option<crate::tparm::PadInfo>>> =
std::sync::OnceLock::new();
C.get_or_init(|| std::sync::Mutex::new(None))
}
pub fn invalidate_pad_info() {
if let Ok(mut g) = pad_cache().lock() {
*g = None;
}
}
fn compute_pad_info() -> crate::tparm::PadInfo {
use crate::terminfo_db;
let baud = unsafe {
let mut t: libc::termios = std::mem::zeroed();
if libc::tcgetattr(libc::STDOUT_FILENO, &mut t) == 0 {
libc::cfgetospeed(&t) as i32
} else {
0
}
};
crate::tparm::PadInfo {
baud,
xon: terminfo_db::tigetflag("xon") == 1,
padding_baud_rate: terminfo_db::tigetnum("pb").max(0),
no_pad_char: terminfo_db::tigetflag("npc") == 1,
pad_char: terminfo_db::tigetstr("pad")
.ok()
.flatten()
.and_then(|v| v.first().copied())
.unwrap_or(0),
}
}
pub fn tputs_write(s: &str) {
write(&tputs(s));
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tputs_passes_through_unpadded_capability() {
assert_eq!(tputs("\x1b[1m"), b"\x1b[1m".to_vec());
assert_eq!(tputs(""), Vec::<u8>::new());
}
#[test]
fn tputs_strips_delay_specification() {
let out = tputs("\x1b[1m$<2>");
assert!(
out.starts_with(b"\x1b[1m"),
"capability text must survive: {:?}",
out
);
assert!(
!out.windows(2).any(|w| w == b"$<"),
"delay spec must not reach the terminal: {:?}",
out
);
}
#[test]
fn nested_region_defers_until_outermost_end() {
let _g = crate::test_util::global_state_lock();
let mut fds: [libc::c_int; 2] = [0; 2];
assert_eq!(unsafe { libc::pipe(fds.as_mut_ptr()) }, 0, "pipe(2) ok");
let saved = SHTTY.load(Ordering::Relaxed);
SHTTY.store(fds[1], Ordering::Relaxed);
begin();
write(b"outer-");
begin();
write(b"inner");
end(); let mut probe = [0u8; 16];
let ready = unsafe {
let mut set: libc::fd_set = std::mem::zeroed();
libc::FD_SET(fds[0], &mut set);
let mut tv = libc::timeval {
tv_sec: 0,
tv_usec: 0,
};
libc::select(
fds[0] + 1,
&mut set,
std::ptr::null_mut(),
std::ptr::null_mut(),
&mut tv,
)
};
assert_eq!(ready, 0, "inner end must not flush the outer frame");
end();
let n = unsafe { libc::read(fds[0], probe.as_mut_ptr() as *mut libc::c_void, probe.len()) };
SHTTY.store(saved, Ordering::Relaxed);
unsafe {
libc::close(fds[0]);
libc::close(fds[1]);
}
assert!(n > 0, "frame reached the fd");
assert_eq!(&probe[..n as usize], b"outer-inner");
}
#[test]
fn write_outside_region_is_immediate() {
let _g = crate::test_util::global_state_lock();
let mut fds: [libc::c_int; 2] = [0; 2];
assert_eq!(unsafe { libc::pipe(fds.as_mut_ptr()) }, 0, "pipe(2) ok");
let saved = SHTTY.load(Ordering::Relaxed);
SHTTY.store(fds[1], Ordering::Relaxed);
write(b"direct");
let mut probe = [0u8; 16];
let n = unsafe { libc::read(fds[0], probe.as_mut_ptr() as *mut libc::c_void, probe.len()) };
SHTTY.store(saved, Ordering::Relaxed);
unsafe {
libc::close(fds[0]);
libc::close(fds[1]);
}
assert_eq!(&probe[..n.max(0) as usize], b"direct");
}
}