1use std::path::{Path, PathBuf};
2use std::sync::{
3 mpsc::{self, Sender, TryRecvError},
4 Arc,
5};
6
7use anyhow::{bail, Context, Result};
8use clap::Parser;
9use crossterm::event::{Event, KeyEvent};
10use opendal::EntryMode;
11use ratatui::layout::Size;
12use sysinfo::Disks;
13
14use crate::app::{
15 ClickableLine, Footer, Header, InternalSettings, Previewer, Session, Tab, ThumbnailManager,
16};
17use crate::common::{
18 current_username, disk_space, disk_used_by_path, filename_from_path, is_in_path,
19 is_sudo_command, path_to_string, row_to_window_index, set_current_dir,
20};
21use crate::config::{from_keyname, Bindings, START_FOLDER};
22use crate::event::FmEvents;
23use crate::io::{
24 build_tokio_greper, execute_and_capture_output, execute_sudo_command_with_password,
25 execute_without_output, get_cloud_token_names, google_drive, reset_sudo_faillock, Args,
26 Internal, Kind, Opener, MIN_WIDTH_FOR_DUAL_PANE,
27};
28use crate::modes::{
29 copy_move, parse_line_output, regex_flagger, shell_command_parser, Content, ContentWindow,
30 CopyMove, CursorOffset, Direction as FuzzyDirection, Display, FileInfo, FileKind, FilterKind,
31 FuzzyFinder, FuzzyKind, InputCompleted, InputSimple, IsoDevice, Menu, MenuHolder, MountAction,
32 MountCommands, Mountable, Navigate, NeedConfirmation, PasswordKind, PasswordUsage, Permissions,
33 PickerCaller, Preview, PreviewBuilder, Search, Selectable, Users, SAME_WINDOW_TOKEN,
34};
35use crate::{log_info, log_line};
36
37pub enum Window {
39 Header,
40 Files,
41 Menu,
42 Footer,
43}
44
45#[derive(Default, Clone, Copy, Debug)]
47pub enum Focus {
48 #[default]
49 LeftFile,
50 LeftMenu,
51 RightFile,
52 RightMenu,
53}
54
55impl Focus {
56 pub fn is_left(&self) -> bool {
59 matches!(self, Self::LeftMenu | Self::LeftFile)
60 }
61
62 pub fn is_file(&self) -> bool {
65 matches!(self, Self::LeftFile | Self::RightFile)
66 }
67
68 pub fn switch(&self) -> Self {
73 match self {
74 Self::LeftFile => Self::RightFile,
75 Self::LeftMenu => Self::RightFile,
76 Self::RightFile => Self::LeftFile,
77 Self::RightMenu => Self::LeftFile,
78 }
79 }
80
81 pub fn to_parent(&self) -> Self {
85 match self {
86 Self::LeftFile | Self::LeftMenu => Self::LeftFile,
87 Self::RightFile | Self::RightMenu => Self::RightFile,
88 }
89 }
90
91 pub fn index(&self) -> usize {
93 *self as usize
94 }
95
96 pub fn is_left_menu(&self) -> bool {
98 matches!(self, Self::LeftMenu)
99 }
100}
101
102pub enum Direction {
104 RightToLeft,
105 LeftToRight,
106}
107
108impl Direction {
109 const fn source_dest(self) -> (usize, usize) {
111 match self {
112 Self::RightToLeft => (1, 0),
113 Self::LeftToRight => (0, 1),
114 }
115 }
116}
117
118pub struct Status {
128 pub tabs: [Tab; 2],
130 pub index: usize,
132 pub fuzzy: Option<FuzzyFinder<String>>,
134 pub menu: MenuHolder,
136 pub session: Session,
138 pub internal_settings: InternalSettings,
140 pub focus: Focus,
142 pub fm_sender: Arc<Sender<FmEvents>>,
144 preview_receiver: mpsc::Receiver<(PathBuf, Preview, usize)>,
146 pub previewer: Previewer,
148 pub thumbnail_manager: Option<ThumbnailManager>,
150}
151
152impl Status {
153 pub fn new(
157 size: Size,
158 opener: Opener,
159 binds: &Bindings,
160 fm_sender: Arc<Sender<FmEvents>>,
161 ) -> Result<Self> {
162 let fuzzy = None;
163 let index = 0;
164
165 let args = Args::parse();
166 let path = &START_FOLDER.get().context("Start folder should be set")?;
167 let start_dir = if path.is_dir() {
168 path
169 } else {
170 path.parent().context("")?
171 };
172 let disks = Disks::new_with_refreshed_list();
173 let session = Session::new(size.width);
174 let internal_settings = InternalSettings::new(opener, size, disks);
175 let menu = MenuHolder::new(start_dir, binds)?;
176 let focus = Focus::default();
177
178 let users_left = Users::default();
179 let users_right = users_left.clone();
180
181 let height = size.height as usize;
182 let tabs = [
183 Tab::new(&args, height, users_left)?,
184 Tab::new(&args, height, users_right)?,
185 ];
186 let (previewer_sender, preview_receiver) = mpsc::channel();
187 let previewer = Previewer::new(previewer_sender);
188 let thumbnail_manager = None;
189 Ok(Self {
190 tabs,
191 index,
192 fuzzy,
193 menu,
194 session,
195 internal_settings,
196 focus,
197 fm_sender,
198 preview_receiver,
199 previewer,
200 thumbnail_manager,
201 })
202 }
203
204 pub fn current_tab(&self) -> &Tab {
206 &self.tabs[self.index]
207 }
208
209 pub fn current_tab_mut(&mut self) -> &mut Tab {
211 &mut self.tabs[self.index]
212 }
213
214 pub fn current_tab_path_str(&self) -> String {
216 self.current_tab().directory_str()
217 }
218
219 pub fn must_quit(&self) -> bool {
221 self.internal_settings.must_quit
222 }
223
224 pub fn focus_follow_index(&mut self) {
226 if (self.index == 0 && !self.focus.is_left()) || (self.index == 1 && self.focus.is_left()) {
227 self.focus = self.focus.switch();
228 }
229 }
230
231 pub fn set_focus_from_mode(&mut self) {
233 if self.index == 0 {
234 if self.tabs[0].menu_mode.is_nothing() {
235 self.focus = Focus::LeftFile;
236 } else {
237 self.focus = Focus::LeftMenu;
238 }
239 } else if self.tabs[1].menu_mode.is_nothing() {
240 self.focus = Focus::RightFile;
241 } else {
242 self.focus = Focus::RightMenu;
243 }
244 }
245
246 pub fn next(&mut self) {
248 if !self.session.dual() {
249 return;
250 }
251 self.index = 1 - self.index;
252 self.focus_follow_index();
253 }
254
255 pub fn select_tab_from_col(&mut self, col: u16) -> Result<()> {
257 if self.session.dual() {
258 if col < self.term_width() / 2 {
259 self.select_left();
260 } else {
261 self.select_right();
262 };
263 } else {
264 self.select_left();
265 }
266 Ok(())
267 }
268
269 fn window_from_row(&self, row: u16, height: u16) -> Window {
270 let win_height = if self.current_tab().menu_mode.is_nothing() {
271 height
272 } else {
273 height / 2
274 };
275 let w_index = row / win_height;
276 if w_index == 1 {
277 Window::Menu
278 } else if row == 1 {
279 Window::Header
280 } else if row == win_height - 2 {
281 Window::Footer
282 } else {
283 Window::Files
284 }
285 }
286
287 pub fn set_focus_from_pos(&mut self, row: u16, col: u16) -> Result<Window> {
290 self.select_tab_from_col(col)?;
291 let window = self.window_from_row(row, self.term_size().1);
292 self.set_focus_from_window_and_index(&window);
293 Ok(window)
294 }
295
296 pub fn click(&mut self, binds: &Bindings, row: u16, col: u16) -> Result<()> {
298 let window = self.set_focus_from_pos(row, col)?;
299 self.click_action_from_window(&window, row, col, binds)?;
300 Ok(())
301 }
302
303 fn has_clicked_on_second_pane_preview(&self) -> bool {
305 self.session.dual() && self.session.preview() && self.index == 1
306 }
307
308 fn click_action_from_window(
309 &mut self,
310 window: &Window,
311 row: u16,
312 col: u16,
313 binds: &Bindings,
314 ) -> Result<()> {
315 match window {
316 Window::Header => self.header_action(col, binds),
317 Window::Files => {
318 if self.has_clicked_on_second_pane_preview() {
319 if let Preview::Tree(tree) = &self.tabs[1].preview {
320 let index = row_to_window_index(row) + self.tabs[1].window.top;
321 let path = &tree.path_from_index(index)?;
322 self.tabs[0].cd_to_file(path)?;
323 self.index = 0;
324 self.focus = Focus::LeftFile;
325 }
326 } else {
327 self.tab_select_row(row)?
328 }
329 self.update_second_pane_for_preview()
330 }
331 Window::Footer => self.footer_action(col, binds),
332 Window::Menu => self.menu_action(row, col),
333 }
334 }
335
336 pub fn tab_select_row(&mut self, row: u16) -> Result<()> {
339 match self.current_tab().display_mode {
340 Display::Directory => self.current_tab_mut().normal_select_row(row),
341 Display::Tree => self.current_tab_mut().tree_select_row(row)?,
342 Display::Fuzzy => self.fuzzy_navigate(FuzzyDirection::Index(row))?,
343 _ => (),
344 }
345 Ok(())
346 }
347
348 fn set_focus_from_window_and_index(&mut self, window: &Window) {
349 self.focus = if self.index == 0 {
350 if matches!(window, Window::Menu) {
351 Focus::LeftMenu
352 } else {
353 Focus::LeftFile
354 }
355 } else if matches!(window, Window::Menu) {
356 Focus::RightMenu
357 } else {
358 Focus::RightFile
359 };
360 }
361
362 pub fn sync_tabs(&mut self, direction: Direction) -> Result<()> {
364 let (source, dest) = direction.source_dest();
365 self.tabs[dest].cd(&self.tabs[source].current_file()?.path)
366 }
367
368 pub fn second_window_height(&self) -> Result<usize> {
371 let (_, height) = self.term_size();
372 Ok((height / 2).saturating_sub(2) as usize)
373 }
374
375 fn menu_action(&mut self, row: u16, col: u16) -> Result<()> {
377 let second_window_height = self.second_window_height()?;
378 let row_offset = (row as usize).saturating_sub(second_window_height);
379 const OFFSET: usize =
380 ContentWindow::WINDOW_PADDING + ContentWindow::WINDOW_MARGIN_TOP_U16 as usize;
381 if row_offset >= OFFSET {
382 let index = row_offset - OFFSET + self.menu.window.top;
383 match self.current_tab().menu_mode {
384 Menu::Navigate(navigate) => match navigate {
385 Navigate::History => self.current_tab_mut().history.set_index(index),
386 navigate => self.menu.set_index(index, navigate),
387 },
388 Menu::InputCompleted(_) => self.menu.completion.set_index(index),
389 _ => (),
390 }
391 self.menu.window.scroll_to(index);
392 } else if row_offset == 3 && self.current_tab().menu_mode.is_input() {
393 let index =
394 col.saturating_sub(self.current_tab().menu_mode.cursor_offset() + 1) as usize;
395 self.menu.input.cursor_move(index);
396 }
397
398 Ok(())
399 }
400
401 pub fn select_left(&mut self) {
403 self.index = 0;
404 self.focus_follow_index();
405 }
406
407 pub fn select_right(&mut self) {
409 self.index = 1;
410 self.focus_follow_index();
411 }
412
413 pub fn refresh_shortcuts(&mut self) {
419 self.menu.refresh_shortcuts(
420 &self.internal_settings.mount_points(),
421 self.tabs[0].current_path(),
422 self.tabs[1].current_path(),
423 );
424 }
425
426 pub fn disk_spaces_of_selected(&self) -> String {
428 disk_space(
429 &self.internal_settings.disks,
430 self.current_tab().current_path(),
431 )
432 }
433
434 pub fn term_size(&self) -> (u16, u16) {
436 self.internal_settings.term_size()
437 }
438
439 fn term_width(&self) -> u16 {
440 self.term_size().0
441 }
442
443 pub fn clear_preview_right(&mut self) {
445 if self.session.dual() && self.session.preview() && !self.tabs[1].preview.is_empty() {
446 self.tabs[1].preview = PreviewBuilder::empty()
447 }
448 }
449
450 pub fn refresh_view(&mut self) -> Result<()> {
452 self.refresh_status()?;
453 self.update_second_pane_for_preview()
454 }
455
456 pub fn reset_tabs_view(&mut self) -> Result<()> {
458 for tab in self.tabs.iter_mut() {
459 match tab.refresh_and_reselect_file() {
460 Ok(()) => (),
461 Err(error) => log_info!("reset_tabs_view error: {error}"),
462 }
463 }
464 Ok(())
465 }
466
467 pub fn leave_menu_mode(&mut self) -> Result<()> {
470 match self.current_tab().menu_mode {
471 Menu::InputSimple(InputSimple::Filter) => {
472 self.current_tab_mut().settings.reset_filter()
473 }
474 Menu::InputCompleted(InputCompleted::Cd) => self.current_tab_mut().cd_origin_path()?,
475 _ => (),
476 }
477 if self.reset_menu_mode()? {
478 self.current_tab_mut().refresh_view()?;
479 } else {
480 self.current_tab_mut().refresh_params();
481 }
482 self.current_tab_mut().reset_visual();
483 Ok(())
484 }
485
486 pub fn leave_preview(&mut self) -> Result<()> {
494 self.current_tab_mut().set_display_mode(Display::Directory);
495 self.current_tab_mut().refresh_and_reselect_file()
496 }
497
498 pub fn reset_menu_mode(&mut self) -> Result<bool> {
501 self.menu.reset();
502 let must_refresh = matches!(self.current_tab().display_mode, Display::Preview);
503 self.set_menu_mode(self.index, Menu::Nothing)?;
504 self.set_height_of_unfocused_menu()?;
505 Ok(must_refresh)
506 }
507
508 fn set_height_of_unfocused_menu(&mut self) -> Result<()> {
509 let unfocused_tab = &self.tabs[1 - self.index];
510 match unfocused_tab.menu_mode {
511 Menu::Nothing => (),
512 unfocused_mode => {
513 let len = self.menu.len(unfocused_mode);
514 let height = self.second_window_height()?;
515 self.menu.window = ContentWindow::new(len, height);
516 }
517 }
518 Ok(())
519 }
520
521 pub fn refresh_status(&mut self) -> Result<()> {
523 self.force_clear();
524 self.refresh_users()?;
525 self.refresh_tabs()?;
526 self.refresh_shortcuts();
527 Ok(())
528 }
529
530 pub fn force_clear(&mut self) {
534 self.internal_settings.force_clear();
535 }
536
537 pub fn should_be_cleared(&self) -> bool {
538 self.internal_settings.should_be_cleared()
539 }
540
541 pub fn refresh_users(&mut self) -> Result<()> {
543 self.tabs[0].users.update();
544 self.tabs[1].users = self.tabs[0].users.clone();
545 Ok(())
546 }
547
548 pub fn refresh_tabs(&mut self) -> Result<()> {
550 self.menu.input.reset();
551 self.menu.completion.reset();
552 self.tabs[0].refresh_and_reselect_file()?;
553 self.tabs[1].refresh_and_reselect_file()
554 }
555
556 pub fn resize(&mut self, width: u16, height: u16) -> Result<()> {
561 let couldnt_dual_but_want = self.couldnt_dual_but_want();
562 self.internal_settings.update_size(width, height);
563 if couldnt_dual_but_want {
564 self.set_dual_pane_if_wide_enough()?;
565 }
566 if !self.wide_enough_for_dual() {
567 self.select_left();
568 }
569 self.resize_all_windows(height)?;
570 self.refresh_status()
571 }
572
573 fn wide_enough_for_dual(&self) -> bool {
574 self.internal_settings.width >= MIN_WIDTH_FOR_DUAL_PANE
575 }
576
577 fn couldnt_dual_but_want(&self) -> bool {
578 !self.wide_enough_for_dual() && self.session.dual()
579 }
580
581 fn use_dual(&self) -> bool {
582 self.wide_enough_for_dual() && self.session.dual()
583 }
584
585 fn left_window_width(&self) -> u16 {
586 if self.use_dual() {
587 self.internal_settings.width / 2
588 } else {
589 self.internal_settings.width
590 }
591 }
592
593 fn resize_all_windows(&mut self, height: u16) -> Result<()> {
594 let height_usize = height as usize;
595 self.tabs[0].set_height(height_usize);
596 self.tabs[1].set_height(height_usize);
597 self.fuzzy_resize(height_usize);
598 self.menu.resize(
599 self.tabs[self.index].menu_mode,
600 self.second_window_height()?,
601 );
602 Ok(())
603 }
604
605 pub fn update_second_pane_for_preview(&mut self) -> Result<()> {
607 if self.are_settings_requiring_dualpane_preview() {
608 if self.can_display_dualpane_preview() {
609 self.set_second_pane_for_preview()?;
610 } else {
611 self.tabs[1].preview = PreviewBuilder::empty();
612 }
613 }
614 Ok(())
615 }
616
617 fn are_settings_requiring_dualpane_preview(&self) -> bool {
618 self.index == 0 && self.session.dual() && self.session.preview()
619 }
620
621 fn can_display_dualpane_preview(&self) -> bool {
622 Session::display_wide_enough(self.term_width())
623 }
624
625 fn set_second_pane_for_preview(&mut self) -> Result<()> {
628 self.tabs[1].set_display_mode(Display::Preview);
629 self.tabs[1].menu_mode = Menu::Nothing;
630 let Some(fileinfo) = self.get_correct_fileinfo_for_preview() else {
631 return Ok(());
632 };
633 log_info!("sending preview request");
634 self.previewer.build(fileinfo.path.to_path_buf(), 1)?;
635 Ok(())
638 }
639
640 pub fn thumbnail_directory_video(&mut self) {
643 if !self.are_settings_requiring_dualpane_preview() || !self.can_display_dualpane_preview() {
644 return;
645 }
646 self.thumbnail_init_or_clear();
647 let videos = self.current_tab().directory.videos();
648 if videos.is_empty() {
649 return;
650 }
651 if let Some(thumbnail_manager) = &self.thumbnail_manager {
652 thumbnail_manager.enqueue(videos);
653 }
654 }
655
656 fn thumbnail_init_or_clear(&mut self) {
658 if self.thumbnail_manager.is_none() {
659 self.thumbnail_manager_init();
660 } else {
661 self.thumbnail_queue_clear();
662 }
663 }
664
665 fn thumbnail_manager_init(&mut self) {
666 self.thumbnail_manager = Some(ThumbnailManager::default());
667 }
668
669 pub fn thumbnail_queue_clear(&self) {
671 if let Some(thumbnail_manager) = &self.thumbnail_manager {
672 thumbnail_manager.clear()
673 }
674 }
675
676 pub fn check_preview(&mut self) -> Result<()> {
682 match self.preview_receiver.try_recv() {
683 Ok((path, preview, index)) => self.attach_preview(path, preview, index)?,
684 Err(TryRecvError::Disconnected) => bail!("Previewer Disconnected"),
685 Err(TryRecvError::Empty) => (),
686 }
687 Ok(())
688 }
689
690 fn attach_preview(&mut self, path: PathBuf, preview: Preview, index: usize) -> Result<()> {
694 let compared_index = self.pick_correct_tab_from(index)?;
695 if !self.preview_has_correct_path(compared_index, path.as_path())? {
696 return Ok(());
697 }
698 self.tabs[index].preview = preview;
699 self.tabs[index]
700 .window
701 .reset(self.tabs[index].preview.len());
702 Ok(())
703 }
704
705 fn pick_correct_tab_from(&self, index: usize) -> Result<usize> {
706 if index == 1 && self.can_display_dualpane_preview() && self.session.preview() {
707 Ok(0)
708 } else {
709 Ok(index)
710 }
711 }
712
713 fn preview_has_correct_path(&self, compared_index: usize, path: &Path) -> Result<bool> {
719 let tab = &self.tabs[compared_index];
720 Ok(tab.display_mode.is_fuzzy()
721 || tab.menu_mode.is_navigate()
722 || tab.current_file()?.path.as_ref() == path)
723 }
724
725 fn get_correct_fileinfo_for_preview(&self) -> Option<FileInfo> {
731 let left_tab = &self.tabs[0];
732 let users = &left_tab.users;
733 if left_tab.display_mode.is_fuzzy() {
734 FileInfo::new(Path::new(&self.fuzzy_current_selection()?), users).ok()
735 } else if self.focus.is_left_menu() {
736 self.fileinfo_from_navigate(left_tab, users)
737 } else {
738 left_tab.current_file().ok()
739 }
740 }
741
742 fn fileinfo_from_navigate(&self, tab: &Tab, users: &Users) -> Option<FileInfo> {
746 match tab.menu_mode {
747 Menu::Navigate(Navigate::History) => {
748 FileInfo::new(tab.history.content().get(tab.history.index())?, users).ok()
749 }
750 Menu::Navigate(Navigate::Shortcut) => {
751 let short = &self.menu.shortcut;
752 FileInfo::new(short.content().get(short.index())?, users).ok()
753 }
754 Menu::Navigate(Navigate::Marks(_)) => {
755 let (_, mark_path) = &self.menu.marks.content().get(self.menu.marks.index())?;
756 FileInfo::new(mark_path, users).ok()
757 }
758 Menu::Navigate(Navigate::Flagged) => {
759 FileInfo::new(self.menu.flagged.selected()?, users).ok()
760 }
761 _ => tab.current_file().ok(),
762 }
763 }
764
765 pub fn should_tabs_images_be_cleared(&self) -> bool {
767 self.tabs[0].settings.should_clear_image || self.tabs[1].settings.should_clear_image
768 }
769
770 pub fn set_tabs_images_cleared(&mut self) {
772 self.tabs[0].settings.should_clear_image = false;
773 self.tabs[1].settings.should_clear_image = false;
774 }
775
776 pub fn set_menu_mode(&mut self, index: usize, menu_mode: Menu) -> Result<()> {
778 self.set_menu_mode_no_refresh(index, menu_mode)?;
779 self.current_tab_mut().reset_visual();
780 self.refresh_status()
781 }
782
783 pub fn set_menu_mode_no_refresh(&mut self, index: usize, menu_mode: Menu) -> Result<()> {
785 if index > 1 {
786 return Ok(());
787 }
788 self.set_height_for_menu_mode(index, menu_mode)?;
789 self.tabs[index].menu_mode = menu_mode;
790 let len = self.menu.len(menu_mode);
791 let height = self.second_window_height()?;
792 self.menu.window = ContentWindow::new(len, height);
793 self.menu.window.scroll_to(self.menu.index(menu_mode));
794 self.set_focus_from_mode();
795 self.menu.input_history.filter_by_mode(menu_mode);
796 Ok(())
797 }
798
799 pub fn set_height_for_menu_mode(&mut self, index: usize, menu_mode: Menu) -> Result<()> {
801 let height = self.internal_settings.term_size().1;
802 let prim_window_height = if menu_mode.is_nothing() {
803 height
804 } else {
805 height / 2
806 };
807 self.tabs[index]
808 .window
809 .set_height(prim_window_height as usize);
810 self.tabs[index]
811 .window
812 .scroll_to(self.tabs[index].window.top);
813 Ok(())
814 }
815
816 pub fn set_dual_pane_if_wide_enough(&mut self) -> Result<()> {
818 if self.wide_enough_for_dual() {
819 self.session.set_dual(true);
820 } else {
821 self.select_left();
822 self.session.set_dual(false);
823 }
824 Ok(())
825 }
826
827 pub fn clear_flags_and_reset_view(&mut self) -> Result<()> {
829 self.menu.flagged.clear();
830 self.reset_tabs_view()
831 }
832
833 fn flagged_or_selected(&self) -> Vec<PathBuf> {
835 if self.menu.flagged.is_empty() {
836 let Ok(file) = self.current_tab().current_file() else {
837 return vec![];
838 };
839 vec![file.path.to_path_buf()]
840 } else {
841 self.menu.flagged.content().to_owned()
842 }
843 }
844
845 fn flagged_or_selected_relative_to(&self, here: &Path) -> Vec<PathBuf> {
846 self.flagged_or_selected()
847 .iter()
848 .filter_map(|abs_path| pathdiff::diff_paths(abs_path, here))
849 .filter(|f| !f.starts_with(".."))
850 .collect()
851 }
852
853 pub fn flagged_in_current_dir(&self) -> Vec<PathBuf> {
860 self.menu.flagged.in_dir(&self.current_tab().directory.path)
861 }
862
863 pub fn flag_all(&mut self) {
865 match self.current_tab().display_mode {
866 Display::Directory => {
867 self.tabs[self.index]
868 .directory
869 .content
870 .iter()
871 .filter(|file| file.filename.as_ref() != "." && file.filename.as_ref() != "..")
872 .for_each(|file| {
873 self.menu.flagged.push(file.path.to_path_buf());
874 });
875 }
876 Display::Tree => self.tabs[self.index].tree.flag_all(&mut self.menu.flagged),
877 _ => (),
878 }
879 }
880
881 pub fn reverse_flags(&mut self) {
884 if self.current_tab().display_mode.is_preview() {
885 self.tabs[self.index]
886 .directory
887 .content
888 .iter()
889 .for_each(|file| self.menu.flagged.toggle(&file.path));
890 }
891 }
892
893 pub fn toggle_flag_for_selected(&mut self) {
895 let Ok(file) = self.current_tab().current_file() else {
896 return;
897 };
898 match self.current_tab().display_mode {
899 Display::Directory => {
900 self.menu.flagged.toggle(&file.path);
901 if !self.current_tab().directory.selected_is_last() {
902 self.tabs[self.index].normal_down_one_row();
903 }
904 let _ = self.update_second_pane_for_preview();
905 }
906 Display::Tree => {
907 self.menu.flagged.toggle(&file.path);
908 if !self.current_tab().tree.selected_is_last() {
909 self.current_tab_mut().tree_select_next();
910 }
911 let _ = self.update_second_pane_for_preview();
912 }
913 Display::Preview => (),
914 Display::Fuzzy => (),
915 }
916 if matches!(
917 self.current_tab().menu_mode,
918 Menu::Navigate(Navigate::Flagged)
919 ) {
920 self.menu.window.set_len(self.menu.flagged.len());
921 }
922 }
923
924 pub fn jump_flagged(&mut self) -> Result<()> {
926 let Some(path) = self.menu.flagged.selected() else {
927 return Ok(());
928 };
929 let path = path.to_owned();
930 let tab = self.current_tab_mut();
931 tab.set_display_mode(Display::Directory);
932 tab.refresh_view()?;
933 tab.jump(path)?;
934 self.update_second_pane_for_preview()
935 }
936
937 pub fn cut_or_copy_flagged_files(&mut self, cut_or_copy: CopyMove) -> Result<()> {
941 let sources = self.menu.flagged.content.clone();
942 let dest = &self.current_tab().directory_of_selected()?.to_owned();
943
944 if self.is_simple_move(&cut_or_copy, &sources, dest) {
945 self.simple_move(&sources, dest)
946 } else {
947 self.complex_move(cut_or_copy, sources, dest)
948 }
949 }
950
951 fn is_simple_move(&self, cut_or_copy: &CopyMove, sources: &[PathBuf], dest: &Path) -> bool {
952 if matches!(cut_or_copy, CopyMove::Copy) {
953 return false;
954 }
955 if sources.len() != 1 {
956 return false;
957 }
958 let Some(s) = disk_used_by_path(&self.internal_settings.disks, &sources[0]) else {
960 return false;
961 };
962 let Some(d) = disk_used_by_path(&self.internal_settings.disks, dest) else {
963 return false;
964 };
965 s.mount_point() == d.mount_point()
966 }
967
968 fn simple_move(&mut self, sources: &[PathBuf], dest: &Path) -> Result<()> {
969 let source = &sources[0];
970 let filename = filename_from_path(source)?;
971 let dest = dest.to_path_buf().join(filename);
972 match std::fs::rename(source, &dest) {
973 Ok(()) => {
974 log_line!(
975 "Moved {source} to {dest}",
976 source = source.display(),
977 dest = dest.display()
978 )
979 }
980 Err(e) => {
981 log_info!("Error: {e:?}");
982 log_line!("Error: {e:?}")
983 }
984 }
985 self.clear_flags_and_reset_view()
986 }
987
988 fn complex_move(
989 &mut self,
990 cut_or_copy: CopyMove,
991 sources: Vec<PathBuf>,
992 dest: &PathBuf,
993 ) -> Result<()> {
994 let mut must_act_now = true;
995 if matches!(cut_or_copy, CopyMove::Copy) {
996 if !self.internal_settings.copy_file_queue.is_empty() {
997 log_info!("cut_or_copy_flagged_files: act later");
998 must_act_now = false;
999 }
1000 self.internal_settings
1001 .copy_file_queue
1002 .push((sources.to_owned(), dest.clone()));
1003 }
1004
1005 if must_act_now {
1006 log_info!("cut_or_copy_flagged_files: act now");
1007 let in_mem = copy_move(
1008 cut_or_copy,
1009 sources,
1010 dest,
1011 self.left_window_width(),
1012 self.internal_settings.term_size().1,
1013 Arc::clone(&self.fm_sender),
1014 )?;
1015 self.internal_settings.store_copy_progress(in_mem);
1016 }
1017 self.clear_flags_and_reset_view()
1018 }
1019
1020 pub fn copy_next_file_in_queue(&mut self) -> Result<()> {
1022 self.internal_settings
1023 .copy_next_file_in_queue(self.fm_sender.clone(), self.left_window_width())
1024 }
1025
1026 pub fn fuzzy_init(&mut self, kind: FuzzyKind) {
1028 self.fuzzy = Some(FuzzyFinder::new(kind).set_height(self.current_tab().window.height));
1029 }
1030
1031 fn fuzzy_drop(&mut self) {
1032 self.fuzzy = None;
1033 }
1034
1035 pub fn fuzzy_find_files(&mut self) -> Result<()> {
1037 let Some(fuzzy) = &self.fuzzy else {
1038 bail!("Fuzzy should be set");
1039 };
1040 let current_path = self.current_tab().current_path().to_path_buf();
1041 fuzzy.find_files(current_path);
1042 Ok(())
1043 }
1044
1045 pub fn fuzzy_help(&mut self, help: String) -> Result<()> {
1047 let Some(fuzzy) = &self.fuzzy else {
1048 bail!("Fuzzy should be set");
1049 };
1050 fuzzy.find_action(help);
1051 Ok(())
1052 }
1053
1054 pub fn fuzzy_find_lines(&mut self) -> Result<()> {
1056 let Some(fuzzy) = &self.fuzzy else {
1057 bail!("Fuzzy should be set");
1058 };
1059 let Some(tokio_greper) = build_tokio_greper() else {
1060 log_info!("ripgrep & grep aren't in $PATH");
1061 return Ok(());
1062 };
1063 fuzzy.find_line(tokio_greper);
1064 Ok(())
1065 }
1066
1067 fn fuzzy_current_selection(&self) -> Option<std::string::String> {
1068 if let Some(fuzzy) = &self.fuzzy {
1069 fuzzy.pick()
1070 } else {
1071 None
1072 }
1073 }
1074
1075 pub fn fuzzy_select(&mut self) -> Result<()> {
1080 let Some(fuzzy) = &self.fuzzy else {
1081 bail!("Fuzzy should be set");
1082 };
1083 if let Some(pick) = fuzzy.pick() {
1084 match fuzzy.kind {
1085 FuzzyKind::File => self.tabs[self.index].cd_to_file(Path::new(&pick))?,
1086 FuzzyKind::Line => self.tabs[self.index].cd_to_file(&parse_line_output(&pick)?)?,
1087 FuzzyKind::Action => self.fuzzy_send_event(&pick)?,
1088 }
1089 } else {
1090 log_info!("Fuzzy had nothing to pick from");
1091 };
1092 self.fuzzy_leave()
1093 }
1094
1095 fn fuzzy_send_event(&self, pick: &str) -> Result<()> {
1099 if let Ok(key) = find_keybind_from_fuzzy(pick) {
1100 self.fm_sender.send(FmEvents::Term(Event::Key(key)))?;
1101 };
1102 Ok(())
1103 }
1104
1105 pub fn fuzzy_leave(&mut self) -> Result<()> {
1107 self.fuzzy_drop();
1108 self.current_tab_mut().set_display_mode(Display::Directory);
1109 self.refresh_view()
1110 }
1111
1112 pub fn fuzzy_backspace(&mut self) -> Result<()> {
1114 let Some(fuzzy) = &mut self.fuzzy else {
1115 bail!("Fuzzy should be set");
1116 };
1117 fuzzy.input.delete_char_left();
1118 fuzzy.update_input(false);
1119 Ok(())
1120 }
1121
1122 pub fn fuzzy_delete(&mut self) -> Result<()> {
1124 let Some(fuzzy) = &mut self.fuzzy else {
1125 bail!("Fuzzy should be set");
1126 };
1127 fuzzy.input.delete_chars_right();
1128 fuzzy.update_input(false);
1129 Ok(())
1130 }
1131
1132 pub fn fuzzy_left(&mut self) -> Result<()> {
1134 let Some(fuzzy) = &mut self.fuzzy else {
1135 bail!("Fuzzy should be set");
1136 };
1137 fuzzy.input.cursor_left();
1138 Ok(())
1139 }
1140
1141 pub fn fuzzy_right(&mut self) -> Result<()> {
1143 let Some(fuzzy) = &mut self.fuzzy else {
1144 bail!("Fuzzy should be set");
1145 };
1146 fuzzy.input.cursor_right();
1147 Ok(())
1148 }
1149
1150 pub fn fuzzy_start(&mut self) -> Result<()> {
1152 self.fuzzy_navigate(FuzzyDirection::Start)
1153 }
1154
1155 pub fn fuzzy_end(&mut self) -> Result<()> {
1157 self.fuzzy_navigate(FuzzyDirection::End)
1158 }
1159
1160 pub fn fuzzy_navigate(&mut self, direction: FuzzyDirection) -> Result<()> {
1162 let Some(fuzzy) = &mut self.fuzzy else {
1163 bail!("Fuzzy should be set");
1164 };
1165 fuzzy.navigate(direction);
1166 if fuzzy.should_preview() {
1167 self.update_second_pane_for_preview()?;
1168 }
1169 Ok(())
1170 }
1171
1172 pub fn fuzzy_tick(&mut self) {
1174 match &mut self.fuzzy {
1175 Some(fuzzy) => {
1176 fuzzy.tick(false);
1177 }
1178 None => (),
1179 }
1180 }
1181
1182 pub fn fuzzy_resize(&mut self, height: usize) {
1184 match &mut self.fuzzy {
1185 Some(fuzzy) => fuzzy.resize(height),
1186 None => (),
1187 }
1188 }
1189
1190 pub fn input_history_next(&mut self) -> Result<()> {
1192 if self.focus.is_file() {
1193 return Ok(());
1194 }
1195 self.menu.input_history_next(&mut self.tabs[self.index])?;
1196 if let Menu::InputCompleted(input_completed) = self.current_tab().menu_mode {
1197 self.complete(input_completed)?;
1198 }
1199 Ok(())
1200 }
1201
1202 pub fn input_history_prev(&mut self) -> Result<()> {
1204 if self.focus.is_file() {
1205 return Ok(());
1206 }
1207 self.menu.input_history_prev(&mut self.tabs[self.index])?;
1208 if let Menu::InputCompleted(input_completed) = self.current_tab().menu_mode {
1209 self.complete(input_completed)?;
1210 }
1211 Ok(())
1212 }
1213
1214 pub fn input_and_complete(&mut self, input_completed: InputCompleted, c: char) -> Result<()> {
1216 self.menu.input.insert(c);
1217 self.complete(input_completed)
1218 }
1219
1220 fn complete(&mut self, input_completed: InputCompleted) -> Result<()> {
1221 match input_completed {
1222 InputCompleted::Search => self.complete_search(),
1223 _ => self.complete_non_search(),
1224 }
1225 }
1226
1227 pub fn complete_tab(&mut self, input_completed: InputCompleted) -> Result<()> {
1229 self.menu.completion_tab();
1230 self.complete_cd_move()?;
1231 if matches!(input_completed, InputCompleted::Search) {
1232 self.update_search()?;
1233 self.search()?;
1234 } else {
1235 self.menu.input_complete(&mut self.tabs[self.index])?
1236 }
1237 Ok(())
1238 }
1239
1240 fn complete_search(&mut self) -> Result<()> {
1241 self.update_search()?;
1242 self.search()?;
1243 self.menu.input_complete(&mut self.tabs[self.index])
1244 }
1245
1246 fn update_search(&mut self) -> Result<()> {
1247 if let Ok(search) = Search::new(&self.menu.input.string()) {
1248 self.current_tab_mut().search = search;
1249 };
1250 Ok(())
1251 }
1252
1253 fn complete_non_search(&mut self) -> Result<()> {
1254 self.complete_cd_move()?;
1255 self.menu.input_complete(&mut self.tabs[self.index])
1256 }
1257
1258 pub fn complete_cd_move(&mut self) -> Result<()> {
1260 if let Menu::InputCompleted(InputCompleted::Cd) = self.current_tab().menu_mode {
1261 let input = self.menu.input.string();
1262 if self.tabs[self.index].try_cd_to_file(input)? {
1263 self.update_second_pane_for_preview()?;
1264 }
1265 }
1266 Ok(())
1267 }
1268
1269 pub fn input_regex(&mut self, char: char) -> Result<()> {
1271 self.menu.input.insert(char);
1272 self.flag_from_regex()?;
1273 Ok(())
1274 }
1275
1276 pub fn flag_from_regex(&mut self) -> Result<()> {
1279 let input = self.menu.input.string();
1280 if input.is_empty() {
1281 return Ok(());
1282 }
1283 let paths = match self.current_tab().display_mode {
1284 Display::Directory => self.tabs[self.index].directory.paths(),
1285 Display::Tree => self.tabs[self.index].tree.paths(),
1286 _ => return Ok(()),
1287 };
1288 regex_flagger(&input, &paths, &mut self.menu.flagged)?;
1289 if !self.menu.flagged.is_empty() {
1290 self.tabs[self.index]
1291 .go_to_file(self.menu.flagged.selected().context("no selected file")?);
1292 }
1293 Ok(())
1294 }
1295
1296 pub fn open_selected_file(&mut self) -> Result<()> {
1298 let path = self.current_tab().current_file()?.path;
1299 self.open_single_file(&path)
1300 }
1301
1302 pub fn open_single_file(&mut self, path: &Path) -> Result<()> {
1304 match self.internal_settings.opener.kind(path) {
1305 Some(Kind::Internal(Internal::NotSupported)) => self.mount_iso_drive(),
1306 Some(_) => self.internal_settings.open_single_file(path),
1307 None => Ok(()),
1308 }
1309 }
1310
1311 pub fn open_flagged_files(&mut self) -> Result<()> {
1313 self.internal_settings
1314 .open_flagged_files(&self.menu.flagged)
1315 }
1316
1317 fn ensure_iso_device_is_some(&mut self) -> Result<()> {
1318 if self.menu.iso_device.is_none() {
1319 let path = path_to_string(&self.current_tab().current_file()?.path);
1320 self.menu.iso_device = Some(IsoDevice::from_path(path));
1321 }
1322 Ok(())
1323 }
1324
1325 fn mount_iso_drive(&mut self) -> Result<()> {
1329 if !self.menu.password_holder.has_sudo() {
1330 self.ask_password(Some(MountAction::MOUNT), PasswordUsage::ISO)?;
1331 } else {
1332 self.ensure_iso_device_is_some()?;
1333 let Some(ref mut iso_device) = self.menu.iso_device else {
1334 return Ok(());
1335 };
1336 if iso_device.mount(¤t_username()?, &mut self.menu.password_holder)? {
1337 log_info!("iso mounter mounted {iso_device:?}");
1338 log_line!("iso : {iso_device}");
1339 let path = iso_device
1340 .mountpoints
1341 .clone()
1342 .expect("mountpoint should be set");
1343 self.current_tab_mut().cd(Path::new(&path))?;
1344 };
1345 self.menu.iso_device = None;
1346 };
1347
1348 Ok(())
1349 }
1350
1351 pub fn umount_iso_drive(&mut self) -> Result<()> {
1354 if let Some(ref mut iso_device) = self.menu.iso_device {
1355 if !self.menu.password_holder.has_sudo() {
1356 self.ask_password(Some(MountAction::UMOUNT), PasswordUsage::ISO)?;
1357 } else {
1358 iso_device.umount(¤t_username()?, &mut self.menu.password_holder)?;
1359 };
1360 }
1361 self.menu.iso_device = None;
1362 Ok(())
1363 }
1364
1365 pub fn mount_encrypted_drive(&mut self) -> Result<()> {
1370 if !self.menu.password_holder.has_sudo() {
1371 self.ask_password(
1372 Some(MountAction::MOUNT),
1373 PasswordUsage::CRYPTSETUP(PasswordKind::SUDO),
1374 )
1375 } else if !self.menu.password_holder.has_cryptsetup() {
1376 self.ask_password(
1377 Some(MountAction::MOUNT),
1378 PasswordUsage::CRYPTSETUP(PasswordKind::CRYPTSETUP),
1379 )
1380 } else {
1381 let Some(Mountable::Encrypted(device)) = &self.menu.mount.selected() else {
1382 return Ok(());
1383 };
1384 if let Ok(true) = device.mount(¤t_username()?, &mut self.menu.password_holder) {
1385 self.go_to_encrypted_drive(device.uuid.clone())?;
1386 }
1387 Ok(())
1388 }
1389 }
1390
1391 pub fn umount_encrypted_drive(&mut self) -> Result<()> {
1394 if !self.menu.password_holder.has_sudo() {
1395 self.ask_password(
1396 Some(MountAction::UMOUNT),
1397 PasswordUsage::CRYPTSETUP(PasswordKind::SUDO),
1398 )
1399 } else {
1400 let Some(Mountable::Encrypted(device)) = &self.menu.mount.selected() else {
1401 log_info!("Cannot find Encrypted device");
1402 return Ok(());
1403 };
1404 let success =
1405 device.umount_close_crypto(¤t_username()?, &mut self.menu.password_holder)?;
1406 log_info!("umount_encrypted_drive: {success}");
1407 Ok(())
1408 }
1409 }
1410 pub fn mount_normal_device(&mut self) -> Result<()> {
1414 let Some(device) = self.menu.mount.selected() else {
1415 return Ok(());
1416 };
1417 if device.is_mounted() {
1418 return Ok(());
1419 }
1420 if device.is_crypto() {
1421 return self.mount_encrypted_drive();
1422 }
1423 let Ok(success) = self.menu.mount.mount_selected_no_password() else {
1424 return Ok(());
1425 };
1426 if success {
1427 self.menu.mount.update(self.internal_settings.disks())?;
1428 self.go_to_normal_drive()?;
1429 return Ok(());
1430 }
1431 if !self.menu.password_holder.has_sudo() {
1432 self.ask_password(Some(MountAction::MOUNT), PasswordUsage::DEVICE)
1433 } else {
1434 if let Ok(true) = self
1435 .menu
1436 .mount
1437 .mount_selected(&mut self.menu.password_holder)
1438 {
1439 self.go_to_normal_drive()?;
1440 }
1441 Ok(())
1442 }
1443 }
1444
1445 pub fn go_to_normal_drive(&mut self) -> Result<()> {
1447 let Some(path) = self.menu.mount.selected_mount_point() else {
1448 return Ok(());
1449 };
1450 let tab = self.current_tab_mut();
1451 tab.cd(&path)?;
1452 tab.refresh_view()
1453 }
1454
1455 pub fn go_to_mount_per_index(&mut self, c: char) -> Result<()> {
1457 let Some(index) = c.to_digit(10) else {
1458 return Ok(());
1459 };
1460 self.menu.mount.set_index(index.saturating_sub(1) as _);
1461 self.go_to_normal_drive()
1462 }
1463
1464 fn go_to_encrypted_drive(&mut self, uuid: Option<String>) -> Result<()> {
1465 self.menu.mount.update(&self.internal_settings.disks)?;
1466 let Some(mountpoint) = self.menu.mount.find_encrypted_by_uuid(uuid) else {
1467 return Ok(());
1468 };
1469 log_info!("mountpoint {mountpoint}");
1470 let tab = self.current_tab_mut();
1471 tab.cd(Path::new(&mountpoint))?;
1472 tab.refresh_view()
1473 }
1474
1475 pub fn umount_normal_device(&mut self) -> Result<()> {
1478 let Some(device) = self.menu.mount.selected() else {
1479 return Ok(());
1480 };
1481 if !device.is_mounted() {
1482 return Ok(());
1483 }
1484 if device.is_crypto() {
1485 return self.umount_encrypted_drive();
1486 }
1487 let Ok(success) = self.menu.mount.umount_selected_no_password() else {
1488 return Ok(());
1489 };
1490 if success {
1491 self.menu.mount.update(self.internal_settings.disks())?;
1492 return Ok(());
1493 }
1494 if !self.menu.password_holder.has_sudo() {
1495 self.ask_password(Some(MountAction::UMOUNT), PasswordUsage::DEVICE)
1496 } else {
1497 self.menu
1498 .mount
1499 .umount_selected(&mut self.menu.password_holder)
1500 }
1501 }
1502
1503 pub fn eject_removable_device(&mut self) -> Result<()> {
1505 if self.menu.mount.is_empty() {
1506 return Ok(());
1507 }
1508 let success = self.menu.mount.eject_removable_device()?;
1509 if success {
1510 self.menu.mount.update(self.internal_settings.disks())?;
1511 }
1512 Ok(())
1513 }
1514
1515 pub fn parse_shell_command_from_input(&mut self) -> Result<bool> {
1518 let shell_command = self.menu.input.string();
1519 self.parse_shell_command(shell_command, None, true)
1520 }
1521
1522 fn build_shell_command(shell_command: String, files: Option<Vec<String>>) -> String {
1523 if let Some(files) = &files {
1524 shell_command + " " + &files.join(" ")
1525 } else {
1526 shell_command
1527 }
1528 }
1529
1530 pub fn parse_shell_command(
1532 &mut self,
1533 shell_command: String,
1534 files: Option<Vec<String>>,
1535 capture_output: bool,
1536 ) -> Result<bool> {
1537 let command = Self::build_shell_command(shell_command, files);
1538 let Ok(args) = shell_command_parser(&command, self) else {
1539 self.set_menu_mode(self.index, Menu::Nothing)?;
1540 return Ok(true);
1541 };
1542 self.execute_parsed_command(args, command, capture_output)
1543 }
1544
1545 fn execute_parsed_command(
1546 &mut self,
1547 mut args: Vec<String>,
1548 shell_command: String,
1549 capture_output: bool,
1550 ) -> Result<bool> {
1551 let executable = args.remove(0);
1552 if is_sudo_command(&executable) {
1553 self.enter_sudo_mode(shell_command)?;
1554 return Ok(false);
1555 }
1556 let params: Vec<&str> = args.iter().map(|s| s.as_str()).collect();
1557 if executable == *SAME_WINDOW_TOKEN {
1558 self.internal_settings.open_in_window(¶ms)?;
1559 return Ok(true);
1560 }
1561 if !is_in_path(&executable) {
1562 log_line!("{executable} isn't in path.");
1563 return Ok(true);
1564 }
1565 if capture_output {
1566 match execute_and_capture_output(executable, ¶ms) {
1567 Ok(output) => self.preview_command_output(output, shell_command),
1568 Err(e) => {
1569 log_info!("Error {e:?}");
1570 log_line!("Command {shell_command} disn't finish properly");
1571 }
1572 }
1573 Ok(true)
1574 } else {
1575 let _ = execute_without_output(executable, ¶ms);
1576 Ok(true)
1577 }
1578 }
1579
1580 fn enter_sudo_mode(&mut self, shell_command: String) -> Result<()> {
1581 self.menu.sudo_command = Some(shell_command);
1582 self.ask_password(None, PasswordUsage::SUDOCOMMAND)?;
1583 Ok(())
1584 }
1585
1586 fn ask_password(
1588 &mut self,
1589 encrypted_action: Option<MountAction>,
1590 password_dest: PasswordUsage,
1591 ) -> Result<()> {
1592 log_info!("ask_password");
1593 self.set_menu_mode(
1594 self.index,
1595 Menu::InputSimple(InputSimple::Password(encrypted_action, password_dest)),
1596 )
1597 }
1598
1599 pub fn execute_password_command(
1602 &mut self,
1603 action: Option<MountAction>,
1604 dest: PasswordUsage,
1605 ) -> Result<()> {
1606 let password = self.menu.input.string();
1607 self.menu.input.reset();
1608 if matches!(dest, PasswordUsage::CRYPTSETUP(PasswordKind::CRYPTSETUP)) {
1609 self.menu.password_holder.set_cryptsetup(password)
1610 } else {
1611 self.menu.password_holder.set_sudo(password)
1612 };
1613 let sudo_command = self.menu.sudo_command.to_owned();
1614 self.reset_menu_mode()?;
1615 self.dispatch_password(action, dest, sudo_command)?;
1616 Ok(())
1617 }
1618
1619 pub fn marks_new(&mut self, c: char) -> Result<()> {
1621 let path = self.current_tab_mut().directory.path.clone();
1622 self.menu.marks.new_mark(c, &path)?;
1623 self.current_tab_mut().refresh_view()?;
1624 self.reset_menu_mode()?;
1625 self.refresh_status()
1626 }
1627
1628 pub fn marks_jump_char(&mut self, c: char) -> Result<()> {
1631 if let Some(path) = self.menu.marks.get(c) {
1632 self.current_tab_mut().cd(&path)?;
1633 }
1634 self.current_tab_mut().refresh_view()?;
1635 self.reset_menu_mode()?;
1636 self.refresh_status()
1637 }
1638
1639 pub fn temp_marks_new(&mut self, c: char) -> Result<()> {
1641 let Some(index) = c.to_digit(10) else {
1642 return Ok(());
1643 };
1644 let path = self.current_tab_mut().directory.path.to_path_buf();
1645 self.menu.temp_marks.set_mark(index as _, path);
1646 self.current_tab_mut().refresh_view()?;
1647 self.reset_menu_mode()?;
1648 self.refresh_status()
1649 }
1650
1651 pub fn temp_marks_erase(&mut self) -> Result<()> {
1653 self.menu.temp_marks.erase_current_mark();
1654 Ok(())
1655 }
1656
1657 pub fn temp_marks_jump_char(&mut self, c: char) -> Result<()> {
1660 let Some(index) = c.to_digit(10) else {
1661 return Ok(());
1662 };
1663 if let Some(path) = self.menu.temp_marks.get_mark(index as _) {
1664 self.tabs[self.index].cd(path)?;
1665 }
1666 self.current_tab_mut().refresh_view()?;
1667 self.reset_menu_mode()?;
1668 self.refresh_status()
1669 }
1670
1671 pub fn confirm_delete_files(&mut self) -> Result<()> {
1673 self.menu.delete_flagged_files()?;
1674 self.reset_menu_mode()?;
1675 self.clear_flags_and_reset_view()?;
1676 self.refresh_status()
1677 }
1678
1679 pub fn confirm_trash_empty(&mut self) -> Result<()> {
1681 self.menu.trash.empty_trash()?;
1682 self.reset_menu_mode()?;
1683 self.clear_flags_and_reset_view()?;
1684 Ok(())
1685 }
1686
1687 pub fn bulk_ask_filenames(&mut self) -> Result<()> {
1689 let flagged = self.flagged_in_current_dir();
1690 let current_path = self.current_tab_path_str();
1691 self.menu.bulk.ask_filenames(flagged, ¤t_path)?;
1692 if let Some(temp_file) = self.menu.bulk.temp_file() {
1693 self.open_single_file(&temp_file)?;
1694 if self.internal_settings.opener.extension_use_term("txt") {
1695 self.fm_sender.send(FmEvents::BulkExecute)?;
1696 } else {
1697 self.menu.bulk.watch_in_thread(self.fm_sender.clone())?;
1698 }
1699 }
1700 Ok(())
1701 }
1702
1703 pub fn bulk_execute(&mut self) -> Result<()> {
1705 self.menu.bulk.get_new_names()?;
1706 self.set_menu_mode(
1707 self.index,
1708 Menu::NeedConfirmation(NeedConfirmation::BulkAction),
1709 )?;
1710 Ok(())
1711 }
1712
1713 pub fn confirm_bulk_action(&mut self) -> Result<()> {
1715 if let (Some(paths), Some(create)) = self.menu.bulk.execute()? {
1716 self.menu.flagged.update(paths);
1717 self.menu.flagged.extend(create);
1718 } else {
1719 self.menu.flagged.clear();
1720 };
1721 self.reset_menu_mode()?;
1722 self.reset_tabs_view()?;
1723 Ok(())
1724 }
1725
1726 fn run_sudo_command(&mut self, sudo_command: Option<String>) -> Result<()> {
1727 let Some(sudo_command) = sudo_command else {
1728 log_info!("No sudo_command received from args.");
1729 return self.menu.clear_sudo_attributes();
1730 };
1731 self.set_menu_mode(self.index, Menu::Nothing)?;
1732 reset_sudo_faillock()?;
1733 let Some(command) = sudo_command.strip_prefix("sudo ") else {
1734 log_info!("run_sudo_command cannot run {sudo_command}. It doesn't start with 'sudo '");
1735 return self.menu.clear_sudo_attributes();
1736 };
1737 let args = shell_command_parser(command, self)?;
1738 if args.is_empty() {
1739 return self.menu.clear_sudo_attributes();
1740 }
1741 let Some(password) = self.menu.password_holder.sudo() else {
1742 log_info!("run_sudo_command password isn't set");
1743 return self.menu.clear_sudo_attributes();
1744 };
1745 let directory_of_selected = self.current_tab().directory_of_selected()?;
1746 let (success, stdout, stderr) =
1747 execute_sudo_command_with_password(&args, &password, directory_of_selected)?;
1748 log_info!("sudo command execution. success: {success}");
1749 self.menu.clear_sudo_attributes()?;
1750 if !success {
1751 log_line!("sudo command failed: {stderr}");
1752 }
1753 self.preview_command_output(stdout, sudo_command.to_owned());
1754 Ok(())
1755 }
1756
1757 #[rustfmt::skip]
1760 pub fn dispatch_password(
1761 &mut self,
1762 action: Option<MountAction>,
1763 dest: PasswordUsage,
1764 sudo_command: Option<String>,
1765 ) -> Result<()> {
1766 match (dest, action) {
1767 (PasswordUsage::ISO, Some(MountAction::MOUNT)) => self.mount_iso_drive(),
1768 (PasswordUsage::ISO, Some(MountAction::UMOUNT)) => self.umount_iso_drive(),
1769 (PasswordUsage::CRYPTSETUP(_), Some(MountAction::MOUNT)) => self.mount_encrypted_drive(),
1770 (PasswordUsage::CRYPTSETUP(_), Some(MountAction::UMOUNT)) => self.umount_encrypted_drive(),
1771 (PasswordUsage::DEVICE, Some(MountAction::MOUNT)) => self.mount_normal_device(),
1772 (PasswordUsage::DEVICE, Some(MountAction::UMOUNT)) => self.umount_normal_device(),
1773 (PasswordUsage::SUDOCOMMAND, _) => self.run_sudo_command(sudo_command),
1774 (_, _) => Ok(()),
1775 }
1776 }
1777
1778 pub fn preview_command_output(&mut self, output: String, command: String) {
1780 log_info!("preview_command_output for {command}:\n{output}");
1781 if output.is_empty() {
1782 return;
1783 }
1784 let _ = self.reset_menu_mode();
1785 self.current_tab_mut().set_display_mode(Display::Preview);
1786 let preview = PreviewBuilder::cli_info(&output, command);
1787 if let Preview::Text(text) = &preview {
1788 log_info!("preview is Text with: {text:?}");
1789 } else {
1790 log_info!("preview is empty ? {empty}", empty = preview.is_empty());
1791 }
1792 self.current_tab_mut().window.reset(preview.len());
1793 self.current_tab_mut().preview = preview;
1794 }
1795
1796 pub fn update_nvim_listen_address(&mut self) {
1798 self.internal_settings.update_nvim_listen_address()
1799 }
1800
1801 pub fn confirm(&mut self, c: char, confirmed_action: NeedConfirmation) -> Result<()> {
1804 if c == 'y' {
1805 if let Ok(must_leave) = self.match_confirmed_mode(confirmed_action) {
1806 if must_leave {
1807 return Ok(());
1808 }
1809 }
1810 }
1811 self.reset_menu_mode()?;
1812 self.current_tab_mut().refresh_view()?;
1813
1814 Ok(())
1815 }
1816
1817 fn match_confirmed_mode(&mut self, confirmed_action: NeedConfirmation) -> Result<bool> {
1819 match confirmed_action {
1820 NeedConfirmation::Delete => self.confirm_delete_files(),
1821 NeedConfirmation::Move => self.cut_or_copy_flagged_files(CopyMove::Move),
1822 NeedConfirmation::Copy => self.cut_or_copy_flagged_files(CopyMove::Copy),
1823 NeedConfirmation::EmptyTrash => self.confirm_trash_empty(),
1824 NeedConfirmation::BulkAction => self.confirm_bulk_action(),
1825 NeedConfirmation::DeleteCloud => {
1826 self.cloud_confirm_delete()?;
1827 return Ok(true);
1828 }
1829 }?;
1830 Ok(false)
1831 }
1832
1833 pub fn header_action(&mut self, col: u16, binds: &Bindings) -> Result<()> {
1835 if self.current_tab().display_mode.is_preview() {
1836 return Ok(());
1837 }
1838 Header::new(self, self.current_tab())?
1839 .action(col, !self.focus.is_left())
1840 .matcher(self, binds)
1841 }
1842
1843 pub fn footer_action(&mut self, col: u16, binds: &Bindings) -> Result<()> {
1845 log_info!("footer clicked col {col}");
1846 let is_right = self.index == 1;
1847 let action = match self.current_tab().display_mode {
1848 Display::Preview => return Ok(()),
1849 Display::Tree | Display::Directory => {
1850 let footer = Footer::new(self, self.current_tab())?;
1851 footer.action(col, is_right).to_owned()
1852 }
1853 Display::Fuzzy => return Ok(()),
1854 };
1855 log_info!("action: {action}");
1856 action.matcher(self, binds)
1857 }
1858
1859 pub fn chmod(&mut self) -> Result<()> {
1865 if self.menu.input.is_empty() || self.menu.flagged.is_empty() {
1866 return Ok(());
1867 }
1868 let input_permission = &self.menu.input.string();
1869 Permissions::set_permissions_of_flagged(input_permission, &self.menu.flagged)?;
1870 self.reset_tabs_view()
1871 }
1872
1873 pub fn set_mode_chmod(&mut self) -> Result<()> {
1875 if self.current_tab_mut().directory.is_empty() {
1876 return Ok(());
1877 }
1878 if self.menu.flagged.is_empty() {
1879 self.toggle_flag_for_selected();
1880 }
1881 self.set_menu_mode(self.index, Menu::InputSimple(InputSimple::Chmod))?;
1882 self.menu.replace_input_by_permissions();
1883 Ok(())
1884 }
1885
1886 pub fn run_custom_command(&mut self, string: &str) -> Result<()> {
1888 self.parse_shell_command(string.to_owned(), None, true)?;
1889 Ok(())
1890 }
1891
1892 pub fn compress(&mut self) -> Result<()> {
1898 let here = &self.current_tab().directory.path;
1899 set_current_dir(here)?;
1900 let files_with_relative_paths = self.flagged_or_selected_relative_to(here);
1901 if files_with_relative_paths.is_empty() {
1902 return Ok(());
1903 }
1904 match self
1905 .menu
1906 .compression
1907 .compress(files_with_relative_paths, here)
1908 {
1909 Ok(()) => (),
1910 Err(error) => log_info!("Error compressing files. Error: {error}"),
1911 }
1912 Ok(())
1913 }
1914
1915 pub fn sort_by_char(&mut self, c: char) -> Result<()> {
1917 self.current_tab_mut().sort(c)?;
1918 self.menu.reset();
1919 self.set_height_for_menu_mode(self.index, Menu::Nothing)?;
1920 self.tabs[self.index].menu_mode = Menu::Nothing;
1921 let len = self.menu.len(Menu::Nothing);
1922 let height = self.second_window_height()?;
1923 self.menu.window = ContentWindow::new(len, height);
1924 self.focus = self.focus.to_parent();
1925 Ok(())
1926 }
1927
1928 pub fn canvas_width(&self) -> Result<u16> {
1930 let full_width = self.internal_settings.term_size().0;
1931 if self.session.dual() && full_width >= MIN_WIDTH_FOR_DUAL_PANE {
1932 Ok(full_width / 2)
1933 } else {
1934 Ok(full_width)
1935 }
1936 }
1937
1938 fn search(&mut self) -> Result<()> {
1944 let Some(search) = self.build_search_from_input() else {
1945 self.current_tab_mut().search = Search::empty();
1946 return Ok(());
1947 };
1948 self.search_and_update(search)
1949 }
1950
1951 fn build_search_from_input(&self) -> Option<Search> {
1952 let searched = &self.menu.input.string();
1953 if searched.is_empty() {
1954 return None;
1955 }
1956 Search::new(searched).ok()
1957 }
1958
1959 fn search_and_update(&mut self, mut search: Search) -> Result<()> {
1960 search.execute_search(self.current_tab_mut())?;
1961 self.current_tab_mut().search = search;
1962 self.update_second_pane_for_preview()
1963 }
1964
1965 fn search_again(&mut self) -> Result<()> {
1966 let search = self.current_tab().search.clone_with_regex();
1967 self.search_and_update(search)
1968 }
1969
1970 pub fn filter(&mut self) -> Result<()> {
1973 let filter = FilterKind::from_input(&self.menu.input.string());
1974 self.current_tab_mut().set_filter(filter)?;
1975 self.search_again()
1976 }
1977
1978 pub fn input_filter(&mut self, c: char) -> Result<()> {
1980 self.menu.input_insert(c)?;
1981 self.filter()
1982 }
1983
1984 pub fn cloud_load_config(&mut self) -> Result<()> {
1986 let Some(picked) = self.menu.picker.selected() else {
1987 log_info!("nothing selected");
1988 return Ok(());
1989 };
1990 let Ok(cloud) = google_drive(picked) else {
1991 log_line!("Invalid config file {picked}");
1992 return Ok(());
1993 };
1994 self.menu.cloud = cloud;
1995 self.set_menu_mode(self.index, Menu::Navigate(Navigate::Cloud))
1996 }
1997
1998 pub fn cloud_open(&mut self) -> Result<()> {
2002 if self.menu.cloud.is_set() {
2003 self.set_menu_mode(self.index, Menu::Navigate(Navigate::Cloud))
2004 } else {
2005 self.cloud_picker()
2006 }
2007 }
2008
2009 fn cloud_picker(&mut self) -> Result<()> {
2010 let content = get_cloud_token_names()?;
2011 self.menu.picker.set(
2012 Some(PickerCaller::Cloud),
2013 Some("Pick a cloud provider".to_owned()),
2014 content,
2015 );
2016 self.set_menu_mode(self.index, Menu::Navigate(Navigate::Picker))
2017 }
2018
2019 pub fn cloud_disconnect(&mut self) -> Result<()> {
2021 self.menu.cloud.disconnect();
2022 self.cloud_open()
2023 }
2024
2025 pub fn cloud_enter_delete_mode(&mut self) -> Result<()> {
2028 self.set_menu_mode(
2029 self.index,
2030 Menu::NeedConfirmation(NeedConfirmation::DeleteCloud),
2031 )
2032 }
2033
2034 pub fn cloud_confirm_delete(&mut self) -> Result<()> {
2036 self.menu.cloud.delete()?;
2037 self.set_menu_mode(self.index, Menu::Navigate(Navigate::Cloud))?;
2038 self.menu.cloud.refresh_current()?;
2039 self.menu.window.scroll_to(self.menu.cloud.index);
2040 Ok(())
2041 }
2042
2043 pub fn cloud_update_metadata(&mut self) -> Result<()> {
2045 self.menu.cloud.update_metadata()
2046 }
2047
2048 pub fn cloud_enter_newdir_mode(&mut self) -> Result<()> {
2050 self.set_menu_mode(self.index, Menu::InputSimple(InputSimple::CloudNewdir))?;
2051 self.refresh_view()
2052 }
2053
2054 pub fn cloud_create_newdir(&mut self, dirname: String) -> Result<()> {
2056 self.menu.cloud.create_newdir(dirname)?;
2057 self.menu.cloud.refresh_current()
2058 }
2059
2060 fn get_normal_selected_file(&self) -> Option<FileInfo> {
2061 let local_file = self.tabs[self.index].current_file().ok()?;
2062 match local_file.file_kind {
2063 FileKind::NormalFile => Some(local_file),
2064 _ => None,
2065 }
2066 }
2067
2068 pub fn cloud_upload_selected_file(&mut self) -> Result<()> {
2070 let Some(local_file) = self.get_normal_selected_file() else {
2071 log_line!("Can only upload normal files.");
2072 return Ok(());
2073 };
2074 self.menu.cloud.upload(&local_file)?;
2075 self.menu.cloud.refresh_current()
2076 }
2077
2078 pub fn cloud_enter_file_or_dir(&mut self) -> Result<()> {
2080 if let Some(entry) = self.menu.cloud.selected() {
2081 match entry.metadata().mode() {
2082 EntryMode::Unknown => (),
2083 EntryMode::FILE => self
2084 .menu
2085 .cloud
2086 .download(self.current_tab().directory_of_selected()?)?,
2087 EntryMode::DIR => {
2088 self.menu.cloud.enter_selected()?;
2089 self.cloud_set_content_window_len()?;
2090 }
2091 };
2092 };
2093 Ok(())
2094 }
2095
2096 fn cloud_set_content_window_len(&mut self) -> Result<()> {
2097 let len = self.menu.cloud.content.len();
2098 let height = self.second_window_height()?;
2099 self.menu.window = ContentWindow::new(len, height);
2100 Ok(())
2101 }
2102
2103 pub fn cloud_move_to_parent(&mut self) -> Result<()> {
2106 self.menu.cloud.move_to_parent()?;
2107 self.cloud_set_content_window_len()?;
2108 Ok(())
2109 }
2110
2111 pub fn toggle_flag_visual(&mut self) {
2112 if self.current_tab().visual {
2113 let Ok(file) = self.current_tab().current_file() else {
2114 return;
2115 };
2116 self.menu.flagged.toggle(&file.path)
2117 }
2118 }
2119}
2120
2121fn find_keybind_from_fuzzy(line: &str) -> Result<KeyEvent> {
2122 let Some(keybind) = line.split(':').next() else {
2123 bail!("No keybind found");
2124 };
2125 let Some(key) = from_keyname(keybind.trim()) else {
2126 bail!("{keybind} isn't a valid Key name.");
2127 };
2128 Ok(key)
2129}