1use super::*;
2
3impl App {
4 pub fn handle_key(&mut self, key: crossterm::event::KeyEvent) -> Option<Action> {
5 if self.help_modal_open {
6 return match (key.code, key.modifiers) {
7 (KeyCode::Esc | KeyCode::Enter, _)
8 | (KeyCode::Char('?'), _)
9 | (KeyCode::Char('q'), _) => Some(Action::Help),
10 (KeyCode::Char('j') | KeyCode::Down, _) => {
11 self.help_scroll_offset = self.help_scroll_offset.saturating_add(1);
12 None
13 }
14 (KeyCode::Char('k') | KeyCode::Up, _) => {
15 self.help_scroll_offset = self.help_scroll_offset.saturating_sub(1);
16 None
17 }
18 (KeyCode::Char('d'), KeyModifiers::CONTROL) => {
19 self.help_scroll_offset = self.help_scroll_offset.saturating_add(8);
20 None
21 }
22 (KeyCode::Char('u'), KeyModifiers::CONTROL) => {
23 self.help_scroll_offset = self.help_scroll_offset.saturating_sub(8);
24 None
25 }
26 _ => None,
27 };
28 }
29
30 if self.command_palette.visible {
31 match (key.code, key.modifiers) {
32 (KeyCode::Enter, _) => return self.command_palette.confirm(),
33 (KeyCode::Esc, _) => return Some(Action::CloseCommandPalette),
34 (KeyCode::Backspace, _) => {
35 self.command_palette.on_backspace();
36 return None;
37 }
38 (KeyCode::Down, _) | (KeyCode::Char('n'), KeyModifiers::CONTROL) => {
39 self.command_palette.select_next();
40 return None;
41 }
42 (KeyCode::Up, _) | (KeyCode::Char('p'), KeyModifiers::CONTROL) => {
43 self.command_palette.select_prev();
44 return None;
45 }
46 (KeyCode::Char(c), KeyModifiers::NONE | KeyModifiers::SHIFT) => {
47 self.command_palette.on_char(c);
48 return None;
49 }
50 _ => return None,
51 }
52 }
53
54 if self.search_bar.active {
56 match (key.code, key.modifiers) {
57 (KeyCode::Enter, _) => return Some(Action::SubmitSearch),
58 (KeyCode::Tab, _) => return Some(Action::CycleSearchMode),
59 (KeyCode::Esc, _) => return Some(Action::CloseSearch),
60 (KeyCode::Backspace, _) => {
61 self.search_bar.on_backspace();
62 self.trigger_live_search();
64 return None;
65 }
66 (KeyCode::Char(c), KeyModifiers::NONE | KeyModifiers::SHIFT) => {
67 self.search_bar.on_char(c);
68 self.trigger_live_search();
70 return None;
71 }
72 _ => return None,
73 }
74 }
75
76 if self.pending_send_confirm.is_some() {
78 match (key.code, key.modifiers) {
79 (KeyCode::Char('s'), KeyModifiers::NONE) => {
80 if let Some(pending) = self.pending_send_confirm.take() {
82 if !pending.allow_send {
83 self.pending_send_confirm = Some(pending);
84 return None;
85 }
86 let parse_addrs = |s: &str| mxr_compose::parse::parse_address_list(s);
87 let reply_headers = pending.fm.in_reply_to.as_ref().map(|in_reply_to| {
88 mxr_core::types::ReplyHeaders {
89 in_reply_to: in_reply_to.clone(),
90 references: pending.fm.references.clone(),
91 }
92 });
93 let account_id = self
94 .envelopes
95 .first()
96 .or(self.all_envelopes.first())
97 .map(|e| e.account_id.clone())
98 .unwrap_or_default();
99 let now = chrono::Utc::now();
100 let draft = mxr_core::Draft {
101 id: mxr_core::id::DraftId::new(),
102 account_id,
103 reply_headers,
104 to: parse_addrs(&pending.fm.to),
105 cc: parse_addrs(&pending.fm.cc),
106 bcc: parse_addrs(&pending.fm.bcc),
107 subject: pending.fm.subject,
108 body_markdown: pending.body,
109 attachments: pending
110 .fm
111 .attach
112 .iter()
113 .map(std::path::PathBuf::from)
114 .collect(),
115 created_at: now,
116 updated_at: now,
117 };
118 self.pending_mutation_queue.push((
119 Request::SendDraft { draft },
120 MutationEffect::StatusOnly("Sent!".into()),
121 ));
122 self.status_message = Some("Sending...".into());
123 let _ = std::fs::remove_file(&pending.draft_path);
124 }
125 return None;
126 }
127 (KeyCode::Char('d'), KeyModifiers::NONE) => {
128 if let Some(pending) = self.pending_send_confirm.take() {
130 if !pending.allow_send {
131 self.pending_send_confirm = Some(pending);
132 return None;
133 }
134 let parse_addrs = |s: &str| mxr_compose::parse::parse_address_list(s);
135 let reply_headers = pending.fm.in_reply_to.as_ref().map(|in_reply_to| {
136 mxr_core::types::ReplyHeaders {
137 in_reply_to: in_reply_to.clone(),
138 references: pending.fm.references.clone(),
139 }
140 });
141 let account_id = self
142 .envelopes
143 .first()
144 .or(self.all_envelopes.first())
145 .map(|e| e.account_id.clone())
146 .unwrap_or_default();
147 let now = chrono::Utc::now();
148 let draft = mxr_core::Draft {
149 id: mxr_core::id::DraftId::new(),
150 account_id,
151 reply_headers,
152 to: parse_addrs(&pending.fm.to),
153 cc: parse_addrs(&pending.fm.cc),
154 bcc: parse_addrs(&pending.fm.bcc),
155 subject: pending.fm.subject,
156 body_markdown: pending.body,
157 attachments: pending
158 .fm
159 .attach
160 .iter()
161 .map(std::path::PathBuf::from)
162 .collect(),
163 created_at: now,
164 updated_at: now,
165 };
166 self.pending_mutation_queue.push((
167 Request::SaveDraftToServer { draft },
168 MutationEffect::StatusOnly("Draft saved to server".into()),
169 ));
170 self.status_message = Some("Saving draft...".into());
171 let _ = std::fs::remove_file(&pending.draft_path);
172 }
173 return None;
174 }
175 (KeyCode::Char('e'), KeyModifiers::NONE) => {
176 if let Some(pending) = self.pending_send_confirm.take() {
178 self.pending_compose = Some(ComposeAction::EditDraft(pending.draft_path));
179 }
180 return None;
181 }
182 (KeyCode::Esc, _) => {
183 if let Some(pending) = self.pending_send_confirm.take() {
185 let _ = std::fs::remove_file(&pending.draft_path);
186 self.status_message = Some("Discarded".into());
187 }
188 return None;
189 }
190 _ => return None,
191 }
192 }
193
194 if self.pending_bulk_confirm.is_some() {
195 return match (key.code, key.modifiers) {
196 (KeyCode::Enter, _)
197 | (KeyCode::Char('y'), KeyModifiers::NONE)
198 | (KeyCode::Char('Y'), KeyModifiers::SHIFT) => Some(Action::OpenSelected),
199 (KeyCode::Esc, _) | (KeyCode::Char('n'), KeyModifiers::NONE) => {
200 self.pending_bulk_confirm = None;
201 self.status_message = Some("Bulk action cancelled".into());
202 None
203 }
204 _ => None,
205 };
206 }
207
208 if self.pending_unsubscribe_confirm.is_some() {
209 return match (key.code, key.modifiers) {
210 (KeyCode::Enter, _)
211 | (KeyCode::Char('u'), KeyModifiers::NONE)
212 | (KeyCode::Char('U'), KeyModifiers::SHIFT) => Some(Action::ConfirmUnsubscribeOnly),
213 (KeyCode::Char('a'), KeyModifiers::NONE)
214 | (KeyCode::Char('A'), KeyModifiers::SHIFT) => {
215 Some(Action::ConfirmUnsubscribeAndArchiveSender)
216 }
217 (KeyCode::Esc, _) => Some(Action::CancelUnsubscribe),
218 _ => None,
219 };
220 }
221
222 if self.snooze_panel.visible {
223 match (key.code, key.modifiers) {
224 (KeyCode::Enter, _) => return Some(Action::Snooze),
225 (KeyCode::Esc, _) => {
226 self.snooze_panel.visible = false;
227 return None;
228 }
229 (KeyCode::Char('j') | KeyCode::Down, _) => {
230 self.snooze_panel.selected_index =
231 (self.snooze_panel.selected_index + 1) % snooze_presets().len();
232 return None;
233 }
234 (KeyCode::Char('k') | KeyCode::Up, _) => {
235 self.snooze_panel.selected_index = self
236 .snooze_panel
237 .selected_index
238 .checked_sub(1)
239 .unwrap_or(snooze_presets().len() - 1);
240 return None;
241 }
242 _ => return None,
243 }
244 }
245
246 if let Some(ref mut url_state) = self.url_modal {
248 match (key.code, key.modifiers) {
249 (KeyCode::Enter | KeyCode::Char('o'), _) => {
250 if let Some(url) = url_state.selected_url().map(|s| s.to_string()) {
251 ui::url_modal::open_url(&url);
252 self.status_message = Some(format!("Opening {url}"));
253 }
254 self.url_modal = None;
255 return None;
256 }
257 (KeyCode::Char('y'), _) => {
258 if let Some(url) = url_state.selected_url().map(|s| s.to_string()) {
259 #[cfg(target_os = "macos")]
261 {
262 use std::io::Write;
263 if let Ok(mut child) = std::process::Command::new("pbcopy")
264 .stdin(std::process::Stdio::piped())
265 .spawn()
266 {
267 if let Some(mut stdin) = child.stdin.take() {
268 let _ = stdin.write_all(url.as_bytes());
269 }
270 let _ = child.wait();
271 }
272 }
273 #[cfg(target_os = "linux")]
274 {
275 use std::io::Write;
276 if let Ok(mut child) = std::process::Command::new("xclip")
277 .args(["-selection", "clipboard"])
278 .stdin(std::process::Stdio::piped())
279 .spawn()
280 {
281 if let Some(mut stdin) = child.stdin.take() {
282 let _ = stdin.write_all(url.as_bytes());
283 }
284 let _ = child.wait();
285 }
286 }
287 self.status_message = Some(format!("Copied: {url}"));
288 }
289 self.url_modal = None;
290 return None;
291 }
292 (KeyCode::Char('j') | KeyCode::Down, _) => {
293 url_state.next();
294 return None;
295 }
296 (KeyCode::Char('k') | KeyCode::Up, _) => {
297 url_state.prev();
298 return None;
299 }
300 (KeyCode::Esc | KeyCode::Char('q'), _) => {
301 self.url_modal = None;
302 return None;
303 }
304 _ => return None,
305 }
306 }
307
308 if self.attachment_panel.visible {
310 match (key.code, key.modifiers) {
311 (KeyCode::Enter | KeyCode::Char('o'), _) => {
312 self.queue_attachment_action(AttachmentOperation::Open);
313 return None;
314 }
315 (KeyCode::Char('d'), _) => {
316 self.queue_attachment_action(AttachmentOperation::Download);
317 return None;
318 }
319 (KeyCode::Char('j') | KeyCode::Down, _) => {
320 if self.attachment_panel.selected_index + 1
321 < self.attachment_panel.attachments.len()
322 {
323 self.attachment_panel.selected_index += 1;
324 }
325 return None;
326 }
327 (KeyCode::Char('k') | KeyCode::Up, _) => {
328 self.attachment_panel.selected_index =
329 self.attachment_panel.selected_index.saturating_sub(1);
330 return None;
331 }
332 (KeyCode::Esc | KeyCode::Char('A'), _) => {
333 self.close_attachment_panel();
334 return None;
335 }
336 _ => return None,
337 }
338 }
339
340 if self.compose_picker.visible {
342 match (key.code, key.modifiers) {
343 (KeyCode::Enter, _) => {
344 let to = self.compose_picker.confirm();
346 if to.is_empty() {
347 self.pending_compose = Some(ComposeAction::New);
348 } else {
349 self.pending_compose = Some(ComposeAction::NewWithTo(to));
350 }
351 return None;
352 }
353 (KeyCode::Tab, _) => {
354 self.compose_picker.add_recipient();
356 return None;
357 }
358 (KeyCode::Esc, _) => {
359 self.compose_picker.close();
360 return None;
361 }
362 (KeyCode::Backspace, _) => {
363 self.compose_picker.on_backspace();
364 return None;
365 }
366 (KeyCode::Down, _) | (KeyCode::Char('n'), KeyModifiers::CONTROL) => {
367 self.compose_picker.select_next();
368 return None;
369 }
370 (KeyCode::Up, _) | (KeyCode::Char('p'), KeyModifiers::CONTROL) => {
371 self.compose_picker.select_prev();
372 return None;
373 }
374 (KeyCode::Char(c), KeyModifiers::NONE | KeyModifiers::SHIFT) => {
375 self.compose_picker.on_char(c);
376 return None;
377 }
378 _ => return None,
379 }
380 }
381
382 if self.label_picker.visible {
384 match (key.code, key.modifiers) {
385 (KeyCode::Enter, _) => {
386 let mode = self.label_picker.mode;
387 if let Some(label_name) = self.label_picker.confirm() {
388 self.pending_label_action = Some((mode, label_name));
389 return match mode {
390 LabelPickerMode::Apply => Some(Action::ApplyLabel),
391 LabelPickerMode::Move => Some(Action::MoveToLabel),
392 };
393 }
394 return None;
395 }
396 (KeyCode::Esc, _) => {
397 self.label_picker.close();
398 return None;
399 }
400 (KeyCode::Backspace, _) => {
401 self.label_picker.on_backspace();
402 return None;
403 }
404 (KeyCode::Down, _) | (KeyCode::Char('n'), KeyModifiers::CONTROL) => {
405 self.label_picker.select_next();
406 return None;
407 }
408 (KeyCode::Up, _) | (KeyCode::Char('p'), KeyModifiers::CONTROL) => {
409 self.label_picker.select_prev();
410 return None;
411 }
412 (KeyCode::Char(c), KeyModifiers::NONE | KeyModifiers::SHIFT) => {
413 self.label_picker.on_char(c);
414 return None;
415 }
416 _ => return None,
417 }
418 }
419
420 if self.screen != Screen::Mailbox {
421 return self.handle_screen_key(key);
422 }
423
424 match self.active_pane {
426 ActivePane::MessageView => match (key.code, key.modifiers) {
427 (KeyCode::Char('j') | KeyCode::Down, _) => {
428 self.move_thread_focus_down();
429 None
430 }
431 (KeyCode::Char('k') | KeyCode::Up, _) => {
432 self.move_thread_focus_up();
433 None
434 }
435 (KeyCode::Char('d'), KeyModifiers::CONTROL) => {
436 self.message_scroll_offset = self.message_scroll_offset.saturating_add(20);
437 None
438 }
439 (KeyCode::Char('u'), KeyModifiers::CONTROL) => {
440 self.message_scroll_offset = self.message_scroll_offset.saturating_sub(20);
441 None
442 }
443 (KeyCode::Char('G'), KeyModifiers::SHIFT) => {
444 self.message_scroll_offset = u16::MAX;
445 None
446 }
447 (KeyCode::Char('h') | KeyCode::Left, KeyModifiers::NONE) => {
449 self.active_pane = ActivePane::MailList;
450 None
451 }
452 (KeyCode::Char('o'), KeyModifiers::NONE) => Some(Action::OpenInBrowser),
454 (KeyCode::Char('L'), KeyModifiers::SHIFT) => Some(Action::OpenLinks),
456 _ => self.input.handle_key(key),
457 },
458 ActivePane::Sidebar => match (key.code, key.modifiers) {
459 (KeyCode::Char('j') | KeyCode::Down, _) => {
460 self.sidebar_move_down();
461 None
462 }
463 (KeyCode::Char('k') | KeyCode::Up, _) => {
464 self.sidebar_move_up();
465 None
466 }
467 (KeyCode::Char('['), _) => {
468 self.collapse_current_sidebar_section();
469 None
470 }
471 (KeyCode::Char(']'), _) => {
472 self.expand_current_sidebar_section();
473 None
474 }
475 (KeyCode::Enter | KeyCode::Char('o'), _) => self.sidebar_select(),
476 (KeyCode::Char('l') | KeyCode::Right, KeyModifiers::NONE) => self.sidebar_select(),
478 _ => self.input.handle_key(key),
479 },
480 ActivePane::MailList => match (key.code, key.modifiers) {
481 (KeyCode::Char('h') | KeyCode::Left, KeyModifiers::NONE) => {
483 self.active_pane = ActivePane::Sidebar;
484 None
485 }
486 (KeyCode::Right, KeyModifiers::NONE) => Some(Action::OpenSelected),
488 _ => self.input.handle_key(key),
489 },
490 }
491 }
492
493 fn handle_screen_key(&mut self, key: crossterm::event::KeyEvent) -> Option<Action> {
494 match self.screen {
495 Screen::Search => self.handle_search_screen_key(key),
496 Screen::Rules => self.handle_rules_screen_key(key),
497 Screen::Diagnostics => self.handle_diagnostics_screen_key(key),
498 Screen::Accounts => self.handle_accounts_screen_key(key),
499 Screen::Mailbox => None,
500 }
501 }
502
503 fn handle_search_screen_key(&mut self, key: crossterm::event::KeyEvent) -> Option<Action> {
504 if self.search_page.editing {
505 return match (key.code, key.modifiers) {
506 (KeyCode::Enter, _) => Some(Action::SubmitSearch),
507 (KeyCode::Esc, _) => {
508 self.search_page.editing = false;
509 None
510 }
511 (KeyCode::Backspace, _) => {
512 self.search_page.query.pop();
513 None
514 }
515 (KeyCode::Char(c), KeyModifiers::NONE | KeyModifiers::SHIFT) => {
516 self.search_page.query.push(c);
517 None
518 }
519 _ => None,
520 };
521 }
522
523 match (key.code, key.modifiers) {
524 (KeyCode::Char('/'), _) => {
525 self.search_page.editing = true;
526 None
527 }
528 (KeyCode::Char('j') | KeyCode::Down, _) => {
529 if self.search_page.selected_index + 1 < self.search_row_count() {
530 self.search_page.selected_index += 1;
531 self.ensure_search_visible();
532 self.auto_preview_search();
533 }
534 None
535 }
536 (KeyCode::Char('k') | KeyCode::Up, _) => {
537 if self.search_page.selected_index > 0 {
538 self.search_page.selected_index -= 1;
539 self.ensure_search_visible();
540 self.auto_preview_search();
541 }
542 None
543 }
544 (KeyCode::Enter | KeyCode::Char('o'), _) => {
545 if let Some(env) = self.selected_search_envelope().cloned() {
546 self.open_envelope(env);
547 self.screen = Screen::Mailbox;
548 self.layout_mode = LayoutMode::ThreePane;
549 self.active_pane = ActivePane::MessageView;
550 }
551 None
552 }
553 (KeyCode::Esc, _) => Some(Action::OpenMailboxScreen),
554 _ => self.input.handle_key(key),
555 }
556 }
557
558 fn handle_rules_screen_key(&mut self, key: crossterm::event::KeyEvent) -> Option<Action> {
559 if self.rules_page.form.visible {
560 return self.handle_rule_form_key(key);
561 }
562
563 match (key.code, key.modifiers) {
564 (KeyCode::Char('j') | KeyCode::Down, _) => {
565 if self.rules_page.selected_index + 1 < self.rules_page.rules.len() {
566 self.rules_page.selected_index += 1;
567 }
568 None
569 }
570 (KeyCode::Char('k') | KeyCode::Up, _) => {
571 self.rules_page.selected_index = self.rules_page.selected_index.saturating_sub(1);
572 None
573 }
574 (KeyCode::Enter | KeyCode::Char('o'), _) => Some(Action::RefreshRules),
575 (KeyCode::Char('e'), _) => Some(Action::ToggleRuleEnabled),
576 (KeyCode::Char('D'), KeyModifiers::SHIFT) => Some(Action::ShowRuleDryRun),
577 (KeyCode::Char('H'), KeyModifiers::SHIFT) => Some(Action::ShowRuleHistory),
578 (KeyCode::Char('#'), _) => Some(Action::DeleteRule),
579 (KeyCode::Char('n'), _) => Some(Action::OpenRuleFormNew),
580 (KeyCode::Char('E'), KeyModifiers::SHIFT) => Some(Action::OpenRuleFormEdit),
581 (KeyCode::Esc, _) => Some(Action::OpenMailboxScreen),
582 _ => self.input.handle_key(key),
583 }
584 }
585
586 fn handle_rule_form_key(&mut self, key: crossterm::event::KeyEvent) -> Option<Action> {
587 match (key.code, key.modifiers) {
588 (KeyCode::Esc, _) => {
589 self.rules_page.form.visible = false;
590 self.rules_page.panel = RulesPanel::Details;
591 None
592 }
593 (KeyCode::Tab, _) => {
594 self.rules_page.form.active_field = (self.rules_page.form.active_field + 1) % 5;
595 None
596 }
597 (KeyCode::BackTab, _) => {
598 self.rules_page.form.active_field =
599 self.rules_page.form.active_field.saturating_sub(1);
600 None
601 }
602 (KeyCode::Enter, _) => Some(Action::SaveRuleForm),
603 (KeyCode::Char(' '), _) if self.rules_page.form.active_field == 4 => {
604 self.rules_page.form.enabled = !self.rules_page.form.enabled;
605 None
606 }
607 (KeyCode::Backspace, _) => {
608 match self.rules_page.form.active_field {
609 0 => {
610 self.rules_page.form.name.pop();
611 }
612 1 => {
613 self.rules_page.form.condition.pop();
614 }
615 2 => {
616 self.rules_page.form.action.pop();
617 }
618 3 => {
619 self.rules_page.form.priority.pop();
620 }
621 _ => {}
622 }
623 None
624 }
625 (KeyCode::Char(c), KeyModifiers::NONE | KeyModifiers::SHIFT) => {
626 match self.rules_page.form.active_field {
627 0 => self.rules_page.form.name.push(c),
628 1 => self.rules_page.form.condition.push(c),
629 2 => self.rules_page.form.action.push(c),
630 3 => self.rules_page.form.priority.push(c),
631 _ => {}
632 }
633 None
634 }
635 _ => None,
636 }
637 }
638
639 fn handle_diagnostics_screen_key(&mut self, key: crossterm::event::KeyEvent) -> Option<Action> {
640 match (key.code, key.modifiers) {
641 (KeyCode::Char('r'), _) => Some(Action::RefreshDiagnostics),
642 (KeyCode::Char('b'), _) => Some(Action::GenerateBugReport),
643 (KeyCode::Esc, _) => Some(Action::OpenMailboxScreen),
644 _ => self.input.handle_key(key),
645 }
646 }
647
648 fn handle_accounts_screen_key(&mut self, key: crossterm::event::KeyEvent) -> Option<Action> {
649 if self.accounts_page.onboarding_modal_open {
650 return match (key.code, key.modifiers) {
651 (KeyCode::Enter | KeyCode::Char(' '), _) => {
652 self.complete_account_setup_onboarding();
653 None
654 }
655 (KeyCode::Char('q'), _) => Some(Action::QuitView),
656 (KeyCode::Esc, _) => {
657 self.accounts_page.onboarding_modal_open = false;
658 None
659 }
660 _ => None,
661 };
662 }
663
664 if self.accounts_page.form.visible {
665 return self.handle_account_form_key(key);
666 }
667
668 match (key.code, key.modifiers) {
669 (KeyCode::Char('j') | KeyCode::Down, _) => {
670 if self.accounts_page.selected_index + 1 < self.accounts_page.accounts.len() {
671 self.accounts_page.selected_index += 1;
672 }
673 None
674 }
675 (KeyCode::Char('k') | KeyCode::Up, _) => {
676 self.accounts_page.selected_index =
677 self.accounts_page.selected_index.saturating_sub(1);
678 None
679 }
680 (KeyCode::Char('n'), _) => Some(Action::OpenAccountFormNew),
681 (KeyCode::Char('r'), _) => Some(Action::RefreshAccounts),
682 (KeyCode::Char('t'), _) => Some(Action::TestAccountForm),
683 (KeyCode::Char('d'), _) => Some(Action::SetDefaultAccount),
684 (KeyCode::Enter | KeyCode::Char('o'), _) => {
685 if let Some(account) = self.selected_account().cloned() {
686 if let Some(config) = account_summary_to_config(&account) {
687 self.accounts_page.form = account_form_from_config(config);
688 self.accounts_page.form.visible = true;
689 } else {
690 self.accounts_page.status = Some(
691 "Runtime-only account is inspectable but not editable here.".into(),
692 );
693 }
694 }
695 None
696 }
697 (KeyCode::Esc, _) if self.accounts_page.onboarding_required => None,
698 (KeyCode::Esc, _) => Some(Action::OpenMailboxScreen),
699 _ => self.input.handle_key(key),
700 }
701 }
702
703 fn handle_account_form_key(&mut self, key: crossterm::event::KeyEvent) -> Option<Action> {
704 if self.accounts_page.form.pending_mode_switch.is_some() {
705 return match (key.code, key.modifiers) {
706 (KeyCode::Enter | KeyCode::Char('y'), _) => {
707 if let Some(mode) = self.accounts_page.form.pending_mode_switch {
708 self.apply_account_form_mode(mode);
709 }
710 None
711 }
712 (KeyCode::Esc | KeyCode::Char('n'), _) => {
713 self.accounts_page.form.pending_mode_switch = None;
714 None
715 }
716 _ => None,
717 };
718 }
719
720 if self.accounts_page.form.editing_field {
721 return match (key.code, key.modifiers) {
722 (KeyCode::Esc, _) | (KeyCode::Enter, _) => {
723 self.accounts_page.form.editing_field = false;
724 None
725 }
726 (KeyCode::Tab, _) => {
727 self.accounts_page.form.editing_field = false;
728 self.accounts_page.form.active_field = (self.accounts_page.form.active_field
729 + 1)
730 % self.account_form_field_count();
731 self.accounts_page.form.field_cursor =
732 account_form_field_value(&self.accounts_page.form)
733 .map(|value| value.chars().count())
734 .unwrap_or(0);
735 None
736 }
737 (KeyCode::BackTab, _) => {
738 self.accounts_page.form.editing_field = false;
739 self.accounts_page.form.active_field =
740 self.accounts_page.form.active_field.saturating_sub(1);
741 self.accounts_page.form.field_cursor =
742 account_form_field_value(&self.accounts_page.form)
743 .map(|value| value.chars().count())
744 .unwrap_or(0);
745 None
746 }
747 (KeyCode::Left, _) => {
748 self.accounts_page.form.field_cursor =
749 self.accounts_page.form.field_cursor.saturating_sub(1);
750 None
751 }
752 (KeyCode::Right, _) => {
753 if let Some(value) = account_form_field_value(&self.accounts_page.form) {
754 self.accounts_page.form.field_cursor =
755 (self.accounts_page.form.field_cursor + 1).min(value.chars().count());
756 }
757 None
758 }
759 (KeyCode::Home, _) => {
760 self.accounts_page.form.field_cursor = 0;
761 None
762 }
763 (KeyCode::End, _) => {
764 self.accounts_page.form.field_cursor =
765 account_form_field_value(&self.accounts_page.form)
766 .map(|value| value.chars().count())
767 .unwrap_or(0);
768 None
769 }
770 (KeyCode::Backspace, _) => {
771 delete_account_form_char(&mut self.accounts_page.form, true);
772 self.refresh_account_form_derived_fields();
773 None
774 }
775 (KeyCode::Delete, _) => {
776 delete_account_form_char(&mut self.accounts_page.form, false);
777 self.refresh_account_form_derived_fields();
778 None
779 }
780 (KeyCode::Char(c), KeyModifiers::NONE | KeyModifiers::SHIFT) => {
781 insert_account_form_char(&mut self.accounts_page.form, c);
782 self.refresh_account_form_derived_fields();
783 None
784 }
785 _ => None,
786 };
787 }
788
789 match (key.code, key.modifiers) {
790 (KeyCode::Esc, _) => {
791 self.accounts_page.form.visible = false;
792 None
793 }
794 (KeyCode::Left | KeyCode::Char('h'), _) => {
795 self.request_account_form_mode_change(false);
796 None
797 }
798 (KeyCode::Right | KeyCode::Char('l'), _) => {
799 self.request_account_form_mode_change(true);
800 None
801 }
802 (KeyCode::Char('j') | KeyCode::Down, _) => {
803 self.accounts_page.form.active_field =
804 (self.accounts_page.form.active_field + 1) % self.account_form_field_count();
805 None
806 }
807 (KeyCode::Char('k') | KeyCode::Up, _) => {
808 self.accounts_page.form.active_field = if self.accounts_page.form.active_field == 0
809 {
810 self.account_form_field_count().saturating_sub(1)
811 } else {
812 self.accounts_page.form.active_field - 1
813 };
814 None
815 }
816 (KeyCode::Tab, _) => {
817 if self.accounts_page.form.active_field == 0 {
818 self.request_account_form_mode_change(true);
819 } else {
820 self.accounts_page.form.active_field = (self.accounts_page.form.active_field
821 + 1)
822 % self.account_form_field_count();
823 }
824 None
825 }
826 (KeyCode::BackTab, _) => {
827 if self.accounts_page.form.active_field == 0 {
828 self.request_account_form_mode_change(false);
829 } else {
830 self.accounts_page.form.active_field =
831 self.accounts_page.form.active_field.saturating_sub(1);
832 }
833 None
834 }
835 (KeyCode::Enter | KeyCode::Char('i'), _) => {
836 if account_form_field_is_editable(&self.accounts_page.form) {
837 self.accounts_page.form.editing_field = true;
838 self.accounts_page.form.field_cursor =
839 account_form_field_value(&self.accounts_page.form)
840 .map(|value| value.chars().count())
841 .unwrap_or(0);
842 None
843 } else if self.accounts_page.form.active_field == 0 {
844 self.request_account_form_mode_change(true);
845 None
846 } else if matches!(self.accounts_page.form.mode, AccountFormMode::Gmail)
847 && self.accounts_page.form.active_field == 4
848 {
849 self.accounts_page.form.gmail_credential_source = next_gmail_credential_source(
850 self.accounts_page.form.gmail_credential_source.clone(),
851 true,
852 );
853 self.accounts_page.form.active_field = self
854 .accounts_page
855 .form
856 .active_field
857 .min(self.account_form_field_count().saturating_sub(1));
858 None
859 } else {
860 None
861 }
862 }
863 (KeyCode::Char('t'), _) => Some(Action::TestAccountForm),
864 (KeyCode::Char('r'), _)
865 if matches!(self.accounts_page.form.mode, AccountFormMode::Gmail) =>
866 {
867 Some(Action::ReauthorizeAccountForm)
868 }
869 (KeyCode::Char('s'), _) => Some(Action::SaveAccountForm),
870 (KeyCode::Char(' '), _) if self.accounts_page.form.active_field == 0 => {
871 self.request_account_form_mode_change(true);
872 None
873 }
874 (KeyCode::Char(' '), _)
875 if matches!(self.accounts_page.form.mode, AccountFormMode::Gmail)
876 && self.accounts_page.form.active_field == 4 =>
877 {
878 self.accounts_page.form.gmail_credential_source = next_gmail_credential_source(
879 self.accounts_page.form.gmail_credential_source.clone(),
880 true,
881 );
882 self.accounts_page.form.active_field = self
883 .accounts_page
884 .form
885 .active_field
886 .min(self.account_form_field_count().saturating_sub(1));
887 None
888 }
889 _ => None,
890 }
891 }
892}