ratatui_kit/terminal/
cross_terminal.rs

1use super::TerminalImpl;
2use crossterm::{
3    cursor,
4    event::{self, EventStream, KeyboardEnhancementFlags},
5    execute, queue, terminal,
6};
7use futures::{StreamExt, stream::BoxStream};
8use ratatui::Frame;
9use std::io::{self, IsTerminal, stdout};
10fn set_panic_hook() {
11    let hook = std::panic::take_hook();
12    std::panic::set_hook(Box::new(move |info| {
13        terminal::disable_raw_mode().unwrap();
14        execute!(
15            stdout(),
16            terminal::LeaveAlternateScreen,
17            event::DisableMouseCapture,
18            event::PopKeyboardEnhancementFlags,
19            cursor::Show
20        )
21        .unwrap();
22        ratatui::restore();
23        hook(info);
24    }));
25}
26
27// ================== 终端核心功能实现 ==================
28
29// 跨平台终端结构体
30// input_is_terminal: 标记标准输入是否为终端设备
31// dest: 标准输出流(用于终端操作)
32// raw_mode_enabled: 原始模式启用状态
33// enabled_keyboard_enhancement: 键盘增强功能状态
34// fullscreen: 是否启用全屏模式
35pub struct CrossTerminal {
36    input_is_terminal: bool,
37    dest: std::io::Stdout,
38    raw_mode_enabled: bool,
39    enabled_keyboard_enhancement: bool,
40    fullscreen: bool,
41    terminal: ratatui::Terminal<ratatui::backend::CrosstermBackend<std::io::Stdout>>,
42}
43
44impl CrossTerminal {
45    // 创建终端实例
46    // fullscreen: 是否启用备用屏幕(全屏模式)
47    pub fn new(fullscreen: bool) -> io::Result<Self> {
48        let mut dest = io::stdout();
49        // 隐藏光标
50        queue!(dest, cursor::Hide)?;
51
52        // 进入备用屏幕(全屏模式)
53        if fullscreen {
54            queue!(dest, terminal::EnterAlternateScreen)?;
55        }
56
57        // 设置panic钩子,确保异常时恢复终端状态
58        set_panic_hook();
59
60        Ok(Self {
61            input_is_terminal: io::stdin().is_terminal(),
62            raw_mode_enabled: false,
63            enabled_keyboard_enhancement: false,
64            fullscreen,
65            terminal: ratatui::Terminal::new(ratatui::backend::CrosstermBackend::new(stdout()))?,
66            dest,
67        })
68    }
69
70    // 启用/禁用原始模式
71    // enabled: 目标模式状态
72    pub fn set_raw_mode_enabled(&mut self, enabled: bool) -> io::Result<()> {
73        if enabled != self.raw_mode_enabled {
74            if enabled {
75                // 支持键盘增强时启用
76                if terminal::supports_keyboard_enhancement().unwrap_or(false) {
77                    execute!(
78                        self.dest,
79                        event::PushKeyboardEnhancementFlags(
80                            KeyboardEnhancementFlags::REPORT_EVENT_TYPES
81                        )
82                    )?;
83                    self.enabled_keyboard_enhancement = true;
84                }
85                // 全屏模式下启用鼠标捕获
86                if self.fullscreen {
87                    execute!(self.dest, event::EnableMouseCapture)?;
88                }
89
90                // 启用原始模式
91                terminal::enable_raw_mode()?;
92            } else {
93                // 禁用原始模式
94                terminal::disable_raw_mode()?;
95                // 恢复键盘增强状态
96                if self.enabled_keyboard_enhancement {
97                    execute!(self.dest, event::PopKeyboardEnhancementFlags)?;
98                    self.enabled_keyboard_enhancement = false;
99                }
100                // 禁用鼠标捕获
101                if self.fullscreen {
102                    execute!(self.dest, event::DisableMouseCapture)?;
103                }
104            }
105
106            self.raw_mode_enabled = enabled;
107        }
108
109        Ok(())
110    }
111}
112
113// ================== 生命周期管理 ==================
114
115impl Drop for CrossTerminal {
116    // 析构函数:自动恢复终端原始状态
117    fn drop(&mut self) {
118        let _ = self.set_raw_mode_enabled(false);
119        if self.fullscreen {
120            let _ = queue!(self.dest, terminal::LeaveAlternateScreen);
121        }
122        let _ = execute!(self.dest, cursor::Show);
123    }
124}
125
126// ================== 终端接口实现 ==================
127
128impl TerminalImpl for CrossTerminal {
129    type Event = event::Event;
130
131    // 查询原始模式状态
132    fn is_raw_mode_enabled(&self) -> bool {
133        self.raw_mode_enabled
134    }
135
136    // 创建事件流
137    fn event_stream(&mut self) -> io::Result<BoxStream<'static, Self::Event>> {
138        if !self.input_is_terminal {
139            return Ok(futures::stream::pending().boxed());
140        }
141
142        // 确保进入原始模式
143        self.set_raw_mode_enabled(true)?;
144
145        // 创建事件流并过滤错误
146        Ok(EventStream::new()
147            .filter_map(|event| async move { event.ok() })
148            .boxed())
149    }
150
151    // 检测Ctrl+C组合键
152    fn received_ctrl_c(event: Self::Event) -> bool {
153        matches!(
154            event,
155            event::Event::Key(event::KeyEvent {
156                code: event::KeyCode::Char('c'),
157                modifiers: event::KeyModifiers::CONTROL,
158                kind: event::KeyEventKind::Press,
159                ..
160            })
161        )
162    }
163
164    fn draw<F>(&mut self, f: F) -> io::Result<()>
165    where
166        F: FnOnce(&mut Frame),
167    {
168        self.terminal.draw(f)?;
169        Ok(())
170    }
171}