use std::io::{self, Write};
use crate::base64::base64_encode;
pub const OSC52_MAX: usize = 74_000;
pub fn is_in_tmux() -> bool {
std::env::var_os("TMUX").is_some()
}
pub fn write_osc52(out: &mut impl Write, text: &str, in_tmux: bool) -> io::Result<()> {
let encoded = base64_encode(text.as_bytes());
if in_tmux {
write!(out, "\x1bPtmux;\x1b\x1b]52;c;{encoded}\x07\x1b\\")?;
} else {
write!(out, "\x1b]52;c;{encoded}\x07")?;
}
out.flush()
}
#[cfg(test)]
mod tests {
use super::*;
fn base64_decode(s: &str) -> Vec<u8> {
const ALPHA: &[u8; 64] =
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let val = |c: u8| -> u8 {
ALPHA
.iter()
.position(|&a| a == c)
.expect("invalid base64 char") as u8
};
let s = s.trim_end_matches('=');
let mut out = Vec::with_capacity(s.len() * 3 / 4);
let bytes = s.as_bytes();
let mut i = 0;
while i + 3 < bytes.len() {
let b = (val(bytes[i]) as u32) << 18
| (val(bytes[i + 1]) as u32) << 12
| (val(bytes[i + 2]) as u32) << 6
| (val(bytes[i + 3]) as u32);
out.push((b >> 16) as u8);
out.push((b >> 8) as u8);
out.push(b as u8);
i += 4;
}
let rem = bytes.len() - i;
if rem == 2 {
let b = (val(bytes[i]) as u32) << 18 | (val(bytes[i + 1]) as u32) << 12;
out.push((b >> 16) as u8);
} else if rem == 3 {
let b = (val(bytes[i]) as u32) << 18
| (val(bytes[i + 1]) as u32) << 12
| (val(bytes[i + 2]) as u32) << 6;
out.push((b >> 16) as u8);
out.push((b >> 8) as u8);
}
out
}
fn extract_body_plain(buf: &[u8]) -> &str {
let s = std::str::from_utf8(buf).expect("not utf8");
let s = s.strip_prefix("\x1b]52;c;").expect("missing OSC prefix");
s.strip_suffix('\x07').expect("missing BEL suffix")
}
fn extract_body_tmux(buf: &[u8]) -> &str {
let s = std::str::from_utf8(buf).expect("not utf8");
let s = s
.strip_prefix("\x1bPtmux;\x1b\x1b]52;c;")
.expect("missing DCS prefix");
s.strip_suffix("\x07\x1b\\").expect("missing DCS suffix")
}
#[test]
fn non_tmux_known_payload() {
let mut buf = Vec::new();
write_osc52(&mut buf, "hello", false).unwrap();
let body = extract_body_plain(&buf);
let decoded = base64_decode(body);
assert_eq!(decoded, b"hello");
}
#[test]
fn tmux_known_payload() {
let mut buf = Vec::new();
write_osc52(&mut buf, "hello", true).unwrap();
let body = extract_body_tmux(&buf);
let decoded = base64_decode(body);
assert_eq!(decoded, b"hello");
}
#[test]
fn non_tmux_empty_payload() {
let mut buf = Vec::new();
write_osc52(&mut buf, "", false).unwrap();
let body = extract_body_plain(&buf);
assert_eq!(body, ""); }
#[test]
fn tmux_empty_payload() {
let mut buf = Vec::new();
write_osc52(&mut buf, "", true).unwrap();
let body = extract_body_tmux(&buf);
assert_eq!(body, "");
}
#[test]
fn write_osc52_oversize_does_not_check() {
let big = "x".repeat(55_501);
let mut buf = Vec::new();
write_osc52(&mut buf, &big, false).unwrap();
assert!(!buf.is_empty(), "bytes should be written");
}
#[test]
fn non_ascii_and_non_printable_round_trip() {
let payload = "\x00\x01\x02\x7f\u{0080}\u{0081}\u{FFFD}";
let mut buf = Vec::new();
write_osc52(&mut buf, payload, false).unwrap();
let body = extract_body_plain(&buf);
let decoded = base64_decode(body);
assert_eq!(decoded, payload.as_bytes());
}
#[test]
fn wire_prefix_suffix_non_tmux() {
let mut buf = Vec::new();
write_osc52(&mut buf, "test", false).unwrap();
let s = std::str::from_utf8(&buf).unwrap();
assert!(s.starts_with("\x1b]52;c;"), "wrong OSC prefix");
assert!(s.ends_with('\x07'), "wrong BEL suffix");
}
#[test]
fn wire_prefix_suffix_tmux() {
let mut buf = Vec::new();
write_osc52(&mut buf, "test", true).unwrap();
let s = std::str::from_utf8(&buf).unwrap();
assert!(
s.starts_with("\x1bPtmux;\x1b\x1b]52;c;"),
"wrong DCS prefix"
);
assert!(s.ends_with("\x07\x1b\\"), "wrong DCS suffix");
}
}