1use crate::action::Action;
2use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
3use std::time::{Duration, Instant};
4
5const MULTI_KEY_TIMEOUT: Duration = Duration::from_millis(500);
6
7#[derive(Debug)]
8pub enum KeyState {
9 Normal,
10 WaitingForSecond { first: char, deadline: Instant },
11}
12
13pub struct InputHandler {
14 state: KeyState,
15}
16
17impl Default for InputHandler {
18 fn default() -> Self {
19 Self::new()
20 }
21}
22
23impl InputHandler {
24 pub fn new() -> Self {
25 Self {
26 state: KeyState::Normal,
27 }
28 }
29
30 pub fn is_pending(&self) -> bool {
31 matches!(self.state, KeyState::WaitingForSecond { .. })
32 }
33
34 pub fn check_timeout(&mut self) -> Option<Action> {
35 if let KeyState::WaitingForSecond { deadline, .. } = &self.state {
36 if Instant::now() > *deadline {
37 self.state = KeyState::Normal;
38 return None;
39 }
40 }
41 None
42 }
43
44 pub fn handle_key(&mut self, key: KeyEvent) -> Option<Action> {
45 self.check_timeout();
46
47 match (&self.state, key.code, key.modifiers) {
48 (KeyState::Normal, KeyCode::Char('g'), KeyModifiers::NONE) => {
50 self.state = KeyState::WaitingForSecond {
51 first: 'g',
52 deadline: Instant::now() + MULTI_KEY_TIMEOUT,
53 };
54 None
55 }
56 (
57 KeyState::WaitingForSecond { first: 'g', .. },
58 KeyCode::Char('g'),
59 KeyModifiers::NONE,
60 ) => {
61 self.state = KeyState::Normal;
62 Some(Action::JumpTop)
63 }
64 (
65 KeyState::WaitingForSecond { first: 'g', .. },
66 KeyCode::Char('i'),
67 KeyModifiers::NONE,
68 ) => {
69 self.state = KeyState::Normal;
70 Some(Action::GoToInbox)
71 }
72 (
73 KeyState::WaitingForSecond { first: 'g', .. },
74 KeyCode::Char('s'),
75 KeyModifiers::NONE,
76 ) => {
77 self.state = KeyState::Normal;
78 Some(Action::GoToStarred)
79 }
80 (
81 KeyState::WaitingForSecond { first: 'g', .. },
82 KeyCode::Char('t'),
83 KeyModifiers::NONE,
84 ) => {
85 self.state = KeyState::Normal;
86 Some(Action::GoToSent)
87 }
88 (
89 KeyState::WaitingForSecond { first: 'g', .. },
90 KeyCode::Char('d'),
91 KeyModifiers::NONE,
92 ) => {
93 self.state = KeyState::Normal;
94 Some(Action::GoToDrafts)
95 }
96 (
97 KeyState::WaitingForSecond { first: 'g', .. },
98 KeyCode::Char('a'),
99 KeyModifiers::NONE,
100 ) => {
101 self.state = KeyState::Normal;
102 Some(Action::GoToAllMail)
103 }
104 (
105 KeyState::WaitingForSecond { first: 'g', .. },
106 KeyCode::Char('l'),
107 KeyModifiers::NONE,
108 ) => {
109 self.state = KeyState::Normal;
110 Some(Action::GoToLabel)
111 }
112 (
113 KeyState::WaitingForSecond { first: 'g', .. },
114 KeyCode::Char('c'),
115 KeyModifiers::NONE,
116 ) => {
117 self.state = KeyState::Normal;
118 Some(Action::EditConfig)
119 }
120 (
121 KeyState::WaitingForSecond { first: 'g', .. },
122 KeyCode::Char('L'),
123 KeyModifiers::SHIFT,
124 ) => {
125 self.state = KeyState::Normal;
126 Some(Action::OpenLogs)
127 }
128
129 (KeyState::Normal, KeyCode::Char('z'), KeyModifiers::NONE) => {
131 self.state = KeyState::WaitingForSecond {
132 first: 'z',
133 deadline: Instant::now() + MULTI_KEY_TIMEOUT,
134 };
135 None
136 }
137 (
138 KeyState::WaitingForSecond { first: 'z', .. },
139 KeyCode::Char('z'),
140 KeyModifiers::NONE,
141 ) => {
142 self.state = KeyState::Normal;
143 Some(Action::CenterCurrent)
144 }
145
146 (KeyState::WaitingForSecond { .. }, _, _) => {
147 self.state = KeyState::Normal;
148 self.handle_key(key)
149 }
150
151 (KeyState::Normal, KeyCode::Char('j') | KeyCode::Down, _) => Some(Action::MoveDown),
153 (KeyState::Normal, KeyCode::Char('k') | KeyCode::Up, _) => Some(Action::MoveUp),
154 (KeyState::Normal, KeyCode::Char('G'), KeyModifiers::SHIFT) => Some(Action::JumpBottom),
155 (KeyState::Normal, KeyCode::Char('d'), KeyModifiers::CONTROL) => Some(Action::PageDown),
156 (KeyState::Normal, KeyCode::Char('u'), KeyModifiers::CONTROL) => Some(Action::PageUp),
157 (KeyState::Normal, KeyCode::Char('H'), KeyModifiers::SHIFT) => {
158 Some(Action::ViewportTop)
159 }
160 (KeyState::Normal, KeyCode::Char('M'), KeyModifiers::SHIFT) => {
161 Some(Action::ViewportMiddle)
162 }
163 (KeyState::Normal, KeyCode::Char('L'), KeyModifiers::SHIFT) => {
164 Some(Action::ViewportBottom)
165 }
166 (KeyState::Normal, KeyCode::Tab, _) => Some(Action::SwitchPane),
167 (KeyState::Normal, KeyCode::Enter, _)
168 | (KeyState::Normal, KeyCode::Char('o'), KeyModifiers::NONE) => {
169 Some(Action::OpenSelected)
170 }
171 (KeyState::Normal, KeyCode::Esc, _) => Some(Action::Back),
172 (KeyState::Normal, KeyCode::Char('q'), _) => Some(Action::QuitView),
173 (KeyState::Normal, KeyCode::Char('1'), KeyModifiers::NONE) => Some(Action::OpenTab1),
174 (KeyState::Normal, KeyCode::Char('2'), KeyModifiers::NONE) => Some(Action::OpenTab2),
175 (KeyState::Normal, KeyCode::Char('3'), KeyModifiers::NONE) => Some(Action::OpenTab3),
176 (KeyState::Normal, KeyCode::Char('4'), KeyModifiers::NONE) => Some(Action::OpenTab4),
177 (KeyState::Normal, KeyCode::Char('5'), KeyModifiers::NONE) => Some(Action::OpenTab5),
178
179 (KeyState::Normal, KeyCode::Char('/'), KeyModifiers::NONE) => Some(Action::OpenSearch),
181 (KeyState::Normal, KeyCode::Char('n'), KeyModifiers::NONE) => {
182 Some(Action::NextSearchResult)
183 }
184 (KeyState::Normal, KeyCode::Char('N'), KeyModifiers::SHIFT) => {
185 Some(Action::PrevSearchResult)
186 }
187
188 (KeyState::Normal, KeyCode::Char('p'), KeyModifiers::CONTROL) => {
190 Some(Action::OpenCommandPalette)
191 }
192
193 (KeyState::Normal, KeyCode::Char('c'), KeyModifiers::NONE) => Some(Action::Compose),
195 (KeyState::Normal, KeyCode::Char('r'), KeyModifiers::NONE) => Some(Action::Reply),
196 (KeyState::Normal, KeyCode::Char('a'), KeyModifiers::NONE) => Some(Action::ReplyAll),
197 (KeyState::Normal, KeyCode::Char('f'), KeyModifiers::NONE) => Some(Action::Forward),
198 (KeyState::Normal, KeyCode::Char('e'), KeyModifiers::NONE) => Some(Action::Archive),
199 (KeyState::Normal, KeyCode::Char('m'), KeyModifiers::NONE) => {
200 Some(Action::MarkReadAndArchive)
201 }
202 (KeyState::Normal, KeyCode::Char('#'), _) => Some(Action::Trash),
203 (KeyState::Normal, KeyCode::Char('!'), _) => Some(Action::Spam),
204 (KeyState::Normal, KeyCode::Char('s'), KeyModifiers::NONE) => Some(Action::Star),
205 (KeyState::Normal, KeyCode::Char('I'), KeyModifiers::SHIFT) => Some(Action::MarkRead),
206 (KeyState::Normal, KeyCode::Char('U'), KeyModifiers::SHIFT) => Some(Action::MarkUnread),
207 (KeyState::Normal, KeyCode::Char('l'), KeyModifiers::NONE) => Some(Action::ApplyLabel),
208 (KeyState::Normal, KeyCode::Char('v'), KeyModifiers::NONE) => Some(Action::MoveToLabel),
209 (KeyState::Normal, KeyCode::Char('x'), KeyModifiers::NONE) => {
210 Some(Action::ToggleSelect)
211 }
212 (KeyState::Normal, KeyCode::Char('D'), KeyModifiers::SHIFT) => {
213 Some(Action::Unsubscribe)
214 }
215 (KeyState::Normal, KeyCode::Char('Z'), KeyModifiers::SHIFT) => Some(Action::Snooze),
216 (KeyState::Normal, KeyCode::Char('O'), KeyModifiers::SHIFT) => {
217 Some(Action::OpenInBrowser)
218 }
219 (KeyState::Normal, KeyCode::Char('R'), KeyModifiers::SHIFT) => {
220 Some(Action::ToggleReaderMode)
221 }
222 (KeyState::Normal, KeyCode::Char('S'), KeyModifiers::SHIFT) => {
223 Some(Action::ToggleSignature)
224 }
225 (KeyState::Normal, KeyCode::Char('A'), KeyModifiers::SHIFT) => {
226 Some(Action::AttachmentList)
227 }
228 (KeyState::Normal, KeyCode::Char('V'), KeyModifiers::SHIFT) => {
229 Some(Action::VisualLineMode)
230 }
231 (KeyState::Normal, KeyCode::Char('E'), KeyModifiers::SHIFT) => {
232 Some(Action::ExportThread)
233 }
234 (KeyState::Normal, KeyCode::Char('F'), KeyModifiers::SHIFT) => {
235 Some(Action::ToggleFullscreen)
236 }
237 (KeyState::Normal, KeyCode::Char('?'), _) => Some(Action::Help),
238
239 _ => None,
240 }
241 }
242}