synd_term/terminal/
mod.rs1use crossterm::{
2 event::{EnableFocusChange, EventStream},
3 terminal::{self, EnterAlternateScreen, LeaveAlternateScreen},
4 ExecutableCommand,
5};
6use futures_util::{future::Either, stream, Stream};
7use ratatui::Frame;
8use std::io::{self, IsTerminal};
9
10#[cfg(not(feature = "integration"))]
11mod backend;
12#[cfg(not(feature = "integration"))]
13pub use backend::{new_backend, TerminalBackend};
14
15#[cfg(feature = "integration")]
16mod integration_backend;
17#[cfg(feature = "integration")]
18pub use integration_backend::{new_backend, Buffer, TerminalBackend};
19
20pub struct Terminal {
22 backend: ratatui::Terminal<TerminalBackend>,
23}
24
25impl Terminal {
26 pub fn new() -> anyhow::Result<Self> {
28 let backend = new_backend();
29 Ok(Terminal::with(ratatui::Terminal::new(backend)?))
30 }
31
32 pub fn with(backend: ratatui::Terminal<TerminalBackend>) -> Self {
33 Self { backend }
34 }
35
36 pub fn init(&mut self) -> io::Result<()> {
38 terminal::enable_raw_mode()?;
39 crossterm::execute!(io::stdout(), EnterAlternateScreen, EnableFocusChange)?;
40
41 let panic_hook = std::panic::take_hook();
42 std::panic::set_hook(Box::new(move |panic| {
43 Self::restore_backend().expect("Failed to reset terminal");
44 panic_hook(panic);
45 }));
46
47 self.backend.hide_cursor()?;
48 self.backend.clear()?;
49
50 Ok(())
51 }
52
53 pub fn restore(&mut self) -> io::Result<()> {
55 Self::restore_backend()?;
56 self.backend.show_cursor()?;
57 Ok(())
58 }
59
60 fn restore_backend() -> io::Result<()> {
61 terminal::disable_raw_mode()?;
62 io::stdout().execute(LeaveAlternateScreen)?;
63 Ok(())
64 }
65
66 pub fn render<F>(&mut self, f: F) -> anyhow::Result<()>
67 where
68 F: FnOnce(&mut Frame),
69 {
70 self.backend.draw(f)?;
71 Ok(())
72 }
73
74 pub fn force_redraw(&mut self) {
75 self.backend.clear().unwrap();
76 }
77
78 #[cfg(feature = "integration")]
79 pub fn buffer(&self) -> &Buffer {
80 self.backend.backend().buffer()
81 }
82}
83
84pub fn event_stream() -> impl Stream<Item = std::io::Result<crossterm::event::Event>> + Unpin {
85 let is_terminal = std::io::stdout().is_terminal();
91
92 if is_terminal {
93 Either::Left(EventStream::new())
94 } else {
95 Either::Right(stream::empty())
96 }
97}