use std::{
collections::HashMap,
sync::atomic::{AtomicU64, Ordering},
time::Duration,
};
use {parking_lot::RwLock, tokio::sync::oneshot};
pub const CAPTURE_TIMEOUT_SECS: u64 = 5;
#[derive(Debug)]
pub enum CaptureError {
NoTuiClient,
Timeout,
Disconnected,
InvalidResponse(String),
}
impl std::fmt::Display for CaptureError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NoTuiClient => {
write!(f, "No TUI client connected. Start TUI first with `reovim tui`")
}
Self::Timeout => {
write!(f, "TUI capture timeout ({CAPTURE_TIMEOUT_SECS}s). TUI may be frozen")
}
Self::Disconnected => write!(f, "TUI client disconnected during capture"),
Self::InvalidResponse(msg) => write!(f, "Invalid capture response: {msg}"),
}
}
}
impl std::error::Error for CaptureError {}
#[derive(Debug, Clone)]
pub struct CaptureResult {
pub width: u64,
pub height: u64,
pub format: String,
pub content: String,
}
pub struct CaptureTracker {
next_id: AtomicU64,
pending: RwLock<HashMap<u64, PendingCapture>>,
}
struct PendingCapture {
sender: oneshot::Sender<CaptureResult>,
}
impl CaptureTracker {
#[must_use]
pub fn new() -> Self {
Self {
next_id: AtomicU64::new(1),
pending: RwLock::new(HashMap::new()),
}
}
pub fn create_pending(&self) -> (u64, oneshot::Receiver<CaptureResult>) {
let id = self.next_id.fetch_add(1, Ordering::Relaxed);
let (tx, rx) = oneshot::channel();
self.pending
.write()
.insert(id, PendingCapture { sender: tx });
(id, rx)
}
pub fn deliver_response(&self, request_id: u64, result: CaptureResult) -> bool {
let pending = self.pending.write().remove(&request_id);
if let Some(pending) = pending {
let _ = pending.sender.send(result);
true
} else {
tracing::warn!("Received capture response for unknown request {request_id}");
false
}
}
pub fn cancel(&self, request_id: u64) -> bool {
self.pending.write().remove(&request_id).is_some()
}
#[must_use]
pub fn pending_count(&self) -> usize {
self.pending.read().len()
}
}
impl Default for CaptureTracker {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for CaptureTracker {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CaptureTracker")
.field("pending_count", &self.pending_count())
.finish()
}
}
pub async fn wait_for_capture(
rx: oneshot::Receiver<CaptureResult>,
) -> Result<CaptureResult, CaptureError> {
match tokio::time::timeout(Duration::from_secs(CAPTURE_TIMEOUT_SECS), rx).await {
Ok(Ok(result)) => Ok(result),
Ok(Err(_)) => Err(CaptureError::Disconnected),
Err(_) => Err(CaptureError::Timeout),
}
}
#[cfg(test)]
#[path = "capture_tests.rs"]
mod tests;