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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
use std::sync::{Arc, Mutex, Weak};

use lazy_static::lazy_static;

use crate::{
    style::Style,
    key::Key,
    backend::ansi::AnsiTerminal,
    terminal_backend::TerminalBackend,
};

#[cfg(windows)]
fn choose_backend() -> Box<TerminalBackend> {
    use crate::backend::windows::WindowsTerminal;
    use crate::os::windows::enable_ansi_mode;

    if enable_ansi_mode().is_ok() {
        Box::new(AnsiTerminal::new())
    } else {
        Box::new(WindowsTerminal)
    }
}

#[cfg(not(windows))]
fn choose_backend() -> Box<TerminalBackend> {
    Box::new(AnsiTerminal::new())
}

/// Provides access to the application's terminal.
pub fn terminal() -> Arc<Mutex<Terminal>> {
    lazy_static! {
        static ref TERMINAL: Mutex<Weak<Mutex<Terminal>>> = Mutex::new(Weak::new());
    }

    let mut maybe_terminal = TERMINAL.lock().unwrap();

    if let Some(terminal) = maybe_terminal.upgrade() {
        terminal
    } else {
        let backend = choose_backend();
        let terminal = Arc::new(Mutex::new(Terminal::with_backend(backend)));

        *maybe_terminal = Arc::downgrade(&terminal);

        terminal
    }
}

pub struct Terminal {
    backend: Box<TerminalBackend>,
    raw_mode_enabled: bool,
    alternate_screen_enabled: bool,
    cursor_hidden: bool,
}

impl Terminal {
    fn with_backend(mut backend: Box<TerminalBackend>) -> Terminal {
        backend.show_cursor();

        Terminal {
            backend,
            raw_mode_enabled: false,
            alternate_screen_enabled: false,
            cursor_hidden: false,
        }
    }

    pub fn enable_raw_mode(&mut self) {
        if !self.raw_mode_enabled {
            self.backend.enable_raw_mode()
                .expect("Could not enable raw mode");
            self.raw_mode_enabled = true;
        }
    }

    pub fn disable_raw_mode(&mut self) {
        if self.raw_mode_enabled {
            self.backend.disable_raw_mode()
                .expect("Could not disable raw mode");
            self.raw_mode_enabled = false;
        }
    }

    pub fn enable_alternate_screen(&mut self) {
        if !self.alternate_screen_enabled {
            self.backend.enable_alternate_screen();
            self.alternate_screen_enabled = true;
        }
    }

    pub fn disable_alternate_screen(&mut self) {
        if self.alternate_screen_enabled {
            self.backend.disable_alternate_screen();
            self.alternate_screen_enabled = false;
        }
    }

    pub fn clear_screen(&mut self) {
        self.backend.clear_screen();
    }

    pub fn show_cursor(&mut self) {
        self.cursor_hidden = false;
        self.backend.show_cursor();
    }

    pub fn hide_cursor(&mut self) {
        self.cursor_hidden = true;
        self.backend.hide_cursor();
    }

    pub fn move_cursor(&mut self, x: usize, y: usize) {
        self.backend.move_cursor(x, y);
    }

    pub fn print<S: AsRef<str>>(&mut self, text: S, style: Style) {
        self.backend.print(text.as_ref(), style);
    }

    pub fn get_size(&self) -> (usize, usize) {
        self.backend.get_size()
            .expect("Could not get terminal size")
    }

    pub fn read_key(&mut self) -> Key {
        self.backend.read_key()
    }
}

impl Drop for Terminal {
    fn drop(&mut self) {
        self.show_cursor();

        if self.raw_mode_enabled {
            match self.backend.disable_raw_mode() {
                Err(e) => eprintln!("Could not disable raw mode at Drop: {}", e),
                Ok(_) => {},
            }
        }

        self.disable_alternate_screen();
    }
}