1use anyhow::Result;
2use crossterm::{
3 cursor,
4 event::{self, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers},
5 execute,
6 terminal::{self, ClearType},
7};
8
9#[cfg(feature = "mouse-support")]
10use crossterm::event::{MouseEvent, MouseEventKind};
11
12#[cfg(feature = "mouse-support")]
13use crossterm::event::{DisableMouseCapture, EnableMouseCapture};
14
15use std::io::{self, Write};
16
17pub struct Terminal {
18 size: (u16, u16),
19}
20
21impl Terminal {
22 pub fn new() -> Result<Self> {
23 let size = terminal::size()?;
24 Ok(Self { size })
25 }
26
27 pub fn enter_raw_mode() -> Result<()> {
28 terminal::enable_raw_mode()?;
29 execute!(io::stdout(), terminal::EnterAlternateScreen)?;
30 #[cfg(feature = "mouse-support")]
31 execute!(io::stdout(), EnableMouseCapture)?;
32 Ok(())
33 }
34
35 pub fn exit_raw_mode() -> Result<()> {
36 #[cfg(feature = "mouse-support")]
37 execute!(io::stdout(), DisableMouseCapture)?;
38 execute!(io::stdout(), terminal::LeaveAlternateScreen)?;
39 terminal::disable_raw_mode()?;
40 Ok(())
41 }
42
43 pub fn clear_screen() -> Result<()> {
44 execute!(io::stdout(), terminal::Clear(ClearType::All))?;
45 Ok(())
46 }
47
48 pub fn size(&self) -> (u16, u16) {
49 self.size
50 }
51
52 #[allow(dead_code)]
53 pub fn update_size(&mut self) -> Result<()> {
54 self.size = terminal::size()?;
55 Ok(())
56 }
57
58 #[allow(dead_code)]
59 pub fn flush() -> Result<()> {
60 io::stdout().flush()?;
61 Ok(())
62 }
63
64 pub fn read_key() -> Result<KeyEvent> {
65 loop {
66 let event = event::read()?;
67
68 match event {
69 Event::Key(key_event) => {
70 if key_event.kind == KeyEventKind::Press
72 || key_event.kind == KeyEventKind::Repeat
73 {
74 return Ok(key_event);
75 }
76 }
77 Event::Resize(_cols, _rows) => {
78 return Ok(KeyEvent::new(KeyCode::F(21), KeyModifiers::NONE));
80 }
81 Event::Paste(_text) => {
82 return Ok(KeyEvent::new(KeyCode::F(20), KeyModifiers::NONE));
86 }
87 #[cfg(feature = "mouse-support")]
88 Event::Mouse(mouse_event) => {
89 if let Some(key_event) = handle_mouse_event(mouse_event) {
91 return Ok(key_event);
92 }
93 }
94 _ => {
95 }
97 }
98 }
99 }
100
101 #[allow(dead_code)]
102 pub fn set_cursor_position(x: u16, y: u16) -> Result<()> {
103 execute!(io::stdout(), cursor::MoveTo(x, y))?;
104 Ok(())
105 }
106
107 #[allow(dead_code)]
108 pub fn hide_cursor() -> Result<()> {
109 execute!(io::stdout(), cursor::Hide)?;
110 Ok(())
111 }
112
113 pub fn show_cursor() -> Result<()> {
114 execute!(io::stdout(), cursor::Show)?;
115 Ok(())
116 }
117}
118
119#[cfg(feature = "mouse-support")]
120fn handle_mouse_event(mouse_event: MouseEvent) -> Option<KeyEvent> {
121 match mouse_event.kind {
122 MouseEventKind::ScrollUp => Some(KeyEvent::new(KeyCode::F(22), KeyModifiers::NONE)),
123 MouseEventKind::ScrollDown => Some(KeyEvent::new(KeyCode::F(23), KeyModifiers::NONE)),
124 _ => None,
125 }
126}
127
128impl Drop for Terminal {
129 fn drop(&mut self) {
130 let _ = Self::exit_raw_mode();
131 let _ = Self::show_cursor();
132 }
133}