tui_input/backend/
crossterm.rs1#[cfg(feature = "ratatui-crossterm")]
2use ratatui::crossterm;
3
4use crate::{Input, InputRequest, StateChanged};
5use crossterm::event::{
6 Event as CrosstermEvent, KeyCode, KeyEvent, KeyEventKind, KeyModifiers,
7};
8use crossterm::{
9 cursor::MoveTo,
10 queue,
11 style::{Attribute as CAttribute, Print, SetAttribute},
12};
13use std::io::{Result, Write};
14
15pub fn to_input_request(evt: &CrosstermEvent) -> Option<InputRequest> {
17 use InputRequest::*;
18 use KeyCode::*;
19 match evt {
20 CrosstermEvent::Key(KeyEvent {
21 code,
22 modifiers,
23 kind,
24 state: _,
25 }) if *kind == KeyEventKind::Press || *kind == KeyEventKind::Repeat => {
26 match (*code, *modifiers) {
27 (Backspace, KeyModifiers::NONE) | (Char('h'), KeyModifiers::CONTROL) => {
28 Some(DeletePrevChar)
29 }
30 (Delete, KeyModifiers::NONE) => Some(DeleteNextChar),
31 (Tab, KeyModifiers::NONE) => None,
32 (Left, KeyModifiers::NONE) | (Char('b'), KeyModifiers::CONTROL) => {
33 Some(GoToPrevChar)
34 }
35 (Left, KeyModifiers::CONTROL) | (Char('b'), KeyModifiers::META) => {
36 Some(GoToPrevWord)
37 }
38 (Right, KeyModifiers::NONE) | (Char('f'), KeyModifiers::CONTROL) => {
39 Some(GoToNextChar)
40 }
41 (Right, KeyModifiers::CONTROL) | (Char('f'), KeyModifiers::META) => {
42 Some(GoToNextWord)
43 }
44 (Char('u'), KeyModifiers::CONTROL) => Some(DeleteLine),
45
46 (Char('w'), KeyModifiers::CONTROL)
47 | (Char('d'), KeyModifiers::META)
48 | (Backspace, KeyModifiers::META)
49 | (Backspace, KeyModifiers::ALT) => Some(DeletePrevWord),
50
51 (Delete, KeyModifiers::CONTROL) => Some(DeleteNextWord),
52 (Char('k'), KeyModifiers::CONTROL) => Some(DeleteTillEnd),
53 (Char('y'), KeyModifiers::CONTROL) => Some(Yank),
54 (Char('a'), KeyModifiers::CONTROL) | (Home, KeyModifiers::NONE) => {
55 Some(GoToStart)
56 }
57 (Char('e'), KeyModifiers::CONTROL) | (End, KeyModifiers::NONE) => {
58 Some(GoToEnd)
59 }
60 (Char(c), KeyModifiers::NONE) => Some(InsertChar(c)),
61 (Char(c), KeyModifiers::SHIFT) => Some(InsertChar(c)),
62 (Char(c), modifiers)
63 if modifiers == KeyModifiers::CONTROL | KeyModifiers::ALT =>
64 {
65 Some(InsertChar(c))
66 }
67 (_, _) => None,
68 }
69 }
70 _ => None,
71 }
72}
73
74pub fn write<W: Write>(
76 stdout: &mut W,
77 value: &str,
78 cursor: usize,
79 (x, y): (u16, u16),
80 width: u16,
81) -> Result<()> {
82 queue!(stdout, MoveTo(x, y), SetAttribute(CAttribute::NoReverse))?;
83
84 let val_width = width.max(1) as usize - 1;
85 let len = value.chars().count();
86 let start = (len.max(val_width) - val_width).min(cursor);
87 let mut chars = value.chars().skip(start);
88 let mut i = start;
89
90 while i < cursor {
92 i += 1;
93 let c = chars.next().unwrap_or(' ');
94 queue!(stdout, Print(c))?;
95 }
96
97 i += 1;
99 let c = chars.next().unwrap_or(' ');
100 queue!(
101 stdout,
102 SetAttribute(CAttribute::Reverse),
103 Print(c),
104 SetAttribute(CAttribute::NoReverse)
105 )?;
106
107 while i <= start + val_width {
109 i += 1;
110 let c = chars.next().unwrap_or(' ');
111 queue!(stdout, Print(c))?;
112 }
113
114 Ok(())
115}
116
117pub trait EventHandler {
119 fn handle_event(&mut self, evt: &CrosstermEvent) -> Option<StateChanged>;
121}
122
123impl EventHandler for Input {
124 fn handle_event(&mut self, evt: &CrosstermEvent) -> Option<StateChanged> {
126 to_input_request(evt).and_then(|req| self.handle(req))
127 }
128}
129
130#[cfg(test)]
131mod tests;