use std::{
io::{self, Write},
process::{Command, Stdio},
};
pub fn copy(text: &str) -> io::Result<()> {
let native = native_copy(text);
let osc52 = osc52(text);
if native {
Ok(())
} else {
osc52
}
}
fn osc52(text: &str) -> io::Result<()> {
let mut stdout = io::stdout();
write!(stdout, "\x1b]52;c;{}\x07", base64(text.as_bytes()))?;
stdout.flush()
}
fn native_copy(text: &str) -> bool {
clipboard_commands()
.iter()
.any(|(command, args)| pipe_to(command, args, text).is_ok())
}
fn clipboard_commands() -> &'static [(&'static str, &'static [&'static str])] {
if cfg!(target_os = "macos") {
&[("pbcopy", &[])]
} else if cfg!(target_os = "windows") {
&[("clip", &[])]
} else {
&[
("wl-copy", &[]),
("xclip", &["-selection", "clipboard"]),
("xsel", &["-b"]),
]
}
}
fn pipe_to(command: &str, args: &[&str], text: &str) -> io::Result<()> {
let mut child = Command::new(command)
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()?;
child
.stdin
.take()
.ok_or_else(|| io::Error::other("clipboard command has no stdin"))?
.write_all(text.as_bytes())?;
child.wait()?;
Ok(())
}
fn base64(input: &[u8]) -> String {
const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
input
.chunks(3)
.flat_map(|chunk| {
let [a, b, c] = [
chunk[0],
*chunk.get(1).unwrap_or(&0),
*chunk.get(2).unwrap_or(&0),
];
let triple = (a as u32) << 16 | (b as u32) << 8 | c as u32;
let indices = [
triple >> 18 & 0x3f,
triple >> 12 & 0x3f,
triple >> 6 & 0x3f,
triple & 0x3f,
];
indices.into_iter().enumerate().map(move |(i, index)| {
if i > chunk.len() {
b'='
} else {
ALPHABET[index as usize]
}
})
})
.map(char::from)
.collect()
}
#[cfg(test)]
mod tests {
use super::base64;
#[test]
fn test_base64_matches_known_vectors() {
let cases = [
("", ""),
("f", "Zg=="),
("fo", "Zm8="),
("foo", "Zm9v"),
("foob", "Zm9vYg=="),
("fooba", "Zm9vYmE="),
("foobar", "Zm9vYmFy"),
("hello world", "aGVsbG8gd29ybGQ="),
];
cases
.into_iter()
.for_each(|(input, expected)| assert_eq!(base64(input.as_bytes()), expected));
}
}