1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
use crossterm::terminal::WindowSize;
use crossterm::{
    cursor,
    terminal::{EnterAlternateScreen, LeaveAlternateScreen},
};
use std::mem;
use std::{
    io::Stdout,
    ops::{Deref, DerefMut},
};

use color_eyre::eyre::Result;
use ratatui::{prelude::CrosstermBackend as Backend, Terminal};

pub type Frame<'a> = ratatui::Frame<'a, Backend<std::io::Stdout>>;

pub struct Term {
    pub terminal: Terminal<Backend<Stdout>>,
}

impl Term {
    pub fn start() -> Result<Self> {
        let terminal = Terminal::new(Backend::new(std::io::stdout()))?;
        let mut term = Self { terminal };
        term.enter()?;
        Ok(term)
    }

    pub fn enter(&mut self) -> Result<()> {
        crossterm::terminal::enable_raw_mode()?;
        crossterm::execute!(std::io::stdout(), EnterAlternateScreen, cursor::Hide)?;
        Ok(())
    }

    pub fn exit(&mut self) -> Result<()> {
        if crossterm::terminal::is_raw_mode_enabled()? {
            self.flush()?;
            crossterm::execute!(std::io::stdout(), LeaveAlternateScreen, cursor::Show)?;
            crossterm::terminal::disable_raw_mode()?;
        }
        Ok(())
    }

    pub fn suspend(&mut self) -> Result<()> {
        self.exit()?;
        Ok(())
    }

    pub fn resume(&mut self) -> Result<()> {
        self.enter()?;
        let size = self.size()?;
        self.resize(size)?;
        Ok(())
    }

    pub fn size() -> WindowSize {
        let mut size = WindowSize {
            rows: 0,
            columns: 0,
            width: 0,
            height: 0,
        };
        if let Ok(s) = crossterm::terminal::window_size() {
            let _ = mem::replace(&mut size, s);
        }

        if size.rows == 0 || size.columns == 0 {
            if let Ok(s) = crossterm::terminal::size() {
                size.columns = s.0;
                size.rows = s.1;
            }
        }

        // TODO: Use `CSI 14 t` to get the actual size of the terminal
        // if size.width == 0 || size.height == 0 {}

        size
    }
}

impl Deref for Term {
    type Target = ratatui::Terminal<Backend<std::io::Stdout>>;

    fn deref(&self) -> &Self::Target {
        &self.terminal
    }
}

impl DerefMut for Term {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.terminal
    }
}

impl Drop for Term {
    fn drop(&mut self) {
        self.exit().unwrap();
    }
}