monade-mprocs 0.3.0

A fork of the popular mprocs utility, includable via cargo as a library
Documentation
use crossterm::{
  event::{DisableMouseCapture, EnableMouseCapture, Event, EventStream},
  execute,
  terminal::{
    disable_raw_mode, enable_raw_mode, Clear, ClearType, EnterAlternateScreen,
    LeaveAlternateScreen,
  },
};
use futures::{FutureExt, StreamExt};
use tokio::select;
use tui::backend::{Backend, CrosstermBackend};

use crate::protocol::{CltToSrv, SrvToClt};

pub async fn client_main(
  tx: tokio::sync::mpsc::Sender<CltToSrv>,
  rx: tokio::sync::mpsc::UnboundedReceiver<SrvToClt>,
) -> anyhow::Result<()> {
  let res1 = match enable_raw_mode() {
    Ok(_) => {
      let res1 = match execute!(
        std::io::stdout(),
        EnterAlternateScreen,
        Clear(ClearType::All),
        EnableMouseCapture,
      ) {
        Ok(_) => client_main_inner(tx, rx).await,
        Err(err) => Err(err.into()),
      };

      let res2 =
        execute!(std::io::stdout(), DisableMouseCapture, LeaveAlternateScreen);

      res1.and(res2.map_err(anyhow::Error::from))
    }
    Err(err) => Err(err.into()),
  };

  let res2 = disable_raw_mode().map_err(anyhow::Error::from);

  res1.and(res2)
}

async fn client_main_inner(
  tx: tokio::sync::mpsc::Sender<CltToSrv>,
  mut rx: tokio::sync::mpsc::UnboundedReceiver<SrvToClt>,
) -> anyhow::Result<()> {
  let mut backend = CrosstermBackend::new(std::io::stdout());

  let init_size = backend.size()?;
  tx.send(CltToSrv::Init {
    width: init_size.width,
    height: init_size.height,
  })
  .await?;

  let mut term_events = EventStream::new();
  loop {
    enum LocalEvent {
      ServerMsg(Option<SrvToClt>),
      TermEvent(Option<std::io::Result<Event>>),
    }
    let event: LocalEvent = select! {
      msg = rx.recv().fuse() => LocalEvent::ServerMsg(msg),
      event = term_events.next().fuse() => LocalEvent::TermEvent(event),
    };
    match event {
      LocalEvent::ServerMsg(msg) => match msg {
        Some(msg) => match msg {
          SrvToClt::Draw { cells } => {
            let cells = cells
              .iter()
              .map(|(a, b, cell)| (*a, *b, tui::buffer::Cell::from(cell)))
              .collect::<Vec<_>>();
            backend.draw(cells.iter().map(|(a, b, cell)| (*a, *b, cell)))?
          }
          SrvToClt::SetCursor { x, y } => backend.set_cursor(x, y)?,
          SrvToClt::ShowCursor => backend.show_cursor()?,
          SrvToClt::HideCursor => backend.hide_cursor()?,
          SrvToClt::Clear => backend.clear()?,
          SrvToClt::Flush => backend.flush()?,
          SrvToClt::Quit => break,
        },
        _ => break,
      },
      LocalEvent::TermEvent(event) => match event {
        Some(Ok(event)) => tx.send(CltToSrv::Key(event)).await?,
        _ => break,
      },
    }
  }

  Ok(())
}