1
//! Local PTY transport.
2
//!
3
//! Owns `enable_raw_mode`/`EnterAlternateScreen` in its constructor and
4
//! tears them down in [`LocalTransport::finish`]. Polls crossterm for
5
//! key and resize events.
6

            
7
use super::{RawEvent, Transport};
8
use crossterm::event::{self as xevent, DisableMouseCapture, EnableMouseCapture, Event};
9
use crossterm::execute;
10
use crossterm::terminal::{
11
    EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode,
12
};
13
use ratatui::Terminal;
14
use ratatui::backend::CrosstermBackend;
15
use std::env;
16
use std::io::{self, Stdout, Write};
17
use std::time::Duration;
18

            
19
pub struct LocalTransport {
20
    terminal: Terminal<CrosstermBackend<Stdout>>,
21
    term: Option<String>,
22
}
23

            
24
impl LocalTransport {
25
    /// Enter raw mode + alternate screen and return a transport bound
26
    /// to stdout.
27
    ///
28
    /// # Errors
29
    ///
30
    /// Returns `Err` if stdout is not a terminal or the kernel rejects
31
    /// the `TCSETATTR` / escape-sequence writes that raw-mode setup
32
    /// needs.
33
    pub fn new() -> io::Result<Self> {
34
        enable_raw_mode()?;
35
        let mut stdout = io::stdout();
36
        execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
37
        let terminal = Terminal::new(CrosstermBackend::new(stdout))?;
38
        Ok(Self {
39
            terminal,
40
            term: env::var("TERM").ok(),
41
        })
42
    }
43
}
44

            
45
impl Transport for LocalTransport {
46
    type Backend = CrosstermBackend<Stdout>;
47

            
48
    fn terminal_mut(&mut self) -> &mut Terminal<Self::Backend> {
49
        &mut self.terminal
50
    }
51

            
52
    fn poll(&mut self, timeout: Duration) -> io::Result<Option<RawEvent>> {
53
        if !xevent::poll(timeout)? {
54
            return Ok(None);
55
        }
56
        match xevent::read()? {
57
            Event::Key(key) => Ok(Some(RawEvent::Key(key))),
58
            Event::Resize(w, h) => Ok(Some(RawEvent::Resize(w, h))),
59
            _ => Ok(None),
60
        }
61
    }
62

            
63
    fn finish(mut self) -> io::Result<()> {
64
        disable_raw_mode()?;
65
        execute!(
66
            self.terminal.backend_mut(),
67
            LeaveAlternateScreen,
68
            DisableMouseCapture
69
        )?;
70
        self.terminal.show_cursor()?;
71
        Ok(())
72
    }
73

            
74
    fn write_passthrough(&mut self, bytes: &[u8]) -> io::Result<()> {
75
        let mut stdout = io::stdout().lock();
76
        stdout.write_all(bytes)?;
77
        stdout.flush()
78
    }
79

            
80
    fn client_term(&self) -> Option<&str> {
81
        self.term.as_deref()
82
    }
83
}