1use super::*;
2use anyhow::Result as AnyhowResult;
3use rust_i18n::t;
4
5fn key_event_to_payload(ev: &crossterm::event::KeyEvent) -> fresh_core::api::KeyEventPayload {
14 use crossterm::event::{KeyCode, KeyModifiers};
15 let key = match ev.code {
16 KeyCode::Char(c) => c.to_string(),
17 KeyCode::Esc => "escape".to_string(),
18 KeyCode::Enter => "enter".to_string(),
19 KeyCode::Tab => "tab".to_string(),
20 KeyCode::BackTab => "backtab".to_string(),
21 KeyCode::Backspace => "backspace".to_string(),
22 KeyCode::Delete => "delete".to_string(),
23 KeyCode::Left => "left".to_string(),
24 KeyCode::Right => "right".to_string(),
25 KeyCode::Up => "up".to_string(),
26 KeyCode::Down => "down".to_string(),
27 KeyCode::Home => "home".to_string(),
28 KeyCode::End => "end".to_string(),
29 KeyCode::PageUp => "pageup".to_string(),
30 KeyCode::PageDown => "pagedown".to_string(),
31 KeyCode::Insert => "insert".to_string(),
32 KeyCode::F(n) => format!("f{}", n),
33 _ => String::new(),
34 };
35 fresh_core::api::KeyEventPayload {
36 key,
37 ctrl: ev.modifiers.contains(KeyModifiers::CONTROL),
38 alt: ev.modifiers.contains(KeyModifiers::ALT),
39 shift: ev.modifiers.contains(KeyModifiers::SHIFT),
40 meta: ev.modifiers.contains(KeyModifiers::SUPER),
41 }
42}
43
44impl Editor {
45 fn try_resolve_next_key_callback(&mut self, key_event: &crossterm::event::KeyEvent) -> bool {
58 let payload = key_event_to_payload(key_event);
59 if let Some(callback_id) = self.pending_next_key_callbacks.pop_front() {
60 let json = serde_json::to_string(&payload).unwrap_or_else(|_| "null".to_string());
61 self.plugin_manager.resolve_callback(callback_id, json);
62 return true;
63 }
64 if self.key_capture_active {
65 self.pending_key_capture_buffer.push_back(payload);
66 return true;
67 }
68 false
69 }
70}
71
72impl Editor {
73 pub(crate) fn popups_capture_keys(&self) -> bool {
87 use crate::input::keybindings::KeyContext;
88 !matches!(self.key_context, KeyContext::FileExplorer)
89 }
90
91 pub(crate) fn resolve_completion_popup_action(
97 &self,
98 event: &crossterm::event::KeyEvent,
99 ) -> Option<crate::input::keybindings::Action> {
100 use crate::input::keybindings::{Action, KeyContext};
101 use crate::view::popup::PopupKind;
102
103 let topmost_kind = if self.global_popups.is_visible() {
104 self.global_popups.top().map(|p| p.kind)
105 } else if self.active_state().popups.is_visible() {
106 self.active_state().popups.top().map(|p| p.kind)
107 } else {
108 None
109 };
110
111 if topmost_kind != Some(PopupKind::Completion) {
112 return None;
113 }
114
115 match self
116 .keybindings
117 .read()
118 .unwrap()
119 .resolve_in_context_only(event, KeyContext::Completion)
120 {
121 Some(action @ (Action::CompletionAccept | Action::CompletionDismiss)) => Some(action),
122 _ => None,
123 }
124 }
125
126 pub fn get_key_context(&self) -> crate::input::keybindings::KeyContext {
128 use crate::input::keybindings::KeyContext;
129
130 if self.settings_state.as_ref().is_some_and(|s| s.visible) {
134 KeyContext::Settings
135 } else if self.menu_state.active_menu.is_some() {
136 KeyContext::Menu
137 } else if self.is_prompting() {
138 KeyContext::Prompt
139 } else if self.popups_capture_keys()
140 && (self.global_popups.is_visible() || self.active_state().popups.is_visible())
141 {
142 KeyContext::Popup
143 } else if self.is_composite_buffer(self.active_buffer()) {
144 KeyContext::CompositeBuffer
145 } else {
146 self.key_context.clone()
148 }
149 }
150
151 pub fn handle_key(
154 &mut self,
155 code: crossterm::event::KeyCode,
156 modifiers: crossterm::event::KeyModifiers,
157 ) -> AnyhowResult<()> {
158 use crate::input::keybindings::Action;
159
160 let _t_total = std::time::Instant::now();
161
162 tracing::trace!(
163 "Editor.handle_key: code={:?}, modifiers={:?}",
164 code,
165 modifiers
166 );
167
168 let key_event = crossterm::event::KeyEvent::new(code, modifiers);
170
171 if self.is_event_debug_active() {
175 self.handle_event_debug_input(&key_event);
176 return Ok(());
177 }
178
179 if self.dispatch_terminal_input(&key_event).is_some() {
181 return Ok(());
182 }
183
184 if self.try_resolve_next_key_callback(&key_event) {
191 return Ok(());
192 }
193
194 let active_split = self.effective_active_split();
203 if let Some(view_state) = self.split_view_states.get_mut(&active_split) {
204 view_state.viewport.clear_skip_ensure_visible();
205 }
206
207 if self.theme_info_popup.is_some() {
209 self.theme_info_popup = None;
210 }
211
212 if self.file_explorer_context_menu.is_some() {
213 if let Some(result) = self.handle_file_explorer_context_menu_key(code, modifiers) {
214 return result;
215 }
216 }
217
218 let mut context = self.get_key_context();
220
221 if matches!(context, crate::input::keybindings::KeyContext::Popup) {
224 let (is_transient_popup, has_selection) = {
228 let popup = self
229 .global_popups
230 .top()
231 .or_else(|| self.active_state().popups.top());
232 (
233 popup.is_some_and(|p| p.transient),
234 popup.is_some_and(|p| p.has_selection()),
235 )
236 };
237
238 let is_copy_key = key_event.code == crossterm::event::KeyCode::Char('c')
240 && key_event
241 .modifiers
242 .contains(crossterm::event::KeyModifiers::CONTROL);
243
244 if is_transient_popup && !(has_selection && is_copy_key) {
245 self.hide_popup();
247 tracing::debug!("Dismissed transient popup on key press");
248 context = self.get_key_context();
250 }
251 }
252
253 if self.dispatch_modal_input(&key_event).is_some() {
255 return Ok(());
256 }
257
258 if context != self.get_key_context() {
261 context = self.get_key_context();
262 }
263
264 let should_check_mode_bindings =
268 matches!(context, crate::input::keybindings::KeyContext::Normal);
269
270 if should_check_mode_bindings {
271 let effective_mode = self.effective_mode().map(|s| s.to_owned());
274
275 if let Some(ref mode_name) = effective_mode {
276 let mode_ctx = crate::input::keybindings::KeyContext::Mode(mode_name.to_string());
277 let key_event = crossterm::event::KeyEvent::new(code, modifiers);
278
279 let (chord_result, resolved_action) = {
281 let keybindings = self.keybindings.read().unwrap();
282 let chord_result =
283 keybindings.resolve_chord(&self.chord_state, &key_event, mode_ctx.clone());
284 let resolved = keybindings.resolve(&key_event, mode_ctx);
285 (chord_result, resolved)
286 };
287 match chord_result {
288 crate::input::keybindings::ChordResolution::Complete(action) => {
289 tracing::debug!("Mode chord resolved to action: {:?}", action);
290 self.chord_state.clear();
291 return self.handle_action(action);
292 }
293 crate::input::keybindings::ChordResolution::Partial => {
294 tracing::debug!("Potential chord prefix in mode '{}'", mode_name);
295 self.chord_state.push((code, modifiers));
296 return Ok(());
297 }
298 crate::input::keybindings::ChordResolution::NoMatch => {
299 if !self.chord_state.is_empty() {
300 tracing::debug!("Chord sequence abandoned in mode, clearing state");
301 self.chord_state.clear();
302 }
303 }
304 }
305
306 if resolved_action != Action::None {
308 return self.handle_action(resolved_action);
309 }
310 }
311
312 if let Some(ref mode_name) = effective_mode {
324 if self.mode_registry.allows_text_input(mode_name) {
325 if let KeyCode::Char(c) = code {
326 let ch = if modifiers.contains(KeyModifiers::SHIFT) {
327 c.to_uppercase().next().unwrap_or(c)
328 } else {
329 c
330 };
331 if !modifiers.intersects(KeyModifiers::CONTROL | KeyModifiers::ALT) {
332 let action_name = format!("mode_text_input:{}", ch);
333 return self.handle_action(Action::PluginAction(action_name));
334 }
335 }
336 tracing::debug!("Blocking unbound key in text-input mode '{}'", mode_name);
337 return Ok(());
338 }
339 }
340 if let Some(ref mode_name) = self.editor_mode {
341 if self.mode_registry.is_read_only(mode_name) {
342 tracing::debug!("Ignoring unbound key in read-only mode '{}'", mode_name);
343 return Ok(());
344 }
345 tracing::debug!(
346 "Mode '{}' is not read-only, allowing key through",
347 mode_name
348 );
349 }
350 }
351
352 {
359 let active_buf = self.active_buffer();
360 let active_split = self.effective_active_split();
361 if self.is_composite_buffer(active_buf) {
362 if let Some(handled) =
363 self.try_route_composite_key(active_split, active_buf, &key_event)
364 {
365 return handled;
366 }
367 }
368 }
369
370 let key_event = crossterm::event::KeyEvent::new(code, modifiers);
372 let (chord_result, action) = {
373 let keybindings = self.keybindings.read().unwrap();
374 let chord_result =
375 keybindings.resolve_chord(&self.chord_state, &key_event, context.clone());
376 let action = keybindings.resolve(&key_event, context.clone());
377 (chord_result, action)
378 };
379
380 match chord_result {
381 crate::input::keybindings::ChordResolution::Complete(action) => {
382 tracing::debug!("Complete chord match -> Action: {:?}", action);
384 self.chord_state.clear();
385 return self.handle_action(action);
386 }
387 crate::input::keybindings::ChordResolution::Partial => {
388 tracing::debug!("Partial chord match - waiting for next key");
390 self.chord_state.push((code, modifiers));
391 return Ok(());
392 }
393 crate::input::keybindings::ChordResolution::NoMatch => {
394 if !self.chord_state.is_empty() {
396 tracing::debug!("Chord sequence abandoned, clearing state");
397 self.chord_state.clear();
398 }
399 }
400 }
401
402 tracing::trace!("Context: {:?} -> Action: {:?}", context, action);
404
405 match action {
408 Action::LspCompletion
409 | Action::LspGotoDefinition
410 | Action::LspReferences
411 | Action::LspHover
412 | Action::None => {
413 }
415 _ => {
416 self.cancel_pending_lsp_requests();
418 }
419 }
420
421 self.handle_action(action)
425 }
426
427 pub(crate) fn handle_action(&mut self, action: Action) -> AnyhowResult<()> {
430 use crate::input::keybindings::Action;
431
432 self.record_macro_action(&action);
434
435 if !matches!(action, Action::DabbrevExpand) {
437 self.reset_dabbrev_state();
438 }
439
440 match action {
441 Action::Quit => self.quit(),
442 Action::ForceQuit => {
443 self.should_quit = true;
444 }
445 Action::Detach => {
446 self.should_detach = true;
447 }
448 Action::Save => {
449 if self.active_state().buffer.file_path().is_none() {
451 self.start_prompt_with_initial_text(
452 t!("file.save_as_prompt").to_string(),
453 PromptType::SaveFileAs,
454 String::new(),
455 );
456 self.init_file_open_state();
457 } else if self.check_save_conflict().is_some() {
458 self.start_prompt(
460 t!("file.file_changed_prompt").to_string(),
461 PromptType::ConfirmSaveConflict,
462 );
463 } else if let Err(e) = self.save() {
464 let msg = format!("{}", e);
465 self.status_message = Some(t!("file.save_failed", error = &msg).to_string());
466 }
467 }
468 Action::SaveAs => {
469 let current_path = self
471 .active_state()
472 .buffer
473 .file_path()
474 .map(|p| {
475 p.strip_prefix(&self.working_dir)
477 .unwrap_or(p)
478 .to_string_lossy()
479 .to_string()
480 })
481 .unwrap_or_default();
482 self.start_prompt_with_initial_text(
483 t!("file.save_as_prompt").to_string(),
484 PromptType::SaveFileAs,
485 current_path,
486 );
487 self.init_file_open_state();
488 }
489 Action::Open => {
490 self.start_prompt(t!("file.open_prompt").to_string(), PromptType::OpenFile);
491 self.prefill_open_file_prompt();
492 self.init_file_open_state();
493 }
494 Action::SwitchProject => {
495 self.start_prompt(
496 t!("file.switch_project_prompt").to_string(),
497 PromptType::SwitchProject,
498 );
499 self.init_folder_open_state();
500 }
501 Action::GotoLine => {
502 let has_line_index = self
503 .buffers
504 .get(&self.active_buffer())
505 .is_none_or(|s| s.buffer.line_count().is_some());
506 if has_line_index {
507 self.start_prompt(
508 t!("file.goto_line_prompt").to_string(),
509 PromptType::GotoLine,
510 );
511 } else {
512 self.start_prompt(
513 t!("goto.scan_confirm_prompt", yes = "y", no = "N").to_string(),
514 PromptType::GotoLineScanConfirm,
515 );
516 }
517 }
518 Action::ScanLineIndex => {
519 self.start_incremental_line_scan(false);
520 }
521 Action::New => {
522 self.new_buffer();
523 }
524 Action::Close | Action::CloseTab => {
525 self.close_tab();
530 }
531 Action::Revert => {
532 if self.active_state().buffer.is_modified() {
534 let revert_key = t!("prompt.key.revert").to_string();
535 let cancel_key = t!("prompt.key.cancel").to_string();
536 self.start_prompt(
537 t!(
538 "prompt.revert_confirm",
539 revert_key = revert_key,
540 cancel_key = cancel_key
541 )
542 .to_string(),
543 PromptType::ConfirmRevert,
544 );
545 } else {
546 if let Err(e) = self.revert_file() {
548 self.set_status_message(
549 t!("error.failed_to_revert", error = e.to_string()).to_string(),
550 );
551 }
552 }
553 }
554 Action::ToggleAutoRevert => {
555 self.toggle_auto_revert();
556 }
557 Action::FormatBuffer => {
558 if let Err(e) = self.format_buffer() {
559 self.set_status_message(
560 t!("error.format_failed", error = e.to_string()).to_string(),
561 );
562 }
563 }
564 Action::TrimTrailingWhitespace => match self.trim_trailing_whitespace() {
565 Ok(true) => {
566 self.set_status_message(t!("whitespace.trimmed").to_string());
567 }
568 Ok(false) => {
569 self.set_status_message(t!("whitespace.no_trailing").to_string());
570 }
571 Err(e) => {
572 self.set_status_message(
573 t!("error.trim_whitespace_failed", error = e).to_string(),
574 );
575 }
576 },
577 Action::EnsureFinalNewline => match self.ensure_final_newline() {
578 Ok(true) => {
579 self.set_status_message(t!("whitespace.newline_added").to_string());
580 }
581 Ok(false) => {
582 self.set_status_message(t!("whitespace.already_has_newline").to_string());
583 }
584 Err(e) => {
585 self.set_status_message(
586 t!("error.ensure_newline_failed", error = e).to_string(),
587 );
588 }
589 },
590 Action::Copy => {
591 let popup = self
593 .global_popups
594 .top()
595 .or_else(|| self.active_state().popups.top());
596 if let Some(popup) = popup {
597 if popup.has_selection() {
598 if let Some(text) = popup.get_selected_text() {
599 self.clipboard.copy(text);
600 self.set_status_message(t!("clipboard.copied").to_string());
601 return Ok(());
602 }
603 }
604 }
605 if self.key_context == crate::input::keybindings::KeyContext::FileExplorer {
606 self.file_explorer_copy();
607 return Ok(());
608 }
609 let buffer_id = self.active_buffer();
611 if self.is_composite_buffer(buffer_id) {
612 if let Some(_handled) = self.handle_composite_action(buffer_id, &Action::Copy) {
613 return Ok(());
614 }
615 }
616 self.copy_selection()
617 }
618 Action::CopyWithTheme(theme) => self.copy_selection_with_theme(&theme),
619 Action::Cut => {
620 if self.key_context == crate::input::keybindings::KeyContext::FileExplorer {
621 self.file_explorer_cut();
622 return Ok(());
623 }
624 if self.is_editing_disabled() {
625 self.set_status_message(t!("buffer.editing_disabled").to_string());
626 return Ok(());
627 }
628 self.cut_selection()
629 }
630 Action::Paste => {
631 if self.key_context == crate::input::keybindings::KeyContext::FileExplorer {
632 self.file_explorer_paste();
633 return Ok(());
634 }
635 if self.is_editing_disabled() {
636 self.set_status_message(t!("buffer.editing_disabled").to_string());
637 return Ok(());
638 }
639 self.paste()
640 }
641 Action::YankWordForward => self.yank_word_forward(),
642 Action::YankWordBackward => self.yank_word_backward(),
643 Action::YankToLineEnd => self.yank_to_line_end(),
644 Action::YankToLineStart => self.yank_to_line_start(),
645 Action::YankViWordEnd => self.yank_vi_word_end(),
646 Action::Undo => {
647 self.handle_undo();
648 }
649 Action::Redo => {
650 self.handle_redo();
651 }
652 Action::ShowHelp => {
653 self.open_help_manual();
654 }
655 Action::ShowKeyboardShortcuts => {
656 self.open_keyboard_shortcuts();
657 }
658 Action::ShowWarnings => {
659 self.show_warnings_popup();
660 }
661 Action::ShowStatusLog => {
662 self.open_status_log();
663 }
664 Action::ShowLspStatus => {
665 self.show_lsp_status_popup();
666 }
667 Action::ShowRemoteIndicatorMenu => {
668 self.show_remote_indicator_popup();
669 }
670 Action::ClearWarnings => {
671 self.clear_warnings();
672 }
673 Action::CommandPalette => {
674 if let Some(prompt) = &self.prompt {
677 if prompt.prompt_type == PromptType::QuickOpen {
678 self.cancel_prompt();
679 return Ok(());
680 }
681 }
682 self.start_quick_open();
683 }
684 Action::QuickOpen => {
685 if let Some(prompt) = &self.prompt {
687 if prompt.prompt_type == PromptType::QuickOpen {
688 self.cancel_prompt();
689 return Ok(());
690 }
691 }
692
693 self.start_quick_open();
695 }
696 Action::QuickOpenBuffers => {
697 if let Some(prompt) = &self.prompt {
698 if prompt.prompt_type == PromptType::QuickOpen {
699 self.cancel_prompt();
700 return Ok(());
701 }
702 }
703 self.start_quick_open_with_prefix("#");
704 }
705 Action::QuickOpenFiles => {
706 if let Some(prompt) = &self.prompt {
707 if prompt.prompt_type == PromptType::QuickOpen {
708 self.cancel_prompt();
709 return Ok(());
710 }
711 }
712 self.start_quick_open_with_prefix("");
713 }
714 Action::ToggleLineWrap => {
715 let new_value = !self.config.editor.line_wrap;
716 self.config_mut().editor.line_wrap = new_value;
717
718 let leaf_ids: Vec<_> = self.split_view_states.keys().copied().collect();
721 for leaf_id in leaf_ids {
722 let buffer_id = self
723 .split_manager
724 .get_buffer_id(leaf_id.into())
725 .unwrap_or(BufferId(0));
726 let effective_wrap = self.resolve_line_wrap_for_buffer(buffer_id);
727 let wrap_column = self.resolve_wrap_column_for_buffer(buffer_id);
728 if let Some(view_state) = self.split_view_states.get_mut(&leaf_id) {
729 view_state.viewport.line_wrap_enabled = effective_wrap;
730 view_state.viewport.wrap_indent = self.config.editor.wrap_indent;
731 view_state.viewport.wrap_column = wrap_column;
732 }
733 }
734
735 let state = if self.config.editor.line_wrap {
736 t!("view.state_enabled").to_string()
737 } else {
738 t!("view.state_disabled").to_string()
739 };
740 self.set_status_message(t!("view.line_wrap_state", state = state).to_string());
741 }
742 Action::ToggleCurrentLineHighlight => {
743 let new_value = !self.config.editor.highlight_current_line;
744 self.config_mut().editor.highlight_current_line = new_value;
745
746 let leaf_ids: Vec<_> = self.split_view_states.keys().copied().collect();
748 for leaf_id in leaf_ids {
749 if let Some(view_state) = self.split_view_states.get_mut(&leaf_id) {
750 view_state.highlight_current_line =
751 self.config.editor.highlight_current_line;
752 }
753 }
754
755 let state = if self.config.editor.highlight_current_line {
756 t!("view.state_enabled").to_string()
757 } else {
758 t!("view.state_disabled").to_string()
759 };
760 self.set_status_message(
761 t!("view.current_line_highlight_state", state = state).to_string(),
762 );
763 }
764 Action::ToggleReadOnly => {
765 let buffer_id = self.active_buffer();
766 let is_now_read_only = self
767 .buffer_metadata
768 .get(&buffer_id)
769 .map(|m| !m.read_only)
770 .unwrap_or(false);
771 self.mark_buffer_read_only(buffer_id, is_now_read_only);
772
773 let state_str = if is_now_read_only {
774 t!("view.state_enabled").to_string()
775 } else {
776 t!("view.state_disabled").to_string()
777 };
778 self.set_status_message(t!("view.read_only_state", state = state_str).to_string());
779 }
780 Action::TogglePageView => {
781 self.handle_toggle_page_view();
782 }
783 Action::SetPageWidth => {
784 let active_split = self.split_manager.active_split();
785 let current = self
786 .split_view_states
787 .get(&active_split)
788 .and_then(|v| v.compose_width.map(|w| w.to_string()))
789 .unwrap_or_default();
790 self.start_prompt_with_initial_text(
791 "Page width (empty = viewport): ".to_string(),
792 PromptType::SetPageWidth,
793 current,
794 );
795 }
796 Action::SetBackground => {
797 let default_path = self
798 .ansi_background_path
799 .as_ref()
800 .and_then(|p| {
801 p.strip_prefix(&self.working_dir)
802 .ok()
803 .map(|rel| rel.to_string_lossy().to_string())
804 })
805 .unwrap_or_else(|| DEFAULT_BACKGROUND_FILE.to_string());
806
807 self.start_prompt_with_initial_text(
808 "Background file: ".to_string(),
809 PromptType::SetBackgroundFile,
810 default_path,
811 );
812 }
813 Action::SetBackgroundBlend => {
814 let default_amount = format!("{:.2}", self.background_fade);
815 self.start_prompt_with_initial_text(
816 "Background blend (0-1): ".to_string(),
817 PromptType::SetBackgroundBlend,
818 default_amount,
819 );
820 }
821 Action::LspCompletion => {
822 self.request_completion();
823 }
824 Action::DabbrevExpand => {
825 self.dabbrev_expand();
826 }
827 Action::LspGotoDefinition => {
828 self.request_goto_definition()?;
829 }
830 Action::LspRename => {
831 self.start_rename()?;
832 }
833 Action::LspHover => {
834 self.request_hover()?;
835 }
836 Action::LspReferences => {
837 self.request_references()?;
838 }
839 Action::LspSignatureHelp => {
840 self.request_signature_help();
841 }
842 Action::LspCodeActions => {
843 self.request_code_actions()?;
844 }
845 Action::LspRestart => {
846 self.handle_lsp_restart();
847 }
848 Action::LspStop => {
849 self.handle_lsp_stop();
850 }
851 Action::LspToggleForBuffer => {
852 self.handle_lsp_toggle_for_buffer();
853 }
854 Action::ToggleInlayHints => {
855 self.toggle_inlay_hints();
856 }
857 Action::DumpConfig => {
858 self.dump_config();
859 }
860 Action::RedrawScreen => {
861 self.request_full_redraw();
862 }
863 Action::SelectTheme => {
864 self.start_select_theme_prompt();
865 }
866 Action::InspectThemeAtCursor => {
867 self.inspect_theme_at_cursor();
868 }
869 Action::SelectKeybindingMap => {
870 self.start_select_keybinding_map_prompt();
871 }
872 Action::SelectCursorStyle => {
873 self.start_select_cursor_style_prompt();
874 }
875 Action::SelectLocale => {
876 self.start_select_locale_prompt();
877 }
878 Action::Search => {
879 let is_search_prompt = self.prompt.as_ref().is_some_and(|p| {
881 matches!(
882 p.prompt_type,
883 PromptType::Search
884 | PromptType::ReplaceSearch
885 | PromptType::QueryReplaceSearch
886 )
887 });
888
889 if is_search_prompt {
890 self.confirm_prompt();
891 } else {
892 self.start_search_prompt(
893 t!("file.search_prompt").to_string(),
894 PromptType::Search,
895 false,
896 );
897 }
898 }
899 Action::Replace => {
900 self.start_search_prompt(
902 t!("file.replace_prompt").to_string(),
903 PromptType::ReplaceSearch,
904 false,
905 );
906 }
907 Action::QueryReplace => {
908 self.search_confirm_each = true;
910 self.start_search_prompt(
911 "Query replace: ".to_string(),
912 PromptType::QueryReplaceSearch,
913 false,
914 );
915 }
916 Action::FindInSelection => {
917 self.start_search_prompt(
918 t!("file.search_prompt").to_string(),
919 PromptType::Search,
920 true,
921 );
922 }
923 Action::FindNext => {
924 self.find_next();
925 }
926 Action::FindPrevious => {
927 self.find_previous();
928 }
929 Action::FindSelectionNext => {
930 self.find_selection_next();
931 }
932 Action::FindSelectionPrevious => {
933 self.find_selection_previous();
934 }
935 Action::AddCursorNextMatch => self.add_cursor_at_next_match(),
936 Action::AddCursorAbove => self.add_cursor_above(),
937 Action::AddCursorBelow => self.add_cursor_below(),
938 Action::NextBuffer => self.next_buffer(),
939 Action::PrevBuffer => self.prev_buffer(),
940 Action::SwitchToPreviousTab => self.switch_to_previous_tab(),
941 Action::SwitchToTabByName => self.start_switch_to_tab_prompt(),
942
943 Action::ScrollTabsLeft => {
945 let active_split_id = self.split_manager.active_split();
946 if let Some(view_state) = self.split_view_states.get_mut(&active_split_id) {
947 view_state.tab_scroll_offset = view_state.tab_scroll_offset.saturating_sub(5);
948 self.set_status_message(t!("status.scrolled_tabs_left").to_string());
949 }
950 }
951 Action::ScrollTabsRight => {
952 let active_split_id = self.split_manager.active_split();
953 if let Some(view_state) = self.split_view_states.get_mut(&active_split_id) {
954 view_state.tab_scroll_offset = view_state.tab_scroll_offset.saturating_add(5);
955 self.set_status_message(t!("status.scrolled_tabs_right").to_string());
956 }
957 }
958 Action::NavigateBack => self.navigate_back(),
959 Action::NavigateForward => self.navigate_forward(),
960 Action::SplitHorizontal => self.split_pane_horizontal(),
961 Action::SplitVertical => self.split_pane_vertical(),
962 Action::CloseSplit => self.close_active_split(),
963 Action::NextSplit => self.next_split(),
964 Action::PrevSplit => self.prev_split(),
965 Action::IncreaseSplitSize => self.adjust_split_size(0.05),
966 Action::DecreaseSplitSize => self.adjust_split_size(-0.05),
967 Action::ToggleMaximizeSplit => self.toggle_maximize_split(),
968 Action::ToggleFileExplorer => self.toggle_file_explorer(),
969 Action::ToggleMenuBar => self.toggle_menu_bar(),
970 Action::ToggleTabBar => self.toggle_tab_bar(),
971 Action::ToggleStatusBar => self.toggle_status_bar(),
972 Action::TogglePromptLine => self.toggle_prompt_line(),
973 Action::ToggleVerticalScrollbar => self.toggle_vertical_scrollbar(),
974 Action::ToggleHorizontalScrollbar => self.toggle_horizontal_scrollbar(),
975 Action::ToggleLineNumbers => self.toggle_line_numbers(),
976 Action::ToggleScrollSync => self.toggle_scroll_sync(),
977 Action::ToggleMouseCapture => self.toggle_mouse_capture(),
978 Action::ToggleMouseHover => self.toggle_mouse_hover(),
979 Action::ToggleDebugHighlights => self.toggle_debug_highlights(),
980 Action::AddRuler => {
982 self.start_prompt(t!("rulers.add_prompt").to_string(), PromptType::AddRuler);
983 }
984 Action::RemoveRuler => {
985 self.start_remove_ruler_prompt();
986 }
987 Action::SetTabSize => {
989 let current = self
990 .buffers
991 .get(&self.active_buffer())
992 .map(|s| s.buffer_settings.tab_size.to_string())
993 .unwrap_or_else(|| "4".to_string());
994 self.start_prompt_with_initial_text(
995 "Tab size: ".to_string(),
996 PromptType::SetTabSize,
997 current,
998 );
999 }
1000 Action::SetLineEnding => {
1001 self.start_set_line_ending_prompt();
1002 }
1003 Action::SetEncoding => {
1004 self.start_set_encoding_prompt();
1005 }
1006 Action::ReloadWithEncoding => {
1007 self.start_reload_with_encoding_prompt();
1008 }
1009 Action::SetLanguage => {
1010 self.start_set_language_prompt();
1011 }
1012 Action::ToggleIndentationStyle => {
1013 if let Some(state) = self.buffers.get_mut(&self.active_buffer()) {
1014 state.buffer_settings.use_tabs = !state.buffer_settings.use_tabs;
1015 let status = if state.buffer_settings.use_tabs {
1016 "Indentation: Tabs"
1017 } else {
1018 "Indentation: Spaces"
1019 };
1020 self.set_status_message(status.to_string());
1021 }
1022 }
1023 Action::ToggleTabIndicators | Action::ToggleWhitespaceIndicators => {
1024 if let Some(state) = self.buffers.get_mut(&self.active_buffer()) {
1025 state.buffer_settings.whitespace.toggle_all();
1026 let status = if state.buffer_settings.whitespace.any_visible() {
1027 t!("toggle.whitespace_indicators_shown")
1028 } else {
1029 t!("toggle.whitespace_indicators_hidden")
1030 };
1031 self.set_status_message(status.to_string());
1032 }
1033 }
1034 Action::ResetBufferSettings => self.reset_buffer_settings(),
1035 Action::FocusFileExplorer => self.focus_file_explorer(),
1036 Action::FocusEditor => self.focus_editor(),
1037 Action::FileExplorerUp => self.file_explorer_navigate_up(),
1038 Action::FileExplorerDown => self.file_explorer_navigate_down(),
1039 Action::FileExplorerPageUp => self.file_explorer_page_up(),
1040 Action::FileExplorerPageDown => self.file_explorer_page_down(),
1041 Action::FileExplorerExpand => self.file_explorer_toggle_expand(),
1042 Action::FileExplorerCollapse => self.file_explorer_collapse(),
1043 Action::FileExplorerOpen => self.file_explorer_open_file()?,
1044 Action::FileExplorerRefresh => self.file_explorer_refresh(),
1045 Action::FileExplorerNewFile => self.file_explorer_new_file(),
1046 Action::FileExplorerNewDirectory => self.file_explorer_new_directory(),
1047 Action::FileExplorerDelete => self.file_explorer_delete(),
1048 Action::FileExplorerRename => self.file_explorer_rename(),
1049 Action::FileExplorerToggleHidden => self.file_explorer_toggle_hidden(),
1050 Action::FileExplorerToggleGitignored => self.file_explorer_toggle_gitignored(),
1051 Action::FileExplorerSearchClear => self.file_explorer_search_clear(),
1052 Action::FileExplorerSearchBackspace => self.file_explorer_search_pop_char(),
1053 Action::FileExplorerCopy => self.file_explorer_copy(),
1054 Action::FileExplorerCut => self.file_explorer_cut(),
1055 Action::FileExplorerPaste => self.file_explorer_paste(),
1056 Action::FileExplorerExtendSelectionUp => self.file_explorer_extend_selection_up(),
1057 Action::FileExplorerExtendSelectionDown => self.file_explorer_extend_selection_down(),
1058 Action::FileExplorerToggleSelect => self.file_explorer_toggle_select(),
1059 Action::FileExplorerSelectAll => self.file_explorer_select_all(),
1060 Action::RemoveSecondaryCursors => {
1061 if let Some(events) = self.action_to_events(Action::RemoveSecondaryCursors) {
1063 let batch = Event::Batch {
1065 events: events.clone(),
1066 description: "Remove secondary cursors".to_string(),
1067 };
1068 self.active_event_log_mut().append(batch.clone());
1069 self.apply_event_to_active_buffer(&batch);
1070
1071 let active_split = self.split_manager.active_split();
1073 let active_buffer = self.active_buffer();
1074 if let Some(view_state) = self.split_view_states.get_mut(&active_split) {
1075 let state = self.buffers.get_mut(&active_buffer).unwrap();
1076 view_state.ensure_cursor_visible(&mut state.buffer, &state.marker_list);
1077 }
1078 }
1079 }
1080
1081 Action::MenuActivate => {
1083 self.handle_menu_activate();
1084 }
1085 Action::MenuClose => {
1086 self.handle_menu_close();
1087 }
1088 Action::MenuLeft => {
1089 self.handle_menu_left();
1090 }
1091 Action::MenuRight => {
1092 self.handle_menu_right();
1093 }
1094 Action::MenuUp => {
1095 self.handle_menu_up();
1096 }
1097 Action::MenuDown => {
1098 self.handle_menu_down();
1099 }
1100 Action::MenuExecute => {
1101 if let Some(action) = self.handle_menu_execute() {
1102 return self.handle_action(action);
1103 }
1104 }
1105 Action::MenuOpen(menu_name) => {
1106 if self.config.editor.menu_bar_mnemonics {
1107 self.handle_menu_open(&menu_name);
1108 }
1109 }
1110
1111 Action::SwitchKeybindingMap(map_name) => {
1112 let is_builtin =
1114 matches!(map_name.as_str(), "default" | "emacs" | "vscode" | "macos");
1115 let is_user_defined = self.config.keybinding_maps.contains_key(&map_name);
1116
1117 if is_builtin || is_user_defined {
1118 self.config_mut().active_keybinding_map = map_name.clone().into();
1120
1121 *self.keybindings.write().unwrap() =
1123 crate::input::keybindings::KeybindingResolver::new(&self.config);
1124
1125 self.set_status_message(
1126 t!("view.keybindings_switched", map = map_name).to_string(),
1127 );
1128 } else {
1129 self.set_status_message(
1130 t!("view.keybindings_unknown", map = map_name).to_string(),
1131 );
1132 }
1133 }
1134
1135 Action::SmartHome => {
1136 let buffer_id = self.active_buffer();
1138 if self.is_composite_buffer(buffer_id) {
1139 if let Some(_handled) =
1140 self.handle_composite_action(buffer_id, &Action::SmartHome)
1141 {
1142 return Ok(());
1143 }
1144 }
1145 self.smart_home();
1146 }
1147 Action::ToggleComment => {
1148 self.toggle_comment();
1149 }
1150 Action::ToggleFold => {
1151 self.toggle_fold_at_cursor();
1152 }
1153 Action::GoToMatchingBracket => {
1154 self.goto_matching_bracket();
1155 }
1156 Action::JumpToNextError => {
1157 self.jump_to_next_error();
1158 }
1159 Action::JumpToPreviousError => {
1160 self.jump_to_previous_error();
1161 }
1162 Action::SetBookmark(key) => {
1163 self.set_bookmark(key);
1164 }
1165 Action::JumpToBookmark(key) => {
1166 self.jump_to_bookmark(key);
1167 }
1168 Action::ClearBookmark(key) => {
1169 self.clear_bookmark(key);
1170 }
1171 Action::ListBookmarks => {
1172 self.list_bookmarks();
1173 }
1174 Action::ToggleSearchCaseSensitive => {
1175 self.search_case_sensitive = !self.search_case_sensitive;
1176 let state = if self.search_case_sensitive {
1177 "enabled"
1178 } else {
1179 "disabled"
1180 };
1181 self.set_status_message(
1182 t!("search.case_sensitive_state", state = state).to_string(),
1183 );
1184 if let Some(prompt) = &self.prompt {
1187 if matches!(
1188 prompt.prompt_type,
1189 PromptType::Search
1190 | PromptType::ReplaceSearch
1191 | PromptType::QueryReplaceSearch
1192 ) {
1193 let query = prompt.input.clone();
1194 self.update_search_highlights(&query);
1195 }
1196 } else if let Some(search_state) = &self.search_state {
1197 let query = search_state.query.clone();
1198 self.perform_search(&query);
1199 }
1200 }
1201 Action::ToggleSearchWholeWord => {
1202 self.search_whole_word = !self.search_whole_word;
1203 let state = if self.search_whole_word {
1204 "enabled"
1205 } else {
1206 "disabled"
1207 };
1208 self.set_status_message(t!("search.whole_word_state", state = state).to_string());
1209 if let Some(prompt) = &self.prompt {
1212 if matches!(
1213 prompt.prompt_type,
1214 PromptType::Search
1215 | PromptType::ReplaceSearch
1216 | PromptType::QueryReplaceSearch
1217 ) {
1218 let query = prompt.input.clone();
1219 self.update_search_highlights(&query);
1220 }
1221 } else if let Some(search_state) = &self.search_state {
1222 let query = search_state.query.clone();
1223 self.perform_search(&query);
1224 }
1225 }
1226 Action::ToggleSearchRegex => {
1227 self.search_use_regex = !self.search_use_regex;
1228 let state = if self.search_use_regex {
1229 "enabled"
1230 } else {
1231 "disabled"
1232 };
1233 self.set_status_message(t!("search.regex_state", state = state).to_string());
1234 if let Some(prompt) = &self.prompt {
1237 if matches!(
1238 prompt.prompt_type,
1239 PromptType::Search
1240 | PromptType::ReplaceSearch
1241 | PromptType::QueryReplaceSearch
1242 ) {
1243 let query = prompt.input.clone();
1244 self.update_search_highlights(&query);
1245 }
1246 } else if let Some(search_state) = &self.search_state {
1247 let query = search_state.query.clone();
1248 self.perform_search(&query);
1249 }
1250 }
1251 Action::ToggleSearchConfirmEach => {
1252 self.search_confirm_each = !self.search_confirm_each;
1253 let state = if self.search_confirm_each {
1254 "enabled"
1255 } else {
1256 "disabled"
1257 };
1258 self.set_status_message(t!("search.confirm_each_state", state = state).to_string());
1259 }
1260 Action::FileBrowserToggleHidden => {
1261 self.file_open_toggle_hidden();
1263 }
1264 Action::StartMacroRecording => {
1265 self.set_status_message(
1267 "Use Ctrl+Shift+R to start recording (will prompt for register)".to_string(),
1268 );
1269 }
1270 Action::StopMacroRecording => {
1271 self.stop_macro_recording();
1272 }
1273 Action::PlayMacro(key) => {
1274 self.play_macro(key);
1275 }
1276 Action::ToggleMacroRecording(key) => {
1277 self.toggle_macro_recording(key);
1278 }
1279 Action::ShowMacro(key) => {
1280 self.show_macro_in_buffer(key);
1281 }
1282 Action::ListMacros => {
1283 self.list_macros_in_buffer();
1284 }
1285 Action::PromptRecordMacro => {
1286 self.start_prompt("Record macro (0-9): ".to_string(), PromptType::RecordMacro);
1287 }
1288 Action::PromptPlayMacro => {
1289 self.start_prompt("Play macro (0-9): ".to_string(), PromptType::PlayMacro);
1290 }
1291 Action::PlayLastMacro => {
1292 if let Some(key) = self.macros.last_register() {
1293 self.play_macro(key);
1294 } else {
1295 self.set_status_message(t!("status.no_macro_recorded").to_string());
1296 }
1297 }
1298 Action::PromptSetBookmark => {
1299 self.start_prompt("Set bookmark (0-9): ".to_string(), PromptType::SetBookmark);
1300 }
1301 Action::PromptJumpToBookmark => {
1302 self.start_prompt(
1303 "Jump to bookmark (0-9): ".to_string(),
1304 PromptType::JumpToBookmark,
1305 );
1306 }
1307 Action::CompositeNextHunk => {
1308 let buf = self.active_buffer();
1309 self.composite_next_hunk_active(buf);
1310 }
1311 Action::CompositePrevHunk => {
1312 let buf = self.active_buffer();
1313 self.composite_prev_hunk_active(buf);
1314 }
1315 Action::None => {}
1316 Action::DeleteBackward => {
1317 if self.is_editing_disabled() {
1318 self.set_status_message(t!("buffer.editing_disabled").to_string());
1319 return Ok(());
1320 }
1321 if let Some(events) = self.action_to_events(Action::DeleteBackward) {
1323 if events.len() > 1 {
1324 let description = "Delete backward".to_string();
1326 if let Some(bulk_edit) = self.apply_events_as_bulk_edit(events, description)
1327 {
1328 self.active_event_log_mut().append(bulk_edit);
1329 }
1330 } else {
1331 for event in events {
1332 self.active_event_log_mut().append(event.clone());
1333 self.apply_event_to_active_buffer(&event);
1334 }
1335 }
1336 }
1337 }
1338 Action::PluginAction(action_name) => {
1339 tracing::debug!("handle_action: PluginAction('{}')", action_name);
1340 #[cfg(feature = "plugins")]
1343 if let Some(result) = self.plugin_manager.execute_action_async(&action_name) {
1344 match result {
1345 Ok(receiver) => {
1346 self.pending_plugin_actions
1348 .push((action_name.clone(), receiver));
1349 }
1350 Err(e) => {
1351 self.set_status_message(
1352 t!("view.plugin_error", error = e.to_string()).to_string(),
1353 );
1354 tracing::error!("Plugin action error: {}", e);
1355 }
1356 }
1357 } else {
1358 self.set_status_message(t!("status.plugin_manager_unavailable").to_string());
1359 }
1360 #[cfg(not(feature = "plugins"))]
1361 {
1362 let _ = action_name;
1363 self.set_status_message(
1364 "Plugins not available (compiled without plugin support)".to_string(),
1365 );
1366 }
1367 }
1368 Action::LoadPluginFromBuffer => {
1369 #[cfg(feature = "plugins")]
1370 {
1371 let buffer_id = self.active_buffer();
1372 let state = self.active_state();
1373 let buffer = &state.buffer;
1374 let total = buffer.total_bytes();
1375 let content =
1376 String::from_utf8_lossy(&buffer.slice_bytes(0..total)).to_string();
1377
1378 let is_ts = buffer
1380 .file_path()
1381 .and_then(|p| p.extension())
1382 .and_then(|e| e.to_str())
1383 .map(|e| e == "ts" || e == "tsx")
1384 .unwrap_or(true);
1385
1386 let name = buffer
1388 .file_path()
1389 .and_then(|p| p.file_name())
1390 .and_then(|s| s.to_str())
1391 .map(|s| s.to_string())
1392 .unwrap_or_else(|| "buffer-plugin".to_string());
1393
1394 match self
1395 .plugin_manager
1396 .load_plugin_from_source(&content, &name, is_ts)
1397 {
1398 Ok(()) => {
1399 self.set_status_message(format!(
1400 "Plugin '{}' loaded from buffer",
1401 name
1402 ));
1403 }
1404 Err(e) => {
1405 self.set_status_message(format!("Failed to load plugin: {}", e));
1406 tracing::error!("LoadPluginFromBuffer error: {}", e);
1407 }
1408 }
1409
1410 self.setup_plugin_dev_lsp(buffer_id, &content);
1412 }
1413 #[cfg(not(feature = "plugins"))]
1414 {
1415 self.set_status_message(
1416 "Plugins not available (compiled without plugin support)".to_string(),
1417 );
1418 }
1419 }
1420 Action::InitReload => {
1421 self.load_init_script(true);
1426 self.fire_plugins_loaded_hook();
1429 }
1430 Action::InitEdit => {
1431 let config_dir = self.dir_context.config_dir.clone();
1434 match crate::init_script::ensure_starter(&config_dir) {
1435 Ok(path) => {
1436 let declarations = self.plugin_manager.plugin_declarations();
1446 crate::init_script::write_plugin_declarations(&config_dir, &declarations);
1447 match self.open_file(&path) {
1448 Ok(_) => {
1449 self.set_status_message(format!("init.ts: {}", path.display()));
1450 }
1451 Err(e) => {
1452 self.set_status_message(format!("init.ts: open failed: {e}"));
1453 }
1454 }
1455 }
1456 Err(e) => {
1457 self.set_status_message(format!("init.ts: create failed: {e}"));
1458 }
1459 }
1460 }
1461 Action::InitCheck => {
1462 let report = crate::init_script::check(&self.dir_context.config_dir);
1465 if report.ok && report.diagnostics.is_empty() {
1466 self.set_status_message("init.ts: ok".into());
1467 } else if !report.ok {
1468 let first = report
1469 .diagnostics
1470 .first()
1471 .map(|d| format!("{}:{}: {}", d.line, d.column, d.message))
1472 .unwrap_or_else(|| "unknown error".into());
1473 self.set_status_message(format!(
1474 "init.ts: {} error(s) — first: {first}",
1475 report.diagnostics.len()
1476 ));
1477 } else {
1478 self.set_status_message(format!(
1479 "init.ts: {} warning(s)",
1480 report.diagnostics.len()
1481 ));
1482 }
1483 }
1484 Action::OpenTerminal => {
1485 self.open_terminal();
1486 }
1487 Action::CloseTerminal => {
1488 self.close_terminal();
1489 }
1490 Action::FocusTerminal => {
1491 if self.is_terminal_buffer(self.active_buffer()) {
1493 self.terminal_mode = true;
1494 self.key_context = KeyContext::Terminal;
1495 self.set_status_message(t!("status.terminal_mode_enabled").to_string());
1496 }
1497 }
1498 Action::TerminalEscape => {
1499 if self.terminal_mode {
1501 self.terminal_mode = false;
1502 self.key_context = KeyContext::Normal;
1503 self.set_status_message(t!("status.terminal_mode_disabled").to_string());
1504 }
1505 }
1506 Action::ToggleKeyboardCapture => {
1507 if self.terminal_mode {
1509 self.keyboard_capture = !self.keyboard_capture;
1510 if self.keyboard_capture {
1511 self.set_status_message(
1512 "Keyboard capture ON - all keys go to terminal (F9 to toggle)"
1513 .to_string(),
1514 );
1515 } else {
1516 self.set_status_message(
1517 "Keyboard capture OFF - UI bindings active (F9 to toggle)".to_string(),
1518 );
1519 }
1520 }
1521 }
1522 Action::TerminalPaste => {
1523 if self.terminal_mode {
1525 if let Some(text) = self.clipboard.paste() {
1526 self.send_terminal_input(text.as_bytes());
1527 }
1528 }
1529 }
1530 Action::ShellCommand => {
1531 self.start_shell_command_prompt(false);
1533 }
1534 Action::ShellCommandReplace => {
1535 self.start_shell_command_prompt(true);
1537 }
1538 Action::OpenSettings => {
1539 self.open_settings();
1540 }
1541 Action::CloseSettings => {
1542 let has_changes = self
1544 .settings_state
1545 .as_ref()
1546 .is_some_and(|s| s.has_changes());
1547 if has_changes {
1548 if let Some(ref mut state) = self.settings_state {
1550 state.show_confirm_dialog();
1551 }
1552 } else {
1553 self.close_settings(false);
1554 }
1555 }
1556 Action::SettingsSave => {
1557 self.save_settings();
1558 }
1559 Action::SettingsReset => {
1560 if let Some(ref mut state) = self.settings_state {
1561 state.reset_current_to_default();
1562 }
1563 }
1564 Action::SettingsInherit => {
1565 if let Some(ref mut state) = self.settings_state {
1566 state.set_current_to_null();
1567 }
1568 }
1569 Action::SettingsToggleFocus => {
1570 if let Some(ref mut state) = self.settings_state {
1571 state.toggle_focus();
1572 }
1573 }
1574 Action::SettingsActivate => {
1575 self.settings_activate_current();
1576 }
1577 Action::SettingsSearch => {
1578 if let Some(ref mut state) = self.settings_state {
1579 state.start_search();
1580 }
1581 }
1582 Action::SettingsHelp => {
1583 if let Some(ref mut state) = self.settings_state {
1584 state.toggle_help();
1585 }
1586 }
1587 Action::SettingsIncrement => {
1588 self.settings_increment_current();
1589 }
1590 Action::SettingsDecrement => {
1591 self.settings_decrement_current();
1592 }
1593 Action::CalibrateInput => {
1594 self.open_calibration_wizard();
1595 }
1596 Action::EventDebug => {
1597 self.open_event_debug();
1598 }
1599 Action::SuspendProcess => {
1600 self.request_suspend();
1601 }
1602 Action::OpenKeybindingEditor => {
1603 self.open_keybinding_editor();
1604 }
1605 Action::PromptConfirm => {
1606 if let Some((input, prompt_type, selected_index)) = self.confirm_prompt() {
1607 use super::prompt_actions::PromptResult;
1608 match self.handle_prompt_confirm_input(input, prompt_type, selected_index) {
1609 PromptResult::ExecuteAction(action) => {
1610 return self.handle_action(action);
1611 }
1612 PromptResult::EarlyReturn => {
1613 return Ok(());
1614 }
1615 PromptResult::Done => {}
1616 }
1617 }
1618 }
1619 Action::PromptConfirmWithText(ref text) => {
1620 if let Some(ref mut prompt) = self.prompt {
1622 prompt.set_input(text.clone());
1623 self.update_prompt_suggestions();
1624 }
1625 if let Some((input, prompt_type, selected_index)) = self.confirm_prompt() {
1626 use super::prompt_actions::PromptResult;
1627 match self.handle_prompt_confirm_input(input, prompt_type, selected_index) {
1628 PromptResult::ExecuteAction(action) => {
1629 return self.handle_action(action);
1630 }
1631 PromptResult::EarlyReturn => {
1632 return Ok(());
1633 }
1634 PromptResult::Done => {}
1635 }
1636 }
1637 }
1638 Action::PopupConfirm => {
1639 use super::popup_actions::PopupConfirmResult;
1640 if let PopupConfirmResult::EarlyReturn = self.handle_popup_confirm() {
1641 return Ok(());
1642 }
1643 }
1644 Action::PopupCancel => {
1645 self.handle_popup_cancel();
1646 }
1647 Action::CompletionAccept => {
1648 use super::popup_actions::PopupConfirmResult;
1649 if let PopupConfirmResult::EarlyReturn = self.handle_popup_confirm() {
1650 return Ok(());
1651 }
1652 }
1653 Action::CompletionDismiss => {
1654 self.handle_popup_cancel();
1655 }
1656 Action::InsertChar(c) => {
1657 if self.is_prompting() {
1658 return self.handle_insert_char_prompt(c);
1659 } else if self.key_context == KeyContext::FileExplorer {
1660 self.file_explorer_search_push_char(c);
1661 } else {
1662 self.handle_insert_char_editor(c)?;
1663 }
1664 }
1665 Action::PromptCopy => {
1667 if let Some(prompt) = &self.prompt {
1668 let text = prompt.selected_text().unwrap_or_else(|| prompt.get_text());
1669 if !text.is_empty() {
1670 self.clipboard.copy(text);
1671 self.set_status_message(t!("clipboard.copied").to_string());
1672 }
1673 }
1674 }
1675 Action::PromptCut => {
1676 if let Some(prompt) = &self.prompt {
1677 let text = prompt.selected_text().unwrap_or_else(|| prompt.get_text());
1678 if !text.is_empty() {
1679 self.clipboard.copy(text);
1680 }
1681 }
1682 if let Some(prompt) = self.prompt.as_mut() {
1683 if prompt.has_selection() {
1684 prompt.delete_selection();
1685 } else {
1686 prompt.clear();
1687 }
1688 }
1689 self.set_status_message(t!("clipboard.cut").to_string());
1690 self.update_prompt_suggestions();
1691 }
1692 Action::PromptPaste => {
1693 if let Some(text) = self.clipboard.paste() {
1694 if let Some(prompt) = self.prompt.as_mut() {
1695 prompt.insert_str(&text);
1696 }
1697 self.update_prompt_suggestions();
1698 }
1699 }
1700 _ => {
1701 self.apply_action_as_events(action)?;
1707 }
1708 }
1709
1710 Ok(())
1711 }
1712}