wsl-clip-core 0.5.1

Core library for wsl-clip clipboard bridge
Documentation
// <FILE>wsl_clip_core/src/bridge/fnc_clipboard_apply.rs</FILE> - <DESC>Minimal bridge clipboard application helper</DESC>
// <VERS>VERSION: 0.2.0 - 2025-12-08T00:00:00Z</VERS>
// <WCTX>Phase 2: first happy-path clipboard integration via SessionStore (text only).</WCTX>
// <CLOG>Added injectable clipboard apply helper and unit test; listener will reuse for happy-path.</CLOG>

use crate::bridge::fnc_session_store::{BridgeSessionId, SessionPayload, SessionStore};
use crate::clipboard;
use anyhow::Result;

/// Options controlling how session payloads are written to the clipboard.
#[derive(Debug, Clone)]
pub struct BridgeClipboardOptions {
    pub strip_ansi: bool,
    pub use_crlf: bool,
}

impl Default for BridgeClipboardOptions {
    fn default() -> Self {
        Self {
            strip_ansi: true,
            use_crlf: true,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BridgeClipboardMode {
    Text,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BridgeClipboardResult {
    pub mode: BridgeClipboardMode,
    pub bytes_copied: u64,
}

/// Internal helper to allow testing with a fake clipboard setter.
pub fn apply_session_to_clipboard_with<F>(
    store: &mut SessionStore,
    id: BridgeSessionId,
    _opts: &BridgeClipboardOptions,
    set_text: F,
) -> Result<BridgeClipboardResult>
where
    F: Fn(&str) -> Result<()>,
{
    let payload = store.get_payload(id)?;
    match payload {
        SessionPayload::InMemory(bytes) => {
            let text = std::str::from_utf8(&bytes)
                .map_err(|e| anyhow::anyhow!("invalid UTF-8 in session payload: {e}"))?;
            set_text(text)?;
            Ok(BridgeClipboardResult {
                mode: BridgeClipboardMode::Text,
                bytes_copied: bytes.len() as u64,
            })
        }
        SessionPayload::TempFile(path) => {
            let bytes = std::fs::read(&path)
                .map_err(|e| anyhow::anyhow!("failed to read session temp file: {e}"))?;
            let _ = std::fs::remove_file(&path);
            let text = std::str::from_utf8(&bytes)
                .map_err(|e| anyhow::anyhow!("invalid UTF-8 in session payload: {e}"))?;
            set_text(text)?;
            Ok(BridgeClipboardResult {
                mode: BridgeClipboardMode::Text,
                bytes_copied: bytes.len() as u64,
            })
        }
    }
}

/// Apply a finalized session to the clipboard (text-only for Phase 2).
/// - Treats payload as UTF-8 text; errors on invalid UTF-8.
/// - Uses clipboard::set_text_content directly (no extra formatting yet).
pub fn apply_session_to_clipboard(
    store: &mut SessionStore,
    id: BridgeSessionId,
    _opts: &BridgeClipboardOptions,
) -> Result<BridgeClipboardResult> {
    apply_session_to_clipboard_with(store, id, _opts, clipboard::set_text_content)
}

/// Apply a warning message directly to the clipboard (used for TTL/size violations).
pub fn apply_warning_to_clipboard<F>(message: &str, set_text: F) -> Result<BridgeClipboardResult>
where
    F: Fn(&str) -> Result<()>,
{
    set_text(message)?;
    Ok(BridgeClipboardResult {
        mode: BridgeClipboardMode::Text,
        bytes_copied: message.len() as u64,
    })
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::bridge::fnc_session_store::{SessionStore, SessionStoreConfig};
    use std::sync::{Arc, Mutex};
    use std::time::Duration;

    #[test]
    fn apply_uses_injected_clipboard() {
        let cfg = SessionStoreConfig {
            default_max_bytes: 1024,
            default_ttl: Duration::from_secs(60),
            default_idle_exit: Duration::from_secs(60),
            spill_threshold: 8,
        };
        let store = SessionStore::new(cfg);
        let mut store = store.lock().unwrap();
        let id = store.open_session(None);
        store.put_chunk(id, b"hello").unwrap();
        store.finalize(id).unwrap();

        let seen = Arc::new(Mutex::new(None::<String>));
        let seen_clone = seen.clone();
        let res = apply_session_to_clipboard_with(
            &mut store,
            id,
            &BridgeClipboardOptions::default(),
            |text| {
                *seen_clone.lock().unwrap() = Some(text.to_string());
                Ok(())
            },
        )
        .unwrap();

        assert_eq!(res.bytes_copied, 5);
        assert_eq!(seen.lock().unwrap().as_deref(), Some("hello"));
    }

    #[test]
    fn apply_warning_writes_message() {
        let seen = Arc::new(Mutex::new(None::<String>));
        let seen_clone = seen.clone();
        let res = apply_warning_to_clipboard("warn-msg", |text| {
            *seen_clone.lock().unwrap() = Some(text.to_string());
            Ok(())
        })
        .unwrap();

        assert_eq!(res.bytes_copied, 8);
        assert_eq!(seen.lock().unwrap().as_deref(), Some("warn-msg"));
    }
}

// <FILE>wsl_clip_core/src/bridge/fnc_clipboard_apply.rs</FILE> - <DESC>Minimal bridge clipboard application helper</DESC>
// <VERS>END OF VERSION: 0.2.0 - 2025-12-08T00:00:00Z</VERS>