dyd 1.7.0

CLI for daily diffing of git repos
Documentation
use crate::app::AppResult;
use crate::git::repo::{Log, RepoStatus};
use crossterm::event::{self, Event as CrosstermEvent, KeyEvent, MouseEvent};
use std::sync::mpsc;
use std::thread;
use std::time::{Duration, Instant};

/// Terminal events.
#[derive(Clone, Debug)]
pub enum Event {
  /// Terminal tick.
  Tick(mpsc::Sender<Event>),
  /// Key press.
  Key(KeyEvent),
  /// Mouse click/scroll.
  Mouse(MouseEvent),
  /// Terminal resize.
  Resize(u16, u16),
  /// The current state of a Repo has changed.
  RepoStatusChange(String, RepoStatus),
  /// The Repo git actions are complete
  RepoStatusComplete(String, Vec<Log>),
}

/// Terminal event handler.
#[derive(Debug)]
pub struct EventHandler {
  /// Event sender channel.
  pub sender: mpsc::Sender<Event>,
  /// Event receiver channel.
  receiver: mpsc::Receiver<Event>,
  /// Event handler thread.
  pub handler: thread::JoinHandle<()>,
}

impl EventHandler {
  /// Constructs a new instance of [`EventHandler`].
  pub fn new(tick_rate: u64) -> Self {
    let tick_rate = Duration::from_millis(tick_rate);
    let (sender, receiver) = mpsc::channel();
    let handler = {
      let sender = sender.clone();
      thread::spawn(move || {
        let mut last_tick = Instant::now();
        loop {
          let timeout = tick_rate
            .checked_sub(last_tick.elapsed())
            .unwrap_or(tick_rate);

          if event::poll(timeout).expect("no events available") {
            match event::read().expect("unable to read event") {
              CrosstermEvent::Key(e) => sender.send(Event::Key(e)),
              CrosstermEvent::Mouse(e) => sender.send(Event::Mouse(e)),
              CrosstermEvent::Resize(w, h) => sender.send(Event::Resize(w, h)),
              _ => Ok(()),
            }
            .expect("failed to send terminal event")
          }

          if last_tick.elapsed() >= tick_rate {
            sender
              .send(Event::Tick(sender.clone()))
              .expect("failed to send tick event");
            last_tick = Instant::now();
          }
        }
      })
    };
    Self {
      sender,
      receiver,
      handler,
    }
  }

  /// Receive the next event from the handler thread.
  ///
  /// This function will always block the current thread if
  /// there is no data available and it's possible for more data to be sent.
  pub fn next(&self) -> AppResult<Event> {
    Ok(self.receiver.recv()?)
  }
}