#![forbid(unsafe_code)]
use std::io::{self, Write};
use crate::terminal_capabilities::TerminalCapabilities;
const ESC: u8 = 0x1b;
const ST: &[u8] = b"\x1b\\";
pub fn tmux_wrap<W: Write>(w: &mut W, sequence: &[u8]) -> io::Result<()> {
w.write_all(b"\x1bPtmux;")?;
for &byte in sequence {
if byte == ESC {
w.write_all(&[ESC, ESC])?;
} else {
w.write_all(&[byte])?;
}
}
w.write_all(ST)
}
pub fn screen_wrap<W: Write>(w: &mut W, sequence: &[u8]) -> io::Result<()> {
w.write_all(b"\x1bP")?;
w.write_all(sequence)?;
w.write_all(ST)
}
pub fn mux_wrap<W: Write>(
w: &mut W,
caps: &TerminalCapabilities,
sequence: &[u8],
) -> io::Result<()> {
if caps.in_tmux {
tmux_wrap(w, sequence)
} else if caps.in_screen {
screen_wrap(w, sequence)
} else {
w.write_all(sequence)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn to_bytes<F: FnOnce(&mut Vec<u8>) -> io::Result<()>>(f: F) -> Vec<u8> {
let mut buf = Vec::new();
f(&mut buf).unwrap();
buf
}
#[test]
fn tmux_wrap_doubles_escapes() {
let osc8 = b"\x1b]8;;https://example.com\x07";
let wrapped = to_bytes(|w| tmux_wrap(w, osc8));
assert!(wrapped.starts_with(b"\x1bPtmux;"));
assert!(wrapped.ends_with(b"\x1b\\"));
let inner = &wrapped[7..wrapped.len() - 2]; let esc_count = inner.windows(2).filter(|w| w == &[ESC, ESC]).count();
assert_eq!(
esc_count, 1,
"Only the ESC from OSC start should be doubled"
);
}
#[test]
fn tmux_wrap_no_escape_passthrough() {
let plain = b"hello world";
let wrapped = to_bytes(|w| tmux_wrap(w, plain));
assert_eq!(wrapped, b"\x1bPtmux;hello world\x1b\\");
}
#[test]
fn tmux_wrap_empty_sequence() {
let wrapped = to_bytes(|w| tmux_wrap(w, b""));
assert_eq!(wrapped, b"\x1bPtmux;\x1b\\");
}
#[test]
fn screen_wrap_basic() {
let seq = b"\x1b]8;;https://example.com\x07";
let wrapped = to_bytes(|w| screen_wrap(w, seq));
assert!(wrapped.starts_with(b"\x1bP"));
assert!(wrapped.ends_with(b"\x1b\\"));
let inner = &wrapped[2..wrapped.len() - 2];
assert_eq!(inner, seq);
}
#[test]
fn screen_wrap_does_not_double_escapes() {
let seq = b"\x1b[?2026h"; let wrapped = to_bytes(|w| screen_wrap(w, seq));
assert_eq!(wrapped, b"\x1bP\x1b[?2026h\x1b\\");
}
#[test]
fn mux_wrap_selects_tmux() {
let mut caps = TerminalCapabilities::basic();
caps.in_tmux = true;
let seq = b"\x1b[?2026h";
let result = to_bytes(|w| mux_wrap(w, &caps, seq));
assert!(result.starts_with(b"\x1bPtmux;"));
}
#[test]
fn mux_wrap_selects_screen() {
let mut caps = TerminalCapabilities::basic();
caps.in_screen = true;
let seq = b"\x1b[?2026h";
let result = to_bytes(|w| mux_wrap(w, &caps, seq));
assert!(result.starts_with(b"\x1bP"));
assert!(!result.starts_with(b"\x1bPtmux;")); }
#[test]
fn mux_wrap_passthrough_for_zellij() {
let mut caps = TerminalCapabilities::basic();
caps.in_zellij = true;
let seq = b"\x1b[?2026h";
let result = to_bytes(|w| mux_wrap(w, &caps, seq));
assert_eq!(result, seq);
}
#[test]
fn mux_wrap_passthrough_for_bare_terminal() {
let caps = TerminalCapabilities::basic();
let seq = b"\x1b[?2026h";
let result = to_bytes(|w| mux_wrap(w, &caps, seq));
assert_eq!(result, seq);
}
#[test]
fn tmux_priority_over_screen() {
let mut caps = TerminalCapabilities::basic();
caps.in_tmux = true;
caps.in_screen = true;
let seq = b"test";
let result = to_bytes(|w| mux_wrap(w, &caps, seq));
assert!(result.starts_with(b"\x1bPtmux;"));
}
#[test]
fn tmux_wrap_multiple_escapes() {
let seq = &[ESC, b'[', b'm', ESC, b']', b'0', ESC, b'\\'];
let wrapped = to_bytes(|w| tmux_wrap(w, seq));
let inner = &wrapped[7..wrapped.len() - 2];
let esc_count = inner.iter().filter(|&&b| b == ESC).count();
assert_eq!(esc_count, 6, "3 ESC bytes should become 6 (doubled)");
}
#[test]
fn tmux_wrap_all_escape_bytes() {
let seq = &[ESC, ESC, ESC];
let wrapped = to_bytes(|w| tmux_wrap(w, seq));
let inner = &wrapped[7..wrapped.len() - 2];
assert_eq!(inner.len(), 6);
assert!(inner.iter().all(|&b| b == ESC));
}
#[test]
fn tmux_wrap_preserves_non_escape_bytes() {
let seq = b"ABCDEF";
let wrapped = to_bytes(|w| tmux_wrap(w, seq));
assert_eq!(wrapped, b"\x1bPtmux;ABCDEF\x1b\\");
}
#[test]
fn tmux_wrap_binary_data() {
let seq: Vec<u8> = (0u8..=255).filter(|&b| b != ESC).collect();
let wrapped = to_bytes(|w| tmux_wrap(w, &seq));
let inner = &wrapped[7..wrapped.len() - 2];
assert_eq!(inner.len(), seq.len());
}
#[test]
fn screen_wrap_empty_sequence() {
let wrapped = to_bytes(|w| screen_wrap(w, b""));
assert_eq!(wrapped, b"\x1bP\x1b\\");
}
#[test]
fn screen_wrap_preserves_all_bytes() {
let seq = &[ESC, ESC, 0x00, 0xFF];
let wrapped = to_bytes(|w| screen_wrap(w, seq));
let inner = &wrapped[2..wrapped.len() - 2];
assert_eq!(inner, seq);
}
#[test]
fn mux_wrap_tmux_priority_over_zellij() {
let mut caps = TerminalCapabilities::basic();
caps.in_tmux = true;
caps.in_zellij = true;
let result = to_bytes(|w| mux_wrap(w, &caps, b"x"));
assert!(result.starts_with(b"\x1bPtmux;"));
}
#[test]
fn mux_wrap_screen_priority_over_zellij() {
let mut caps = TerminalCapabilities::basic();
caps.in_screen = true;
caps.in_zellij = true;
let result = to_bytes(|w| mux_wrap(w, &caps, b"x"));
assert!(result.starts_with(b"\x1bP"));
assert!(!result.starts_with(b"\x1bPtmux;"));
}
#[test]
fn esc_constant_value() {
assert_eq!(ESC, 0x1b);
}
#[test]
fn st_constant_value() {
assert_eq!(ST, b"\x1b\\");
}
#[test]
fn tmux_wrap_large_sequence() {
let seq = vec![b'A'; 10_000];
let wrapped = to_bytes(|w| tmux_wrap(w, &seq));
assert_eq!(wrapped.len(), 10_009);
}
#[test]
fn screen_wrap_large_sequence() {
let seq = vec![b'B'; 10_000];
let wrapped = to_bytes(|w| screen_wrap(w, &seq));
assert_eq!(wrapped.len(), 10_004);
}
}