1use std::borrow::Borrow;
2use std::path;
3
4use anyhow::{Context, Result};
5use indicatif::InMemoryTerm;
6
7use crate::app::{Direction, Focus, Status, Tab};
8use crate::common::{
9 content_to_clipboard, filename_to_clipboard, filepath_to_clipboard, get_clipboard,
10 open_in_current_neovim, set_clipboard, set_current_dir, tilde, CONFIG_PATH,
11};
12use crate::config::{Bindings, START_FOLDER};
13use crate::io::{read_log, External};
14use crate::log_info;
15use crate::log_line;
16use crate::modes::{
17 help_string, lsblk_and_udisksctl_installed, ContentWindow, Direction as FuzzyDirection,
18 Display, FuzzyKind, InputCompleted, InputSimple, LeaveMenu, MarkAction, Menu, Navigate,
19 NeedConfirmation, PreviewBuilder, Search, Selectable,
20};
21
22pub struct EventAction {}
25
26impl EventAction {
27 pub fn quit(status: &mut Status) -> Result<()> {
31 if status.focus.is_file() {
32 status.internal_settings.quit();
33 } else {
34 status.reset_menu_mode()?;
35 }
36 Ok(())
37 }
38
39 pub fn refresh_view(status: &mut Status) -> Result<()> {
41 status.refresh_view()
42 }
43
44 pub fn refresh_if_needed(status: &mut Status) -> Result<()> {
46 status.menu.flagged.remove_non_existant();
47 status.tabs[0].refresh_if_needed()?;
48 status.tabs[1].refresh_if_needed()
49 }
50
51 pub fn resize(status: &mut Status, width: u16, height: u16) -> Result<()> {
52 status.resize(width, height)
53 }
54
55 pub fn reset_mode(status: &mut Status) -> Result<()> {
58 if status.focus.is_file() && status.current_tab().display_mode.is_preview() {
59 status.leave_preview()?;
60 }
61 if matches!(status.current_tab().menu_mode, Menu::Nothing) {
62 status.current_tab_mut().reset_visual();
63 return Ok(());
64 };
65 status.leave_menu_mode()?;
66 status.menu.input.reset();
67 status.menu.completion.reset();
68 Ok(())
69 }
70
71 pub fn toggle_display_full(status: &mut Status) -> Result<()> {
74 status.session.toggle_metadata();
75 Ok(())
76 }
77
78 pub fn toggle_dualpane(status: &mut Status) -> Result<()> {
81 status.clear_preview_right();
82 status.session.toggle_dual();
83 status.select_left();
84 Ok(())
85 }
86
87 pub fn toggle_preview_second(status: &mut Status) -> Result<()> {
89 if !status.session.dual() {
90 Self::toggle_dualpane(status)?;
91 status.session.set_preview();
92 } else {
93 status.session.toggle_preview();
94 }
95 if status.session.preview() {
96 status.update_second_pane_for_preview()
97 } else {
98 status.set_menu_mode(1, Menu::Nothing)?;
99 status.tabs[1].display_mode = Display::Directory;
100 status.tabs[1].refresh_view()
101 }
102 }
103
104 pub fn tree(status: &mut Status) -> Result<()> {
107 if !status.focus.is_file() {
108 return Ok(());
109 }
110 status.current_tab_mut().toggle_tree_mode()?;
111 status.refresh_view()
112 }
113
114 pub fn tree_fold(status: &mut Status) -> Result<()> {
117 if !status.focus.is_file() {
118 return Ok(());
119 }
120 let tab = status.current_tab_mut();
121 tab.tree.toggle_fold();
122 Ok(())
123 }
124
125 pub fn tree_unfold_all(status: &mut Status) -> Result<()> {
129 if !status.focus.is_file() {
130 return Ok(());
131 }
132 let tab = status.current_tab_mut();
133 tab.tree.unfold_all();
134 Ok(())
135 }
136
137 pub fn tree_fold_all(status: &mut Status) -> Result<()> {
141 if !status.focus.is_file() {
142 return Ok(());
143 }
144 let tab = status.current_tab_mut();
145 tab.tree.fold_all();
146 Ok(())
147 }
148
149 pub fn display_flagged(status: &mut Status) -> Result<()> {
152 if !status.focus.is_file() {
153 return Ok(());
154 }
155 let menu_mode = &status.current_tab().menu_mode;
156 if matches!(menu_mode, Menu::Navigate(Navigate::Flagged)) {
157 status.leave_menu_mode()?;
158 } else if matches!(menu_mode, Menu::Nothing) {
159 status.set_menu_mode(status.index, Menu::Navigate(Navigate::Flagged))?;
160 }
161 Ok(())
162 }
163
164 pub fn preview(status: &mut Status) -> Result<()> {
169 if !status.focus.is_file() {
170 return Ok(());
171 }
172 status.current_tab_mut().make_preview()
173 }
174
175 pub fn toggle_hidden(status: &mut Status) -> Result<()> {
177 if !status.focus.is_file() || status.current_tab().display_mode.is_preview() {
178 return Ok(());
179 }
180 status.current_tab_mut().toggle_hidden()
181 }
182
183 pub fn clear_flags(status: &mut Status) -> Result<()> {
185 if status.focus.is_file() {
186 status.menu.flagged.clear();
187 }
188 Ok(())
189 }
190
191 pub fn flag_all(status: &mut Status) -> Result<()> {
193 if status.focus.is_file() {
194 status.flag_all();
195 }
196 Ok(())
197 }
198
199 pub fn flagged_to_clipboard(status: &mut Status) -> Result<()> {
201 set_clipboard(status.menu.flagged.content_to_string());
202 Ok(())
203 }
204
205 pub fn flagged_from_clipboard(status: &mut Status) -> Result<()> {
208 let Some(files) = get_clipboard() else {
209 return Ok(());
210 };
211 if files.is_empty() {
212 return Ok(());
213 }
214 log_info!("clipboard read: {files}");
215 status.menu.flagged.replace_by_string(files);
216 Ok(())
217 }
218
219 pub fn reverse_flags(status: &mut Status) -> Result<()> {
222 if status.focus.is_file() {
223 status.reverse_flags();
224 }
225 Ok(())
226 }
227
228 pub fn toggle_flag(status: &mut Status) -> Result<()> {
230 if status.focus.is_file() {
231 status.toggle_flag_for_selected();
232 }
233 Ok(())
234 }
235
236 pub fn rename(status: &mut Status) -> Result<()> {
241 if matches!(
242 status.current_tab().menu_mode,
243 Menu::InputSimple(InputSimple::Rename)
244 ) {
245 status.reset_menu_mode()?;
246 return Ok(());
247 };
248 let selected = status.current_tab().current_file()?;
249 if selected.path == status.current_tab().directory.path {
250 return Ok(());
251 }
252 if let Some(parent) = status.current_tab().directory.path.parent() {
253 if selected.path == std::sync::Arc::from(parent) {
254 return Ok(());
255 }
256 }
257 let old_name = &selected.filename;
258 status.set_menu_mode(status.index, Menu::InputSimple(InputSimple::Rename))?;
259 status.menu.input.replace(old_name);
260 Ok(())
261 }
262
263 pub fn copy_paste(status: &mut Status) -> Result<()> {
268 if matches!(
269 status.current_tab().menu_mode,
270 Menu::NeedConfirmation(NeedConfirmation::Copy)
271 ) {
272 status.reset_menu_mode()?;
273 } else {
274 Self::set_copy_paste(status, NeedConfirmation::Copy)?;
275 }
276 Ok(())
277 }
278
279 pub fn cut_paste(status: &mut Status) -> Result<()> {
284 if matches!(
285 status.current_tab().menu_mode,
286 Menu::NeedConfirmation(NeedConfirmation::Move)
287 ) {
288 status.reset_menu_mode()?;
289 } else {
290 Self::set_copy_paste(status, NeedConfirmation::Move)?;
291 }
292 Ok(())
293 }
294
295 fn set_copy_paste(status: &mut Status, copy_or_move: NeedConfirmation) -> Result<()> {
296 if status.menu.flagged.is_empty() {
297 return Ok(());
298 }
299 status.set_menu_mode(status.index, Menu::NeedConfirmation(copy_or_move))
300 }
301
302 pub fn symlink(status: &mut Status) -> Result<()> {
304 if !status.focus.is_file() {
305 return Ok(());
306 }
307 for original_file in status.menu.flagged.content.iter() {
308 let filename = original_file
309 .as_path()
310 .file_name()
311 .context("event symlink: File not found")?;
312 let link = status.current_tab().directory_of_selected()?.join(filename);
313 std::os::unix::fs::symlink(original_file, &link)?;
314 log_line!(
315 "Symlink {link} links to {original_file}",
316 original_file = original_file.display(),
317 link = link.display()
318 );
319 }
320 status.clear_flags_and_reset_view()
321 }
322
323 pub fn delete_file(status: &mut Status) -> Result<()> {
327 if status.menu.flagged.is_empty() {
328 Self::toggle_flag(status)?;
329 }
330 status.set_menu_mode(
331 status.index,
332 Menu::NeedConfirmation(NeedConfirmation::Delete),
333 )
334 }
335
336 pub fn chmod(status: &mut Status) -> Result<()> {
338 if matches!(
339 status.current_tab().menu_mode,
340 Menu::InputSimple(InputSimple::Chmod)
341 ) {
342 status.reset_menu_mode()?;
343 } else {
344 status.set_mode_chmod()?;
345 }
346 Ok(())
347 }
348
349 fn new_node(status: &mut Status, input_kind: InputSimple) -> Result<()> {
351 if !matches!(input_kind, InputSimple::Newdir | InputSimple::Newfile) {
352 return Ok(());
353 }
354 if matches!(
355 status.current_tab().menu_mode,
356 Menu::InputSimple(InputSimple::Newdir | InputSimple::Newfile)
357 ) {
358 status.reset_menu_mode()?;
359 return Ok(());
360 }
361 if matches!(
362 status.current_tab().display_mode,
363 Display::Directory | Display::Tree
364 ) {
365 status.set_menu_mode(status.index, Menu::InputSimple(input_kind))?;
366 }
367 Ok(())
368 }
369
370 pub fn new_dir(status: &mut Status) -> Result<()> {
372 Self::new_node(status, InputSimple::Newdir)
373 }
374
375 pub fn new_file(status: &mut Status) -> Result<()> {
377 Self::new_node(status, InputSimple::Newfile)
378 }
379
380 fn enter_file(status: &mut Status) -> Result<()> {
381 match status.current_tab_mut().display_mode {
382 Display::Directory => Self::normal_enter_file(status),
383 Display::Tree => Self::tree_enter_file(status),
384 _ => Ok(()),
385 }
386 }
387
388 fn normal_enter_file(status: &mut Status) -> Result<()> {
390 let tab = status.current_tab_mut();
391 if tab.display_mode.is_tree() {
392 return EventAction::open_file(status);
393 };
394 if tab.directory.is_empty() {
395 return Ok(());
396 }
397 if tab.directory.is_selected_dir()? {
398 tab.go_to_selected_dir()?;
399 status.thumbnail_directory_video();
400 Ok(())
401 } else {
402 EventAction::open_file(status)
403 }
404 }
405
406 fn tree_enter_file(status: &mut Status) -> Result<()> {
408 let path = status.current_tab().current_file()?.path;
409 if path.is_dir() {
410 status.current_tab_mut().tree_enter_dir(path)
411 } else {
412 EventAction::open_file(status)
413 }
414 }
415
416 pub fn open_file(status: &mut Status) -> Result<()> {
424 if !status.focus.is_file() {
425 return Ok(());
426 }
427 if status.menu.flagged.is_empty() {
428 status.open_selected_file()
429 } else {
430 status.open_flagged_files()
431 }
432 }
433
434 pub fn open_all(status: &mut Status) -> Result<()> {
435 status.open_flagged_files()
436 }
437
438 pub fn exec(status: &mut Status) -> Result<()> {
441 if matches!(
442 status.current_tab().menu_mode,
443 Menu::InputCompleted(InputCompleted::Exec)
444 ) {
445 status.reset_menu_mode()?;
446 return Ok(());
447 }
448 if status.menu.flagged.is_empty() {
449 status
450 .menu
451 .flagged
452 .push(status.current_tab().current_file()?.path.to_path_buf());
453 }
454 status.set_menu_mode(status.index, Menu::InputCompleted(InputCompleted::Exec))
455 }
456
457 pub fn sort(status: &mut Status) -> Result<()> {
459 if matches!(
460 status.current_tab().menu_mode,
461 Menu::InputSimple(InputSimple::Sort)
462 ) {
463 status.reset_menu_mode()?;
464 }
465 status.set_height_for_menu_mode(status.index, Menu::Nothing)?;
466 status.tabs[status.index].menu_mode = Menu::Nothing;
467 let len = status.menu.len(Menu::Nothing);
468 let height = status.second_window_height()?;
469 status.menu.window = ContentWindow::new(len, height);
470 status.tabs[status.index].menu_mode = Menu::InputSimple(InputSimple::Sort);
471 status.set_focus_from_mode();
472 Ok(())
473 }
474
475 pub fn filter(status: &mut Status) -> Result<()> {
478 if matches!(
479 status.current_tab().menu_mode,
480 Menu::InputSimple(InputSimple::Filter)
481 ) {
482 status.reset_menu_mode()?;
483 } else if matches!(
484 status.current_tab().display_mode,
485 Display::Tree | Display::Directory
486 ) {
487 status.set_menu_mode(status.index, Menu::InputSimple(InputSimple::Filter))?;
488 }
489 Ok(())
490 }
491
492 pub fn bulk(status: &mut Status) -> Result<()> {
496 if !status.focus.is_file() {
497 return Ok(());
498 }
499 status.bulk_ask_filenames()?;
500 Ok(())
501 }
502
503 pub fn search(status: &mut Status) -> Result<()> {
506 if matches!(
507 status.current_tab().menu_mode,
508 Menu::InputCompleted(InputCompleted::Search)
509 ) {
510 status.reset_menu_mode()?;
511 }
512 let tab = status.current_tab_mut();
513 tab.search = Search::empty();
514 status.set_menu_mode(status.index, Menu::InputCompleted(InputCompleted::Search))
515 }
516
517 pub fn regex_match(status: &mut Status) -> Result<()> {
520 if matches!(
521 status.current_tab().menu_mode,
522 Menu::InputSimple(InputSimple::RegexMatch)
523 ) {
524 status.reset_menu_mode()?;
525 }
526 if matches!(
527 status.current_tab().display_mode,
528 Display::Tree | Display::Directory
529 ) {
530 status.set_menu_mode(status.index, Menu::InputSimple(InputSimple::RegexMatch))
531 } else {
532 Ok(())
533 }
534 }
535
536 pub fn help(status: &mut Status, binds: &Bindings) -> Result<()> {
539 if !status.focus.is_file() {
540 return Ok(());
541 }
542 let help = help_string(binds, &status.internal_settings.opener);
543 status.current_tab_mut().set_display_mode(Display::Preview);
544 status.current_tab_mut().preview = PreviewBuilder::help(&help);
545 let len = status.current_tab().preview.len();
546 status.current_tab_mut().window.reset(len);
547 Ok(())
548 }
549
550 pub fn log(status: &mut Status) -> Result<()> {
552 if !status.focus.is_file() {
553 return Ok(());
554 }
555 let Ok(log) = read_log() else {
556 return Ok(());
557 };
558 let tab = status.current_tab_mut();
559 tab.set_display_mode(Display::Preview);
560 tab.preview = PreviewBuilder::log(log);
561 tab.window.reset(tab.preview.len());
562 tab.preview_go_bottom();
563 Ok(())
564 }
565
566 pub fn cd(status: &mut Status) -> Result<()> {
568 if matches!(
569 status.current_tab().menu_mode,
570 Menu::InputCompleted(InputCompleted::Cd)
571 ) {
572 status.reset_menu_mode()?;
573 } else {
574 status.set_menu_mode(status.index, Menu::InputCompleted(InputCompleted::Cd))?;
575
576 status.tabs[status.index].save_origin_path();
577 status.menu.completion.reset();
578 }
579 Ok(())
580 }
581
582 pub fn shell(status: &mut Status) -> Result<()> {
586 if !status.focus.is_file() {
587 return Ok(());
588 }
589 set_current_dir(status.current_tab().current_path())?;
590 status.internal_settings.disable_display();
591 External::open_shell_in_window()?;
592 status.internal_settings.enable_display();
593 Ok(())
594 }
595
596 pub fn shell_command(status: &mut Status) -> Result<()> {
599 if matches!(
600 status.current_tab().menu_mode,
601 Menu::InputSimple(InputSimple::ShellCommand)
602 ) {
603 status.reset_menu_mode()?;
604 }
605 status.set_menu_mode(status.index, Menu::InputSimple(InputSimple::ShellCommand))
606 }
607
608 pub fn tui_menu(status: &mut Status) -> Result<()> {
610 if matches!(
611 status.current_tab().menu_mode,
612 Menu::Navigate(Navigate::TuiApplication)
613 ) {
614 status.reset_menu_mode()?;
615 } else {
616 if status.menu.tui_applications.is_not_set() {
617 status.menu.tui_applications.setup();
618 }
619 status.set_menu_mode(status.index, Menu::Navigate(Navigate::TuiApplication))?;
620 }
621 Ok(())
622 }
623
624 pub fn cli_menu(status: &mut Status) -> Result<()> {
627 if matches!(
628 status.current_tab().menu_mode,
629 Menu::Navigate(Navigate::CliApplication)
630 ) {
631 status.reset_menu_mode()?;
632 } else {
633 if status.menu.cli_applications.is_empty() {
634 status.menu.cli_applications.setup();
635 }
636 status.set_menu_mode(status.index, Menu::Navigate(Navigate::CliApplication))?;
637 }
638 Ok(())
639 }
640
641 pub fn history(status: &mut Status) -> Result<()> {
644 if matches!(
645 status.current_tab().menu_mode,
646 Menu::Navigate(Navigate::History)
647 ) {
648 status.reset_menu_mode()?;
649 } else if matches!(
650 status.current_tab().display_mode,
651 Display::Directory | Display::Tree
652 ) {
653 status.set_menu_mode(status.index, Menu::Navigate(Navigate::History))?;
654 }
655 Ok(())
656 }
657
658 pub fn marks_new(status: &mut Status) -> Result<()> {
660 if matches!(
661 status.current_tab().menu_mode,
662 Menu::Navigate(Navigate::Marks(MarkAction::New))
663 ) {
664 status.reset_menu_mode()?;
665 } else {
666 if status.menu.marks.is_empty() {
667 status.menu.marks.setup();
668 }
669 status.set_menu_mode(
670 status.index,
671 Menu::Navigate(Navigate::Marks(MarkAction::New)),
672 )?;
673 }
674 Ok(())
675 }
676
677 pub fn marks_jump(status: &mut Status) -> Result<()> {
679 if matches!(
680 status.current_tab().menu_mode,
681 Menu::Navigate(Navigate::Marks(MarkAction::Jump))
682 ) {
683 status.reset_menu_mode()?;
684 } else {
685 if status.menu.marks.is_empty() {
686 status.menu.marks.setup();
687 }
688 if status.menu.marks.is_empty() {
689 return Ok(());
690 }
691 status.set_menu_mode(
692 status.index,
693 Menu::Navigate(Navigate::Marks(MarkAction::Jump)),
694 )?;
695 }
696 Ok(())
697 }
698
699 pub fn temp_marks_jump(status: &mut Status) -> Result<()> {
701 if matches!(
702 status.current_tab().menu_mode,
703 Menu::Navigate(Navigate::TempMarks(MarkAction::Jump))
704 ) {
705 status.reset_menu_mode()?;
706 } else {
707 status.set_menu_mode(
708 status.index,
709 Menu::Navigate(Navigate::TempMarks(MarkAction::Jump)),
710 )?;
711 }
712 Ok(())
713 }
714
715 pub fn temp_marks_new(status: &mut Status) -> Result<()> {
717 if matches!(
718 status.current_tab().menu_mode,
719 Menu::Navigate(Navigate::TempMarks(MarkAction::New))
720 ) {
721 status.reset_menu_mode()?;
722 } else {
723 status.set_menu_mode(
724 status.index,
725 Menu::Navigate(Navigate::TempMarks(MarkAction::New)),
726 )?;
727 }
728 Ok(())
729 }
730
731 pub fn shortcut(status: &mut Status) -> Result<()> {
735 if matches!(
736 status.current_tab().menu_mode,
737 Menu::Navigate(Navigate::Shortcut)
738 ) {
739 status.reset_menu_mode()?;
740 } else {
741 status.refresh_shortcuts();
742 set_current_dir(status.current_tab().directory_of_selected()?)?;
743 status.set_menu_mode(status.index, Menu::Navigate(Navigate::Shortcut))?;
744 }
745 Ok(())
746 }
747
748 pub fn nvim_filepicker(status: &mut Status) -> Result<()> {
755 if !status.focus.is_file() {
756 return Ok(());
757 }
758 status.update_nvim_listen_address();
759 if status.internal_settings.nvim_server.is_empty() {
760 return Ok(());
761 };
762 let nvim_server = status.internal_settings.nvim_server.clone();
763 if status.menu.flagged.is_empty() {
764 let Ok(fileinfo) = status.current_tab().current_file() else {
765 return Ok(());
766 };
767 open_in_current_neovim(&fileinfo.path, &nvim_server);
768 } else {
769 let flagged = status.menu.flagged.content.clone();
770 for file_path in flagged.iter() {
771 open_in_current_neovim(file_path, &nvim_server)
772 }
773 }
774
775 Ok(())
776 }
777
778 pub fn set_nvim_server(status: &mut Status) -> Result<()> {
781 if matches!(
782 status.current_tab().menu_mode,
783 Menu::InputSimple(InputSimple::SetNvimAddr)
784 ) {
785 status.reset_menu_mode()?;
786 return Ok(());
787 };
788 status.set_menu_mode(status.index, Menu::InputSimple(InputSimple::SetNvimAddr))
789 }
790
791 pub fn back(status: &mut Status) -> Result<()> {
793 if !status.focus.is_file() {
794 return Ok(());
795 }
796 status.current_tab_mut().back()?;
797 status.update_second_pane_for_preview()
798 }
799
800 pub fn home(status: &mut Status) -> Result<()> {
802 if !status.focus.is_file() {
803 return Ok(());
804 }
805 let home_cow = tilde("~");
806 let home: &str = home_cow.borrow();
807 let home_path = path::Path::new(home);
808 status.current_tab_mut().cd(home_path)?;
809 status.update_second_pane_for_preview()
810 }
811
812 pub fn go_root(status: &mut Status) -> Result<()> {
813 if !status.focus.is_file() {
814 return Ok(());
815 }
816 let root_path = std::path::PathBuf::from("/");
817 status.current_tab_mut().cd(&root_path)?;
818 status.update_second_pane_for_preview()
819 }
820
821 pub fn go_start(status: &mut Status) -> Result<()> {
822 if !status.focus.is_file() {
823 return Ok(());
824 }
825 status
826 .current_tab_mut()
827 .cd(START_FOLDER.get().context("Start folder should be set")?)?;
828 status.update_second_pane_for_preview()
829 }
830
831 pub fn search_next(status: &mut Status) -> Result<()> {
832 if !status.focus.is_file() {
833 return Ok(());
834 }
835 match status.current_tab().display_mode {
836 Display::Tree => status.tabs[status.index]
837 .search
838 .tree(&mut status.tabs[status.index].tree),
839 Display::Directory => status.current_tab_mut().directory_search_next(),
840 Display::Preview | Display::Fuzzy => {
841 return Ok(());
842 }
843 }
844 status.refresh_status()?;
845 status.update_second_pane_for_preview()?;
846 Ok(())
847 }
848
849 pub fn move_up(status: &mut Status) -> Result<()> {
852 if status.focus.is_file() {
853 Self::move_display_up(status)?;
854 } else {
855 let tab = status.current_tab_mut();
856 match tab.menu_mode {
857 Menu::Nothing => Self::move_display_up(status)?,
858 Menu::Navigate(Navigate::History) => tab.history.prev(),
859 Menu::Navigate(navigate) => status.menu.prev(navigate),
860 Menu::InputCompleted(input_completed) => {
861 status.menu.completion_prev(input_completed)
862 }
863 _ => (),
864 };
865 }
866 status.update_second_pane_for_preview()
867 }
868
869 pub fn next_thing(status: &mut Status) -> Result<()> {
873 if status.current_tab().display_mode.is_tree() && status.focus.is_file() {
874 status.current_tab_mut().tree_next_sibling();
875 } else {
876 status.input_history_prev()?;
877 }
878 Ok(())
879 }
880
881 pub fn previous_thing(status: &mut Status) -> Result<()> {
885 if status.current_tab().display_mode.is_tree() && status.focus.is_file() {
886 status.current_tab_mut().tree_prev_sibling();
887 } else {
888 status.input_history_next()?;
889 }
890 Ok(())
891 }
892
893 fn move_display_up(status: &mut Status) -> Result<()> {
894 let tab = status.current_tab_mut();
895 match tab.display_mode {
896 Display::Directory => {
897 tab.normal_up_one_row();
898 status.toggle_flag_visual();
899 }
900 Display::Preview => tab.preview_page_up(),
901 Display::Tree => {
902 tab.tree_select_prev();
903 status.toggle_flag_visual();
904 }
905 Display::Fuzzy => status.fuzzy_navigate(FuzzyDirection::Up)?,
906 }
907 Ok(())
908 }
909
910 fn move_display_down(status: &mut Status) -> Result<()> {
911 let tab = status.current_tab_mut();
912 match tab.display_mode {
913 Display::Directory => {
914 tab.normal_down_one_row();
915 status.toggle_flag_visual();
916 }
917 Display::Preview => tab.preview_page_down(),
918 Display::Tree => {
919 tab.tree_select_next();
920 status.toggle_flag_visual()
921 }
922 Display::Fuzzy => status.fuzzy_navigate(FuzzyDirection::Down)?,
923 }
924 Ok(())
925 }
926 pub fn move_down(status: &mut Status) -> Result<()> {
929 if status.focus.is_file() {
930 Self::move_display_down(status)?
931 } else {
932 match status.current_tab_mut().menu_mode {
933 Menu::Nothing => Self::move_display_down(status)?,
934 Menu::Navigate(Navigate::History) => status.current_tab_mut().history.next(),
935 Menu::Navigate(navigate) => status.menu.next(navigate),
936 Menu::InputCompleted(input_completed) => {
937 status.menu.completion_next(input_completed)
938 }
939 _ => (),
940 };
941 }
942 status.update_second_pane_for_preview()
943 }
944
945 pub fn move_left(status: &mut Status) -> Result<()> {
948 if status.focus.is_file() {
949 Self::file_move_left(status.current_tab_mut())?;
950 } else {
951 let tab = status.current_tab_mut();
952 match tab.menu_mode {
953 Menu::InputSimple(_) | Menu::InputCompleted(_) => {
954 status.menu.input.cursor_left();
955 }
956 Menu::Nothing => Self::file_move_left(tab)?,
957 Menu::Navigate(Navigate::Cloud) => status.cloud_move_to_parent()?,
958 _ => (),
959 }
960 }
961 status.update_second_pane_for_preview()
962 }
963
964 fn file_move_left(tab: &mut Tab) -> Result<()> {
965 match tab.display_mode {
966 Display::Directory => tab.move_to_parent()?,
967 Display::Tree => tab.tree_select_parent()?,
968 _ => (),
969 };
970 Ok(())
971 }
972
973 pub fn move_right(status: &mut Status) -> Result<()> {
976 if status.focus.is_file() {
977 Self::enter_file(status)
978 } else {
979 let tab: &mut Tab = status.current_tab_mut();
980 match tab.menu_mode {
981 Menu::InputSimple(_) | Menu::InputCompleted(_) => {
982 status.menu.input.cursor_right();
983 Ok(())
984 }
985 Menu::Navigate(Navigate::Cloud) => status.cloud_enter_file_or_dir(),
986 Menu::Nothing => Self::enter_file(status),
987 _ => Ok(()),
988 }
989 }
990 }
991
992 pub fn focus_follow_mouse(status: &mut Status, row: u16, col: u16) -> Result<()> {
993 status.set_focus_from_pos(row, col)?;
994 Ok(())
995 }
996
997 fn click(status: &mut Status, binds: &Bindings, row: u16, col: u16) -> Result<()> {
999 status.click(binds, row, col)
1000 }
1001
1002 pub fn left_click(status: &mut Status, binds: &Bindings, row: u16, col: u16) -> Result<()> {
1004 Self::click(status, binds, row, col)
1005 }
1006
1007 pub fn right_click(status: &mut Status, binds: &Bindings, row: u16, col: u16) -> Result<()> {
1009 Self::click(status, binds, row, col)?;
1010 Self::context(status)
1011 }
1012
1013 pub fn wheel_up(status: &mut Status, row: u16, col: u16) -> Result<()> {
1015 status.set_focus_from_pos(row, col)?;
1016 Self::move_up(status)
1017 }
1018
1019 pub fn wheel_down(status: &mut Status, row: u16, col: u16) -> Result<()> {
1021 status.set_focus_from_pos(row, col)?;
1022 Self::move_down(status)
1023 }
1024
1025 pub fn middle_click(status: &mut Status, binds: &Bindings, row: u16, col: u16) -> Result<()> {
1027 if Self::click(status, binds, row, col).is_ok() {
1028 if status.focus.is_file() {
1029 Self::enter_file(status)?;
1030 } else {
1031 LeaveMenu::leave_menu(status, binds)?;
1032 }
1033 };
1034 Ok(())
1035 }
1036
1037 pub fn backspace(status: &mut Status) -> Result<()> {
1039 if status.focus.is_file() {
1040 return Ok(());
1041 }
1042 match status.current_tab().menu_mode {
1043 Menu::Navigate(Navigate::Marks(_)) => {
1044 status.menu.marks.remove_selected()?;
1045 }
1046 Menu::Navigate(Navigate::TempMarks(_)) => {
1047 status.menu.temp_marks.erase_current_mark();
1048 }
1049 Menu::InputSimple(_) | Menu::InputCompleted(_) => {
1050 status.menu.input.delete_char_left();
1051 }
1052 _ => (),
1053 }
1054 Ok(())
1055 }
1056
1057 pub fn delete(status: &mut Status) -> Result<()> {
1060 if status.focus.is_file() {
1061 Self::delete_file(status)
1062 } else {
1063 match status.current_tab_mut().menu_mode {
1064 Menu::InputSimple(_) | Menu::InputCompleted(_) => {
1065 status.menu.input.delete_chars_right();
1066 Ok(())
1067 }
1068 _ => Ok(()),
1069 }
1070 }
1071 }
1072
1073 pub fn delete_line(status: &mut Status) -> Result<()> {
1074 if status.focus.is_file() {
1075 status.sync_tabs(Direction::RightToLeft)?;
1076 }
1077 match status.current_tab_mut().menu_mode {
1078 Menu::InputSimple(_) => {
1079 status.menu.input.delete_line();
1080 }
1081 Menu::InputCompleted(_) => {
1082 status.menu.input.delete_line();
1083 status.menu.completion_reset();
1084 }
1085 _ => (),
1086 }
1087 Ok(())
1088 }
1089
1090 pub fn delete_left(status: &mut Status) -> Result<()> {
1092 if status.focus.is_file() {
1093 return Ok(());
1094 }
1095 match status.current_tab_mut().menu_mode {
1096 Menu::InputSimple(_) => {
1097 status.menu.input.delete_left();
1098 }
1099 Menu::InputCompleted(_) => {
1100 status.menu.input.delete_left();
1101 status.menu.completion_reset();
1102 }
1103 _ => (),
1104 }
1105 Ok(())
1106 }
1107
1108 pub fn key_home(status: &mut Status) -> Result<()> {
1110 if status.focus.is_file() {
1111 let tab = status.current_tab_mut();
1112 match tab.display_mode {
1113 Display::Directory => tab.normal_go_top(),
1114 Display::Preview => tab.preview_go_top(),
1115 Display::Tree => tab.tree_go_to_root()?,
1116 Display::Fuzzy => status.fuzzy_start()?,
1117 };
1118 status.update_second_pane_for_preview()
1119 } else {
1120 match status.current_tab().menu_mode {
1121 Menu::InputSimple(_) | Menu::InputCompleted(_) => status.menu.input.cursor_start(),
1122 Menu::Navigate(navigate) => status.menu.set_index(0, navigate),
1123 _ => (),
1124 }
1125 Ok(())
1126 }
1127 }
1128
1129 pub fn end(status: &mut Status) -> Result<()> {
1131 if status.focus.is_file() {
1132 let tab = status.current_tab_mut();
1133 match tab.display_mode {
1134 Display::Directory => tab.normal_go_bottom(),
1135 Display::Preview => tab.preview_go_bottom(),
1136 Display::Tree => tab.tree_go_to_bottom_leaf(),
1137 Display::Fuzzy => status.fuzzy_end()?,
1138 };
1139 status.update_second_pane_for_preview()?;
1140 } else {
1141 match status.current_tab().menu_mode {
1142 Menu::InputSimple(_) | Menu::InputCompleted(_) => status.menu.input.cursor_end(),
1143 Menu::Navigate(navigate) => status.menu.select_last(navigate),
1144 _ => (),
1145 }
1146 }
1147 Ok(())
1148 }
1149
1150 pub fn page_up(status: &mut Status) -> Result<()> {
1152 if status.focus.is_file() {
1153 Self::file_page_up(status)?;
1154 } else {
1155 let tab = status.current_tab_mut();
1156 match tab.menu_mode {
1157 Menu::Nothing => Self::file_page_up(status)?,
1158 Menu::Navigate(navigate) => status.menu.page_up(navigate),
1159 Menu::InputCompleted(input_completed) => {
1160 for _ in 0..10 {
1161 status.menu.completion_prev(input_completed)
1162 }
1163 }
1164 _ => (),
1165 };
1166 }
1167 Ok(())
1168 }
1169
1170 fn file_page_up(status: &mut Status) -> Result<()> {
1171 let tab = status.current_tab_mut();
1172 match tab.display_mode {
1173 Display::Directory => {
1174 tab.normal_page_up();
1175 status.update_second_pane_for_preview()?;
1176 }
1177 Display::Preview => tab.preview_page_up(),
1178 Display::Tree => {
1179 tab.tree_page_up();
1180 status.update_second_pane_for_preview()?;
1181 }
1182 Display::Fuzzy => status.fuzzy_navigate(FuzzyDirection::PageUp)?,
1183 };
1184 Ok(())
1185 }
1186
1187 pub fn page_down(status: &mut Status) -> Result<()> {
1189 if status.focus.is_file() {
1190 Self::file_page_down(status)?;
1191 } else {
1192 let tab = status.current_tab_mut();
1193 match tab.menu_mode {
1194 Menu::Nothing => Self::file_page_down(status)?,
1195 Menu::Navigate(navigate) => status.menu.page_down(navigate),
1196 Menu::InputCompleted(input_completed) => {
1197 for _ in 0..10 {
1198 status.menu.completion_next(input_completed)
1199 }
1200 }
1201 _ => (),
1202 };
1203 }
1204 Ok(())
1205 }
1206
1207 fn file_page_down(status: &mut Status) -> Result<()> {
1208 let tab = status.current_tab_mut();
1209 match tab.display_mode {
1210 Display::Directory => {
1211 tab.normal_page_down();
1212 status.update_second_pane_for_preview()?;
1213 }
1214 Display::Preview => tab.preview_page_down(),
1215 Display::Tree => {
1216 tab.tree_page_down();
1217 status.update_second_pane_for_preview()?;
1218 }
1219 Display::Fuzzy => status.fuzzy_navigate(FuzzyDirection::PageDown)?,
1220 };
1221 Ok(())
1222 }
1223
1224 pub fn enter(status: &mut Status, binds: &Bindings) -> Result<()> {
1230 if status.focus.is_file() {
1231 Self::enter_file(status)
1232 } else {
1233 LeaveMenu::leave_menu(status, binds)
1234 }
1235 }
1236
1237 pub fn tab(status: &mut Status) -> Result<()> {
1240 if status.focus.is_file() {
1241 status.next()
1242 } else if let Menu::InputCompleted(input_completed) = status.current_tab_mut().menu_mode {
1243 status.complete_tab(input_completed)?;
1244 }
1245 Ok(())
1246 }
1247
1248 pub fn fuzzyfind(status: &mut Status) -> Result<()> {
1250 status.force_clear();
1251 status.fuzzy_init(FuzzyKind::File);
1252 status.current_tab_mut().set_display_mode(Display::Fuzzy);
1253 status.fuzzy_find_files()?;
1254 status.update_second_pane_for_preview()
1255 }
1256
1257 pub fn fuzzyfind_line(status: &mut Status) -> Result<()> {
1259 status.force_clear();
1260 status.fuzzy_init(FuzzyKind::Line);
1261 status.current_tab_mut().set_display_mode(Display::Fuzzy);
1262 status.fuzzy_find_lines()
1263 }
1264
1265 pub fn fuzzyfind_help(status: &mut Status, binds: &Bindings) -> Result<()> {
1267 status.fuzzy_init(FuzzyKind::Action);
1268 status.current_tab_mut().set_display_mode(Display::Fuzzy);
1269 let help = help_string(binds, &status.internal_settings.opener);
1270 status.fuzzy_help(help)?;
1271 Ok(())
1272 }
1273
1274 pub fn copy_content(status: &Status) -> Result<()> {
1276 if !status.focus.is_file() {
1277 return Ok(());
1278 }
1279 match status.current_tab().display_mode {
1280 Display::Tree | Display::Directory => {
1281 let Ok(file_info) = status.current_tab().current_file() else {
1282 return Ok(());
1283 };
1284 content_to_clipboard(&file_info.path);
1285 }
1286 _ => return Ok(()),
1287 }
1288 Ok(())
1289 }
1290 pub fn copy_filename(status: &Status) -> Result<()> {
1292 if !status.focus.is_file() {
1293 return Ok(());
1294 }
1295 match status.current_tab().display_mode {
1296 Display::Tree | Display::Directory => {
1297 let Ok(file_info) = status.current_tab().current_file() else {
1298 return Ok(());
1299 };
1300 filename_to_clipboard(&file_info.path);
1301 }
1302 _ => return Ok(()),
1303 }
1304 Ok(())
1305 }
1306
1307 pub fn copy_filepath(status: &Status) -> Result<()> {
1309 if !status.focus.is_file() {
1310 return Ok(());
1311 }
1312 match status.current_tab().display_mode {
1313 Display::Tree | Display::Directory => {
1314 let Ok(file_info) = status.current_tab().current_file() else {
1315 return Ok(());
1316 };
1317 filepath_to_clipboard(&file_info.path);
1318 }
1319 _ => return Ok(()),
1320 }
1321 Ok(())
1322 }
1323
1324 pub fn trash_move_file(status: &mut Status) -> Result<()> {
1331 if !status.focus.is_file() {
1332 return Ok(());
1333 }
1334 if status.menu.flagged.is_empty() {
1335 Self::toggle_flag(status)?;
1336 }
1337
1338 status.menu.trash.update()?;
1339 for flagged in status.menu.flagged.content.iter() {
1340 let _ = status.menu.trash.trash(flagged);
1341 }
1342 status.menu.flagged.clear();
1343 status.current_tab_mut().refresh_view()?;
1344 Ok(())
1345 }
1346
1347 pub fn trash_empty(status: &mut Status) -> Result<()> {
1350 if matches!(
1351 status.current_tab().menu_mode,
1352 Menu::NeedConfirmation(NeedConfirmation::EmptyTrash)
1353 ) {
1354 status.reset_menu_mode()?;
1355 } else {
1356 status.menu.trash.update()?;
1357 status.set_menu_mode(
1358 status.index,
1359 Menu::NeedConfirmation(NeedConfirmation::EmptyTrash),
1360 )?;
1361 }
1362 Ok(())
1363 }
1364
1365 pub fn trash_open(status: &mut Status) -> Result<()> {
1370 if matches!(
1371 status.current_tab().menu_mode,
1372 Menu::Navigate(Navigate::Trash)
1373 ) {
1374 status.reset_menu_mode()?;
1375 } else {
1376 status.menu.trash.update()?;
1377 status.set_menu_mode(status.index, Menu::Navigate(Navigate::Trash))?;
1378 }
1379 Ok(())
1380 }
1381
1382 pub fn trash_restore(status: &mut Status) -> Result<()> {
1383 LeaveMenu::trash(status)
1384 }
1385
1386 pub fn open_config(status: &mut Status) -> Result<()> {
1388 if !status.focus.is_file() {
1389 return Ok(());
1390 }
1391 match status.open_single_file(&path::PathBuf::from(tilde(CONFIG_PATH).to_string())) {
1392 Ok(_) => log_line!("Opened the config file {CONFIG_PATH}"),
1393 Err(e) => log_info!("Error opening {:?}: the config file {}", CONFIG_PATH, e),
1394 }
1395 Ok(())
1396 }
1397
1398 pub fn compress(status: &mut Status) -> Result<()> {
1400 if matches!(
1401 status.current_tab().menu_mode,
1402 Menu::Navigate(Navigate::Compress)
1403 ) {
1404 status.reset_menu_mode()?;
1405 } else {
1406 if status.menu.compression.is_empty() {
1407 status.menu.compression.setup();
1408 }
1409 status.set_menu_mode(status.index, Menu::Navigate(Navigate::Compress))?;
1410 }
1411 Ok(())
1412 }
1413
1414 pub fn context(status: &mut Status) -> Result<()> {
1416 if matches!(
1417 status.current_tab().menu_mode,
1418 Menu::Navigate(Navigate::Context)
1419 ) {
1420 status.reset_menu_mode()?;
1421 } else {
1422 if status.menu.context.is_empty() {
1423 status.menu.context.setup();
1424 } else {
1425 status.menu.context.reset();
1426 }
1427 status.set_menu_mode(status.index, Menu::Navigate(Navigate::Context))?;
1428 }
1429 Ok(())
1430 }
1431
1432 pub fn action(status: &mut Status) -> Result<()> {
1435 if matches!(
1436 status.current_tab().menu_mode,
1437 Menu::InputCompleted(InputCompleted::Action)
1438 ) {
1439 status.reset_menu_mode()?;
1440 } else {
1441 status.set_menu_mode(status.index, Menu::InputCompleted(InputCompleted::Action))?;
1442 status.menu.completion.reset();
1443 }
1444 Ok(())
1445 }
1446
1447 pub fn custom(status: &mut Status, input_string: &str) -> Result<()> {
1449 status.run_custom_command(input_string)
1450 }
1451
1452 pub fn remote_mount(status: &mut Status) -> Result<()> {
1455 if matches!(
1456 status.current_tab().menu_mode,
1457 Menu::InputSimple(InputSimple::Remote)
1458 ) {
1459 status.reset_menu_mode()?;
1460 }
1461 status.set_menu_mode(status.index, Menu::InputSimple(InputSimple::Remote))
1462 }
1463
1464 pub fn cloud_drive(status: &mut Status) -> Result<()> {
1465 status.cloud_open()
1466 }
1467
1468 pub fn select_pane(status: &mut Status, col: u16) -> Result<()> {
1470 status.select_tab_from_col(col)
1471 }
1472
1473 pub fn focus_go_left(status: &mut Status) -> Result<()> {
1474 match status.focus {
1475 Focus::LeftMenu | Focus::LeftFile => (),
1476 Focus::RightFile => {
1477 status.index = 0;
1478 status.focus = Focus::LeftFile;
1479 }
1480 Focus::RightMenu => {
1481 status.index = 0;
1482 if matches!(status.tabs[0].menu_mode, Menu::Nothing) {
1483 status.focus = Focus::LeftFile;
1484 } else {
1485 status.focus = Focus::LeftMenu;
1486 }
1487 }
1488 }
1489 Ok(())
1490 }
1491
1492 pub fn focus_go_right(status: &mut Status) -> Result<()> {
1493 match status.focus {
1494 Focus::RightMenu | Focus::RightFile => (),
1495 Focus::LeftFile => {
1496 status.index = 1;
1497 status.focus = Focus::RightFile;
1498 }
1499 Focus::LeftMenu => {
1500 status.index = 1;
1501 if matches!(status.tabs[1].menu_mode, Menu::Nothing) {
1502 status.focus = Focus::RightFile;
1503 } else {
1504 status.focus = Focus::RightMenu;
1505 }
1506 }
1507 }
1508 Ok(())
1509 }
1510
1511 pub fn focus_go_down(status: &mut Status) -> Result<()> {
1512 match status.focus {
1513 Focus::RightMenu | Focus::LeftMenu => (),
1514 Focus::LeftFile => {
1515 if !matches!(status.tabs[0].menu_mode, Menu::Nothing) {
1516 status.focus = Focus::LeftMenu;
1517 }
1518 }
1519 Focus::RightFile => {
1520 if !matches!(status.tabs[1].menu_mode, Menu::Nothing) {
1521 status.focus = Focus::RightMenu;
1522 }
1523 }
1524 }
1525 Ok(())
1526 }
1527
1528 pub fn focus_go_up(status: &mut Status) -> Result<()> {
1529 match status.focus {
1530 Focus::LeftFile | Focus::RightFile => (),
1531 Focus::LeftMenu => status.focus = Focus::LeftFile,
1532 Focus::RightMenu => status.focus = Focus::RightFile,
1533 }
1534 Ok(())
1535 }
1536
1537 pub fn sync_ltr(status: &mut Status) -> Result<()> {
1538 if status.focus.is_file() {
1539 status.sync_tabs(Direction::LeftToRight)?;
1540 }
1541 Ok(())
1542 }
1543
1544 pub fn bulk_confirm(status: &mut Status) -> Result<()> {
1545 status.bulk_execute()
1546 }
1547
1548 pub fn file_copied(status: &mut Status) -> Result<()> {
1549 log_info!(
1550 "file copied - pool: {pool:?}",
1551 pool = status.internal_settings.copy_file_queue
1552 );
1553 status.internal_settings.copy_file_remove_head()?;
1554 if status.internal_settings.copy_file_queue.is_empty() {
1555 status.internal_settings.unset_copy_progress()
1556 } else {
1557 status.copy_next_file_in_queue()?;
1558 }
1559 Ok(())
1560 }
1561
1562 pub fn display_copy_progress(status: &mut Status, content: InMemoryTerm) -> Result<()> {
1563 status.internal_settings.store_copy_progress(content);
1564 Ok(())
1565 }
1566
1567 pub fn check_preview_fuzzy_tick(status: &mut Status) -> Result<()> {
1568 status.fuzzy_tick();
1569 status.check_preview()
1570 }
1571
1572 pub fn visual(status: &mut Status) -> Result<()> {
1573 status.current_tab_mut().toggle_visual();
1574 status.toggle_flag_visual();
1575
1576 Ok(())
1577 }
1578
1579 pub fn mount(status: &mut Status) -> Result<()> {
1581 if matches!(
1582 status.current_tab().menu_mode,
1583 Menu::Navigate(Navigate::Mount)
1584 ) {
1585 status.reset_menu_mode()?;
1586 } else if lsblk_and_udisksctl_installed() {
1587 status.menu.mount.update(status.internal_settings.disks())?;
1588 status.set_menu_mode(status.index, Menu::Navigate(Navigate::Mount))?;
1589 }
1590 Ok(())
1591 }
1592}