wedi_core/
terminal.rs

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                    // 處理正常的 Press 和 Repeat 事件
71                    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                    // 視窗大小改變,返回特殊標記
79                    return Ok(KeyEvent::new(KeyCode::F(21), KeyModifiers::NONE));
80                }
81                Event::Paste(_text) => {
82                    // Windows Terminal 的 Ctrl+V 觸發 Paste 事件
83                    // 返回一個特殊按鍵標記,攜帶文本長度信息
84                    // 實際文本需要從剪貼簿讀取
85                    return Ok(KeyEvent::new(KeyCode::F(20), KeyModifiers::NONE));
86                }
87                #[cfg(feature = "mouse-support")]
88                Event::Mouse(mouse_event) => {
89                    // 滑鼠滾輪事件轉換為虛擬按鍵
90                    if let Some(key_event) = handle_mouse_event(mouse_event) {
91                        return Ok(key_event);
92                    }
93                }
94                _ => {
95                    // 忽略其他事件(鼠標、調整大小等)
96                }
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}