use std::io::{IsTerminal, Stderr, stderr};
use crossterm::event::{DisableMouseCapture, EnableMouseCapture};
use crossterm::execute;
use crossterm::terminal::{
Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode,
};
use ratatui::Terminal;
use ratatui::backend::CrosstermBackend;
use crate::error::{Error, Result};
use crate::tui::App;
use crate::tui::view;
type Backend = CrosstermBackend<Stderr>;
pub struct Tui {
terminal: Terminal<Backend>,
mouse: bool,
}
impl Tui {
pub fn enter(mouse: bool) -> Result<Tui> {
if !stderr().is_terminal() {
return Err(Error::operation(
"refusing to start the TUI: stderr is not a terminal",
));
}
enable_raw_mode()?;
match build_terminal(mouse) {
Ok(terminal) => Ok(Tui { terminal, mouse }),
Err(e) => {
let _ = restore(mouse);
Err(e)
}
}
}
pub fn draw(&mut self, app: &App) -> Result<()> {
self.terminal.draw(|frame| view::render(app, frame))?;
Ok(())
}
pub fn suspend(&mut self) -> Result<()> {
restore(self.mouse)
}
pub fn resume(&mut self) -> Result<()> {
enable_raw_mode()?;
execute!(stderr(), EnterAlternateScreen, Clear(ClearType::All))?;
if self.mouse {
execute!(stderr(), EnableMouseCapture)?;
}
self.terminal = Terminal::new(CrosstermBackend::new(stderr()))?;
Ok(())
}
pub fn size(&self) -> (u16, u16) {
self.terminal
.size()
.map(|s| (s.width, s.height))
.unwrap_or((100, 30))
}
}
impl Drop for Tui {
fn drop(&mut self) {
let _ = restore(self.mouse);
}
}
fn build_terminal(mouse: bool) -> Result<Terminal<Backend>> {
execute!(stderr(), EnterAlternateScreen)?;
if mouse {
execute!(stderr(), EnableMouseCapture)?;
}
Ok(Terminal::new(CrosstermBackend::new(stderr()))?)
}
fn restore(mouse: bool) -> Result<()> {
if mouse {
let _ = execute!(stderr(), DisableMouseCapture);
}
let _ = execute!(stderr(), LeaveAlternateScreen);
disable_raw_mode()?;
Ok(())
}
pub fn install_panic_hook() {
let original = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
let _ = restore(true);
original(info);
}));
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn enter_refuses_when_stderr_is_not_a_terminal() {
if stderr().is_terminal() {
return;
}
assert!(Tui::enter(false).is_err());
}
}