use std::io::{self, Write};
const MAX_PAYLOAD: usize = 4096;
pub fn copy(text: &str) -> Result<(), &'static str> {
if text.is_empty() {
return Err("nothing to copy");
}
if text.len() > MAX_PAYLOAD {
return Err("selection is too large to copy");
}
let payload = base64(text.as_bytes());
let seq = format!("\x1b]52;c;{payload}\x07");
let mut out = io::stdout();
out.write_all(seq.as_bytes())
.and_then(|()| out.flush())
.map_err(|_| "terminal write failed")
}
fn base64(input: &[u8]) -> String {
const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut out = String::with_capacity(input.len().div_ceil(3) * 4);
for chunk in input.chunks(3) {
let b = [
chunk[0],
*chunk.get(1).unwrap_or(&0),
*chunk.get(2).unwrap_or(&0),
];
let n = (u32::from(b[0]) << 16) | (u32::from(b[1]) << 8) | u32::from(b[2]);
let idx = [
(n >> 18) & 0x3f,
(n >> 12) & 0x3f,
(n >> 6) & 0x3f,
n & 0x3f,
];
out.push(ALPHABET[idx[0] as usize] as char);
out.push(ALPHABET[idx[1] as usize] as char);
out.push(if chunk.len() > 1 {
ALPHABET[idx[2] as usize] as char
} else {
'='
});
out.push(if chunk.len() > 2 {
ALPHABET[idx[3] as usize] as char
} else {
'='
});
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn base64_matches_the_rfc_test_vectors() {
assert_eq!(base64(b""), "");
assert_eq!(base64(b"f"), "Zg==");
assert_eq!(base64(b"fo"), "Zm8=");
assert_eq!(base64(b"foo"), "Zm9v");
assert_eq!(base64(b"foob"), "Zm9vYg==");
assert_eq!(base64(b"fooba"), "Zm9vYmE=");
assert_eq!(base64(b"foobar"), "Zm9vYmFy");
}
#[test]
fn base64_handles_binary_and_high_bytes() {
assert_eq!(base64(&[0x00, 0x00, 0x00]), "AAAA");
assert_eq!(base64(&[0xff, 0xff, 0xff]), "////");
assert_eq!(base64(&[0xfb, 0xff, 0xbf]), "+/+/");
}
#[test]
fn base64_output_length_is_always_a_multiple_of_four() {
for len in 0..64 {
let input = vec![b'x'; len];
assert_eq!(base64(&input).len() % 4, 0, "bad padding at length {len}");
}
}
#[test]
fn base64_round_trips_a_realistic_container_id() {
let id = "3f2b9c1a4e8d7f6b5a4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2b";
let encoded = base64(id.as_bytes());
assert!(encoded.is_ascii());
assert!(!encoded.contains('\n'), "OSC 52 payloads must be one line");
}
#[test]
fn empty_selection_is_rejected() {
assert!(copy("").is_err());
}
#[test]
fn oversized_selection_is_rejected() {
let big = "x".repeat(MAX_PAYLOAD + 1);
assert!(copy(&big).is_err());
}
}