From 066e7502a455c67f68ddd2fa3d8a7af40ae2c450 Mon Sep 17 00:00:00 2001 From: Erik Kundt Date: Wed, 25 Sep 2024 16:53:08 +0200 Subject: [PATCH] lib: Introduce typed inline and fullscreen terminal --- src/terminal.rs | 92 +++++++++++++++++++++++++++++++++++++------------ src/ui/im.rs | 15 ++++---- src/ui/rm.rs | 10 +++--- 3 files changed, 81 insertions(+), 36 deletions(-) diff --git a/src/terminal.rs b/src/terminal.rs index 628e301a..bb214a14 100644 --- a/src/terminal.rs +++ b/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>; +pub type Backend = TermionBackendExt; + +pub type InlineTerminal = ratatui::terminal::Terminal>>; +pub type FullscreenTerminal = + ratatui::terminal::Terminal>>>; + +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(&mut self, f: F) -> io::Result + where + F: FnOnce(&mut Frame), + { + match self { + Terminal::Inline(inner) => inner.draw(f), + Terminal::Fullscreen(inner) => inner.draw(f), + } + } +} + +impl TryFrom for Terminal { + type Error = anyhow::Error; + + fn try_from(viewport: Viewport) -> Result { + 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 ratatui::backend::Backend for TermionBackendExt { } } -/// Setup a `Terminal` with inline viewport using the `termion` backend. -pub fn setup(viewport: Viewport) -> anyhow::Result> { - 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) -> 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. diff --git a/src/ui/im.rs b/src/ui/im.rs index ab3445d4..1e9ea4bf 100644 --- a/src/ui/im.rs +++ b/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> = 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 } diff --git a/src/ui/rm.rs b/src/ui/rm.rs index 00a42f4a..2dee7aed 100644 --- a/src/ui/rm.rs +++ b/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 }