use crossterm::clipboard::CopyToClipboard;
use crossterm::execute;
use std::io::{stdout, Write};
use std::sync::Mutex;
static SYSTEM_CLIPBOARD: Mutex<Option<arboard::Clipboard>> = Mutex::new(None);
#[derive(Debug, Clone, Default)]
pub struct Clipboard {
internal: String,
}
impl Clipboard {
pub fn new() -> Self {
Self {
internal: String::new(),
}
}
pub fn copy(&mut self, text: String) {
self.internal = text.clone();
let osc52_result = execute!(stdout(), CopyToClipboard::to_clipboard_from(&text));
if let Err(e) = &osc52_result {
tracing::debug!("Crossterm OSC 52 clipboard copy failed: {}", e);
}
let _ = stdout().flush();
if let Ok(mut guard) = SYSTEM_CLIPBOARD.lock() {
if guard.is_none() {
match arboard::Clipboard::new() {
Ok(cb) => *guard = Some(cb),
Err(e) => {
tracing::debug!("arboard clipboard init failed: {}", e);
}
}
}
if let Some(clipboard) = guard.as_mut() {
if let Err(e) = clipboard.set_text(&text) {
tracing::debug!("arboard copy failed: {}, recreating clipboard", e);
drop(guard);
if let Ok(mut guard) = SYSTEM_CLIPBOARD.lock() {
if let Ok(new_clipboard) = arboard::Clipboard::new() {
*guard = Some(new_clipboard);
if let Some(cb) = guard.as_mut() {
let _ = cb.set_text(&text);
}
}
}
}
}
}
}
pub fn paste(&mut self) -> Option<String> {
if let Ok(mut guard) = SYSTEM_CLIPBOARD.lock() {
if guard.is_none() {
if let Ok(cb) = arboard::Clipboard::new() {
*guard = Some(cb);
}
}
if let Some(clipboard) = guard.as_mut() {
if let Ok(text) = clipboard.get_text() {
if !text.is_empty() {
self.internal = text.clone();
return Some(text);
}
}
}
}
if self.internal.is_empty() {
None
} else {
Some(self.internal.clone())
}
}
pub fn get_internal(&self) -> &str {
&self.internal
}
pub fn set_internal(&mut self, text: String) {
self.internal = text;
}
pub fn paste_internal(&self) -> Option<String> {
if self.internal.is_empty() {
None
} else {
Some(self.internal.clone())
}
}
pub fn is_empty(&self) -> bool {
if !self.internal.is_empty() {
return false;
}
if let Ok(mut guard) = SYSTEM_CLIPBOARD.lock() {
if guard.is_none() {
if let Ok(cb) = arboard::Clipboard::new() {
*guard = Some(cb);
}
}
if let Some(clipboard) = guard.as_mut() {
if let Ok(text) = clipboard.get_text() {
return text.is_empty();
}
}
}
true
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_clipboard_internal() {
let mut clipboard = Clipboard::new();
assert!(clipboard.get_internal().is_empty());
clipboard.set_internal("test".to_string());
assert_eq!(clipboard.get_internal(), "test");
}
#[test]
fn test_clipboard_copy_updates_internal() {
let mut clipboard = Clipboard::new();
clipboard.copy("hello".to_string());
assert_eq!(clipboard.get_internal(), "hello");
}
}