Skip to main content

tui/transport/
local.rs

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
7use super::{RawEvent, Transport};
8use crossterm::event::{self as xevent, DisableMouseCapture, EnableMouseCapture, Event};
9use crossterm::execute;
10use crossterm::terminal::{
11    EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode,
12};
13use ratatui::Terminal;
14use ratatui::backend::CrosstermBackend;
15use std::env;
16use std::io::{self, Stdout, Write};
17use std::time::Duration;
18
19pub struct LocalTransport {
20    terminal: Terminal<CrosstermBackend<Stdout>>,
21    term: Option<String>,
22}
23
24impl 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
45impl 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}