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.wide_enough_for_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 }
1114 Err(e) => {
1115 log_info!("Error: {e:?}");
1116 log_line!("Error: {e:?}")
1117 }
1118 }
1119 }
1120 self.clear_flags_and_reset_view()
1121 }
1122
1123 fn complex_move(
1124 &mut self,
1125 cut_or_copy: CopyMove,
1126 sources: Vec<PathBuf>,
1127 dest: &Path,
1128 ) -> Result<()> {
1129 let mut must_act_now = true;
1130 if matches!(cut_or_copy, CopyMove::Copy) {
1131 if !self.internal_settings.copy_file_queue.is_empty() {
1132 log_info!("cut_or_copy_flagged_files: act later");
1133 must_act_now = false;
1134 }
1135 self.internal_settings
1136 .copy_file_queue
1137 .push((sources.to_owned(), dest.to_path_buf()));
1138 }
1139
1140 if must_act_now {
1141 log_info!("cut_or_copy_flagged_files: act now");
1142 let in_mem = copy_move(
1143 cut_or_copy,
1144 sources,
1145 dest,
1146 self.left_window_width(),
1147 self.term_size().height,
1148 Arc::clone(&self.fm_sender),
1149 )?;
1150 self.internal_settings.store_copy_progress(in_mem);
1151 }
1152 self.clear_flags_and_reset_view()
1153 }
1154
1155 pub fn copy_next_file_in_queue(&mut self) -> Result<()> {
1157 self.internal_settings
1158 .copy_next_file_in_queue(self.fm_sender.clone(), self.left_window_width())
1159 }
1160
1161 pub fn paste_input(&mut self, pasted: &str) -> Result<()> {
1163 if self.focus.is_file() && self.current_tab().display_mode.is_fuzzy() {
1164 let Some(fuzzy) = &mut self.fuzzy else {
1165 return Ok(());
1166 };
1167 fuzzy.input.insert_string(pasted);
1168 } else if !self.focus.is_file() && self.current_tab().menu_mode.is_input() {
1169 self.menu.input.insert_string(pasted);
1170 }
1171 Ok(())
1172 }
1173
1174 pub fn paste_pathes(&mut self, pasted: &str) -> Result<()> {
1176 for pasted in pasted.split_whitespace() {
1177 self.paste_path(pasted)?;
1178 }
1179 Ok(())
1180 }
1181
1182 fn paste_path(&mut self, pasted: &str) -> Result<()> {
1183 let pasted = Path::new(&pasted);
1185 if !pasted.is_absolute() {
1186 log_info!("pasted {pasted} isn't absolute.", pasted = pasted.display());
1187 return Ok(());
1188 }
1189 if !pasted.exists() {
1190 log_info!("pasted {pasted} doesn't exist.", pasted = pasted.display());
1191 return Ok(());
1192 }
1193 let dest = self.current_tab().current_directory_path().to_path_buf();
1195 let Some(dest_filename) = build_dest_path(pasted, &dest) else {
1196 return Ok(());
1197 };
1198 if dest_filename == pasted {
1199 log_info!("pasted is same directory.");
1200 return Ok(());
1201 }
1202 if dest_filename.exists() {
1203 log_info!(
1204 "pasted {dest_filename} already exists",
1205 dest_filename = dest_filename.display()
1206 );
1207 return Ok(());
1208 }
1209 let sources = vec![pasted.to_path_buf()];
1210
1211 log_info!("pasted copy {sources:?} to {dest:?}");
1212 if self.is_simple_move(&CopyMove::Move, &sources, &dest) {
1213 self.simple_move(&sources, &dest)
1214 } else {
1215 self.complex_move(CopyMove::Copy, sources, &dest)
1216 }
1217 }
1218
1219 pub fn fuzzy_init(&mut self, kind: FuzzyKind) {
1221 self.fuzzy = Some(FuzzyFinder::new(kind).set_height(self.current_tab().window.height));
1222 }
1223
1224 fn fuzzy_drop(&mut self) {
1225 self.fuzzy = None;
1226 }
1227
1228 pub fn fuzzy_find_files(&mut self) -> Result<()> {
1230 let Some(fuzzy) = &self.fuzzy else {
1231 bail!("Fuzzy should be set");
1232 };
1233 let current_path = self.current_tab().current_directory_path().to_path_buf();
1234 fuzzy.find_files(current_path);
1235 Ok(())
1236 }
1237
1238 pub fn fuzzy_help(&mut self, help: String) -> Result<()> {
1240 let Some(fuzzy) = &self.fuzzy else {
1241 bail!("Fuzzy should be set");
1242 };
1243 fuzzy.find_action(help);
1244 Ok(())
1245 }
1246
1247 pub fn fuzzy_find_lines(&mut self) -> Result<()> {
1249 let Some(fuzzy) = &self.fuzzy else {
1250 bail!("Fuzzy should be set");
1251 };
1252 let Some(tokio_greper) = build_tokio_greper() else {
1253 log_info!("ripgrep & grep aren't in $PATH");
1254 return Ok(());
1255 };
1256 fuzzy.find_line(tokio_greper);
1257 Ok(())
1258 }
1259
1260 fn fuzzy_current_selection(&self) -> Option<std::string::String> {
1261 if let Some(fuzzy) = &self.fuzzy {
1262 fuzzy.pick()
1263 } else {
1264 None
1265 }
1266 }
1267
1268 pub fn fuzzy_select(&mut self) -> Result<()> {
1273 let Some(fuzzy) = &self.fuzzy else {
1274 bail!("Fuzzy should be set");
1275 };
1276 if let Some(pick) = fuzzy.pick() {
1277 match fuzzy.kind {
1278 FuzzyKind::File => self.tabs[self.index].cd_to_file(Path::new(&pick))?,
1279 FuzzyKind::Line => {
1280 self.tabs[self.index].cd_to_file(&parse_line_output(&pick)?.0)?
1281 }
1282 FuzzyKind::Action => self.fuzzy_send_event(&pick)?,
1283 }
1284 } else {
1285 log_info!("Fuzzy had nothing to pick from");
1286 };
1287 self.fuzzy_leave()
1288 }
1289
1290 pub fn fuzzy_toggle_flag_selected(&mut self) -> Result<()> {
1291 let Some(fuzzy) = &self.fuzzy else {
1292 bail!("Fuzzy should be set");
1293 };
1294 if let Some(pick) = fuzzy.pick() {
1295 if let FuzzyKind::File = fuzzy.kind {
1296 self.menu.flagged.toggle(Path::new(&pick));
1297 self.fuzzy_navigate(FuzzyDirection::Down)?;
1298 }
1299 } else {
1300 log_info!("Fuzzy had nothing to select from");
1301 };
1302 Ok(())
1303 }
1304
1305 fn fuzzy_send_event(&self, pick: &str) -> Result<()> {
1309 if let Ok(key) = find_keybind_from_fuzzy(pick) {
1310 self.fm_sender.send(FmEvents::Term(Event::Key(key)))?;
1311 };
1312 Ok(())
1313 }
1314
1315 pub fn fuzzy_leave(&mut self) -> Result<()> {
1317 self.fuzzy_drop();
1318 self.current_tab_mut().set_display_mode(Display::Directory);
1319 self.refresh_view()
1320 }
1321
1322 pub fn fuzzy_backspace(&mut self) -> Result<()> {
1324 let Some(fuzzy) = &mut self.fuzzy else {
1325 bail!("Fuzzy should be set");
1326 };
1327 fuzzy.input.delete_char_left();
1328 fuzzy.update_input(false);
1329 Ok(())
1330 }
1331
1332 pub fn fuzzy_delete(&mut self) -> Result<()> {
1334 let Some(fuzzy) = &mut self.fuzzy else {
1335 bail!("Fuzzy should be set");
1336 };
1337 fuzzy.input.delete_chars_right();
1338 fuzzy.update_input(false);
1339 Ok(())
1340 }
1341
1342 pub fn fuzzy_left(&mut self) -> Result<()> {
1344 let Some(fuzzy) = &mut self.fuzzy else {
1345 bail!("Fuzzy should be set");
1346 };
1347 fuzzy.input.cursor_left();
1348 Ok(())
1349 }
1350
1351 pub fn fuzzy_right(&mut self) -> Result<()> {
1353 let Some(fuzzy) = &mut self.fuzzy else {
1354 bail!("Fuzzy should be set");
1355 };
1356 fuzzy.input.cursor_right();
1357 Ok(())
1358 }
1359
1360 pub fn fuzzy_start(&mut self) -> Result<()> {
1362 self.fuzzy_navigate(FuzzyDirection::Start)
1363 }
1364
1365 pub fn fuzzy_end(&mut self) -> Result<()> {
1367 self.fuzzy_navigate(FuzzyDirection::End)
1368 }
1369
1370 pub fn fuzzy_navigate(&mut self, direction: FuzzyDirection) -> Result<()> {
1372 let Some(fuzzy) = &mut self.fuzzy else {
1373 bail!("Fuzzy should be set");
1374 };
1375 fuzzy.navigate(direction);
1376 if fuzzy.should_preview() {
1377 self.update_second_pane_for_preview()?;
1378 }
1379 Ok(())
1380 }
1381
1382 pub fn fuzzy_tick(&mut self) {
1384 if let Some(fuzzy) = &mut self.fuzzy {
1385 fuzzy.tick(false);
1386 }
1387 }
1388
1389 pub fn fuzzy_resize(&mut self, height: usize) {
1391 if let Some(fuzzy) = &mut self.fuzzy {
1392 fuzzy.resize(height)
1393 }
1394 }
1395
1396 pub fn input_history_next(&mut self) -> Result<()> {
1398 if self.focus.is_file() {
1399 return Ok(());
1400 }
1401 self.menu.input_history_next(&mut self.tabs[self.index])?;
1402 if let Menu::InputCompleted(input_completed) = self.current_tab().menu_mode {
1403 self.complete(input_completed)?;
1404 }
1405 Ok(())
1406 }
1407
1408 pub fn input_history_prev(&mut self) -> Result<()> {
1410 if self.focus.is_file() {
1411 return Ok(());
1412 }
1413 self.menu.input_history_prev(&mut self.tabs[self.index])?;
1414 if let Menu::InputCompleted(input_completed) = self.current_tab().menu_mode {
1415 self.complete(input_completed)?;
1416 }
1417 Ok(())
1418 }
1419
1420 pub fn input_and_complete(&mut self, input_completed: InputCompleted, c: char) -> Result<()> {
1422 self.menu.input.insert(c);
1423 self.complete(input_completed)
1424 }
1425
1426 pub fn complete(&mut self, input_completed: InputCompleted) -> Result<()> {
1427 match input_completed {
1428 InputCompleted::Search => self.complete_search(),
1429 _ => self.complete_non_search(),
1430 }
1431 }
1432
1433 pub fn complete_tab(&mut self, input_completed: InputCompleted) -> Result<()> {
1435 self.menu.completion_tab();
1436 self.complete_cd_move()?;
1437 if matches!(input_completed, InputCompleted::Search) {
1438 self.update_search()?;
1439 self.search()?;
1440 } else {
1441 self.menu.input_complete(&mut self.tabs[self.index])?
1442 }
1443 Ok(())
1444 }
1445
1446 fn complete_search(&mut self) -> Result<()> {
1447 self.update_search()?;
1448 self.search()?;
1449 self.menu.input_complete(&mut self.tabs[self.index])
1450 }
1451
1452 fn update_search(&mut self) -> Result<()> {
1453 if let Ok(search) = Search::new(&self.menu.input.string()) {
1454 self.current_tab_mut().search = search;
1455 };
1456 Ok(())
1457 }
1458
1459 pub fn follow_search(&mut self) -> Result<()> {
1462 let Some(proposition) = self.menu.completion.selected() else {
1463 return Ok(());
1464 };
1465 let current_path = match self.current_tab().display_mode {
1466 Display::Directory => self.current_tab().current_directory_path(),
1467 Display::Tree => self.current_tab().tree.root_path(),
1468 _ => {
1469 return Ok(());
1470 }
1471 };
1472 let mut full_path = current_path.to_path_buf();
1473 full_path.push(proposition);
1474 self.current_tab_mut().select_by_path(Arc::from(full_path));
1475 Ok(())
1476 }
1477
1478 fn complete_non_search(&mut self) -> Result<()> {
1479 self.complete_cd_move()?;
1480 self.menu.input_complete(&mut self.tabs[self.index])
1481 }
1482
1483 pub fn complete_cd_move(&mut self) -> Result<()> {
1485 if let Menu::InputCompleted(InputCompleted::Cd) = self.current_tab().menu_mode {
1486 let input = self.menu.input.string();
1487 if self.tabs[self.index].try_cd_to_file(input)? {
1488 self.update_second_pane_for_preview()?;
1489 }
1490 }
1491 Ok(())
1492 }
1493
1494 pub fn input_regex(&mut self, char: char) -> Result<()> {
1496 self.menu.input.insert(char);
1497 self.flag_from_regex()?;
1498 Ok(())
1499 }
1500
1501 pub fn flag_from_regex(&mut self) -> Result<()> {
1504 let input = self.menu.input.string();
1505 if input.is_empty() {
1506 return Ok(());
1507 }
1508 let paths = match self.current_tab().display_mode {
1509 Display::Directory => self.tabs[self.index].directory.paths(),
1510 Display::Tree => self.tabs[self.index].tree.paths(),
1511 _ => return Ok(()),
1512 };
1513 regex_flagger(&input, &paths, &mut self.menu.flagged)?;
1514 if !self.menu.flagged.is_empty() {
1515 self.tabs[self.index]
1516 .go_to_file(self.menu.flagged.selected().context("no selected file")?);
1517 }
1518 Ok(())
1519 }
1520
1521 pub fn open_selected_file(&mut self) -> Result<()> {
1523 let path = self
1524 .current_tab()
1525 .selected_path()
1526 .context("No selected path")?;
1527 self.open_single_file(&path)
1528 }
1529
1530 pub fn open_single_file(&mut self, path: &Path) -> Result<()> {
1532 match self.internal_settings.opener.kind(path) {
1533 Some(Kind::Internal(Internal::NotSupported)) => self.mount_iso_drive(),
1534 Some(_) => self.internal_settings.open_single_file(path),
1535 None => Ok(()),
1536 }
1537 }
1538
1539 pub fn open_flagged_files(&mut self) -> Result<()> {
1541 self.internal_settings
1542 .open_flagged_files(&self.menu.flagged)
1543 }
1544
1545 fn ensure_iso_device_is_some(&mut self) -> Result<()> {
1546 if self.menu.iso_device.is_none() {
1547 let path = path_to_string(
1548 &self
1549 .current_tab()
1550 .selected_path()
1551 .context("No selected path")?,
1552 );
1553 self.menu.iso_device = Some(IsoDevice::from_path(path));
1554 }
1555 Ok(())
1556 }
1557
1558 fn mount_iso_drive(&mut self) -> Result<()> {
1562 if !self.menu.password_holder.has_sudo() {
1563 self.ask_password(Some(MountAction::MOUNT), PasswordUsage::ISO)?;
1564 } else {
1565 self.ensure_iso_device_is_some()?;
1566 let Some(ref mut iso_device) = self.menu.iso_device else {
1567 return Ok(());
1568 };
1569 if iso_device.mount(¤t_username()?, &mut self.menu.password_holder)? {
1570 log_info!("iso mounter mounted {iso_device:?}");
1571 log_line!("iso : {iso_device}");
1572 let path = iso_device
1573 .mountpoints
1574 .clone()
1575 .expect("mountpoint should be set");
1576 self.current_tab_mut().cd(Path::new(&path))?;
1577 };
1578 self.menu.iso_device = None;
1579 };
1580
1581 Ok(())
1582 }
1583
1584 pub fn umount_iso_drive(&mut self) -> Result<()> {
1587 if let Some(ref mut iso_device) = self.menu.iso_device {
1588 if !self.menu.password_holder.has_sudo() {
1589 self.ask_password(Some(MountAction::UMOUNT), PasswordUsage::ISO)?;
1590 } else {
1591 iso_device.umount(¤t_username()?, &mut self.menu.password_holder)?;
1592 };
1593 }
1594 self.menu.iso_device = None;
1595 Ok(())
1596 }
1597
1598 pub fn mount_encrypted_drive(&mut self) -> Result<()> {
1603 if !self.menu.password_holder.has_sudo() {
1604 self.ask_password(
1605 Some(MountAction::MOUNT),
1606 PasswordUsage::CRYPTSETUP(PasswordKind::SUDO),
1607 )
1608 } else if !self.menu.password_holder.has_cryptsetup() {
1609 self.ask_password(
1610 Some(MountAction::MOUNT),
1611 PasswordUsage::CRYPTSETUP(PasswordKind::CRYPTSETUP),
1612 )
1613 } else {
1614 let Some(Mountable::Encrypted(device)) = &self.menu.mount.selected() else {
1615 return Ok(());
1616 };
1617 if let Ok(true) = device.mount(¤t_username()?, &mut self.menu.password_holder) {
1618 self.go_to_encrypted_drive(device.uuid.clone())?;
1619 }
1620 Ok(())
1621 }
1622 }
1623
1624 pub fn umount_encrypted_drive(&mut self) -> Result<()> {
1627 if !self.menu.password_holder.has_sudo() {
1628 self.ask_password(
1629 Some(MountAction::UMOUNT),
1630 PasswordUsage::CRYPTSETUP(PasswordKind::SUDO),
1631 )
1632 } else {
1633 let Some(Mountable::Encrypted(device)) = &self.menu.mount.selected() else {
1634 log_info!("Cannot find Encrypted device");
1635 return Ok(());
1636 };
1637 let success =
1638 device.umount_close_crypto(¤t_username()?, &mut self.menu.password_holder)?;
1639 log_info!("umount_encrypted_drive: {success}");
1640 Ok(())
1641 }
1642 }
1643 pub fn mount_normal_device(&mut self) -> Result<()> {
1647 let Some(device) = self.menu.mount.selected() else {
1648 return Ok(());
1649 };
1650 if device.is_mounted() {
1651 return Ok(());
1652 }
1653 if device.is_crypto() {
1654 return self.mount_encrypted_drive();
1655 }
1656 let Ok(success) = self.menu.mount.mount_selected_no_password() else {
1657 return Ok(());
1658 };
1659 if success {
1660 self.menu.mount.update(self.internal_settings.disks())?;
1661 self.go_to_normal_drive()?;
1662 return Ok(());
1663 }
1664 if !self.menu.password_holder.has_sudo() {
1665 self.ask_password(Some(MountAction::MOUNT), PasswordUsage::DEVICE)
1666 } else {
1667 if let Ok(true) = self
1668 .menu
1669 .mount
1670 .mount_selected(&mut self.menu.password_holder)
1671 {
1672 self.go_to_normal_drive()?;
1673 }
1674 Ok(())
1675 }
1676 }
1677
1678 pub fn go_to_normal_drive(&mut self) -> Result<()> {
1680 let Some(path) = self.menu.mount.selected_mount_point() else {
1681 return Ok(());
1682 };
1683 let tab = self.current_tab_mut();
1684 tab.cd(&path)?;
1685 tab.refresh_view()
1686 }
1687
1688 pub fn go_to_mount_per_index(&mut self, c: char) -> Result<()> {
1690 let Some(index) = c.to_digit(10) else {
1691 return Ok(());
1692 };
1693 self.menu.mount.set_index(index.saturating_sub(1) as _);
1694 self.go_to_normal_drive()
1695 }
1696
1697 fn go_to_encrypted_drive(&mut self, uuid: Option<String>) -> Result<()> {
1698 self.menu.mount.update(&self.internal_settings.disks)?;
1699 let Some(mountpoint) = self.menu.mount.find_encrypted_by_uuid(uuid) else {
1700 return Ok(());
1701 };
1702 log_info!("mountpoint {mountpoint}");
1703 let tab = self.current_tab_mut();
1704 tab.cd(Path::new(&mountpoint))?;
1705 tab.refresh_view()
1706 }
1707
1708 pub fn umount_normal_device(&mut self) -> Result<()> {
1711 let Some(device) = self.menu.mount.selected() else {
1712 return Ok(());
1713 };
1714 if !device.is_mounted() {
1715 return Ok(());
1716 }
1717 if device.is_crypto() {
1718 return self.umount_encrypted_drive();
1719 }
1720 let Ok(success) = self.menu.mount.umount_selected_no_password() else {
1721 return Ok(());
1722 };
1723 if success {
1724 self.menu.mount.update(self.internal_settings.disks())?;
1725 return Ok(());
1726 }
1727 if !self.menu.password_holder.has_sudo() {
1728 self.ask_password(Some(MountAction::UMOUNT), PasswordUsage::DEVICE)
1729 } else {
1730 self.menu
1731 .mount
1732 .umount_selected(&mut self.menu.password_holder)
1733 }
1734 }
1735
1736 pub fn eject_removable_device(&mut self) -> Result<()> {
1738 if self.menu.mount.is_empty() {
1739 return Ok(());
1740 }
1741 let success = self.menu.mount.eject_removable_device()?;
1742 if success {
1743 self.menu.mount.update(self.internal_settings.disks())?;
1744 }
1745 Ok(())
1746 }
1747
1748 pub fn execute_shell_command_from_input(&mut self) -> Result<bool> {
1751 let shell_command = self.menu.input.string();
1752 self.execute_shell_command(shell_command, None, true)
1753 }
1754
1755 pub fn execute_shell_command(
1757 &mut self,
1758 shell_command: String,
1759 files: Option<Vec<String>>,
1760 capture_output: bool,
1761 ) -> Result<bool> {
1762 let command = append_files_to_shell_command(shell_command, files);
1763 let Ok(args) = shell_command_parser(&command, self) else {
1764 self.set_menu_mode(self.index, Menu::Nothing)?;
1765 return Ok(true);
1766 };
1767 self.execute_parsed_command(args, command, capture_output)
1768 }
1769
1770 fn execute_parsed_command(
1771 &mut self,
1772 mut args: Vec<String>,
1773 shell_command: String,
1774 capture_output: bool,
1775 ) -> Result<bool> {
1776 let executable = args.remove(0);
1777 if is_sudo_command(&executable) {
1778 self.enter_sudo_mode(shell_command)?;
1779 return Ok(false);
1780 }
1781 let params: Vec<&str> = args.iter().map(|s| s.as_str()).collect();
1782 if executable == *SAME_WINDOW_TOKEN {
1783 self.internal_settings.open_in_window(¶ms)?;
1784 return Ok(true);
1785 }
1786 if !is_in_path(&executable) {
1787 log_line!("{executable} isn't in path.");
1788 return Ok(true);
1789 }
1790 if capture_output {
1791 match execute_and_capture_output(executable, ¶ms) {
1792 Ok(output) => self.preview_command_output(output, shell_command),
1793 Err(e) => {
1794 log_info!("Error {e:?}");
1795 log_line!("Command {shell_command} disn't finish properly");
1796 }
1797 }
1798 Ok(true)
1799 } else {
1800 let _ = execute_without_output(executable, ¶ms);
1801 Ok(true)
1802 }
1803 }
1804
1805 fn enter_sudo_mode(&mut self, shell_command: String) -> Result<()> {
1806 self.menu.sudo_command = Some(shell_command);
1807 self.ask_password(None, PasswordUsage::SUDOCOMMAND)?;
1808 Ok(())
1809 }
1810
1811 fn ask_password(
1813 &mut self,
1814 encrypted_action: Option<MountAction>,
1815 password_dest: PasswordUsage,
1816 ) -> Result<()> {
1817 log_info!("ask_password");
1818 self.set_menu_mode(
1819 self.index,
1820 Menu::InputSimple(InputSimple::Password(encrypted_action, password_dest)),
1821 )
1822 }
1823
1824 pub fn execute_password_command(
1827 &mut self,
1828 action: Option<MountAction>,
1829 dest: PasswordUsage,
1830 ) -> Result<()> {
1831 let password = self.menu.input.string();
1832 self.menu.input.reset();
1833 if matches!(dest, PasswordUsage::CRYPTSETUP(PasswordKind::CRYPTSETUP)) {
1834 self.menu.password_holder.set_cryptsetup(password)
1835 } else {
1836 self.menu.password_holder.set_sudo(password)
1837 };
1838 let sudo_command = self.menu.sudo_command.to_owned();
1839 self.reset_menu_mode()?;
1840 self.dispatch_password(action, dest, sudo_command)?;
1841 Ok(())
1842 }
1843
1844 pub fn marks_new(&mut self, c: char) -> Result<()> {
1846 let path = self.current_tab_mut().directory.path.clone();
1847 self.menu.marks.new_mark(c, &path)?;
1848 self.current_tab_mut().refresh_view()?;
1849 self.reset_menu_mode()?;
1850 self.refresh_status()
1851 }
1852
1853 pub fn marks_jump_char(&mut self, c: char) -> Result<()> {
1856 if let Some(path) = self.menu.marks.get(c) {
1857 self.current_tab_mut().cd(&path)?;
1858 }
1859 self.current_tab_mut().refresh_view()?;
1860 self.reset_menu_mode()?;
1861 self.refresh_status()
1862 }
1863
1864 pub fn temp_marks_new(&mut self, c: char) -> Result<()> {
1866 let Some(index) = c.to_digit(10) else {
1867 return Ok(());
1868 };
1869 let path = self.current_tab_mut().directory.path.to_path_buf();
1870 self.menu.temp_marks.set_mark(index as _, path);
1871 self.current_tab_mut().refresh_view()?;
1872 self.reset_menu_mode()?;
1873 self.refresh_status()
1874 }
1875
1876 pub fn temp_marks_erase(&mut self) -> Result<()> {
1878 self.menu.temp_marks.erase_current_mark();
1879 Ok(())
1880 }
1881
1882 pub fn temp_marks_jump_char(&mut self, c: char) -> Result<()> {
1885 let Some(index) = c.to_digit(10) else {
1886 return Ok(());
1887 };
1888 if let Some(path) = self.menu.temp_marks.get_mark(index as _) {
1889 self.tabs[self.index].cd(path)?;
1890 }
1891 self.current_tab_mut().refresh_view()?;
1892 self.reset_menu_mode()?;
1893 self.refresh_status()
1894 }
1895
1896 pub fn confirm_delete_files(&mut self) -> Result<()> {
1899 if self.menu.flagged.contains(self.current_tab().root_path()) {
1900 log_info!("Can't delete current root path");
1901 log_line!("Can't delete current root path");
1902 return Ok(());
1903 }
1904 self.menu.delete_flagged_files()?;
1905 self.reset_menu_mode()?;
1906 self.clear_flags_and_reset_view()?;
1907 self.refresh_status()
1908 }
1909
1910 pub fn confirm_trash_empty(&mut self) -> Result<()> {
1912 self.menu.trash.empty_trash()?;
1913 self.reset_menu_mode()?;
1914 self.clear_flags_and_reset_view()?;
1915 Ok(())
1916 }
1917
1918 pub fn bulk_ask_filenames(&mut self) -> Result<()> {
1920 let flagged = self.flagged_in_current_dir();
1921 let current_path = self.current_tab_path_str();
1922 self.menu.bulk.ask_filenames(flagged, ¤t_path)?;
1923 if let Some(temp_file) = self.menu.bulk.temp_file() {
1924 self.open_single_file(&temp_file)?;
1925 if self.internal_settings.opener.extension_use_term("txt") {
1926 self.fm_sender.send(FmEvents::BulkExecute)?;
1927 } else {
1928 self.menu.bulk.watch_in_thread(self.fm_sender.clone())?;
1929 }
1930 }
1931 Ok(())
1932 }
1933
1934 pub fn bulk_execute(&mut self) -> Result<()> {
1936 self.menu.bulk.get_new_names()?;
1937 self.set_menu_mode(
1938 self.index,
1939 Menu::NeedConfirmation(NeedConfirmation::BulkAction),
1940 )?;
1941 Ok(())
1942 }
1943
1944 pub fn confirm_bulk_action(&mut self) -> Result<()> {
1946 if let (Some(paths), Some(create)) = self.menu.bulk.execute()? {
1947 self.menu.flagged.update(paths);
1948 self.menu.flagged.extend(create);
1949 } else {
1950 self.menu.flagged.clear();
1951 };
1952 self.reset_menu_mode()?;
1953 self.reset_tabs_view()?;
1954 Ok(())
1955 }
1956
1957 fn run_sudo_command(&mut self, sudo_command: Option<String>) -> Result<()> {
1958 let Some(sudo_command) = sudo_command else {
1959 log_info!("No sudo_command received from args.");
1960 return self.menu.clear_sudo_attributes();
1961 };
1962 self.set_menu_mode(self.index, Menu::Nothing)?;
1963 reset_sudo_faillock()?;
1964 let Some(command) = sudo_command.strip_prefix("sudo ") else {
1965 log_info!("run_sudo_command cannot run {sudo_command}. It doesn't start with 'sudo '");
1966 return self.menu.clear_sudo_attributes();
1967 };
1968 let args = shell_command_parser(command, self)?;
1969 if args.is_empty() {
1970 return self.menu.clear_sudo_attributes();
1971 }
1972 let Some(password) = self.menu.password_holder.sudo() else {
1973 log_info!("run_sudo_command password isn't set");
1974 return self.menu.clear_sudo_attributes();
1975 };
1976 let directory_of_selected = self.current_tab().directory_of_selected()?;
1977 let (success, stdout, stderr) =
1978 execute_sudo_command_with_password(&args, &password, directory_of_selected)?;
1979 log_info!("sudo command execution. success: {success}");
1980 self.menu.clear_sudo_attributes()?;
1981 if !success {
1982 log_line!("sudo command failed: {stderr}");
1983 }
1984 self.preview_command_output(stdout, sudo_command.to_owned());
1985 Ok(())
1986 }
1987
1988 #[rustfmt::skip]
1991 pub fn dispatch_password(
1992 &mut self,
1993 action: Option<MountAction>,
1994 dest: PasswordUsage,
1995 sudo_command: Option<String>,
1996 ) -> Result<()> {
1997 match (dest, action) {
1998 (PasswordUsage::ISO, Some(MountAction::MOUNT)) => self.mount_iso_drive(),
1999 (PasswordUsage::ISO, Some(MountAction::UMOUNT)) => self.umount_iso_drive(),
2000 (PasswordUsage::CRYPTSETUP(_), Some(MountAction::MOUNT)) => self.mount_encrypted_drive(),
2001 (PasswordUsage::CRYPTSETUP(_), Some(MountAction::UMOUNT)) => self.umount_encrypted_drive(),
2002 (PasswordUsage::DEVICE, Some(MountAction::MOUNT)) => self.mount_normal_device(),
2003 (PasswordUsage::DEVICE, Some(MountAction::UMOUNT)) => self.umount_normal_device(),
2004 (PasswordUsage::SUDOCOMMAND, _) => self.run_sudo_command(sudo_command),
2005 (_, _) => Ok(()),
2006 }
2007 }
2008
2009 pub fn preview_command_output(&mut self, output: String, command: String) {
2011 log_info!("preview_command_output for {command}:\n{output}");
2012 if output.is_empty() {
2013 return;
2014 }
2015 let _ = self.reset_menu_mode();
2016 self.current_tab_mut().set_display_mode(Display::Preview);
2017 let preview = PreviewBuilder::cli_info(&output, command);
2018 if let Preview::Text(text) = &preview {
2019 log_info!("preview is Text with: {text:?}");
2020 } else {
2021 log_info!("preview is empty ? {empty}", empty = preview.is_empty());
2022 }
2023 self.current_tab_mut().window.reset(preview.len());
2024 self.current_tab_mut().preview = preview;
2025 }
2026
2027 pub fn update_nvim_listen_address(&mut self) {
2029 self.internal_settings.update_nvim_listen_address()
2030 }
2031
2032 pub fn confirm(&mut self, c: char, confirmed_action: NeedConfirmation) -> Result<()> {
2035 if c == 'y' {
2036 if let Ok(must_leave) = self.match_confirmed_mode(confirmed_action) {
2037 if must_leave {
2038 return Ok(());
2039 }
2040 }
2041 }
2042 self.reset_menu_mode()?;
2043 self.current_tab_mut().refresh_view()?;
2044
2045 Ok(())
2046 }
2047
2048 fn match_confirmed_mode(&mut self, confirmed_action: NeedConfirmation) -> Result<bool> {
2050 match confirmed_action {
2051 NeedConfirmation::Delete => self.confirm_delete_files(),
2052 NeedConfirmation::Move => self.cut_or_copy_flagged_files(CopyMove::Move),
2053 NeedConfirmation::Copy => self.cut_or_copy_flagged_files(CopyMove::Copy),
2054 NeedConfirmation::EmptyTrash => self.confirm_trash_empty(),
2055 NeedConfirmation::BulkAction => self.confirm_bulk_action(),
2056 NeedConfirmation::DeleteCloud => {
2057 self.cloud_confirm_delete()?;
2058 return Ok(true);
2059 }
2060 }?;
2061 Ok(false)
2062 }
2063
2064 pub fn header_action(&mut self, col: u16, binds: &Bindings) -> Result<()> {
2066 if self.current_tab().display_mode.is_preview() {
2067 return Ok(());
2068 }
2069 Header::new(self, self.current_tab())?
2070 .action(col, !self.focus.is_left())
2071 .matcher(self, binds)
2072 }
2073
2074 pub fn footer_action(&mut self, col: u16, binds: &Bindings) -> Result<()> {
2076 log_info!("footer clicked col {col}");
2077 let is_right = self.index == 1;
2078 let action = match self.current_tab().display_mode {
2079 Display::Preview => return Ok(()),
2080 Display::Tree | Display::Directory => {
2081 let footer = Footer::new(self, self.current_tab())?;
2082 footer.action(col, is_right).to_owned()
2083 }
2084 Display::Fuzzy => return Ok(()),
2085 };
2086 log_info!("action: {action}");
2087 action.matcher(self, binds)
2088 }
2089
2090 pub fn chmod(&mut self) -> Result<()> {
2096 if self.menu.input.is_empty() || self.menu.flagged.is_empty() {
2097 return Ok(());
2098 }
2099 let input_permission = &self.menu.input.string();
2100 Permissions::set_permissions_of_flagged(input_permission, &self.menu.flagged)?;
2101 self.reset_tabs_view()
2102 }
2103
2104 pub fn set_mode_chmod(&mut self) -> Result<()> {
2106 if self.current_tab_mut().directory.is_empty() {
2107 return Ok(());
2108 }
2109 if self.menu.flagged.is_empty() {
2110 self.toggle_flag_for_selected();
2111 }
2112 self.set_menu_mode(self.index, Menu::InputSimple(InputSimple::Chmod))?;
2113 self.menu.replace_input_by_permissions();
2114 Ok(())
2115 }
2116
2117 pub fn run_custom_command(&mut self, string: &str) -> Result<()> {
2119 self.execute_shell_command(string.to_owned(), None, true)?;
2120 Ok(())
2121 }
2122
2123 pub fn compress(&mut self) -> Result<()> {
2129 let here = &self.current_tab().directory.path;
2130 set_current_dir(here)?;
2131 let files_with_relative_paths = self.flagged_or_selected_relative_to(here);
2132 if files_with_relative_paths.is_empty() {
2133 return Ok(());
2134 }
2135 match self
2136 .menu
2137 .compression
2138 .compress(files_with_relative_paths, here)
2139 {
2140 Ok(()) => (),
2141 Err(error) => log_info!("Error compressing files. Error: {error}"),
2142 }
2143 Ok(())
2144 }
2145
2146 pub fn sort_by_char(&mut self, c: char) -> Result<()> {
2148 self.current_tab_mut().sort(c)?;
2149 self.menu.reset();
2150 self.set_height_for_menu_mode(self.index, Menu::Nothing)?;
2151 self.tabs[self.index].menu_mode = Menu::Nothing;
2152 let len = self.menu.len(Menu::Nothing);
2153 let height = self.second_window_height()?;
2154 self.menu.window = ContentWindow::new(len, height);
2155 self.focus = self.focus.to_parent();
2156 Ok(())
2157 }
2158
2159 pub fn canvas_width(&self) -> Result<u16> {
2161 let full_width = self.term_width();
2162 if self.session.dual() && full_width >= MIN_WIDTH_FOR_DUAL_PANE {
2163 Ok(full_width / 2)
2164 } else {
2165 Ok(full_width)
2166 }
2167 }
2168
2169 fn search(&mut self) -> Result<()> {
2175 let Some(search) = self.build_search_from_input() else {
2176 self.current_tab_mut().search = Search::empty();
2177 return Ok(());
2178 };
2179 self.search_and_update(search)
2180 }
2181
2182 fn build_search_from_input(&self) -> Option<Search> {
2183 let searched = &self.menu.input.string();
2184 if searched.is_empty() {
2185 return None;
2186 }
2187 Search::new(searched).ok()
2188 }
2189
2190 fn search_and_update(&mut self, mut search: Search) -> Result<()> {
2191 search.execute_search(self.current_tab_mut())?;
2192 self.current_tab_mut().search = search;
2193 self.update_second_pane_for_preview()
2194 }
2195
2196 fn search_again(&mut self) -> Result<()> {
2197 let search = self.current_tab().search.clone_with_regex();
2198 self.search_and_update(search)
2199 }
2200
2201 pub fn filter(&mut self) -> Result<()> {
2204 let filter = FilterKind::from_input(&self.menu.input.string());
2205 self.current_tab_mut().set_filter(filter)?;
2206 self.search_again()
2207 }
2208
2209 pub fn input_filter(&mut self, c: char) -> Result<()> {
2211 self.menu.input_insert(c)?;
2212 self.filter()
2213 }
2214
2215 pub fn open_picker(&mut self) -> Result<()> {
2217 if self.current_tab().menu_mode.is_picker() || self.menu.input_history.filtered_is_empty() {
2218 return Ok(());
2219 }
2220 let menu = self.current_tab().menu_mode;
2221 let content = self.menu.input_history.filtered_as_list();
2222 self.menu.picker.set(
2223 Some(PickerCaller::Menu(menu)),
2224 menu.name_for_picker(),
2225 content,
2226 );
2227
2228 self.set_menu_mode(self.index, Menu::Navigate(Navigate::Picker))
2229 }
2230
2231 pub fn cloud_load_config(&mut self) -> Result<()> {
2233 let Some(picked) = self.menu.picker.selected() else {
2234 log_info!("nothing selected");
2235 return Ok(());
2236 };
2237 let Ok(cloud) = google_drive(picked) else {
2238 log_line!("Invalid config file {picked}");
2239 return Ok(());
2240 };
2241 self.menu.cloud = cloud;
2242 self.set_menu_mode(self.index, Menu::Navigate(Navigate::Cloud))
2243 }
2244
2245 pub fn cloud_open(&mut self) -> Result<()> {
2249 if self.menu.cloud.is_set() {
2250 self.set_menu_mode(self.index, Menu::Navigate(Navigate::Cloud))
2251 } else {
2252 self.cloud_picker()
2253 }
2254 }
2255
2256 fn cloud_picker(&mut self) -> Result<()> {
2257 let content = get_cloud_token_names()?;
2258 self.menu.picker.set(
2259 Some(PickerCaller::Cloud),
2260 Some("Pick a cloud provider".to_owned()),
2261 content,
2262 );
2263 self.set_menu_mode(self.index, Menu::Navigate(Navigate::Picker))
2264 }
2265
2266 pub fn cloud_disconnect(&mut self) -> Result<()> {
2268 self.menu.cloud.disconnect();
2269 self.cloud_open()
2270 }
2271
2272 pub fn cloud_enter_delete_mode(&mut self) -> Result<()> {
2275 self.set_menu_mode(
2276 self.index,
2277 Menu::NeedConfirmation(NeedConfirmation::DeleteCloud),
2278 )
2279 }
2280
2281 pub fn cloud_confirm_delete(&mut self) -> Result<()> {
2283 self.menu.cloud.delete()?;
2284 self.set_menu_mode(self.index, Menu::Navigate(Navigate::Cloud))?;
2285 self.menu.cloud.refresh_current()?;
2286 self.menu.window.scroll_to(self.menu.cloud.index);
2287 Ok(())
2288 }
2289
2290 pub fn cloud_update_metadata(&mut self) -> Result<()> {
2292 self.menu.cloud.update_metadata()
2293 }
2294
2295 pub fn cloud_enter_newdir_mode(&mut self) -> Result<()> {
2297 self.set_menu_mode(self.index, Menu::InputSimple(InputSimple::CloudNewdir))?;
2298 self.refresh_view()
2299 }
2300
2301 pub fn cloud_create_newdir(&mut self, dirname: String) -> Result<()> {
2303 self.menu.cloud.create_newdir(dirname)?;
2304 self.menu.cloud.refresh_current()
2305 }
2306
2307 fn get_normal_selected_file(&self) -> Option<FileInfo> {
2308 let local_file = self.tabs[self.index].current_file().ok()?;
2309 match local_file.file_kind {
2310 FileKind::NormalFile => Some(local_file),
2311 _ => None,
2312 }
2313 }
2314
2315 pub fn cloud_upload_selected_file(&mut self) -> Result<()> {
2317 let Some(local_file) = self.get_normal_selected_file() else {
2318 log_line!("Can only upload normal files.");
2319 return Ok(());
2320 };
2321 self.menu.cloud.upload(&local_file)?;
2322 self.menu.cloud.refresh_current()
2323 }
2324
2325 pub fn cloud_enter_file_or_dir(&mut self) -> Result<()> {
2327 if let Some(entry) = self.menu.cloud.selected() {
2328 match entry.metadata().mode() {
2329 EntryMode::Unknown => (),
2330 EntryMode::FILE => self
2331 .menu
2332 .cloud
2333 .download(self.current_tab().directory_of_selected()?)?,
2334 EntryMode::DIR => {
2335 self.menu.cloud.enter_selected()?;
2336 self.cloud_set_content_window_len()?;
2337 }
2338 };
2339 };
2340 Ok(())
2341 }
2342
2343 fn cloud_set_content_window_len(&mut self) -> Result<()> {
2344 let len = self.menu.cloud.content.len();
2345 let height = self.second_window_height()?;
2346 self.menu.window = ContentWindow::new(len, height);
2347 Ok(())
2348 }
2349
2350 pub fn cloud_move_to_parent(&mut self) -> Result<()> {
2353 self.menu.cloud.move_to_parent()?;
2354 self.cloud_set_content_window_len()?;
2355 Ok(())
2356 }
2357
2358 pub fn toggle_flag_visual(&mut self) {
2359 if self.current_tab().visual {
2360 let Some(path) = self.current_tab().selected_path() else {
2361 return;
2362 };
2363 self.menu.flagged.toggle(&path)
2364 }
2365 }
2366
2367 pub fn parse_ipc(&mut self, msg: String) -> Result<()> {
2376 let mut split = msg.split_whitespace();
2377 match split.next() {
2378 Some("GO") => {
2379 log_info!("Received IPC command GO");
2380 if let Some(dest) = split.next() {
2381 log_info!("Received IPC command GO to {dest}");
2382 let dest = tilde(dest);
2383 self.current_tab_mut().cd_to_file(dest.as_ref())?
2384 }
2385 }
2386 Some("KEY") => {
2387 log_info!("Received IPC command KEY");
2388 if let Some(keyname) = split.next() {
2389 if let Some(key) = from_keyname(keyname) {
2390 self.fm_sender.send(FmEvents::Term(Event::Key(key)))?;
2391 log_info!("Sent key event: {key:?}");
2392 }
2393 }
2394 }
2395 Some("ACTION") => {
2396 log_info!("Received IPC command ACTION");
2397 if let Some(action_str) = split.next() {
2398 if let Ok(action) = ActionMap::from_str(action_str) {
2399 log_info!("Sent action event: {action:?}");
2400 self.fm_sender.send(FmEvents::Action(action))?;
2401 }
2402 }
2403 }
2404 Some(_unknown) => log_info!("Received unknown IPC command: {msg}"),
2405 None => (),
2406 };
2407 Ok(())
2408 }
2409}
2410
2411fn find_keybind_from_fuzzy(line: &str) -> Result<KeyEvent> {
2412 let Some(keybind) = line.split(':').next() else {
2413 bail!("No keybind found");
2414 };
2415 let Some(key) = from_keyname(keybind.trim()) else {
2416 bail!("{keybind} isn't a valid Key name.");
2417 };
2418 Ok(key)
2419}