1use std::io;
2use std::time::Duration;
3
4use crossterm::event::{self, Event, EventStream, KeyCode, KeyEventKind, KeyModifiers};
5use futures_util::{Stream, StreamExt};
6
7mod detection;
8mod session;
9
10pub use detection::{TerminalCapabilities, detect_terminal_capabilities};
11pub use session::{
12 InlineReservePlan, TerminalGuard, TerminalSession, Viewport, ViewportPolicy,
13 cursor_y_after_history_entries, fit_viewport_height, initial_viewport_plan,
14 inline_reserve_plan, reanchor_viewport_plan, resize_viewport_plan, safe_exit_row,
15};
16
17#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
18pub struct TerminalMode {
19 pub raw_mode: bool,
20 pub alternate_screen: bool,
21}
22
23impl TerminalMode {
24 pub fn enter(self) -> Self {
25 Self {
26 raw_mode: true,
27 alternate_screen: false,
28 }
29 }
30
31 pub fn exit(self) -> Self {
32 Self {
33 raw_mode: false,
34 alternate_screen: false,
35 }
36 }
37}
38
39#[derive(Clone, Copy, Debug, PartialEq, Eq)]
40pub enum Key {
41 Backspace,
42 Enter,
43 Tab,
44 BackTab,
45 Esc,
46 Up,
47 Down,
48 Left,
49 Right,
50 Char(char),
51 CtrlC,
52}
53
54#[derive(Clone, Copy, Debug, PartialEq, Eq)]
55pub enum InputEvent {
56 Key(Key),
57 Resize(u16, u16),
58}
59
60pub fn map_event(event: Event) -> Option<InputEvent> {
61 match event {
62 Event::Key(key) if key.kind != KeyEventKind::Press => None,
63 Event::Key(key) => Some(InputEvent::Key(match (key.code, key.modifiers) {
64 (KeyCode::Backspace, _) => Key::Backspace,
65 (KeyCode::Enter, _) => Key::Enter,
66 (KeyCode::Tab, _) => Key::Tab,
67 (KeyCode::BackTab, _) => Key::BackTab,
68 (KeyCode::Esc, _) => Key::Esc,
69 (KeyCode::Up, _) => Key::Up,
70 (KeyCode::Down, _) => Key::Down,
71 (KeyCode::Left, _) => Key::Left,
72 (KeyCode::Right, _) => Key::Right,
73 (KeyCode::Char('c'), modifiers) if modifiers.contains(KeyModifiers::CONTROL) => {
74 Key::CtrlC
75 }
76 (KeyCode::Char(ch), _) => Key::Char(ch),
77 _ => return None,
78 })),
79 Event::Resize(width, height) => Some(InputEvent::Resize(width, height)),
80 _ => None,
81 }
82}
83
84pub fn poll_event(timeout: Duration) -> io::Result<Option<InputEvent>> {
85 if event::poll(timeout)? {
86 Ok(map_event(event::read()?))
87 } else {
88 Ok(None)
89 }
90}
91
92pub async fn next_input_event_from_stream<S>(
93 stream: &mut S,
94 timeout: Duration,
95) -> io::Result<Option<InputEvent>>
96where
97 S: Stream<Item = io::Result<Event>> + Unpin,
98{
99 tokio::select! {
100 maybe_event = stream.next() => {
101 match maybe_event {
102 Some(Ok(event)) => Ok(map_event(event)),
103 Some(Err(error)) => Err(error),
104 None => Ok(None),
105 }
106 }
107 _ = tokio::time::sleep(timeout) => Ok(None),
108 }
109}
110
111pub struct InputEventStream {
112 inner: EventStream,
113}
114
115impl Default for InputEventStream {
116 fn default() -> Self {
117 Self {
118 inner: EventStream::new(),
119 }
120 }
121}
122
123impl InputEventStream {
124 pub async fn next_event(&mut self, timeout: Duration) -> io::Result<Option<InputEvent>> {
125 next_input_event_from_stream(&mut self.inner, timeout).await
126 }
127}