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