intuitils 0.0.7

utilities for intuis projects
Documentation
use std::{
    io,
    ops::{Deref, DerefMut},
};

use color_eyre::Result;
use crossterm::{
    cursor,
    event::{DisableMouseCapture, EnableMouseCapture, Event as CrosstermEvent, KeyEventKind},
    terminal::{disable_raw_mode, is_raw_mode_enabled, EnterAlternateScreen, LeaveAlternateScreen},
};
use futures::{FutureExt, StreamExt};
use ratatui::prelude::*;
use tokio::{
    sync::mpsc::{self, UnboundedReceiver, UnboundedSender},
    task::JoinHandle,
};

pub struct Terminal {
    terminal: ratatui::Terminal<CrosstermBackend<std::io::Stdout>>,
    task: JoinHandle<Result<()>>,
    event_rx: UnboundedReceiver<CrosstermEvent>,
    event_tx: UnboundedSender<CrosstermEvent>,
}

impl Deref for Terminal {
    type Target = ratatui::Terminal<CrosstermBackend<std::io::Stdout>>;

    fn deref(&self) -> &Self::Target {
        &self.terminal
    }
}

impl DerefMut for Terminal {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.terminal
    }
}

impl Terminal {
    pub fn new() -> Result<Self> {
        let ratatui_terminal = ratatui::Terminal::new(CrosstermBackend::new(std::io::stdout()))?;
        let (event_tx, event_rx) = mpsc::unbounded_channel();
        let task = tokio::task::spawn(async { Ok(()) });
        Ok(Self {
            terminal: ratatui_terminal,
            task,
            event_rx,
            event_tx,
        })
    }

    pub fn init(&mut self) -> Result<()> {
        crossterm::terminal::enable_raw_mode()?;
        crossterm::execute!(
            std::io::stdout(),
            EnterAlternateScreen,
            cursor::Hide,
            EnableMouseCapture
        )?;
        let event_tx = self.event_tx.clone();

        self.task = tokio::task::spawn(async move {
            let mut reader = crossterm::event::EventStream::new();
            loop {
                let crossterm_event = reader.next().fuse();
                tokio::select! {
                    event = crossterm_event => Self::handle_crossterm_event(event, &event_tx)?,
                }
            }
        });

        Ok(())
    }

    pub fn exit(&mut self) -> Result<()> {
        self.task.abort();

        if is_raw_mode_enabled()? {
            self.terminal.flush()?;
            crossterm::execute!(
                std::io::stdout(),
                LeaveAlternateScreen,
                cursor::Show,
                DisableMouseCapture
            )?;
            disable_raw_mode()?;
        }

        Ok(())
    }

    fn handle_crossterm_event(
        event: Option<Result<CrosstermEvent, io::Error>>,
        event_tx: &UnboundedSender<CrosstermEvent>,
    ) -> Result<()> {
        match event {
            Some(Ok(CrosstermEvent::Key(key))) => {
                if key.kind == KeyEventKind::Press {
                    event_tx.send(CrosstermEvent::Key(key))?;
                }
            }
            Some(Ok(event)) => event_tx.send(event)?,
            Some(Err(e)) => Err(e)?,
            _ => (),
        }
        Ok(())
    }

    pub async fn next(&mut self) -> Option<CrosstermEvent> {
        self.event_rx.recv().await
    }
}