Radish alpha
r
Radicle terminal user interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
lib: Introduce typed inline and fullscreen terminal
Erik Kundt committed 1 year ago
commit 066e7502a455c67f68ddd2fa3d8a7af40ae2c450
parent a147d2359dd9a9851737ea1e18b2a8197d100728
3 files changed +81 -36
modified src/terminal.rs
@@ -2,17 +2,85 @@ use std::io::{self, Write};
use std::thread;
use std::time::Instant;

+
use ratatui::termion::screen::{AlternateScreen, IntoAlternateScreen};
use termion::input::TermRead;
use termion::raw::{IntoRawMode, RawTerminal};

-
use ratatui::prelude::*;
+
use ratatui::{prelude::*, CompletedFrame};
use ratatui::{TerminalOptions, Viewport};

use tokio::sync::mpsc::{self};

use super::event::Event;

-
pub type Backend = TermionBackendExt<RawTerminal<io::Stdout>>;
+
pub type Backend<S> = TermionBackendExt<S>;
+

+
pub type InlineTerminal = ratatui::terminal::Terminal<Backend<RawTerminal<io::Stdout>>>;
+
pub type FullscreenTerminal =
+
    ratatui::terminal::Terminal<Backend<AlternateScreen<RawTerminal<io::Stdout>>>>;
+

+
pub enum Terminal {
+
    Inline(InlineTerminal),
+
    Fullscreen(FullscreenTerminal),
+
}
+

+
impl Terminal {
+
    pub fn restore(&mut self) -> io::Result<()> {
+
        match self {
+
            Terminal::Fullscreen(inner) => {
+
                inner.clear()?;
+
            }
+
            Terminal::Inline(inner) => {
+
                // TODO(erikli): Check if still needed.
+
                let size = inner.get_frame().size();
+
                inner.set_cursor(size.x, size.y)?;
+
            }
+
        }
+

+
        Ok(())
+
    }
+

+
    pub fn draw<F>(&mut self, f: F) -> io::Result<CompletedFrame>
+
    where
+
        F: FnOnce(&mut Frame),
+
    {
+
        match self {
+
            Terminal::Inline(inner) => inner.draw(f),
+
            Terminal::Fullscreen(inner) => inner.draw(f),
+
        }
+
    }
+
}
+

+
impl TryFrom<Viewport> for Terminal {
+
    type Error = anyhow::Error;
+

+
    fn try_from(viewport: Viewport) -> Result<Self, Self::Error> {
+
        match viewport {
+
            Viewport::Fullscreen => {
+
                let stdout = io::stdout().into_raw_mode()?.into_alternate_screen()?;
+
                let options = TerminalOptions { viewport };
+
                let mut terminal = ratatui::terminal::Terminal::with_options(
+
                    TermionBackendExt::new(stdout),
+
                    options,
+
                )?;
+

+
                terminal.clear()?;
+

+
                Ok(Terminal::Fullscreen(terminal))
+
            }
+
            _ => {
+
                let stdout = io::stdout().into_raw_mode()?;
+
                let options = TerminalOptions { viewport };
+
                let terminal = ratatui::terminal::Terminal::with_options(
+
                    TermionBackendExt::new(stdout),
+
                    options,
+
                )?;
+

+
                Ok(Terminal::Inline(terminal))
+
            }
+
        }
+
    }
+
}

/// FIXME Remove workaround after a new `ratatui` version with
/// https://github.com/ratatui-org/ratatui/pull/981/ included was released.
@@ -93,26 +161,6 @@ impl<W: Write> ratatui::backend::Backend for TermionBackendExt<W> {
    }
}

-
/// Setup a `Terminal` with inline viewport using the `termion` backend.
-
pub fn setup(viewport: Viewport) -> anyhow::Result<Terminal<Backend>> {
-
    let is_fullscreen = viewport == Viewport::Fullscreen;
-
    let stdout = io::stdout().into_raw_mode()?;
-
    let options = TerminalOptions { viewport };
-
    let mut terminal = Terminal::with_options(TermionBackendExt::new(stdout), options)?;
-

-
    if is_fullscreen {
-
        terminal.clear()?;
-
    }
-

-
    Ok(terminal)
-
}
-

