use std::io::{BufWriter, Stdout};
use ratatui::Terminal;
use ratatui::backend::CrosstermBackend;
use super::draw::Drawer;
pub(crate) type Screen = Terminal<CrosstermBackend<BufWriter<Stdout>>>;
const FRAME_BUFFER: usize = 64 << 10;
pub(crate) fn take_screen() -> std::io::Result<Screen> {
let hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
ratatui::restore();
hook(info);
}));
crossterm::terminal::enable_raw_mode()?;
crossterm::execute!(std::io::stdout(), crossterm::terminal::EnterAlternateScreen)?;
let out = BufWriter::with_capacity(FRAME_BUFFER, std::io::stdout());
Terminal::new(CrosstermBackend::new(out))
}
pub(crate) fn restore_terminal() {
use std::io::Write;
let_go(&mut std::io::stdout());
if let Err(e) = ratatui::try_restore() {
let _ = writeln!(std::io::stderr(), "Failed to restore terminal: {e}");
}
}
fn let_go(out: &mut impl std::io::Write) {
release_modes(out);
let _ = crossterm::execute!(out, crossterm::event::PopKeyboardEnhancementFlags);
}
pub(crate) fn release_modes(out: &mut impl std::io::Write) {
let _ = crossterm::execute!(out, crossterm::event::DisableMouseCapture);
let _ = crossterm::execute!(out, crossterm::event::DisableFocusChange);
let _ = crossterm::execute!(out, crossterm::event::DisableBracketedPaste);
}
pub(crate) fn push_keyboard_flags() {
let _ = crossterm::execute!(
std::io::stdout(),
crossterm::event::PushKeyboardEnhancementFlags(
crossterm::event::KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES
)
);
}
pub(crate) fn bracket_pastes(out: &mut impl std::io::Write) {
let _ = crossterm::execute!(out, crossterm::event::EnableBracketedPaste);
}
pub(crate) fn follow_focus(out: &mut impl std::io::Write) {
let _ = crossterm::execute!(out, crossterm::event::EnableFocusChange);
}
pub(crate) struct QuietTerminal(pub(crate) Option<Screen>, Drawer);
impl QuietTerminal {
pub(crate) fn new(terminal: Screen) -> Self {
Self(Some(terminal), Drawer::new(false))
}
pub(crate) fn draw(&mut self, render: impl FnOnce(&mut ratatui::Frame)) -> std::io::Result<()> {
let terminal = self.0.as_mut().expect("held until dropped");
self.1.draw(terminal, render)
}
pub(crate) fn clear(&mut self) -> std::io::Result<()> {
let terminal = self.0.as_mut().expect("held until dropped");
self.1.clear(terminal)
}
pub(crate) fn repaint(&mut self) {
self.1.repaint();
}
pub(crate) fn repaint_before_moving(&mut self) {
self.1.repaint_before_moving();
}
pub(crate) fn scroll_with_region(&mut self, on: bool) {
self.1.set_scroll(on);
}
}
impl Drop for QuietTerminal {
fn drop(&mut self) {
if let Some(mut terminal) = self.0.take()
&& terminal.show_cursor().is_err()
{
std::mem::forget(terminal);
}
}
}
pub(crate) struct TakenTerminal {
pub(crate) restored: bool,
}
impl TakenTerminal {
pub(crate) fn restore(&mut self) {
if !std::mem::replace(&mut self.restored, true) {
restore_terminal();
}
}
}
impl Drop for TakenTerminal {
fn drop(&mut self) {
if !std::thread::panicking() {
self.restore();
}
}
}
#[cfg(all(test, unix))]
mod tests {
use super::*;
#[test]
fn letting_go_turns_off_focus_reports() {
let mut out = Vec::new();
let_go(&mut out);
let out = String::from_utf8(out).unwrap();
assert!(out.contains("\x1b[?1004l"), "{out:?}");
assert!(out.contains("\x1b[?2004l"), "{out:?}");
assert!(out.contains("\x1b[<1u"), "{out:?}");
let mut released = Vec::new();
release_modes(&mut released);
let released = String::from_utf8(released).unwrap();
for mode in ["\x1b[?1000l", "\x1b[?1004l", "\x1b[?2004l"] {
assert!(released.contains(mode), "{mode:?} in {released:?}");
}
assert!(!released.contains("\x1b[<1u"), "{released:?}");
let mut on = Vec::new();
follow_focus(&mut on);
assert_eq!(on, b"\x1b[?1004h");
let mut on = Vec::new();
bracket_pastes(&mut on);
assert_eq!(on, b"\x1b[?2004h");
}
}