mirror of
https://seed.radicle.dev/rad:z39mP9rQAaGmERfUMPULfPUi473tY.git
synced 2026-11-03 18:30:41 +08:00
lib: Introduce typed inline and fullscreen terminal
This commit is contained in:
+70
-22
@@ -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.
|
||||
|
||||
+6
-9
@@ -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
|
||||
}
|
||||
|
||||
+5
-5
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user