-
/// Restore the `Terminal` on quit.
-
pub fn restore(terminal: &mut Terminal<Backend>) -> anyhow::Result<()> {
-
    terminal.clear()?;
-
    Ok(())
-
}
-

/// Spawn one thread that polls `stdin` for new user input and another thread
/// that polls UNIX signals, e.g. `SIGWINCH` when the terminal window size is
/// being changed.
modified src/ui/im.rs
@@ -21,6 +21,7 @@ use crate::event::Event;
use crate::store::Update;
use crate::task::Interrupted;
use crate::terminal;
+
use crate::terminal::Terminal;
use crate::ui::theme::Theme;
use crate::ui::{Column, ToRow};

@@ -53,20 +54,19 @@ impl Frontend {
    {
        let mut ticker = tokio::time::interval(RENDERING_TICK_RATE);

-
        let mut terminal = terminal::setup(viewport)?;
+
        let mut terminal = Terminal::try_from(viewport)?;
+
        let mut events_rx = terminal::events();

        let mut state = state_rx.recv().await.unwrap();
        let mut ctx = Context::default().with_sender(state_tx);

-
        let mut events_rx = terminal::events();
-

        let result: anyhow::Result<Interrupted<P>> = loop {
            tokio::select! {
                // Tick to terminate the select every N milliseconds
                _ = ticker.tick() => (),
+
                // Handle input events
                Some(event) = events_rx.recv() => {
                    log::info!("Received event: {:?}", event);
-

                    match event {
                        Event::Key(key) => ctx.store_input(key),
                        Event::Resize => (),
@@ -79,9 +79,7 @@ impl Frontend {
                // Catch and handle interrupt signal to gracefully shutdown
                Ok(interrupted) = interrupt_rx.recv() => {
                    log::info!("Received interrupt: {:?}", interrupted);
-

-
                    let size = terminal.get_frame().size();
-
                    let _ = terminal.set_cursor(size.x, size.y);
+
                    terminal.restore()?;

                    break Ok(interrupted);
                }
@@ -96,8 +94,7 @@ impl Frontend {

            ctx.clear_inputs();
        };
-

-
        terminal::restore(&mut terminal)?;
+
        terminal.restore()?;

        result
    }
modified src/ui/rm.rs
@@ -11,6 +11,7 @@ use crate::event::Event;
use crate::store::Update;
use crate::task::Interrupted;
use crate::terminal;
+
use crate::terminal::Terminal;
use crate::ui::rm::widget::RenderProps;
use crate::ui::rm::widget::Widget;

@@ -53,7 +54,7 @@ impl Frontend {
    {
        let mut ticker = tokio::time::interval(RENDERING_TICK_RATE);

-
        let mut terminal = terminal::setup(viewport)?;
+
        let mut terminal = Terminal::try_from(viewport)?;
        let mut events_rx = terminal::events();

        let mut root = {
@@ -67,6 +68,7 @@ impl Frontend {
            tokio::select! {
                // Tick to terminate the select every N milliseconds
                _ = ticker.tick() => (),
+
                // Handle input events
                Some(event) = events_rx.recv() => match event {
                    Event::Key(key) => root.handle_event(key),
                    Event::Resize => (),
@@ -77,16 +79,14 @@ impl Frontend {
                },
                // Catch and handle interrupt signal to gracefully shutdown
                Ok(interrupted) = interrupt_rx.recv() => {
-
                    let size = terminal.get_frame().size();
-
                    let _ = terminal.set_cursor(size.x, size.y);
+
                    terminal.restore()?;

                    break Ok(interrupted);
                }
            }
            terminal.draw(|frame| root.render(RenderProps::from(frame.size()), frame))?;
        };
-

-
        terminal::restore(&mut terminal)?;
+
        terminal.restore()?;

        result
    }