termit 0.7.0

Terminal UI over crossterm
Documentation
use crate::{
    input::{events::AppEvent, DefaultStdIn},
    output::command::{TerminalCommander, TerminalRequest},
    sys::{Control, DefaultControl},
    Termit,
};

use std::io;

/// Your main gate to TUI to set up Termit.
///
/// ```no_run
/// # use termit::*;
/// # use std::io;
/// # use log::warn;
/// # async fn sample() -> io::Result<()> {
/// let mut termit = Terminal::try_system_default()?.into_termit::<NoAppEvent>();
/// # Ok(())
/// # }
/// ```
///
/// Terminal on it's own doesn't reset the terminal back to it's initial state (raw mode, styles, captures...)
/// Termit does that. We get a [`Termit`] instance by calling [`Terminal::into_termit()`]. Use that to build your app.
pub struct Terminal<C, I, O> {
    pub control: C,
    pub input: I,
    pub output: O,
}

impl<'a, C, I, O> Terminal<C, I, O>
where
    C: Control + 'a,
    I: futures_lite::AsyncRead + 'a,
    O: io::Write + 'a,
{
    pub fn into_termit<A: AppEvent>(self) -> Termit<'a, A> {
        Termit::from(self)
    }
}

#[derive(Default, Copy, Clone)]
struct NoIo;

impl Default for Terminal<NoIo, NoIo, NoIo> {
    fn default() -> Self {
        Self {
            control: Default::default(),
            input: Default::default(),
            output: Default::default(),
        }
    }
}

impl Terminal<DefaultControl, DefaultStdIn, io::Stdout> {
    pub fn try_system_default() -> io::Result<Self> {
        crate::sys::terminal()
    }
}

impl<C, I, O> Terminal<C, I, O> {
    pub fn new(control: C, input: I, output: O) -> Terminal<C, I, O> {
        Terminal {
            control,
            input,
            output,
        }
    }
}

impl<C, I, O> Terminal<C, I, O>
where
    O: io::Write,
{
    /// Change the controlling tty.
    /// Mind you, this will reset raw mode on the previous one.
    pub fn controlling<C2>(self, control: C2) -> Terminal<C2, I, O> {
        let Self {
            control: _,
            input,
            output,
        } = self;
        Terminal {
            control,
            input,
            output,
        }
    }
    pub fn reading<I2>(self, input: I2) -> Terminal<C, I2, O> {
        let Self {
            control,
            input: _,
            output,
        } = self;
        Terminal {
            control,
            input,
            output,
        }
    }
    pub fn writing<O2>(self, output: O2) -> Terminal<C, I, O2>
    where
        O2: io::Write,
    {
        let Self {
            control,
            input,
            output: _,
        } = self;
        Terminal {
            control,
            input,
            output,
        }
    }
}

impl<C, I, O> TerminalCommander for Terminal<C, I, O>
where
    O: io::Write,
    C: Control,
{
    fn send<T: TerminalRequest>(&mut self, request: T) -> io::Result<()> {
        let mut commander = (&self.control, &mut self.output);
        commander.send(request)
    }
}

impl io::Write for NoIo {
    fn write(&mut self, _buf: &[u8]) -> io::Result<usize> {
        Err(io::Error::new(io::ErrorKind::BrokenPipe, "no io"))
    }
    fn flush(&mut self) -> io::Result<()> {
        Err(io::Error::new(io::ErrorKind::BrokenPipe, "no io"))
    }
}
impl futures_lite::AsyncRead for NoIo {
    fn poll_read(
        self: std::pin::Pin<&mut Self>,
        _cx: &mut std::task::Context<'_>,
        _buf: &mut [u8],
    ) -> std::task::Poll<io::Result<usize>> {
        std::task::Poll::Ready(Err(io::Error::new(io::ErrorKind::BrokenPipe, "no io")))
    }
}
impl Control for NoIo {
    fn enable_raw_mode(&self) -> std::io::Result<()> {
        Err(io::Error::new(io::ErrorKind::BrokenPipe, "no io"))
    }
    fn get_size(&self) -> std::io::Result<crate::prelude::Point> {
        Err(io::Error::new(io::ErrorKind::BrokenPipe, "no io"))
    }
    fn is_tty(&self) -> bool {
        false
    }
    fn is_raw(&self) -> bool {
        false
    }
    fn is_ansi(&self) -> bool {
        false
    }
    fn output(&self) -> &crate::sys::ControlOut {
        panic!("no output")
    }
    fn input(&self) -> &crate::sys::ControlIn {
        panic!("no input")
    }
    fn clone(&self) -> Self
    where
        Self: Sized,
    {
        NoIo
    }
    #[cfg(all(windows, feature = "sys"))]
    fn get_initial_style(&self) -> u16 {
        panic!("no input")
    }
    #[cfg(all(windows, feature = "sys"))]
    fn use_alternate_screen(&self, _alternate: bool) -> std::io::Result<()> {
        Err(io::Error::new(io::ErrorKind::BrokenPipe, "no io"))
    }
}

#[test]
fn get_tty_exists() {
    let _: io::Result<_> = crate::sys::get_tty();
}

#[test]
fn terminal_exists() {
    let _: io::Result<Terminal<_, _, _>> = crate::sys::terminal();
}