diffs-rs 0.1.1

Fast terminal diff viewer and embeddable Rust diff engine
Documentation
use std::{
    io,
    sync::{
        Arc,
        atomic::{AtomicBool, AtomicI32, Ordering},
        mpsc::{self, Receiver, SyncSender, TryRecvError},
    },
    thread::{self, JoinHandle},
    time::Duration,
};

use anyhow::Result;
use crossterm::event::{self, Event};
use signal_hook::iterator::{Handle, Signals};

pub struct TerminalInput {
    keys: Receiver<io::Result<Event>>,
    signal: Arc<AtomicI32>,
    stop: Arc<AtomicBool>,
    signals: Handle,
    key_thread: Option<JoinHandle<()>>,
    signal_thread: Option<JoinHandle<()>>,
}

impl TerminalInput {
    pub fn start(wake: SyncSender<()>) -> Result<Self> {
        let mut events = Signals::new([
            signal_hook::consts::SIGINT,
            signal_hook::consts::SIGTERM,
            signal_hook::consts::SIGHUP,
        ])?;
        let signals = events.handle();
        let signal = Arc::new(AtomicI32::new(0));
        let target = signal.clone();
        let notify = wake.clone();
        let signal_thread = thread::spawn(move || {
            for event in events.forever() {
                target.store(event, Ordering::Relaxed);
                let _ = notify.try_send(());
            }
        });
        let stop = Arc::new(AtomicBool::new(false));
        let stopped = stop.clone();
        let (send, keys) = mpsc::sync_channel(1);
        let key_thread = thread::spawn(move || {
            while !stopped.load(Ordering::Acquire) {
                let mut event = match event::poll(Duration::from_millis(25)) {
                    Ok(false) => continue,
                    Ok(true) => event::read(),
                    Err(error) => Err(error),
                };
                let failed = event.is_err();
                loop {
                    if stopped.load(Ordering::Acquire) {
                        return;
                    }
                    match send.try_send(event) {
                        Ok(()) => break,
                        Err(mpsc::TrySendError::Disconnected(_)) => return,
                        Err(mpsc::TrySendError::Full(pending)) => event = pending,
                    }
                    thread::sleep(Duration::from_millis(5));
                }
                let _ = wake.try_send(());
                if failed {
                    return;
                }
            }
        });
        Ok(Self {
            keys,
            signal,
            stop,
            signals,
            key_thread: Some(key_thread),
            signal_thread: Some(signal_thread),
        })
    }

    pub fn signal(&self) -> i32 {
        self.signal.load(Ordering::Relaxed)
    }

    pub fn try_event(&self) -> Result<io::Result<Event>, TryRecvError> {
        self.keys.try_recv()
    }

    pub fn close(&mut self) -> Result<()> {
        self.stop.store(true, Ordering::Release);
        self.signals.close();
        let keys = self.key_thread.take().map(|thread| thread.join());
        let signals = self.signal_thread.take().map(|thread| thread.join());
        let keys = keys
            .transpose()
            .map(|_| ())
            .map_err(|_| anyhow::anyhow!("The terminal input worker panicked"));
        let signals = signals
            .transpose()
            .map(|_| ())
            .map_err(|_| anyhow::anyhow!("The signal worker panicked"));
        crate::cleanup::finish(keys, signals)
    }
}

impl Drop for TerminalInput {
    fn drop(&mut self) {
        let _ = self.close();
    }
}