1use std::path::{Path, PathBuf};
2use std::str::FromStr;
3use std::sync::{
4 mpsc::{self, Sender, TryRecvError},
5 Arc,
6};
7
8use anyhow::{bail, Context, Result};
9use clap::Parser;
10use crossterm::event::{Event, KeyEvent};
11use opendal::EntryMode;
12use parking_lot::lock_api::Mutex;
13use parking_lot::RawMutex;
14use ratatui::layout::Size;
15use sysinfo::Disks;
16use walkdir::WalkDir;
17
18use crate::app::{
19 ClickableLine, Footer, Header, InternalSettings, PreviewResponse, Previewer, Session, Tab,
20 ThumbnailManager,
21};
22use crate::common::{
23 build_dest_path, current_username, disk_space, filename_from_path, is_in_path, is_sudo_command,
24 path_to_string, row_to_window_index, set_current_dir, tilde, MountPoint,
25};
26use crate::config::{from_keyname, Bindings, START_FOLDER};
27use crate::event::{ActionMap, FmEvents};
28use crate::io::{
29 build_tokio_greper, execute_and_capture_output, execute_sudo_command_with_password,
30 execute_without_output, get_cloud_token_names, google_drive, reset_sudo_faillock, Args,
31 Internal, Kind, Opener, MIN_WIDTH_FOR_DUAL_PANE,
32};
33use crate::modes::{
34 append_files_to_shell_command, copy_move, parse_line_output, regex_flagger,
35 shell_command_parser, Content, ContentWindow, CopyMove, CursorOffset,
36 Direction as FuzzyDirection, Display, FileInfo, FileKind, FilterKind, FuzzyFinder, FuzzyKind,
37 InputCompleted, InputSimple, IsoDevice, Menu, MenuHolder, MountAction, MountCommands,
38 Mountable, Navigate, NeedConfirmation, PasswordKind, PasswordUsage, Permissions, PickerCaller,
39 Preview, PreviewBuilder, ReEnterMenu, Search, Selectable, Users, SAME_WINDOW_TOKEN,
40};
41use crate::{log_info, log_line};
42
43pub enum Window {
45 Header,
46 Files,
47 Menu,
48 Footer,
49}
50
51#[derive(Default, Clone, Copy, Debug)]
53pub enum Focus {
54 #[default]
55 LeftFile,
56 LeftMenu,
57 RightFile,
58 RightMenu,
59}
60
61impl Focus {
62 pub fn is_left(&self) -> bool {
65 matches!(self, Self::LeftMenu | Self::LeftFile)
66 }
67
68 pub fn is_file(&self) -> bool {
71 matches!(self, Self::LeftFile | Self::RightFile)
72 }
73
74 pub fn switch(&self) -> Self {
79 match self {
80 Self::LeftFile => Self::RightFile,
81 Self::LeftMenu => Self::RightFile,
82 Self::RightFile => Self::LeftFile,
83 Self::RightMenu => Self::LeftFile,
84 }
85 }
86
87 pub fn to_parent(&self) -> Self {
91 match self {
92 Self::LeftFile | Self::LeftMenu => Self::LeftFile,
93 Self::RightFile | Self::RightMenu => Self::RightFile,
94 }
95 }
96
97 pub fn index(&self) -> usize {
99 *self as usize
100 }
101
102 pub fn tab_index(&self) -> usize {
104 self.index() >> 1
105 }
106
107 pub fn is_left_menu(&self) -> bool {
109 matches!(self, Self::LeftMenu)
110 }
111}
112
113pub enum Direction {
115 RightToLeft,
116 LeftToRight,
117}
118
119impl Direction {
120 const fn source_dest(self) -> (usize, usize) {
122 match self {
123 Self::RightToLeft => (1, 0),
124 Self::LeftToRight => (0, 1),
125 }
126 }
127}
128
129pub struct Status {
139 pub tabs: [Tab; 2],
141 pub index: usize,
143 pub fuzzy: Option<FuzzyFinder<String>>,
145 pub menu: MenuHolder,
147 pub session: Session,
149 pub internal_settings: InternalSettings,
151 pub focus: Focus,
153 pub fm_sender: Arc<Sender<FmEvents>>,
155 preview_receiver: mpsc::Receiver<PreviewResponse>,
157 pub previewer: Previewer,
159 pub thumbnail_manager: Option<ThumbnailManager>,
161}
162
163impl Status {
164 pub fn arc_mutex_new(
169 size: Size,
170 opener: Opener,
171 binds: &Bindings,
172 fm_sender: Arc<Sender<FmEvents>>,
173 ) -> Result<Arc<Mutex<RawMutex, Self>>> {
174 Ok(Arc::new(Mutex::new(Self::new(
175 size, opener, binds, fm_sender,
176 )?)))
177 }
178
179 fn new(
180 size: Size,
181 opener: Opener,
182 binds: &Bindings,
183 fm_sender: Arc<Sender<FmEvents>>,
184 ) -> Result<Self> {
185 let fuzzy = None;
186 let index = 0;
187
188 let args = Args::parse();
189 let path = &START_FOLDER.get().context("Start folder should be set")?;
190 let start_dir = if path.is_dir() {
191 path
192 } else {
193 path.parent().context("")?
194 };
195 let disks = Disks::new_with_refreshed_list();
196 let session = Session::new(size.width);
197 let internal_settings = InternalSettings::new(opener, size, disks);
198 let menu = MenuHolder::new(start_dir, binds)?;
199 let focus = Focus::default();
200
201 let users_left = Users::default();
202 let users_right = users_left.clone();
203
204 let height = size.height as usize;
205 let tabs = [
206 Tab::new(&args, height, users_left)?,
207 Tab::new(&args, height, users_right)?,
208 ];
209 let (previewer_sender, preview_receiver) = mpsc::channel();
210 let previewer = Previewer::new(previewer_sender);
211 let thumbnail_manager = None;
212 Ok(Self {
213 tabs,
214 index,
215 fuzzy,
216 menu,
217 session,
218 internal_settings,
219 focus,
220 fm_sender,
221 preview_receiver,
222 previewer,
223 thumbnail_manager,
224 })
225 }
226
227 pub fn current_tab(&self) -> &Tab {
229 &self.tabs[self.index]
230 }
231
232 pub fn current_tab_mut(&mut self) -> &mut Tab {
234 &mut self.tabs[self.index]
235 }
236
237 pub fn current_tab_path_str(&self) -> String {
239 self.current_tab().directory_str()
240 }
241
242 pub fn must_quit(&self) -> bool {
244 self.internal_settings.must_quit
245 }
246
247 pub fn focus_follow_index(&mut self) {
249 if (self.index == 0 && !self.focus.is_left()) || (self.index == 1 && self.focus.is_left()) {
250 self.focus = self.focus.switch();
251 }
252 }
253
254 pub fn set_focus_from_mode(&mut self) {
256 if self.index == 0 {
257 if self.tabs[0].menu_mode.is_nothing() {
258 self.focus = Focus::LeftFile;
259 } else {
260 self.focus = Focus::LeftMenu;
261 }
262 } else if self.tabs[1].menu_mode.is_nothing() {
263 self.focus = Focus::RightFile;
264 } else {
265 self.focus = Focus::RightMenu;
266 }
267 }
268
269 pub fn next(&mut self) {
271 if !self.session.dual() {
272 return;
273 }
274 self.index = 1 - self.index;
275 self.focus_follow_index();
276 }
277
278 pub fn select_tab_from_col(&mut self, col: u16) -> Result<()> {
280 if self.session.dual() {
281 if col < self.term_width() / 2 {
282 self.select_left();
283 } else {
284 self.select_right();
285 };
286 } else {
287 self.select_left();
288 }
289 Ok(())
290 }
291
292 fn window_from_row(&self, row: u16, height: u16) -> Window {
293 let win_height = if self.current_tab().menu_mode.is_nothing() {
294 height
295 } else {
296 height / 2
297 };
298 let w_index = row / win_height;
299 if w_index == 1 {
300 Window::Menu
301 } else if row == 1 {
302 Window::Header
303 } else if row == win_height - 2 {
304 Window::Footer
305 } else {
306 Window::Files
307 }
308 }
309
310 pub fn set_focus_from_pos(&mut self, row: u16, col: u16) -> Result<Window> {
313 self.select_tab_from_col(col)?;
314 let window = self.window_from_row(row, self.term_size().height);
315 self.set_focus_from_window_and_index(&window);
316 Ok(window)
317 }
318
319 pub fn click(&mut self, binds: &Bindings, row: u16, col: u16) -> Result<()> {
321 let window = self.set_focus_from_pos(row, col)?;
322 self.click_action_from_window(&window, row, col, binds)?;
323 Ok(())
324 }
325
326 fn has_clicked_on_second_pane_preview(&self) -> bool {
328 self.session.dual() && self.session.preview() && self.index == 1
329 }
330
331 fn click_action_from_window(
332 &mut self,
333 window: &Window,
334 row: u16,
335 col: u16,
336 binds: &Bindings,
337 ) -> Result<()> {
338 match window {
339 Window::Header => self.header_action(col, binds),
340 Window::Files => {
341 if self.has_clicked_on_second_pane_preview() {
342 if let Preview::Tree(tree) = &self.tabs[1].preview {
343 let index = row_to_window_index(row) + self.tabs[1].window.top;
344 let path = &tree.path_from_index(index)?;
345 self.select_file_from_right_tree(path)?;
346 }
347 } else {
348 self.tab_select_row(row)?;
349 self.update_second_pane_for_preview()?;
350 }
351 Ok(())
352 }
353 Window::Footer => self.footer_action(col, binds),
354 Window::Menu => self.menu_action(row, col),
355 }
356 }
357
358 pub fn enter_from_preview(&mut self) -> Result<()> {
362 if let Preview::Tree(tree) = &self.tabs[1].preview {
363 let index = tree.displayable().index();
364 let path = &tree.path_from_index(index)?;
365 self.select_file_from_right_tree(path)?;
366 } else {
367 let filepath = self.tabs[self.focus.tab_index()].preview.filepath();
368 if filepath.exists() {
369 self.open_single_file(&filepath)?;
370 }
371 }
372 Ok(())
373 }
374
375 fn select_file_from_right_tree(&mut self, path: &Path) -> Result<()> {
376 self.tabs[0].cd_to_file(path)?;
377 self.index = 0;
378 self.focus = Focus::LeftFile;
379 self.update_second_pane_for_preview()
380 }
381
382 pub fn tab_select_row(&mut self, row: u16) -> Result<()> {
385 match self.current_tab().display_mode {
386 Display::Directory => self.current_tab_mut().normal_select_row(row),
387 Display::Tree => self.current_tab_mut().tree_select_row(row)?,
388 Display::Fuzzy => self.fuzzy_navigate(FuzzyDirection::Index(row))?,
389 _ => (),
390 }
391 Ok(())
392 }
393
394 #[rustfmt::skip]
395 fn set_focus_from_window_and_index(&mut self, window: &Window) {
396 self.focus = match (self.index == 0, window) {
397 (true, Window::Menu) => Focus::LeftMenu,
398 (true, _) => Focus::LeftFile,
399 (false, Window::Menu) => Focus::RightMenu,
400 (false, _) => Focus::RightFile
401 };
402 }
403
404 pub fn sync_tabs(&mut self, direction: Direction) -> Result<()> {
406 let (source, dest) = direction.source_dest();
407 self.tabs[dest].cd(&self.tabs[source]
408 .selected_path()
409 .context("No selected path")?)
410 }
411
412 pub fn second_window_height(&self) -> Result<usize> {
415 let height = self.term_size().height;
416 Ok((height / 2).saturating_sub(2) as usize)
417 }
418
419 fn menu_action(&mut self, row: u16, col: u16) -> Result<()> {
421 let second_window_height = self.second_window_height()?;
422 let row_offset = (row as usize).saturating_sub(second_window_height);
423 const OFFSET: usize =
424 ContentWindow::WINDOW_PADDING + ContentWindow::WINDOW_MARGIN_TOP_U16 as usize;
425 if row_offset >= OFFSET {
426 let index = row_offset - OFFSET + self.menu.window.top;
427 match self.current_tab().menu_mode {
428 Menu::Navigate(navigate) => match navigate {
429 Navigate::History => self.current_tab_mut().history.set_index(index),
430 navigate => self.menu.set_index(index, navigate),
431 },
432 Menu::InputCompleted(input_completed) => {
433 self.menu.completion.set_index(index);
434 if matches!(input_completed, InputCompleted::Search) {
435 self.follow_search()?;
436 }
437 }
438 Menu::NeedConfirmation(need_confirmation)
439 if need_confirmation.use_flagged_files() =>
440 {
441 self.menu.flagged.set_index(index)
442 }
443 Menu::NeedConfirmation(NeedConfirmation::EmptyTrash) => {
444 self.menu.trash.set_index(index)
445 }
446 Menu::NeedConfirmation(NeedConfirmation::BulkAction) => {
447 self.menu.bulk.set_index(index)
448 }
449 _ => (),
450 }
451 self.menu.window.scroll_to(index);
452 } else if row_offset == 3 && self.current_tab().menu_mode.is_input() {
453 let index =
454 col.saturating_sub(self.current_tab().menu_mode.cursor_offset() + 1) as usize;
455 self.menu.input.cursor_move(index);
456 }
457
458 Ok(())
459 }
460
461 pub fn select_left(&mut self) {
463 self.index = 0;
464 self.focus_follow_index();
465 }
466
467 pub fn select_right(&mut self) {
469 self.index = 1;
470 self.focus_follow_index();
471 }
472
473 pub fn refresh_shortcuts(&mut self) {
479 self.menu.refresh_shortcuts(
480 &self.internal_settings.mount_points_vec(),
481 self.tabs[0].current_directory_path(),
482 self.tabs[1].current_directory_path(),
483 );
484 }
485
486 pub fn disk_spaces_of_selected(&self) -> String {
488 disk_space(
489 &self.internal_settings.disks,
490 self.current_tab().current_directory_path(),
491 )
492 }
493
494 pub fn term_size(&self) -> Size {
496 self.internal_settings.term_size()
497 }
498
499 pub fn term_width(&self) -> u16 {
501 self.term_size().width
502 }
503
504 pub fn clear_preview_right(&mut self) {
506 if self.session.dual() && self.session.preview() && !self.tabs[1].preview.is_empty() {
507 self.tabs[1].preview = PreviewBuilder::empty()
508 }
509 }
510
511 pub fn refresh_view(&mut self) -> Result<()> {
513 self.refresh_status()?;
514 self.update_second_pane_for_preview()
515 }
516
517 pub fn reset_tabs_view(&mut self) -> Result<()> {
519 for tab in self.tabs.iter_mut() {
520 match tab.refresh_and_reselect_file() {
521 Ok(()) => (),
522 Err(error) => log_info!("reset_tabs_view error: {error}"),
523 }
524 }
525 Ok(())
526 }
527
528 pub fn leave_menu_mode(&mut self) -> Result<()> {
531 match self.current_tab().menu_mode {
532 Menu::InputSimple(InputSimple::Filter) => {
533 self.current_tab_mut().settings.reset_filter()
534 }
535 Menu::InputCompleted(InputCompleted::Cd) => self.current_tab_mut().cd_origin_path()?,
536 _ => (),
537 }
538 if self.reset_menu_mode()? {
539 self.current_tab_mut().refresh_view()?;
540 } else {
541 self.current_tab_mut().refresh_params();
542 }
543 self.current_tab_mut().reset_visual();
544 Ok(())
545 }
546
547 pub fn leave_preview(&mut self) -> Result<()> {
555 self.current_tab_mut().set_display_mode(Display::Directory);
556 self.current_tab_mut().refresh_and_reselect_file()
557 }
558
559 pub fn reset_menu_mode(&mut self) -> Result<bool> {
564 if self.current_tab().menu_mode.is_picker() {
565 if let Some(PickerCaller::Menu(menu)) = self.menu.picker.caller {
566 menu.reenter(self)?;
567 return Ok(false);
568 }
569 }
570 self.menu.reset();
571 let must_refresh = matches!(self.current_tab().display_mode, Display::Preview);
572 self.set_menu_mode(self.index, Menu::Nothing)?;
573 self.set_height_of_unfocused_menu()?;
574 Ok(must_refresh)
575 }
576
577 fn set_height_of_unfocused_menu(&mut self) -> Result<()> {
578 let unfocused_tab = &self.tabs[1 - self.index];
579 match unfocused_tab.menu_mode {
580 Menu::Nothing => (),
581 unfocused_mode => {
582 let len = self.menu.len(unfocused_mode);
583 let height = self.second_window_height()?;
584 self.menu.window = ContentWindow::new(len, height);
585 }
586 }
587 Ok(())
588 }
589
590 pub fn refresh_status(&mut self) -> Result<()> {
592 self.force_clear();
593 self.refresh_users()?;
594 self.refresh_tabs()?;
595 self.refresh_shortcuts();
596 Ok(())
597 }
598
599 pub fn force_clear(&mut self) {
603 self.internal_settings.force_clear();
604 }
605
606 pub fn should_be_cleared(&self) -> bool {
607 self.internal_settings.should_be_cleared()
608 }
609
610 pub fn refresh_users(&mut self) -> Result<()> {
612 self.tabs[0].users.update();
613 self.tabs[1].users = self.tabs[0].users.clone();
614 Ok(())
615 }
616
617 pub fn refresh_tabs(&mut self) -> Result<()> {
619 self.menu.input.reset();
620 self.menu.completion.reset();
621 self.tabs[0].refresh_and_reselect_file()?;
622 self.tabs[1].refresh_and_reselect_file()
623 }
624
625 pub fn resize(&mut self, width: u16, height: u16) -> Result<()> {
630 let couldnt_dual_but_want = self.couldnt_dual_but_want();
631 self.internal_settings.update_size(width, height);
632 if couldnt_dual_but_want {
633 self.set_dual_pane_if_wide_enough()?;
634 }
635 if !self.use_dual() {
636 self.select_left();
637 }
638 self.resize_all_windows(height)?;
639 self.refresh_status()
640 }
641
642 fn wide_enough_for_dual(&self) -> bool {
643 self.term_width() >= MIN_WIDTH_FOR_DUAL_PANE
644 }
645
646 fn couldnt_dual_but_want(&self) -> bool {
647 !self.wide_enough_for_dual() && self.session.dual()
648 }
649
650 fn use_dual(&self) -> bool {
651 self.wide_enough_for_dual() && self.session.dual()
652 }
653
654 fn left_window_width(&self) -> u16 {
655 if self.use_dual() {
656 self.term_width() / 2
657 } else {
658 self.term_width()
659 }
660 }
661
662 fn resize_all_windows(&mut self, height: u16) -> Result<()> {
663 let height_usize = height as usize;
664 self.tabs[0].set_height(height_usize);
665 self.tabs[1].set_height(height_usize);
666 self.fuzzy_resize(height_usize);
667 self.menu.resize(
668 self.tabs[self.index].menu_mode,
669 self.second_window_height()?,
670 );
671 Ok(())
672 }
673
674 pub fn update_second_pane_for_preview(&mut self) -> Result<()> {
676 if self.are_settings_requiring_dualpane_preview() {
677 if self.can_display_dualpane_preview() {
678 self.set_second_pane_for_preview()?;
679 } else {
680 self.tabs[1].preview = PreviewBuilder::empty();
681 }
682 }
683 Ok(())
684 }
685
686 fn are_settings_requiring_dualpane_preview(&self) -> bool {
687 self.index == 0 && self.session.dual() && self.session.preview()
688 }
689
690 fn can_display_dualpane_preview(&self) -> bool {
691 Session::display_wide_enough(self.term_width())
692 }
693
694 fn set_second_pane_for_preview(&mut self) -> Result<()> {
697 self.tabs[1].set_display_mode(Display::Preview);
698 self.tabs[1].menu_mode = Menu::Nothing;
699 let Ok((Some(fileinfo), line_index)) = self.get_correct_fileinfo_for_preview() else {
700 return Ok(());
701 };
702 log_info!("sending preview request");
703
704 self.previewer
705 .build(fileinfo.path.to_path_buf(), 1, line_index)?;
706
707 Ok(())
708 }
709
710 pub fn thumbnail_directory_video(&mut self) {
713 if !self.are_settings_requiring_dualpane_preview() || !self.can_display_dualpane_preview() {
714 return;
715 }
716 self.thumbnail_init_or_clear();
717 let videos = self.current_tab().directory.videos();
718 if videos.is_empty() {
719 return;
720 }
721 if let Some(thumbnail_manager) = &self.thumbnail_manager {
722 thumbnail_manager.enqueue(videos);
723 }
724 }
725
726 fn thumbnail_init_or_clear(&mut self) {
728 if self.thumbnail_manager.is_none() {
729 self.thumbnail_manager_init();
730 } else {
731 self.thumbnail_queue_clear();
732 }
733 }
734
735 fn thumbnail_manager_init(&mut self) {
736 self.thumbnail_manager = Some(ThumbnailManager::default());
737 }
738
739 pub fn thumbnail_queue_clear(&self) {
741 if let Some(thumbnail_manager) = &self.thumbnail_manager {
742 thumbnail_manager.clear()
743 }
744 }
745
746 pub fn check_preview(&mut self) -> Result<()> {
752 match self.preview_receiver.try_recv() {
753 Ok(preview_response) => self.attach_preview(preview_response)?,
754 Err(TryRecvError::Disconnected) => bail!("Previewer Disconnected"),
755 Err(TryRecvError::Empty) => (),
756 }
757 Ok(())
758 }
759
760 fn attach_preview(&mut self, preview_response: PreviewResponse) -> Result<()> {
764 let PreviewResponse {
765 path,
766 tab_index,
767 line_nr,
768 preview,
769 } = preview_response;
770 let compared_index = self.pick_correct_tab_from(tab_index)?;
771 if !self.preview_has_correct_path(compared_index, path.as_path())? {
772 return Ok(());
773 }
774 let tab = &mut self.tabs[tab_index];
775 tab.preview = preview;
776 tab.window.reset(tab.preview.len());
777 if let Some(line_nr) = line_nr {
778 tab.window.scroll_to(line_nr);
779 }
780 Ok(())
781 }
782
783 fn pick_correct_tab_from(&self, index: usize) -> Result<usize> {
784 if index == 1 && self.can_display_dualpane_preview() && self.session.preview() {
785 Ok(0)
786 } else {
787 Ok(index)
788 }
789 }
790
791 fn preview_has_correct_path(&self, compared_index: usize, path: &Path) -> Result<bool> {
797 let tab = &self.tabs[compared_index];
798 Ok(tab.display_mode.is_fuzzy()
799 || tab.menu_mode.is_navigate()
800 || tab.selected_path().context("No selected path")?.as_ref() == path)
801 }
802
803 fn get_correct_fileinfo_for_preview(&self) -> Result<(Option<FileInfo>, Option<usize>)> {
809 let left_tab = &self.tabs[0];
810 let users = &left_tab.users;
811 if left_tab.display_mode.is_fuzzy() {
812 let (opt_path, line_nr) =
813 parse_line_output(&self.fuzzy_current_selection().context("No selection")?)?;
814 Ok((FileInfo::new(&opt_path, users).ok(), line_nr))
815 } else if self.focus.is_left_menu() {
816 Ok((self.fileinfo_from_navigate(left_tab, users), None))
817 } else {
818 Ok((left_tab.current_file().ok(), None))
819 }
820 }
821
822 fn fileinfo_from_navigate(&self, tab: &Tab, users: &Users) -> Option<FileInfo> {
826 match tab.menu_mode {
827 Menu::Navigate(Navigate::History) => {
828 FileInfo::new(tab.history.content().get(tab.history.index())?, users).ok()
829 }
830 Menu::Navigate(Navigate::Shortcut) => {
831 let short = &self.menu.shortcut;
832 FileInfo::new(short.content().get(short.index())?, users).ok()
833 }
834 Menu::Navigate(Navigate::Marks(_)) => {
835 let (_, mark_path) = &self.menu.marks.content().get(self.menu.marks.index())?;
836 FileInfo::new(mark_path, users).ok()
837 }
838 Menu::Navigate(Navigate::Flagged) => {
839 FileInfo::new(self.menu.flagged.selected()?, users).ok()
840 }
841 _ => tab.current_file().ok(),
842 }
843 }
844
845 pub fn should_tabs_images_be_cleared(&self) -> bool {
847 self.tabs[0].settings.should_clear_image || self.tabs[1].settings.should_clear_image
848 }
849
850 pub fn set_tabs_images_cleared(&mut self) {
852 self.tabs[0].settings.should_clear_image = false;
853 self.tabs[1].settings.should_clear_image = false;
854 }
855
856 pub fn set_menu_mode(&mut self, index: usize, menu_mode: Menu) -> Result<()> {
858 self.set_menu_mode_no_refresh(index, menu_mode)?;
859 self.current_tab_mut().reset_visual();
860 self.refresh_status()
861 }
862
863 pub fn set_menu_mode_no_refresh(&mut self, index: usize, menu_mode: Menu) -> Result<()> {
865 if index > 1 {
866 return Ok(());
867 }
868 self.set_height_for_menu_mode(index, menu_mode)?;
869 self.tabs[index].menu_mode = menu_mode;
870 let len = self.menu.len(menu_mode);
871 let height = self.second_window_height()?;
872 self.menu.window = ContentWindow::new(len, height);
873 self.menu.window.scroll_to(self.menu.index(menu_mode));
874 self.set_focus_from_mode();
875 self.menu.input_history.filter_by_mode(menu_mode);
876 Ok(())
877 }
878
879 pub fn set_height_for_menu_mode(&mut self, index: usize, menu_mode: Menu) -> Result<()> {
881 let height = self.term_size().height;
882 let prim_window_height = if menu_mode.is_nothing() {
883 height
884 } else {
885 height / 2
886 };
887 self.tabs[index]
888 .window
889 .set_height(prim_window_height as usize);
890 self.tabs[index]
891 .window
892 .scroll_to(self.tabs[index].window.top);
893 Ok(())
894 }
895
896 pub fn set_dual_pane_if_wide_enough(&mut self) -> Result<()> {
898 if self.wide_enough_for_dual() {
899 self.session.set_dual(true);
900 } else {
901 self.select_left();
902 self.session.set_dual(false);
903 }
904 Ok(())
905 }
906
907 pub fn clear_flags_and_reset_view(&mut self) -> Result<()> {
909 self.menu.flagged.clear();
910 self.reset_tabs_view()
911 }
912
913 fn flagged_or_selected(&self) -> Vec<PathBuf> {
915 if self.menu.flagged.is_empty() {
916 let Some(path) = self.current_tab().selected_path() else {
917 return vec![];
918 };
919 vec![path.to_path_buf()]
920 } else {
921 self.menu.flagged.content().to_owned()
922 }
923 }
924
925 fn flagged_or_selected_relative_to(&self, here: &Path) -> Vec<PathBuf> {
926 self.flagged_or_selected()
927 .iter()
928 .filter_map(|abs_path| pathdiff::diff_paths(abs_path, here))
929 .filter(|f| !f.starts_with(".."))
930 .collect()
931 }
932
933 pub fn flagged_in_current_dir(&self) -> Vec<PathBuf> {
940 self.menu.flagged.in_dir(&self.current_tab().directory.path)
941 }
942
943 pub fn flag_all(&mut self) {
945 match self.current_tab().display_mode {
946 Display::Directory => {
947 self.tabs[self.index]
948 .directory
949 .content
950 .iter()
951 .filter(|file| file.filename.as_ref() != "." && file.filename.as_ref() != "..")
952 .for_each(|file| {
953 self.menu.flagged.push(file.path.to_path_buf());
954 });
955 }
956 Display::Tree => self.tabs[self.index].tree.flag_all(&mut self.menu.flagged),
957 _ => (),
958 }
959 }
960
961 pub fn reverse_flags(&mut self) {
964 log_info!("Reverse flags");
965 if !self.current_tab().display_mode.is_preview() {
966 self.tabs[self.index]
967 .directory
968 .content
969 .iter()
970 .for_each(|file| self.menu.flagged.toggle(&file.path));
971 }
972 }
973
974 pub fn toggle_flag_for_children(&mut self) {
976 let Some(path) = self.current_tab().selected_path() else {
977 return;
978 };
979 match self.current_tab().display_mode {
980 Display::Directory => self.toggle_flag_for_selected(),
981 Display::Tree => {
982 if !path.is_dir() {
983 self.menu.flagged.toggle(&path);
984 } else {
985 for entry in WalkDir::new(&path).into_iter().filter_map(|e| e.ok()) {
986 let p = entry.path();
987 if !p.is_dir() {
988 self.menu.flagged.toggle(p);
989 }
990 }
991 }
992 let _ = self.update_second_pane_for_preview();
993 }
994 Display::Preview => (),
995 Display::Fuzzy => (),
996 }
997 }
998 pub fn toggle_flag_for_selected(&mut self) {
1000 let Some(path) = self.current_tab().selected_path() else {
1001 return;
1002 };
1003 match self.current_tab().display_mode {
1004 Display::Directory => {
1005 self.menu.flagged.toggle(&path);
1006 if !self.current_tab().directory.selected_is_last() {
1007 self.tabs[self.index].normal_down_one_row();
1008 }
1009 let _ = self.update_second_pane_for_preview();
1010 }
1011 Display::Tree => {
1012 self.menu.flagged.toggle(&path);
1013 if !self.current_tab().tree.selected_is_last() {
1014 self.current_tab_mut().tree_select_next();
1015 }
1016 let _ = self.update_second_pane_for_preview();
1017 }
1018 Display::Preview => self.menu.flagged.toggle(&path),
1019 Display::Fuzzy => (),
1020 }
1021 if matches!(
1022 self.current_tab().menu_mode,
1023 Menu::Navigate(Navigate::Flagged)
1024 ) {
1025 self.menu.window.set_len(self.menu.flagged.len());
1026 }
1027 }
1028
1029 pub fn jump_flagged(&mut self) -> Result<()> {
1031 let Some(path) = self.menu.flagged.selected() else {
1032 return Ok(());
1033 };
1034 let path = path.to_owned();
1035 let tab = self.current_tab_mut();
1036 tab.set_display_mode(Display::Directory);
1037 tab.refresh_view()?;
1038 tab.jump(path)?;
1039 self.update_second_pane_for_preview()
1040 }
1041
1042 pub fn cut_or_copy_flagged_files(&mut self, cut_or_copy: CopyMove) -> Result<()> {
1046 let sources = self.menu.flagged.content.clone();
1047 let dest = &self.current_tab().directory_of_selected()?.to_owned();
1048 self.cut_or_copy_files(cut_or_copy, sources, dest)
1049 }
1050
1051 fn cut_or_copy_files(
1052 &mut self,
1053 cut_or_copy: CopyMove,
1054 mut sources: Vec<PathBuf>,
1055 dest: &Path,
1056 ) -> Result<()> {
1057 if matches!(cut_or_copy, CopyMove::Move) {
1058 sources = Self::remove_subdir_of_dest(sources, dest);
1059 if sources.is_empty() {
1060 return Ok(());
1061 }
1062 }
1063
1064 if self.is_simple_move(&cut_or_copy, &sources, dest) {
1065 self.simple_move(&sources, dest)
1066 } else {
1067 self.complex_move(cut_or_copy, sources, dest)
1068 }
1069 }
1070
1071 fn remove_subdir_of_dest(mut sources: Vec<PathBuf>, dest: &Path) -> Vec<PathBuf> {
1074 for index in (0..sources.len()).rev() {
1075 if sources[index].starts_with(dest) {
1076 log_info!("Cannot move to a subdirectory of itself");
1077 log_line!("Cannot move to a subdirectory of itself");
1078 sources.remove(index);
1079 }
1080 }
1081 sources
1082 }
1083
1084 fn is_simple_move(&self, cut_or_copy: &CopyMove, sources: &[PathBuf], dest: &Path) -> bool {
1086 if cut_or_copy.is_copy() || sources.is_empty() {
1087 return false;
1088 }
1089 let mount_points = self.internal_settings.mount_points_set();
1090 let Some(source_mount_point) = sources[0].mount_point(&mount_points) else {
1091 return false;
1092 };
1093 for source in sources {
1094 if source.mount_point(&mount_points) != Some(source_mount_point) {
1095 return false;
1096 }
1097 }
1098 Some(source_mount_point) == dest.mount_point(&mount_points)
1099 }
1100
1101 fn simple_move(&mut self, sources: &[PathBuf], dest: &Path) -> Result<()> {
1102 for source in sources {
1103 let filename = filename_from_path(source)?;
1104 let dest = dest.to_path_buf().join(filename);
1105 log_info!("simple_move {source:?} -> {dest:?}");
1106 match std::fs::rename(source, &dest) {
1107 Ok(()) => {
1108 log_line!(
1109 "Moved {source} to {dest}",
1110 source = source.display(),
1111 dest = dest.display()
1112 );
1113 self.rename_marks(source, dest)?;
1114 }
1115 Err(e) => {
1116 log_info!("Error: {e:?}");
1117 log_line!("Error: {e:?}")
1118 }
1119 }
1120 }
1121 self.clear_flags_and_reset_view()
1122 }
1123
1124 fn complex_move(
1125 &mut self,
1126 cut_or_copy: CopyMove,
1127 sources: Vec<PathBuf>,
1128 dest: &Path,
1129 ) -> Result<()> {
1130 let mut must_act_now = true;
1131 if matches!(cut_or_copy, CopyMove::Copy) {
1132 if !self.internal_settings.copy_file_queue.is_empty() {
1133 log_info!("cut_or_copy_flagged_files: act later");
1134 must_act_now = false;
1135 }
1136 self.internal_settings
1137 .copy_file_queue
1138 .push((sources.to_owned(), dest.to_path_buf()));
1139 }
1140
1141 if must_act_now {
1142 log_info!("cut_or_copy_flagged_files: act now");
1143 let in_mem = copy_move(
1144 cut_or_copy,
1145 sources,
1146 dest,
1147 self.left_window_width(),
1148 self.term_size().height,
1149 Arc::clone(&self.fm_sender),
1150 )?;
1151 self.internal_settings.store_copy_progress(in_mem);
1152 }
1153 self.clear_flags_and_reset_view()
1154 }
1155
1156 pub fn copy_next_file_in_queue(&mut self) -> Result<()> {
1158 self.internal_settings
1159 .copy_next_file_in_queue(self.fm_sender.clone(), self.left_window_width())
1160 }
1161
1162 pub fn paste_input(&mut self, pasted: &str) -> Result<()> {
1164 if self.focus.is_file() && self.current_tab().display_mode.is_fuzzy() {
1165 let Some(fuzzy) = &mut self.fuzzy else {
1166 return Ok(());
1167 };
1168 fuzzy.input.insert_string(pasted);
1169 } else if !self.focus.is_file() && self.current_tab().menu_mode.is_input() {
1170 self.menu.input.insert_string(pasted);
1171 }
1172 Ok(())
1173 }
1174
1175 pub fn paste_pathes(&mut self, pasted: &str) -> Result<()> {
1177 for pasted in pasted.split_whitespace() {
1178 self.paste_path(pasted)?;
1179 }
1180 Ok(())
1181 }
1182
1183 fn paste_path(&mut self, pasted: &str) -> Result<()> {
1184 let pasted = Path::new(&pasted);
1186 if !pasted.is_absolute() {
1187 log_info!("pasted {pasted} isn't absolute.", pasted = pasted.display());
1188 return Ok(());
1189 }
1190 if !pasted.exists() {
1191 log_info!("pasted {pasted} doesn't exist.", pasted = pasted.display());
1192 return Ok(());
1193 }
1194 let dest = self.current_tab().root_path().to_path_buf();
1196 let Some(dest_filename) = build_dest_path(pasted, &dest) else {
1197 return Ok(());
1198 };
1199 if dest_filename == pasted {
1200 log_info!("pasted is same directory.");
1201 return Ok(());
1202 }
1203 if dest_filename.exists() {
1204 log_info!(
1205 "pasted {dest_filename} already exists",
1206 dest_filename = dest_filename.display()
1207 );
1208 return Ok(());
1209 }
1210 let sources = vec![pasted.to_path_buf()];
1211
1212 log_info!("pasted copy {sources:?} to {dest:?}");
1213 if self.is_simple_move(&CopyMove::Move, &sources, &dest) {
1214 self.simple_move(&sources, &dest)
1215 } else {
1216 self.complex_move(CopyMove::Copy, sources, &dest)
1217 }
1218 }
1219
1220 pub fn fuzzy_init(&mut self, kind: FuzzyKind) {
1222 self.fuzzy = Some(FuzzyFinder::new(kind).set_height(self.current_tab().window.height));
1223 }
1224
1225 fn fuzzy_drop(&mut self) {
1226 self.fuzzy = None;
1227 }
1228
1229 pub fn fuzzy_find_files(&mut self) -> Result<()> {
1231 let Some(fuzzy) = &self.fuzzy else {
1232 bail!("Fuzzy should be set");
1233 };
1234 let current_path = self.current_tab().current_directory_path().to_path_buf();
1235 fuzzy.find_files(current_path);
1236 Ok(())
1237 }
1238
1239 pub fn fuzzy_help(&mut self, help: String) -> Result<()> {
1241 let Some(fuzzy) = &self.fuzzy else {
1242 bail!("Fuzzy should be set");
1243 };
1244 fuzzy.find_action(help);
1245 Ok(())
1246 }
1247
1248 pub fn fuzzy_find_lines(&mut self) -> Result<()> {
1250 let Some(fuzzy) = &self.fuzzy else {
1251 bail!("Fuzzy should be set");
1252 };
1253 let Some(tokio_greper) = build_tokio_greper() else {
1254 log_info!("ripgrep & grep aren't in $PATH");
1255 return Ok(());
1256 };
1257 fuzzy.find_line(tokio_greper);
1258 Ok(())
1259 }
1260
1261 fn fuzzy_current_selection(&self) -> Option<std::string::String> {
1262 if let Some(fuzzy) = &self.fuzzy {
1263 fuzzy.pick()
1264 } else {
1265 None
1266 }
1267 }
1268
1269 pub fn fuzzy_select(&mut self) -> Result<()> {
1274 let Some(fuzzy) = &self.fuzzy else {
1275 bail!("Fuzzy should be set");
1276 };
1277 if let Some(pick) = fuzzy.pick() {
1278 match fuzzy.kind {
1279 FuzzyKind::File => self.tabs[self.index].cd_to_file(Path::new(&pick))?,
1280 FuzzyKind::Line => {
1281 self.tabs[self.index].cd_to_file(&parse_line_output(&pick)?.0)?
1282 }
1283 FuzzyKind::Action => self.fuzzy_send_event(&pick)?,
1284 }
1285 } else {
1286 log_info!("Fuzzy had nothing to pick from");
1287 };
1288 self.fuzzy_leave()
1289 }
1290
1291 pub fn fuzzy_toggle_flag_selected(&mut self) -> Result<()> {
1292 let Some(fuzzy) = &self.fuzzy else {
1293 bail!("Fuzzy should be set");
1294 };
1295 if let Some(pick) = fuzzy.pick() {
1296 if let FuzzyKind::File = fuzzy.kind {
1297 self.menu.flagged.toggle(Path::new(&pick));
1298 self.fuzzy_navigate(FuzzyDirection::Down)?;
1299 }
1300 } else {
1301 log_info!("Fuzzy had nothing to select from");
1302 };
1303 Ok(())
1304 }
1305
1306 pub fn fuzzy_open_file(&mut self) -> Result<()> {
1309 let Some(fuzzy) = &self.fuzzy else {
1310 bail!("Fuzzy should be set");
1311 };
1312 if let Some(pick) = fuzzy.pick() {
1313 match fuzzy.kind {
1314 FuzzyKind::File => {
1315 self.open_single_file(Path::new(&pick))?;
1316 }
1317 FuzzyKind::Line => {
1318 let (path, _) = parse_line_output(&pick)?;
1319 self.open_single_file(&path)?;
1320 }
1321 _ => (),
1322 }
1323 } else {
1324 log_info!("Fuzzy had nothing to select from");
1325 };
1326 Ok(())
1327 }
1328
1329 fn fuzzy_send_event(&self, pick: &str) -> Result<()> {
1333 if let Ok(key) = find_keybind_from_fuzzy(pick) {
1334 self.fm_sender.send(FmEvents::Term(Event::Key(key)))?;
1335 };
1336 Ok(())
1337 }
1338
1339 pub fn fuzzy_leave(&mut self) -> Result<()> {
1341 self.fuzzy_drop();
1342 self.current_tab_mut().set_display_mode(Display::Directory);
1343 self.refresh_view()
1344 }
1345
1346 pub fn fuzzy_backspace(&mut self) -> Result<()> {
1348 let Some(fuzzy) = &mut self.fuzzy else {
1349 bail!("Fuzzy should be set");
1350 };
1351 fuzzy.input.delete_char_left();
1352 fuzzy.update_input(false);
1353 Ok(())
1354 }
1355
1356 pub fn fuzzy_delete(&mut self) -> Result<()> {
1358 let Some(fuzzy) = &mut self.fuzzy else {
1359 bail!("Fuzzy should be set");
1360 };
1361 fuzzy.input.delete_chars_right();
1362 fuzzy.update_input(false);
1363 Ok(())
1364 }
1365
1366 pub fn fuzzy_left(&mut self) -> Result<()> {
1368 let Some(fuzzy) = &mut self.fuzzy else {
1369 bail!("Fuzzy should be set");
1370 };
1371 fuzzy.input.cursor_left();
1372 Ok(())
1373 }
1374
1375 pub fn fuzzy_right(&mut self) -> Result<()> {
1377 let Some(fuzzy) = &mut self.fuzzy else {
1378 bail!("Fuzzy should be set");
1379 };
1380 fuzzy.input.cursor_right();
1381 Ok(())
1382 }
1383
1384 pub fn fuzzy_start(&mut self) -> Result<()> {
1386 self.fuzzy_navigate(FuzzyDirection::Start)
1387 }
1388
1389 pub fn fuzzy_end(&mut self) -> Result<()> {
1391 self.fuzzy_navigate(FuzzyDirection::End)
1392 }
1393
1394 pub fn fuzzy_navigate(&mut self, direction: FuzzyDirection) -> Result<()> {
1396 let Some(fuzzy) = &mut self.fuzzy else {
1397 bail!("Fuzzy should be set");
1398 };
1399 fuzzy.navigate(direction);
1400 if fuzzy.should_preview() {
1401 self.update_second_pane_for_preview()?;
1402 }
1403 Ok(())
1404 }
1405
1406 pub fn fuzzy_tick(&mut self) {
1408 if let Some(fuzzy) = &mut self.fuzzy {
1409 fuzzy.tick(false);
1410 }
1411 }
1412
1413 pub fn fuzzy_resize(&mut self, height: usize) {
1415 if let Some(fuzzy) = &mut self.fuzzy {
1416 fuzzy.resize(height)
1417 }
1418 }
1419
1420 pub fn input_history_next(&mut self) -> Result<()> {
1422 if self.focus.is_file() {
1423 return Ok(());
1424 }
1425 self.menu.input_history_next(&mut self.tabs[self.index])?;
1426 if let Menu::InputCompleted(input_completed) = self.current_tab().menu_mode {
1427 self.complete(input_completed)?;
1428 }
1429 Ok(())
1430 }
1431
1432 pub fn input_history_prev(&mut self) -> Result<()> {
1434 if self.focus.is_file() {
1435 return Ok(());
1436 }
1437 self.menu.input_history_prev(&mut self.tabs[self.index])?;
1438 if let Menu::InputCompleted(input_completed) = self.current_tab().menu_mode {
1439 self.complete(input_completed)?;
1440 }
1441 Ok(())
1442 }
1443
1444 pub fn input_and_complete(&mut self, input_completed: InputCompleted, c: char) -> Result<()> {
1446 self.menu.input.insert(c);
1447 self.complete(input_completed)
1448 }
1449
1450 pub fn complete(&mut self, input_completed: InputCompleted) -> Result<()> {
1451 match input_completed {
1452 InputCompleted::Search => self.complete_search(),
1453 _ => self.complete_non_search(),
1454 }
1455 }
1456
1457 pub fn complete_tab(&mut self, input_completed: InputCompleted) -> Result<()> {
1459 self.menu.completion_tab();
1460 self.complete_cd_move()?;
1461 if matches!(input_completed, InputCompleted::Search) {
1462 self.update_search()?;
1463 self.search()?;
1464 } else {
1465 self.menu.input_complete(&mut self.tabs[self.index])?
1466 }
1467 Ok(())
1468 }
1469
1470 fn complete_search(&mut self) -> Result<()> {
1471 self.update_search()?;
1472 self.search()?;
1473 self.menu.input_complete(&mut self.tabs[self.index])
1474 }
1475
1476 fn update_search(&mut self) -> Result<()> {
1477 if let Ok(search) = Search::new(&self.menu.input.string()) {
1478 self.current_tab_mut().search = search;
1479 };
1480 Ok(())
1481 }
1482
1483 pub fn follow_search(&mut self) -> Result<()> {
1486 let Some(proposition) = self.menu.completion.selected() else {
1487 return Ok(());
1488 };
1489 let current_path = match self.current_tab().display_mode {
1490 Display::Directory => self.current_tab().current_directory_path(),
1491 Display::Tree => self.current_tab().tree.root_path(),
1492 _ => {
1493 return Ok(());
1494 }
1495 };
1496 let mut full_path = current_path.to_path_buf();
1497 full_path.push(proposition);
1498 self.current_tab_mut().select_by_path(Arc::from(full_path));
1499 Ok(())
1500 }
1501
1502 fn complete_non_search(&mut self) -> Result<()> {
1503 self.complete_cd_move()?;
1504 self.menu.input_complete(&mut self.tabs[self.index])
1505 }
1506
1507 pub fn complete_cd_move(&mut self) -> Result<()> {
1509 if let Menu::InputCompleted(InputCompleted::Cd) = self.current_tab().menu_mode {
1510 let input = self.menu.input.string();
1511 if self.tabs[self.index].try_cd_to_file(input)? {
1512 self.update_second_pane_for_preview()?;
1513 }
1514 }
1515 Ok(())
1516 }
1517
1518 pub fn input_regex(&mut self, char: char) -> Result<()> {
1520 self.menu.input.insert(char);
1521 self.flag_from_regex()?;
1522 Ok(())
1523 }
1524
1525 pub fn flag_from_regex(&mut self) -> Result<()> {
1528 let input = self.menu.input.string();
1529 if input.is_empty() {
1530 return Ok(());
1531 }
1532 let paths = match self.current_tab().display_mode {
1533 Display::Directory => self.tabs[self.index].directory.paths(),
1534 Display::Tree => self.tabs[self.index].tree.paths(),
1535 _ => return Ok(()),
1536 };
1537 regex_flagger(&input, &paths, &mut self.menu.flagged)?;
1538 if !self.menu.flagged.is_empty() {
1539 self.tabs[self.index]
1540 .go_to_file(self.menu.flagged.selected().context("no selected file")?);
1541 }
1542 Ok(())
1543 }
1544
1545 pub fn open_selected_file(&mut self) -> Result<()> {
1547 let path = self
1548 .current_tab()
1549 .selected_path()
1550 .context("No selected path")?;
1551 self.open_single_file(&path)
1552 }
1553
1554 pub fn open_single_file(&mut self, path: &Path) -> Result<()> {
1556 match self.internal_settings.opener.kind(path) {
1557 Some(Kind::Internal(Internal::NotSupported)) => self.mount_iso_drive(),
1558 Some(_) => self.internal_settings.open_single_file(path),
1559 None => Ok(()),
1560 }
1561 }
1562
1563 pub fn open_flagged_files(&mut self) -> Result<()> {
1565 self.internal_settings
1566 .open_flagged_files(&self.menu.flagged)
1567 }
1568
1569 fn ensure_iso_device_is_some(&mut self) -> Result<()> {
1570 if self.menu.iso_device.is_none() {
1571 let path = path_to_string(
1572 &self
1573 .current_tab()
1574 .selected_path()
1575 .context("No selected path")?,
1576 );
1577 self.menu.iso_device = Some(IsoDevice::from_path(path));
1578 }
1579 Ok(())
1580 }
1581
1582 fn mount_iso_drive(&mut self) -> Result<()> {
1586 if !self.menu.password_holder.has_sudo() {
1587 self.ask_password(Some(MountAction::MOUNT), PasswordUsage::ISO)?;
1588 } else {
1589 self.ensure_iso_device_is_some()?;
1590 let Some(ref mut iso_device) = self.menu.iso_device else {
1591 return Ok(());
1592 };
1593 if iso_device.mount(¤t_username()?, &mut self.menu.password_holder)? {
1594 log_info!("iso mounter mounted {iso_device:?}");
1595 log_line!("iso : {iso_device}");
1596 let path = iso_device
1597 .mountpoints
1598 .clone()
1599 .expect("mountpoint should be set");
1600 self.current_tab_mut().cd(Path::new(&path))?;
1601 };
1602 self.menu.iso_device = None;
1603 };
1604
1605 Ok(())
1606 }
1607
1608 pub fn umount_iso_drive(&mut self) -> Result<()> {
1611 if let Some(ref mut iso_device) = self.menu.iso_device {
1612 if !self.menu.password_holder.has_sudo() {
1613 self.ask_password(Some(MountAction::UMOUNT), PasswordUsage::ISO)?;
1614 } else {
1615 iso_device.umount(¤t_username()?, &mut self.menu.password_holder)?;
1616 };
1617 }
1618 self.menu.iso_device = None;
1619 Ok(())
1620 }
1621
1622 pub fn mount_encrypted_drive(&mut self) -> Result<()> {
1627 if !self.menu.password_holder.has_sudo() {
1628 self.ask_password(
1629 Some(MountAction::MOUNT),
1630 PasswordUsage::CRYPTSETUP(PasswordKind::SUDO),
1631 )
1632 } else if !self.menu.password_holder.has_cryptsetup() {
1633 self.ask_password(
1634 Some(MountAction::MOUNT),
1635 PasswordUsage::CRYPTSETUP(PasswordKind::CRYPTSETUP),
1636 )
1637 } else {
1638 let Some(Mountable::Encrypted(device)) = &self.menu.mount.selected() else {
1639 return Ok(());
1640 };
1641 if let Ok(true) = device.mount(¤t_username()?, &mut self.menu.password_holder) {
1642 self.go_to_encrypted_drive(device.uuid.clone())?;
1643 }
1644 Ok(())
1645 }
1646 }
1647
1648 pub fn umount_encrypted_drive(&mut self) -> Result<()> {
1651 if !self.menu.password_holder.has_sudo() {
1652 self.ask_password(
1653 Some(MountAction::UMOUNT),
1654 PasswordUsage::CRYPTSETUP(PasswordKind::SUDO),
1655 )
1656 } else {
1657 let Some(Mountable::Encrypted(device)) = &self.menu.mount.selected() else {
1658 log_info!("Cannot find Encrypted device");
1659 return Ok(());
1660 };
1661 let success =
1662 device.umount_close_crypto(¤t_username()?, &mut self.menu.password_holder)?;
1663 log_info!("umount_encrypted_drive: {success}");
1664 Ok(())
1665 }
1666 }
1667 pub fn mount_normal_device(&mut self) -> Result<()> {
1671 let Some(device) = self.menu.mount.selected() else {
1672 return Ok(());
1673 };
1674 if device.is_mounted() {
1675 return Ok(());
1676 }
1677 if device.is_crypto() {
1678 return self.mount_encrypted_drive();
1679 }
1680 let Ok(success) = self.menu.mount.mount_selected_no_password() else {
1681 return Ok(());
1682 };
1683 if success {
1684 self.menu.mount.update(self.internal_settings.disks())?;
1685 self.go_to_normal_drive()?;
1686 return Ok(());
1687 }
1688 if !self.menu.password_holder.has_sudo() {
1689 self.ask_password(Some(MountAction::MOUNT), PasswordUsage::DEVICE)
1690 } else {
1691 if let Ok(true) = self
1692 .menu
1693 .mount
1694 .mount_selected(&mut self.menu.password_holder)
1695 {
1696 self.go_to_normal_drive()?;
1697 }
1698 Ok(())
1699 }
1700 }
1701
1702 pub fn go_to_normal_drive(&mut self) -> Result<()> {
1704 let Some(path) = self.menu.mount.selected_mount_point() else {
1705 return Ok(());
1706 };
1707 let tab = self.current_tab_mut();
1708 tab.cd(&path)?;
1709 tab.refresh_view()
1710 }
1711
1712 pub fn go_to_mount_per_index(&mut self, c: char) -> Result<()> {
1714 let Some(index) = c.to_digit(10) else {
1715 return Ok(());
1716 };
1717 self.menu.mount.set_index(index.saturating_sub(1) as _);
1718 self.go_to_normal_drive()
1719 }
1720
1721 fn go_to_encrypted_drive(&mut self, uuid: Option<String>) -> Result<()> {
1722 self.menu.mount.update(&self.internal_settings.disks)?;
1723 let Some(mountpoint) = self.menu.mount.find_encrypted_by_uuid(uuid) else {
1724 return Ok(());
1725 };
1726 log_info!("mountpoint {mountpoint}");
1727 let tab = self.current_tab_mut();
1728 tab.cd(Path::new(&mountpoint))?;
1729 tab.refresh_view()
1730 }
1731
1732 pub fn umount_normal_device(&mut self) -> Result<()> {
1735 let Some(device) = self.menu.mount.selected() else {
1736 return Ok(());
1737 };
1738 if !device.is_mounted() {
1739 return Ok(());
1740 }
1741 if device.is_crypto() {
1742 return self.umount_encrypted_drive();
1743 }
1744 let Ok(success) = self.menu.mount.umount_selected_no_password() else {
1745 return Ok(());
1746 };
1747 if success {
1748 self.menu.mount.update(self.internal_settings.disks())?;
1749 return Ok(());
1750 }
1751 if !self.menu.password_holder.has_sudo() {
1752 self.ask_password(Some(MountAction::UMOUNT), PasswordUsage::DEVICE)
1753 } else {
1754 self.menu
1755 .mount
1756 .umount_selected(&mut self.menu.password_holder)
1757 }
1758 }
1759
1760 pub fn eject_removable_device(&mut self) -> Result<()> {
1762 if self.menu.mount.is_empty() {
1763 return Ok(());
1764 }
1765 let success = self.menu.mount.eject_removable_device()?;
1766 if success {
1767 self.menu.mount.update(self.internal_settings.disks())?;
1768 }
1769 Ok(())
1770 }
1771
1772 pub fn execute_shell_command_from_input(&mut self) -> Result<bool> {
1775 let shell_command = self.menu.input.string();
1776 self.execute_shell_command(shell_command, None, true)
1777 }
1778
1779 pub fn execute_shell_command(
1781 &mut self,
1782 shell_command: String,
1783 files: Option<Vec<String>>,
1784 capture_output: bool,
1785 ) -> Result<bool> {
1786 let command = append_files_to_shell_command(shell_command, files);
1787 let Ok(args) = shell_command_parser(&command, self) else {
1788 self.set_menu_mode(self.index, Menu::Nothing)?;
1789 return Ok(true);
1790 };
1791 self.execute_parsed_command(args, command, capture_output)
1792 }
1793
1794 fn execute_parsed_command(
1795 &mut self,
1796 mut args: Vec<String>,
1797 shell_command: String,
1798 capture_output: bool,
1799 ) -> Result<bool> {
1800 let executable = args.remove(0);
1801 if is_sudo_command(&executable) {
1802 self.enter_sudo_mode(shell_command)?;
1803 return Ok(false);
1804 }
1805 let params: Vec<&str> = args.iter().map(|s| s.as_str()).collect();
1806 if executable == *SAME_WINDOW_TOKEN {
1807 self.internal_settings.open_in_window(¶ms)?;
1808 return Ok(true);
1809 }
1810 if !is_in_path(&executable) {
1811 log_line!("{executable} isn't in path.");
1812 return Ok(true);
1813 }
1814 if capture_output {
1815 match execute_and_capture_output(executable, ¶ms) {
1816 Ok(output) => self.preview_command_output(output, shell_command),
1817 Err(e) => {
1818 log_info!("Error {e:?}");
1819 log_line!("Command {shell_command} disn't finish properly");
1820 }
1821 }
1822 Ok(true)
1823 } else {
1824 let _ = execute_without_output(executable, ¶ms);
1825 Ok(true)
1826 }
1827 }
1828
1829 fn enter_sudo_mode(&mut self, shell_command: String) -> Result<()> {
1830 self.menu.sudo_command = Some(shell_command);
1831 self.ask_password(None, PasswordUsage::SUDOCOMMAND)?;
1832 Ok(())
1833 }
1834
1835 fn ask_password(
1837 &mut self,
1838 encrypted_action: Option<MountAction>,
1839 password_dest: PasswordUsage,
1840 ) -> Result<()> {
1841 log_info!("ask_password");
1842 self.set_menu_mode(
1843 self.index,
1844 Menu::InputSimple(InputSimple::Password(encrypted_action, password_dest)),
1845 )
1846 }
1847
1848 pub fn execute_password_command(
1851 &mut self,
1852 action: Option<MountAction>,
1853 dest: PasswordUsage,
1854 ) -> Result<()> {
1855 let password = self.menu.input.string();
1856 self.menu.input.reset();
1857 if matches!(dest, PasswordUsage::CRYPTSETUP(PasswordKind::CRYPTSETUP)) {
1858 self.menu.password_holder.set_cryptsetup(password)
1859 } else {
1860 self.menu.password_holder.set_sudo(password)
1861 };
1862 let sudo_command = self.menu.sudo_command.to_owned();
1863 self.reset_menu_mode()?;
1864 self.dispatch_password(action, dest, sudo_command)?;
1865 Ok(())
1866 }
1867
1868 pub fn marks_new(&mut self, c: char) -> Result<()> {
1870 let path = self.current_tab_mut().directory.path.clone();
1871 self.menu.marks.new_mark(c, &path)?;
1872 self.current_tab_mut().refresh_view()?;
1873 self.reset_menu_mode()?;
1874 self.refresh_status()
1875 }
1876
1877 pub fn marks_jump_char(&mut self, c: char) -> Result<()> {
1880 if let Some(path) = self.menu.marks.get(c) {
1881 self.current_tab_mut().cd(&path)?;
1882 }
1883 self.current_tab_mut().refresh_view()?;
1884 self.reset_menu_mode()?;
1885 self.refresh_status()
1886 }
1887
1888 pub fn temp_marks_new(&mut self, c: char) -> Result<()> {
1890 let Some(index) = c.to_digit(10) else {
1891 return Ok(());
1892 };
1893 let path = self.current_tab_mut().directory.path.to_path_buf();
1894 self.menu.temp_marks.set_mark(index as _, path);
1895 self.current_tab_mut().refresh_view()?;
1896 self.reset_menu_mode()?;
1897 self.refresh_status()
1898 }
1899
1900 pub fn temp_marks_erase(&mut self) -> Result<()> {
1902 self.menu.temp_marks.erase_current_mark();
1903 Ok(())
1904 }
1905
1906 pub fn temp_marks_jump_char(&mut self, c: char) -> Result<()> {
1909 let Some(index) = c.to_digit(10) else {
1910 return Ok(());
1911 };
1912 if let Some(path) = self.menu.temp_marks.get_mark(index as _) {
1913 self.tabs[self.index].cd(path)?;
1914 }
1915 self.current_tab_mut().refresh_view()?;
1916 self.reset_menu_mode()?;
1917 self.refresh_status()
1918 }
1919
1920 pub fn confirm_delete_files(&mut self) -> Result<()> {
1923 if self.menu.flagged.contains(self.current_tab().root_path()) {
1924 log_info!("Can't delete current root path");
1925 log_line!("Can't delete current root path");
1926 return Ok(());
1927 }
1928 self.menu.delete_flagged_files()?;
1929 self.reset_menu_mode()?;
1930 self.clear_flags_and_reset_view()?;
1931 self.refresh_status()
1932 }
1933
1934 pub fn confirm_trash_empty(&mut self) -> Result<()> {
1936 self.menu.trash.empty_trash()?;
1937 self.reset_menu_mode()?;
1938 self.clear_flags_and_reset_view()?;
1939 Ok(())
1940 }
1941
1942 pub fn bulk_ask_filenames(&mut self) -> Result<()> {
1944 let flagged = self.flagged_in_current_dir();
1945 let current_path = self.current_tab_path_str();
1946 self.menu.bulk.ask_filenames(flagged, ¤t_path)?;
1947 if let Some(temp_file) = self.menu.bulk.temp_file() {
1948 self.open_single_file(&temp_file)?;
1949 if self.internal_settings.opener.extension_use_term("txt") {
1950 self.fm_sender.send(FmEvents::BulkExecute)?;
1951 } else {
1952 self.menu.bulk.watch_in_thread(self.fm_sender.clone())?;
1953 }
1954 }
1955 Ok(())
1956 }
1957
1958 pub fn bulk_execute(&mut self) -> Result<()> {
1960 self.menu.bulk.get_new_names()?;
1961 self.set_menu_mode(
1962 self.index,
1963 Menu::NeedConfirmation(NeedConfirmation::BulkAction),
1964 )?;
1965 Ok(())
1966 }
1967
1968 pub fn confirm_bulk_action(&mut self) -> Result<()> {
1970 if let (Some(renamed), Some(created)) = self.menu.bulk.execute()? {
1971 for (old_path, new_path) in &renamed {
1972 log_info!("old_path {old_path:?} -> new_path {new_path:?}");
1973 self.rename_marks(old_path, new_path)?;
1974 }
1975 self.menu
1976 .flagged
1977 .update(renamed.into_iter().map(|(_, p)| p).collect());
1978 self.menu.flagged.extend(created);
1979 } else {
1980 self.menu.flagged.clear();
1981 };
1982 self.reset_menu_mode()?;
1983 self.reset_tabs_view()?;
1984 Ok(())
1985 }
1986
1987 fn run_sudo_command(&mut self, sudo_command: Option<String>) -> Result<()> {
1988 let Some(sudo_command) = sudo_command else {
1989 log_info!("No sudo_command received from args.");
1990 return self.menu.clear_sudo_attributes();
1991 };
1992 self.set_menu_mode(self.index, Menu::Nothing)?;
1993 reset_sudo_faillock()?;
1994 let Some(command) = sudo_command.strip_prefix("sudo ") else {
1995 log_info!("run_sudo_command cannot run {sudo_command}. It doesn't start with 'sudo '");
1996 return self.menu.clear_sudo_attributes();
1997 };
1998 let args = shell_command_parser(command, self)?;
1999 if args.is_empty() {
2000 return self.menu.clear_sudo_attributes();
2001 }
2002 let Some(password) = self.menu.password_holder.sudo() else {
2003 log_info!("run_sudo_command password isn't set");
2004 return self.menu.clear_sudo_attributes();
2005 };
2006 let directory_of_selected = self.current_tab().directory_of_selected()?;
2007 let (success, stdout, stderr) =
2008 execute_sudo_command_with_password(&args, &password, directory_of_selected)?;
2009 log_info!("sudo command execution. success: {success}");
2010 self.menu.clear_sudo_attributes()?;
2011 if !success {
2012 log_line!("sudo command failed: {stderr}");
2013 }
2014 self.preview_command_output(stdout, sudo_command.to_owned());
2015 Ok(())
2016 }
2017
2018 #[rustfmt::skip]
2021 pub fn dispatch_password(
2022 &mut self,
2023 action: Option<MountAction>,
2024 dest: PasswordUsage,
2025 sudo_command: Option<String>,
2026 ) -> Result<()> {
2027 match (dest, action) {
2028 (PasswordUsage::ISO, Some(MountAction::MOUNT)) => self.mount_iso_drive(),
2029 (PasswordUsage::ISO, Some(MountAction::UMOUNT)) => self.umount_iso_drive(),
2030 (PasswordUsage::CRYPTSETUP(_), Some(MountAction::MOUNT)) => self.mount_encrypted_drive(),
2031 (PasswordUsage::CRYPTSETUP(_), Some(MountAction::UMOUNT)) => self.umount_encrypted_drive(),
2032 (PasswordUsage::DEVICE, Some(MountAction::MOUNT)) => self.mount_normal_device(),
2033 (PasswordUsage::DEVICE, Some(MountAction::UMOUNT)) => self.umount_normal_device(),
2034 (PasswordUsage::SUDOCOMMAND, _) => self.run_sudo_command(sudo_command),
2035 (_, _) => Ok(()),
2036 }
2037 }
2038
2039 pub fn preview_command_output(&mut self, output: String, command: String) {
2041 log_info!("preview_command_output for {command}:\n{output}");
2042 if output.is_empty() {
2043 return;
2044 }
2045 let _ = self.reset_menu_mode();
2046 self.current_tab_mut().set_display_mode(Display::Preview);
2047 let preview = PreviewBuilder::cli_info(&output, command);
2048 if let Preview::Text(text) = &preview {
2049 log_info!("preview is Text with: {text:?}");
2050 } else {
2051 log_info!("preview is empty ? {empty}", empty = preview.is_empty());
2052 }
2053 self.current_tab_mut().window.reset(preview.len());
2054 self.current_tab_mut().preview = preview;
2055 }
2056
2057 pub fn update_nvim_listen_address(&mut self) {
2059 self.internal_settings.update_nvim_listen_address()
2060 }
2061
2062 pub fn confirm(&mut self, c: char, confirmed_action: NeedConfirmation) -> Result<()> {
2065 if c == 'y' {
2066 if let Ok(must_leave) = self.match_confirmed_mode(confirmed_action) {
2067 if must_leave {
2068 return Ok(());
2069 }
2070 }
2071 }
2072 self.reset_menu_mode()?;
2073 self.current_tab_mut().refresh_view()?;
2074
2075 Ok(())
2076 }
2077
2078 fn match_confirmed_mode(&mut self, confirmed_action: NeedConfirmation) -> Result<bool> {
2080 match confirmed_action {
2081 NeedConfirmation::Delete => self.confirm_delete_files(),
2082 NeedConfirmation::Move => self.cut_or_copy_flagged_files(CopyMove::Move),
2083 NeedConfirmation::Copy => self.cut_or_copy_flagged_files(CopyMove::Copy),
2084 NeedConfirmation::EmptyTrash => self.confirm_trash_empty(),
2085 NeedConfirmation::BulkAction => self.confirm_bulk_action(),
2086 NeedConfirmation::DeleteCloud => {
2087 self.cloud_confirm_delete()?;
2088 return Ok(true);
2089 }
2090 }?;
2091 Ok(false)
2092 }
2093
2094 pub fn header_action(&mut self, col: u16, binds: &Bindings) -> Result<()> {
2096 if self.current_tab().display_mode.is_preview() {
2097 return Ok(());
2098 }
2099 Header::new(self, self.current_tab())?
2100 .action(col, !self.focus.is_left())
2101 .matcher(self, binds)
2102 }
2103
2104 pub fn footer_action(&mut self, col: u16, binds: &Bindings) -> Result<()> {
2106 log_info!("footer clicked col {col}");
2107 let is_right = self.index == 1;
2108 let action = match self.current_tab().display_mode {
2109 Display::Preview => return Ok(()),
2110 Display::Tree | Display::Directory => {
2111 let footer = Footer::new(self, self.current_tab())?;
2112 footer.action(col, is_right).to_owned()
2113 }
2114 Display::Fuzzy => return Ok(()),
2115 };
2116 log_info!("action: {action}");
2117 action.matcher(self, binds)
2118 }
2119
2120 pub fn chmod(&mut self) -> Result<()> {
2126 if self.menu.input.is_empty() || self.menu.flagged.is_empty() {
2127 return Ok(());
2128 }
2129 let input_permission = &self.menu.input.string();
2130 Permissions::set_permissions_of_flagged(input_permission, &self.menu.flagged)?;
2131 self.reset_tabs_view()
2132 }
2133
2134 pub fn set_mode_chmod(&mut self) -> Result<()> {
2136 if self.current_tab_mut().directory.is_empty() {
2137 return Ok(());
2138 }
2139 if self.menu.flagged.is_empty() {
2140 self.toggle_flag_for_selected();
2141 }
2142 self.set_menu_mode(self.index, Menu::InputSimple(InputSimple::Chmod))?;
2143 self.menu.replace_input_by_permissions();
2144 Ok(())
2145 }
2146
2147 pub fn run_custom_command(&mut self, string: &str) -> Result<()> {
2149 self.execute_shell_command(string.to_owned(), None, true)?;
2150 Ok(())
2151 }
2152
2153 pub fn compress(&mut self) -> Result<()> {
2159 let here = &self.current_tab().directory.path;
2160 set_current_dir(here)?;
2161 let files_with_relative_paths = self.flagged_or_selected_relative_to(here);
2162 if files_with_relative_paths.is_empty() {
2163 return Ok(());
2164 }
2165 match self
2166 .menu
2167 .compression
2168 .compress(files_with_relative_paths, here)
2169 {
2170 Ok(()) => (),
2171 Err(error) => log_info!("Error compressing files. Error: {error}"),
2172 }
2173 Ok(())
2174 }
2175
2176 pub fn sort_by_char(&mut self, c: char) -> Result<()> {
2178 self.current_tab_mut().sort(c)?;
2179 self.menu.reset();
2180 self.set_height_for_menu_mode(self.index, Menu::Nothing)?;
2181 self.tabs[self.index].menu_mode = Menu::Nothing;
2182 let len = self.menu.len(Menu::Nothing);
2183 let height = self.second_window_height()?;
2184 self.menu.window = ContentWindow::new(len, height);
2185 self.focus = self.focus.to_parent();
2186 Ok(())
2187 }
2188
2189 pub fn canvas_width(&self) -> Result<u16> {
2191 let full_width = self.term_width();
2192 if self.session.dual() && full_width >= MIN_WIDTH_FOR_DUAL_PANE {
2193 Ok(full_width / 2)
2194 } else {
2195 Ok(full_width)
2196 }
2197 }
2198
2199 fn search(&mut self) -> Result<()> {
2205 let Some(search) = self.build_search_from_input() else {
2206 self.current_tab_mut().search = Search::empty();
2207 return Ok(());
2208 };
2209 self.search_and_update(search)
2210 }
2211
2212 fn build_search_from_input(&self) -> Option<Search> {
2213 let searched = &self.menu.input.string();
2214 if searched.is_empty() {
2215 return None;
2216 }
2217 Search::new(searched).ok()
2218 }
2219
2220 fn search_and_update(&mut self, mut search: Search) -> Result<()> {
2221 search.execute_search(self.current_tab_mut())?;
2222 self.current_tab_mut().search = search;
2223 self.update_second_pane_for_preview()
2224 }
2225
2226 fn search_again(&mut self) -> Result<()> {
2227 let search = self.current_tab().search.clone_with_regex();
2228 self.search_and_update(search)
2229 }
2230
2231 pub fn filter(&mut self) -> Result<()> {
2234 let filter = FilterKind::from_input(&self.menu.input.string());
2235 self.current_tab_mut().set_filter(filter)?;
2236 self.search_again()
2237 }
2238
2239 pub fn input_filter(&mut self, c: char) -> Result<()> {
2241 self.menu.input_insert(c)?;
2242 self.filter()
2243 }
2244
2245 pub fn open_picker(&mut self) -> Result<()> {
2247 if self.current_tab().menu_mode.is_picker() || self.menu.input_history.filtered_is_empty() {
2248 return Ok(());
2249 }
2250 let menu = self.current_tab().menu_mode;
2251 let content = self.menu.input_history.filtered_as_list();
2252 self.menu.picker.set(
2253 Some(PickerCaller::Menu(menu)),
2254 menu.name_for_picker(),
2255 content,
2256 );
2257
2258 self.set_menu_mode(self.index, Menu::Navigate(Navigate::Picker))
2259 }
2260
2261 pub fn cloud_load_config(&mut self) -> Result<()> {
2263 let Some(picked) = self.menu.picker.selected() else {
2264 log_info!("nothing selected");
2265 return Ok(());
2266 };
2267 let Ok(cloud) = google_drive(picked) else {
2268 log_line!("Invalid config file {picked}");
2269 return Ok(());
2270 };
2271 self.menu.cloud = cloud;
2272 self.set_menu_mode(self.index, Menu::Navigate(Navigate::Cloud))
2273 }
2274
2275 pub fn cloud_open(&mut self) -> Result<()> {
2279 if self.menu.cloud.is_set() {
2280 self.set_menu_mode(self.index, Menu::Navigate(Navigate::Cloud))
2281 } else {
2282 self.cloud_picker()
2283 }
2284 }
2285
2286 fn cloud_picker(&mut self) -> Result<()> {
2287 let content = get_cloud_token_names()?;
2288 self.menu.picker.set(
2289 Some(PickerCaller::Cloud),
2290 Some("Pick a cloud provider".to_owned()),
2291 content,
2292 );
2293 self.set_menu_mode(self.index, Menu::Navigate(Navigate::Picker))
2294 }
2295
2296 pub fn cloud_disconnect(&mut self) -> Result<()> {
2298 self.menu.cloud.disconnect();
2299 self.cloud_open()
2300 }
2301
2302 pub fn cloud_enter_delete_mode(&mut self) -> Result<()> {
2305 self.set_menu_mode(
2306 self.index,
2307 Menu::NeedConfirmation(NeedConfirmation::DeleteCloud),
2308 )
2309 }
2310
2311 pub fn cloud_confirm_delete(&mut self) -> Result<()> {
2313 self.menu.cloud.delete()?;
2314 self.set_menu_mode(self.index, Menu::Navigate(Navigate::Cloud))?;
2315 self.menu.cloud.refresh_current()?;
2316 self.menu.window.scroll_to(self.menu.cloud.index);
2317 Ok(())
2318 }
2319
2320 pub fn cloud_update_metadata(&mut self) -> Result<()> {
2322 self.menu.cloud.update_metadata()
2323 }
2324
2325 pub fn cloud_enter_newdir_mode(&mut self) -> Result<()> {
2327 self.set_menu_mode(self.index, Menu::InputSimple(InputSimple::CloudNewdir))?;
2328 self.refresh_view()
2329 }
2330
2331 pub fn cloud_create_newdir(&mut self, dirname: String) -> Result<()> {
2333 self.menu.cloud.create_newdir(dirname)?;
2334 self.menu.cloud.refresh_current()
2335 }
2336
2337 fn get_normal_selected_file(&self) -> Option<FileInfo> {
2338 let local_file = self.tabs[self.index].current_file().ok()?;
2339 match local_file.file_kind {
2340 FileKind::NormalFile => Some(local_file),
2341 _ => None,
2342 }
2343 }
2344
2345 pub fn cloud_upload_selected_file(&mut self) -> Result<()> {
2347 let Some(local_file) = self.get_normal_selected_file() else {
2348 log_line!("Can only upload normal files.");
2349 return Ok(());
2350 };
2351 self.menu.cloud.upload(&local_file)?;
2352 self.menu.cloud.refresh_current()
2353 }
2354
2355 pub fn cloud_enter_file_or_dir(&mut self) -> Result<()> {
2357 if let Some(entry) = self.menu.cloud.selected() {
2358 match entry.metadata().mode() {
2359 EntryMode::Unknown => (),
2360 EntryMode::FILE => self
2361 .menu
2362 .cloud
2363 .download(self.current_tab().directory_of_selected()?)?,
2364 EntryMode::DIR => {
2365 self.menu.cloud.enter_selected()?;
2366 self.cloud_set_content_window_len()?;
2367 }
2368 };
2369 };
2370 Ok(())
2371 }
2372
2373 fn cloud_set_content_window_len(&mut self) -> Result<()> {
2374 let len = self.menu.cloud.content.len();
2375 let height = self.second_window_height()?;
2376 self.menu.window = ContentWindow::new(len, height);
2377 Ok(())
2378 }
2379
2380 pub fn cloud_move_to_parent(&mut self) -> Result<()> {
2383 self.menu.cloud.move_to_parent()?;
2384 self.cloud_set_content_window_len()?;
2385 Ok(())
2386 }
2387
2388 pub fn toggle_flag_visual(&mut self) {
2389 if self.current_tab().visual {
2390 let Some(path) = self.current_tab().selected_path() else {
2391 return;
2392 };
2393 self.menu.flagged.toggle(&path)
2394 }
2395 }
2396
2397 pub fn parse_ipc(&mut self, msg: String) -> Result<()> {
2406 let mut split = msg.split_whitespace();
2407 match split.next() {
2408 Some("GO") => {
2409 log_info!("Received IPC command GO");
2410 if let Some(dest) = split.next() {
2411 log_info!("Received IPC command GO to {dest}");
2412 let dest = tilde(dest);
2413 self.current_tab_mut().cd_to_file(dest.as_ref())?
2414 }
2415 }
2416 Some("KEY") => {
2417 log_info!("Received IPC command KEY");
2418 if let Some(keyname) = split.next() {
2419 if let Some(key) = from_keyname(keyname) {
2420 self.fm_sender.send(FmEvents::Term(Event::Key(key)))?;
2421 log_info!("Sent key event: {key:?}");
2422 }
2423 }
2424 }
2425 Some("ACTION") => {
2426 log_info!("Received IPC command ACTION");
2427 if let Some(action_str) = split.next() {
2428 if let Ok(action) = ActionMap::from_str(action_str) {
2429 log_info!("Sent action event: {action:?}");
2430 self.fm_sender.send(FmEvents::Action(action))?;
2431 }
2432 }
2433 }
2434 Some(_unknown) => log_info!("Received unknown IPC command: {msg}"),
2435 None => (),
2436 };
2437 Ok(())
2438 }
2439
2440 pub fn rename_marks<P>(&mut self, old_path: &Path, new_path: P) -> Result<()>
2443 where
2444 P: AsRef<Path>,
2445 {
2446 log_info!("{old_path:?} -> {new_path:?}", new_path = new_path.as_ref());
2447 self.menu.temp_marks.move_path(old_path, new_path.as_ref());
2448 self.menu.marks.move_path(old_path, new_path.as_ref())
2449 }
2450}
2451
2452fn find_keybind_from_fuzzy(line: &str) -> Result<KeyEvent> {
2453 let Some(keybind) = line.split(':').next() else {
2454 bail!("No keybind found");
2455 };
2456 let Some(key) = from_keyname(keybind.trim()) else {
2457 bail!("{keybind} isn't a valid Key name.");
2458 };
2459 Ok(key)
2460}