Skip to main content

tui_input/backend/
crossterm.rs

1#[cfg(feature = "ratatui-crossterm")]
2use ratatui::crossterm;
3
4use super::layout::layout;
5use crate::{Input, InputRequest, StateChanged};
6use crossterm::event::{
7    Event as CrosstermEvent, KeyCode, KeyEvent, KeyEventKind, KeyModifiers,
8};
9use crossterm::{
10    cursor::MoveTo,
11    queue,
12    style::{Attribute as CAttribute, Print, SetAttribute},
13};
14use std::io::{Result, Write};
15
16/// Converts crossterm event into input requests.
17pub fn to_input_request(evt: &CrosstermEvent) -> Option<InputRequest> {
18    use InputRequest::*;
19    use KeyCode::*;
20    match evt {
21        CrosstermEvent::Key(KeyEvent {
22            code,
23            modifiers,
24            kind,
25            state: _,
26        }) if *kind == KeyEventKind::Press || *kind == KeyEventKind::Repeat => {
27            match (*code, *modifiers) {
28                (Backspace, KeyModifiers::NONE) | (Char('h'), KeyModifiers::CONTROL) => {
29                    Some(DeletePrevChar)
30                }
31                (Delete, KeyModifiers::NONE) => Some(DeleteNextChar),
32                (Tab, KeyModifiers::NONE) => None,
33                (Left, KeyModifiers::NONE) | (Char('b'), KeyModifiers::CONTROL) => {
34                    Some(GoToPrevChar)
35                }
36                (Left, KeyModifiers::CONTROL) | (Char('b'), KeyModifiers::META) => {
37                    Some(GoToPrevWord)
38                }
39                (Right, KeyModifiers::NONE) | (Char('f'), KeyModifiers::CONTROL) => {
40                    Some(GoToNextChar)
41                }
42                (Right, KeyModifiers::CONTROL) | (Char('f'), KeyModifiers::META) => {
43                    Some(GoToNextWord)
44                }
45                (Char('u'), KeyModifiers::CONTROL) => Some(DeleteLine),
46
47                (Char('w'), KeyModifiers::CONTROL)
48                | (Char('d'), KeyModifiers::META)
49                | (Backspace, KeyModifiers::META)
50                | (Backspace, KeyModifiers::ALT) => Some(DeletePrevWord),
51
52                (Delete, KeyModifiers::CONTROL) => Some(DeleteNextWord),
53                (Char('k'), KeyModifiers::CONTROL) => Some(DeleteTillEnd),
54                (Char('y'), KeyModifiers::CONTROL) => Some(Yank),
55                (Char('a'), KeyModifiers::CONTROL) | (Home, KeyModifiers::NONE) => {
56                    Some(GoToStart)
57                }
58                (Char('e'), KeyModifiers::CONTROL) | (End, KeyModifiers::NONE) => {
59                    Some(GoToEnd)
60                }
61                (Char(c), KeyModifiers::NONE) => Some(InsertChar(c)),
62                (Char(c), KeyModifiers::SHIFT) => Some(InsertChar(c)),
63                (Char(c), modifiers)
64                    if modifiers == KeyModifiers::CONTROL | KeyModifiers::ALT =>
65                {
66                    Some(InsertChar(c))
67                }
68                (_, _) => None,
69            }
70        }
71        _ => None,
72    }
73}
74
75/// Renders the input UI at the given position with the given width.
76pub fn write<W: Write>(
77    stdout: &mut W,
78    value: &str,
79    cursor: usize,
80    (x, y): (u16, u16),
81    width: u16,
82) -> Result<()> {
83    let l = layout(value, cursor, width);
84    let pad = l.padding();
85    queue!(
86        stdout,
87        MoveTo(x, y),
88        SetAttribute(CAttribute::NoReverse),
89        Print(l.before),
90        SetAttribute(CAttribute::Reverse),
91        Print(l.cursor),
92        SetAttribute(CAttribute::NoReverse),
93        Print(l.after),
94        Print(pad)
95    )
96}
97
98/// Import this trait to implement `Input::handle_event()` for crossterm.
99pub trait EventHandler {
100    /// Handle crossterm event.
101    fn handle_event(&mut self, evt: &CrosstermEvent) -> Option<StateChanged>;
102}
103
104impl EventHandler for Input {
105    /// Handle crossterm event.
106    fn handle_event(&mut self, evt: &CrosstermEvent) -> Option<StateChanged> {
107        to_input_request(evt).and_then(|req| self.handle(req))
108    }
109}
110
111#[cfg(test)]
112mod tests;