use rdom_core::{NodeId, NodeType, Range};
use crate::TuiDom;
mod serialize;
pub use serialize::serialize_selection;
pub trait Clipboard: 'static {
fn read_text(&mut self) -> Option<String>;
fn write_text(&mut self, text: String);
}
pub struct SystemClipboard;
impl SystemClipboard {
pub fn new() -> Self {
Self
}
}
impl Default for SystemClipboard {
fn default() -> Self {
Self::new()
}
}
impl Clipboard for SystemClipboard {
fn read_text(&mut self) -> Option<String> {
arboard::Clipboard::new().ok()?.get_text().ok()
}
fn write_text(&mut self, text: String) {
if let Ok(mut cb) = arboard::Clipboard::new() {
let _ = cb.set_text(text);
}
}
}
#[derive(Debug, Default)]
pub struct MemoryClipboard {
buf: Option<String>,
}
impl MemoryClipboard {
pub fn new() -> Self {
Self::default()
}
pub fn with_text(text: impl Into<String>) -> Self {
Self {
buf: Some(text.into()),
}
}
pub fn peek(&self) -> Option<&str> {
self.buf.as_deref()
}
}
impl Clipboard for MemoryClipboard {
fn read_text(&mut self) -> Option<String> {
self.buf.clone()
}
fn write_text(&mut self, text: String) {
self.buf = Some(text);
}
}
pub struct OscClipboard<W: std::io::Write + Send + 'static> {
writer: W,
}
impl OscClipboard<std::io::Stdout> {
pub fn stdout() -> Self {
Self {
writer: std::io::stdout(),
}
}
}
impl<W: std::io::Write + Send + 'static> OscClipboard<W> {
pub fn with_writer(writer: W) -> Self {
Self { writer }
}
}
impl<W: std::io::Write + Send + 'static> Clipboard for OscClipboard<W> {
fn read_text(&mut self) -> Option<String> {
None
}
fn write_text(&mut self, text: String) {
let encoded = base64_encode(text.as_bytes());
let _ = write!(self.writer, "\x1b]52;c;{}\x1b\\", encoded);
let _ = self.writer.flush();
}
}
const BASE64_TABLE: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
fn base64_encode(bytes: &[u8]) -> String {
let mut out = String::with_capacity(bytes.len().div_ceil(3) * 4);
let mut chunks = bytes.chunks_exact(3);
for chunk in chunks.by_ref() {
let b0 = chunk[0] as u32;
let b1 = chunk[1] as u32;
let b2 = chunk[2] as u32;
let n = (b0 << 16) | (b1 << 8) | b2;
out.push(BASE64_TABLE[((n >> 18) & 0x3F) as usize] as char);
out.push(BASE64_TABLE[((n >> 12) & 0x3F) as usize] as char);
out.push(BASE64_TABLE[((n >> 6) & 0x3F) as usize] as char);
out.push(BASE64_TABLE[(n & 0x3F) as usize] as char);
}
let rem = chunks.remainder();
match rem.len() {
0 => {}
1 => {
let n = (rem[0] as u32) << 16;
out.push(BASE64_TABLE[((n >> 18) & 0x3F) as usize] as char);
out.push(BASE64_TABLE[((n >> 12) & 0x3F) as usize] as char);
out.push('=');
out.push('=');
}
2 => {
let n = ((rem[0] as u32) << 16) | ((rem[1] as u32) << 8);
out.push(BASE64_TABLE[((n >> 18) & 0x3F) as usize] as char);
out.push(BASE64_TABLE[((n >> 12) & 0x3F) as usize] as char);
out.push(BASE64_TABLE[((n >> 6) & 0x3F) as usize] as char);
out.push('=');
}
_ => unreachable!(),
}
out
}
pub(crate) fn copy_target(dom: &TuiDom) -> Option<NodeId> {
let sel = dom.selection()?;
element_ancestor(dom, sel.anchor.node)
}
fn element_ancestor(dom: &TuiDom, id: NodeId) -> Option<NodeId> {
let mut cur = Some(id);
while let Some(n) = cur {
if dom.node(n).node_type() == NodeType::Element {
return Some(n);
}
cur = dom.node(n).parent_node().map(|p| p.id());
}
None
}
pub(crate) fn current_selection_text(dom: &TuiDom) -> Option<(String, Range)> {
let range = dom.selection_range()?;
if range.is_collapsed() {
return None;
}
let text = serialize_selection(dom, &range);
Some((text, range))
}
#[cfg(test)]
mod tests;