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