Skip to main content

ansiq_surface/
lib.rs

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}