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