Skip to main content

fm/app/
tab.rs

1use std::borrow::Borrow;
2use std::cmp::min;
3use std::iter::{Enumerate, Skip, Take};
4use std::path;
5use std::slice;
6use std::sync::Arc;
7
8use anyhow::{Context, Result};
9
10use crate::common::{
11    has_last_modification_happened_less_than, path_to_string, row_to_window_index, set_current_dir,
12    update_zoxide,
13};
14use crate::config::START_FOLDER;
15use crate::io::Args;
16use crate::log_info;
17use crate::modes::{
18    Content, ContentWindow, Directory, Display, FileInfo, FileKind, FilterKind, Go, History,
19    IndexToIndex, Menu, Preview, PreviewBuilder, Search, Selectable, SortKind, To, Tree,
20    TreeBuilder, Users,
21};
22
23/// Settings of a tab.
24/// Do we display hidden files ?
25/// What kind of filter is used ?
26/// What kind of sort is used ?
27/// Should the last image be cleared ?
28pub struct TabSettings {
29    /// read from command line
30    pub show_hidden: bool,
31    /// The filter use before displaying files
32    pub filter: FilterKind,
33    /// The kind of sort used to display the files.
34    pub sort_kind: SortKind,
35    /// should the last displayed image be erased ?
36    pub should_clear_image: bool,
37    /// the max depth of the tree
38    pub tree_max_depth: usize,
39}
40
41impl TabSettings {
42    fn new(args: &Args) -> Self {
43        let filter = FilterKind::All;
44        let show_hidden = args.all;
45        let sort_kind = SortKind::default();
46        let should_clear_image = false;
47        let tree_max_depth = 5;
48        Self {
49            show_hidden,
50            filter,
51            sort_kind,
52            should_clear_image,
53            tree_max_depth,
54        }
55    }
56
57    fn toggle_hidden(&mut self) {
58        self.show_hidden = !self.show_hidden;
59    }
60
61    /// Apply the filter.
62    pub fn set_filter(&mut self, filter: FilterKind) {
63        self.filter = filter
64    }
65
66    pub fn reset_filter(&mut self) {
67        self.filter = FilterKind::All;
68    }
69
70    /// Update the kind of sort from a char typed by the user.
71    fn update_sort_from_char(&mut self, c: char) {
72        self.sort_kind.update_from_char(c)
73    }
74}
75
76/// Holds every thing about the current tab of the application.
77/// Most of the mutation is done externally.
78pub struct Tab {
79    /// Kind of display: `Preview, Normal, Tree`
80    pub display_mode: Display,
81
82    /// Files in current path
83    pub directory: Directory,
84    /// Tree representation of the same path
85    pub tree: Tree,
86    /// Lines of the previewed files.
87    /// Empty if not in preview mode.
88    pub preview: Preview,
89
90    /// The menu currently opened in this tab.
91    /// Most of the time is spent in `EditMode::Nothing`
92    pub menu_mode: Menu,
93
94    /// The indexes of displayed file
95    pub window: ContentWindow,
96    /// Height of the terminal window
97    pub height: usize,
98
99    /// Internal & display settings:
100    /// show hidden files ?
101    /// sort method
102    /// filter kind
103    pub settings: TabSettings,
104
105    /// Last searched string
106    pub search: Search,
107    // pub searched: Search,
108    /// Visited directories
109    pub history: History,
110    /// Users & groups
111    pub users: Users,
112    /// Saved path before entering "CD" mode.
113    /// Used if the cd is canceled
114    pub origin_path: Option<std::path::PathBuf>,
115    /// Is visual selection enabled ? Visual flag allows user to flag quickly by moving the selection.
116    pub visual: bool,
117}
118
119impl Tab {
120    /// Creates a new tab from args and height.
121    ///
122    /// # Description
123    ///
124    /// It reads a path from args, which is defaulted to the starting path.
125    /// It explores the path and creates a content.
126    /// The path is then selected. If no path was provide from args, the current folder `.` is selected.
127    /// Every other attribute has its default value.
128    ///
129    /// # Errors
130    ///
131    /// it may fail if the path:
132    /// - doesn't exist
133    /// - can't be explored
134    /// - has no parent and isn't a directory (which can't happen)
135    pub fn new(args: &Args, height: usize, users: Users) -> Result<Self> {
136        let path = &START_FOLDER.get().context("Startfolder should be set")?;
137        let start_dir = Self::start_dir(path)?;
138        let settings = TabSettings::new(args);
139        let mut directory =
140            Directory::new(start_dir, &users, &settings.filter, settings.show_hidden)?;
141        let display_mode = Display::default();
142        let menu_mode = Menu::Nothing;
143        let mut window = ContentWindow::new(directory.content.len(), height);
144        let preview = Preview::Empty;
145        let history = History::new(path);
146        let search = Search::empty();
147        let index = directory.select_file(path);
148        let tree = Tree::default();
149        let origin_path = None;
150        let visual = false;
151
152        window.scroll_to(index);
153        Ok(Self {
154            display_mode,
155            menu_mode,
156            window,
157            directory,
158            height,
159            preview,
160            search,
161            history,
162            users,
163            tree,
164            settings,
165            origin_path,
166            visual,
167        })
168    }
169
170    fn start_dir(path: &path::Path) -> Result<&path::Path> {
171        if path.is_dir() {
172            Ok(path)
173        } else {
174            Ok(path.parent().context("Path has no parent")?)
175        }
176    }
177
178    /// Returns the directory owning the selected file.
179    /// In Tree mode, it's the current directory if the selected node is a directory,
180    /// its parent otherwise.
181    /// In normal mode it's the current working directory.
182    pub fn directory_of_selected(&self) -> Result<&path::Path> {
183        match self.display_mode {
184            Display::Tree => self.tree.directory_of_selected().context("No parent"),
185            _ => Ok(&self.directory.path),
186        }
187    }
188
189    /// Current path of this tab in directory display mode.
190    pub fn current_directory_path(&self) -> &path::Path {
191        self.directory.path.borrow()
192    }
193
194    /// Root path of current display.
195    /// In tree mode it's the path of the root,
196    /// In other display modes, it's the path of the current directory.
197    /// It's only used to ensure we don't delete the current root which is forbidden.
198    pub fn root_path(&self) -> &path::Path {
199        if self.display_mode.is_tree() {
200            self.tree.root_path()
201        } else {
202            self.current_directory_path()
203        }
204    }
205
206    /// Fileinfo of the selected element.
207    pub fn current_file(&self) -> Result<FileInfo> {
208        match self.display_mode {
209            Display::Tree => FileInfo::new(&self.tree.selected_path_or_parent()?, &self.users),
210            Display::Preview if !self.preview.is_empty() => {
211                FileInfo::new(&self.preview.filepath(), &self.users)
212            }
213            _ => Ok(self
214                .directory
215                .selected()
216                .context("current_file: no selected file")?
217                .to_owned()),
218        }
219    }
220
221    pub fn selected_path(&self) -> Option<Arc<std::path::Path>> {
222        match self.display_mode {
223            Display::Tree => Some(Arc::from(self.tree.selected_path())),
224            Display::Preview if !self.preview.is_empty() => Some(self.preview.filepath()),
225            _ => Some(self.directory.selected()?.path.clone()),
226        }
227    }
228
229    /// Number of displayed element in this tab.
230    fn display_len(&self) -> usize {
231        match self.display_mode {
232            Display::Tree => self.tree.display_len(),
233            Display::Preview => self.preview.len(),
234            Display::Directory => self.directory.len(),
235            Display::Fuzzy => 0,
236        }
237    }
238
239    /// Path of the currently selected file.
240    pub fn current_file_string(&self) -> Result<String> {
241        Ok(path_to_string(
242            &self.selected_path().context("No selected path")?,
243        ))
244    }
245
246    /// Returns true if the current mode requires 2 windows.
247    /// Only Tree, Normal & Preview doesn't require 2 windows.
248    pub fn need_menu_window(&self) -> bool {
249        !matches!(self.menu_mode, Menu::Nothing)
250    }
251
252    /// Returns a string of the current directory path.
253    pub fn directory_str(&self) -> String {
254        path_to_string(&self.directory.path)
255    }
256
257    /// Refresh everything but the view
258    pub fn refresh_params(&mut self) {
259        if matches!(self.preview, Preview::Image(_)) {
260            self.settings.should_clear_image = true;
261        }
262        self.preview = PreviewBuilder::empty();
263        if self.display_mode.is_tree() {
264            self.remake_same_tree()
265        } else {
266            self.tree = Tree::default()
267        };
268    }
269
270    fn remake_same_tree(&mut self) {
271        let current_path = self.tree.selected_path().to_owned();
272        self.make_tree(None);
273        self.tree.go(To::Path(&current_path))
274    }
275
276    /// Refresh the current view.
277    /// displayed files is reset.
278    /// The first file is selected.
279    pub fn refresh_view(&mut self) -> Result<()> {
280        self.directory.reset_files(&self.settings, &self.users)?;
281        self.window.reset(self.display_len());
282        self.refresh_params();
283        Ok(())
284    }
285
286    /// Refresh the view if files were modified in current directory.
287    /// If a refresh occurs, tries to select the same file as before.
288    /// If it can't, the first file (`.`) is selected.
289    /// Does nothing in `DisplayMode::Preview`.
290    pub fn refresh_if_needed(&mut self) -> Result<()> {
291        if match self.display_mode {
292            Display::Preview => false,
293            Display::Directory => {
294                has_last_modification_happened_less_than(&self.directory.path, 10)?
295            }
296            Display::Tree => self.tree.has_modified_dirs(),
297            Display::Fuzzy => false,
298        } {
299            self.refresh_and_reselect_file()
300        } else {
301            Ok(())
302        }
303    }
304
305    /// Change the display mode.
306    pub fn set_display_mode(&mut self, new_display_mode: Display) {
307        self.reset_visual();
308        self.search.reset_paths();
309        self.reset_preview();
310        self.display_mode = new_display_mode
311    }
312
313    /// Makes a new tree of the current path.
314    fn make_tree(&mut self, sort_kind: Option<SortKind>) {
315        let sort_kind = sort_kind.unwrap_or_default();
316        self.settings.sort_kind = sort_kind;
317        let path = self.directory.path.clone();
318        let users = &self.users;
319        self.tree = TreeBuilder::new(path.clone(), users)
320            .with_hidden(self.settings.show_hidden)
321            .with_filter_kind(&self.settings.filter)
322            .with_sort_kind(sort_kind)
323            .with_max_depth(self.settings.tree_max_depth)
324            .build();
325    }
326
327    fn make_tree_for_parent(&mut self) -> Result<()> {
328        let Some(parent) = self.tree.root_path().parent() else {
329            return Ok(());
330        };
331        self.cd(parent.to_owned().as_ref())?;
332        self.make_tree(Some(self.settings.sort_kind));
333        Ok(())
334    }
335
336    /// Enter or leave display tree mode.
337    pub fn toggle_tree_mode(&mut self) -> Result<()> {
338        let current_path = self.selected_path().context("No selected_path")?;
339        if self.display_mode.is_tree() {
340            {
341                self.tree = Tree::default();
342                self.refresh_view()
343            }?;
344            self.set_display_mode(Display::Directory);
345        } else {
346            self.make_tree(None);
347            self.window.reset(self.tree.displayable().lines().len());
348            self.set_display_mode(Display::Tree);
349        }
350        self.go_to_file(current_path);
351        Ok(())
352    }
353
354    /// Creates a new preview for the selected file.
355    /// If the selected file is a directory, it will create a tree.
356    /// Does nothing if directory is empty or in flagged or preview display mode.
357    pub fn make_preview(&mut self) -> Result<()> {
358        if self.directory.is_empty() {
359            return Ok(());
360        }
361        let Ok(file_info) = self.current_file() else {
362            return Ok(());
363        };
364        match file_info.file_kind {
365            FileKind::Directory => self.toggle_tree_mode()?,
366            _ => self.make_preview_unchecked(file_info),
367        }
368
369        Ok(())
370    }
371
372    /// Creates a preview and assign it.
373    /// Doesn't check if it's the correct action to do according to display.
374    fn make_preview_unchecked(&mut self, file_info: FileInfo) {
375        let preview = PreviewBuilder::new(&file_info.path)
376            .build()
377            .unwrap_or_default();
378        self.set_display_mode(Display::Preview);
379        self.window.reset(preview.len());
380        self.preview = preview;
381    }
382
383    /// Reset the preview to empty. Used to save some memory.
384    fn reset_preview(&mut self) {
385        if matches!(self.preview, Preview::Image(_)) {
386            log_info!("Clear the image");
387            self.settings.should_clear_image = true;
388        }
389        if self.display_mode.is_preview() {
390            self.preview = PreviewBuilder::empty();
391        }
392    }
393
394    /// Refresh the folder, reselect the last selected file, move the window to it.
395    pub fn refresh_and_reselect_file(&mut self) -> Result<()> {
396        let selected_path = self.clone_selected_path()?;
397        self.refresh_view()?;
398        self.select_by_path(selected_path);
399        Ok(())
400    }
401
402    fn clone_selected_path(&self) -> Result<Arc<path::Path>> {
403        Ok(self.selected_path().context("No selected path")?.clone())
404    }
405
406    /// Select the given file from its path.
407    /// Action depends of the display mode.
408    /// For directory or tree, it selects the file and scroll to it.
409    /// when the file doesn't exists,
410    /// - in directory mode, the first file is selected;
411    /// - in tree mode, the root is selected.
412    ///
413    /// For preview or fuzzy, it does nothing
414    pub fn select_by_path(&mut self, selected_path: Arc<path::Path>) {
415        match self.display_mode {
416            Display::Directory => {
417                let index = self.directory.select_file(&selected_path);
418                self.scroll_to(index)
419            }
420            Display::Tree => {
421                self.tree.go(To::Path(&selected_path));
422                let index = self.tree.displayable().index();
423                self.scroll_to(index);
424            }
425            Display::Preview | Display::Fuzzy => (),
426        }
427    }
428
429    /// Reset the display mode and its view.
430    pub fn reset_display_mode_and_view(&mut self) -> Result<()> {
431        if self.display_mode.is_preview() {
432            self.set_display_mode(Display::Directory);
433        }
434        self.refresh_view()
435    }
436
437    pub fn set_filter(&mut self, filter: FilterKind) -> Result<()> {
438        self.settings.set_filter(filter);
439        self.directory.reset_files(&self.settings, &self.users)?;
440        if self.display_mode.is_tree() {
441            self.make_tree(None);
442        }
443        self.window.reset(self.directory.content.len());
444        Ok(())
445    }
446
447    /// Set the height of the window and itself.
448    pub fn set_height(&mut self, height: usize) {
449        self.window.set_height(height);
450        self.height = height;
451    }
452
453    /// Display or hide hidden files (filename starting with .).
454    pub fn toggle_hidden(&mut self) -> Result<()> {
455        self.settings.toggle_hidden();
456        self.directory.reset_files(&self.settings, &self.users)?;
457        self.window.reset(self.directory.content.len());
458        if self.display_mode.is_tree() {
459            self.make_tree(None)
460        }
461        Ok(())
462    }
463
464    /// Set the line index to `index` and scroll there.
465    pub fn scroll_to(&mut self, index: usize) {
466        self.window.scroll_to(index);
467    }
468
469    /// Sort the file with given criteria
470    /// Valid kind of sorts are :
471    /// by kind : directory first, files next, in alphanumeric order
472    /// by filename,
473    /// by date of modification,
474    /// by size,
475    /// by extension.
476    /// The first letter is used to identify the method.
477    /// If the user types an uppercase char, the sort is reverse.
478    pub fn sort(&mut self, c: char) -> Result<()> {
479        if self.directory.content.is_empty() {
480            return Ok(());
481        }
482        match self.display_mode {
483            Display::Directory => self.sort_directory(c)?,
484            Display::Tree => self.sort_tree(c),
485            _ => (),
486        }
487        Ok(())
488    }
489
490    fn sort_directory(&mut self, c: char) -> Result<()> {
491        let path = self.selected_path().context("No selected path")?;
492        self.settings.update_sort_from_char(c);
493        self.directory.sort(&self.settings.sort_kind);
494        self.normal_go_top();
495        self.directory.select_file(&path);
496        Ok(())
497    }
498
499    fn sort_tree(&mut self, c: char) {
500        self.settings.update_sort_from_char(c);
501        let selected_path = self.tree.selected_path().to_owned();
502        self.make_tree(Some(self.settings.sort_kind));
503        self.tree.go(To::Path(&selected_path));
504    }
505
506    pub fn set_sortkind_per_mode(&mut self) {
507        self.settings.sort_kind = match self.display_mode {
508            Display::Tree => SortKind::tree_default(),
509            _ => SortKind::default(),
510        };
511    }
512
513    pub fn cd_to_file<P>(&mut self, path: P) -> Result<()>
514    where
515        P: AsRef<path::Path>,
516    {
517        log_info!("cd_to_file: #{path}#", path = path.as_ref().display());
518        if !path.as_ref().exists() {
519            log_info!("{path} doesn't exist.", path = path.as_ref().display());
520            return Ok(());
521        }
522        let parent = match path.as_ref().parent() {
523            Some(parent) => parent,
524            None => path::Path::new("/"),
525        };
526        self.cd(parent)?;
527        self.go_to_file(path);
528        Ok(())
529    }
530
531    pub fn try_cd_to_file(&mut self, path_str: String) -> Result<bool> {
532        let path = path::Path::new(&path_str);
533        if path.exists() {
534            self.cd_to_file(path)?;
535            Ok(true)
536        } else {
537            Ok(false)
538        }
539    }
540
541    /// Set the pathcontent to a new path.
542    /// Reset the window.
543    /// Add the last path to the history of visited paths.
544    /// Does nothing in preview or flagged display mode.
545    pub fn cd(&mut self, path: &path::Path) -> Result<()> {
546        if self.display_mode.is_preview() {
547            return Ok(());
548        }
549        self.search.reset_paths();
550        match set_current_dir(path) {
551            Ok(()) => (),
552            Err(error) => {
553                log_info!("can't reach {path}. Error {error}", path = path.display());
554                return Ok(());
555            }
556        }
557        self.history
558            .push(&self.selected_path().context("No selected_path")?);
559        self.directory
560            .change_directory(path, &self.settings, &self.users)?;
561        if self.display_mode.is_tree() {
562            self.make_tree(Some(self.settings.sort_kind));
563            self.window.reset(self.tree.displayable().lines().len());
564        } else {
565            self.window.reset(self.directory.content.len());
566        }
567        update_zoxide(path)?;
568        Ok(())
569    }
570
571    pub fn back(&mut self) -> Result<()> {
572        if self.display_mode.is_preview() {
573            return Ok(());
574        }
575        if self.history.content.is_empty() {
576            return Ok(());
577        }
578        let Some(file) = self.history.content.pop() else {
579            return Ok(());
580        };
581        self.history.content.pop();
582        self.cd_to_file(&file)?;
583        Ok(())
584    }
585
586    /// Select a file in current view, either directory or tree mode.
587    pub fn go_to_file<P>(&mut self, file: P)
588    where
589        P: AsRef<path::Path>,
590    {
591        if self.display_mode.is_tree() {
592            self.tree.go(To::Path(file.as_ref()));
593        } else {
594            let index = self.directory.select_file(file.as_ref());
595            self.scroll_to(index);
596        }
597    }
598
599    /// Jump to the jump target.
600    /// Change the pathcontent and the tree if the jump target isn't in the
601    /// currently displayed files.
602    pub fn jump(&mut self, jump_target: path::PathBuf) -> Result<()> {
603        let target_dir = match jump_target.parent() {
604            Some(parent) => parent,
605            None => &jump_target,
606        };
607        match self.display_mode {
608            Display::Preview => return Ok(()),
609            Display::Directory => self.jump_directory(&jump_target, target_dir)?,
610            Display::Tree => self.jump_tree(&jump_target, target_dir)?,
611            Display::Fuzzy => return Ok(()),
612        }
613        Ok(())
614    }
615
616    fn jump_directory(&mut self, jump_target: &path::Path, target_dir: &path::Path) -> Result<()> {
617        if !self.directory.paths().contains(&jump_target) {
618            self.cd(target_dir)?
619        }
620        let index = self.directory.select_file(jump_target);
621        self.scroll_to(index);
622        Ok(())
623    }
624
625    fn jump_tree(&mut self, jump_target: &path::Path, target_dir: &path::Path) -> Result<()> {
626        if !self.tree.paths().contains(&target_dir) {
627            self.cd(target_dir)?;
628            self.make_tree(None);
629        }
630        self.tree.go(To::Path(jump_target));
631        Ok(())
632    }
633
634    /// Move back to a previously visited path.
635    /// It may fail if the user has no permission to visit the path
636    pub fn history_cd_to_last(&mut self) -> Result<()> {
637        let Some(file) = self.history.selected() else {
638            return Ok(());
639        };
640        let file = file.to_owned();
641        self.cd_to_file(&file)?;
642        self.history.drop_queue();
643        Ok(())
644    }
645
646    /// Move to the parent of current path
647    pub fn move_to_parent(&mut self) -> Result<()> {
648        let path = self.directory.path.clone();
649        let Some(parent) = path.parent() else {
650            return Ok(());
651        };
652        if self.history.is_this_the_last(parent) {
653            self.back()?;
654            return Ok(());
655        }
656        self.cd_to_file(&path)
657    }
658
659    /// Select the file at index and move the window to this file.
660    pub fn go_to_index(&mut self, index: usize) {
661        self.directory.select_index(index);
662        self.window.scroll_to(index);
663    }
664
665    /// Move to the currently selected directory.
666    /// Fail silently if the current directory is empty or if the selected
667    /// file isn't a directory.
668    pub fn go_to_selected_dir(&mut self) -> Result<()> {
669        self.cd(&self
670            .directory
671            .selected()
672            .context("Empty directory")?
673            .path
674            .clone())?;
675        Ok(())
676    }
677
678    /// Move down one row if possible.
679    pub fn normal_down_one_row(&mut self) {
680        self.directory.next();
681        self.window.scroll_down_one(self.directory.index)
682    }
683
684    /// Move up one row if possible.
685    pub fn normal_up_one_row(&mut self) {
686        self.directory.prev();
687        self.window.scroll_up_one(self.directory.index)
688    }
689
690    /// Move to the top of the current directory.
691    pub fn normal_go_top(&mut self) {
692        self.directory.select_index(0);
693        self.window.scroll_to(0)
694    }
695
696    /// Move to the bottom of current view.
697    pub fn normal_go_bottom(&mut self) {
698        let last_index = self.directory.content.len() - 1;
699        self.directory.select_index(last_index);
700        self.window.scroll_to(last_index)
701    }
702
703    /// Move 10 files up
704    pub fn normal_page_up(&mut self) {
705        let up_index = self.directory.index.saturating_sub(10);
706        self.directory.select_index(up_index);
707        self.window.scroll_to(up_index)
708    }
709
710    /// Move down 10 rows
711    pub fn normal_page_down(&mut self) {
712        let down_index = min(self.directory.content.len() - 1, self.directory.index + 10);
713        self.directory.select_index(down_index);
714        self.window.scroll_to(down_index);
715    }
716
717    /// Fold every child node in the tree.
718    /// Recursively explore the tree and fold every node. Reset the display.
719    pub fn tree_go_to_root(&mut self) -> Result<()> {
720        self.tree.go(To::Root);
721        self.window.scroll_to(0);
722        Ok(())
723    }
724
725    /// Select the parent of current node.
726    /// If we were at the root node, move to the parent and make a new tree.
727    pub fn tree_select_parent(&mut self) -> Result<()> {
728        if self.tree.is_on_root() {
729            self.make_tree_for_parent()?;
730        } else {
731            self.tree.go(To::Parent);
732        }
733        self.window.scroll_to(self.tree.displayable().index());
734        Ok(())
735    }
736
737    /// Move down 10 times in the tree
738    pub fn tree_page_down(&mut self) {
739        self.tree.page_down();
740        self.window.scroll_to(self.tree.displayable().index());
741    }
742
743    /// Move up 10 times in the tree
744    pub fn tree_page_up(&mut self) {
745        self.tree.page_up();
746        self.window.scroll_to(self.tree.displayable().index());
747    }
748
749    /// Select the next sibling.
750    pub fn tree_select_next(&mut self) {
751        self.tree.go(To::Next);
752        self.window.scroll_down_one(self.tree.displayable().index());
753    }
754
755    /// Select the previous siblging
756    pub fn tree_select_prev(&mut self) {
757        self.tree.go(To::Prev);
758        self.window.scroll_up_one(self.tree.displayable().index());
759    }
760
761    /// Go to the last leaf.
762    pub fn tree_go_to_bottom_leaf(&mut self) {
763        self.tree.go(To::Last);
764        self.window.scroll_to(self.tree.displayable().index());
765    }
766
767    /// Navigate to the next sibling of current file in tree mode.
768    pub fn tree_next_sibling(&mut self) {
769        self.tree.go(To::NextSibling);
770        self.window.scroll_to(self.tree.displayable().index());
771    }
772
773    /// Navigate to the previous sibling of current file in tree mode.
774    pub fn tree_prev_sibling(&mut self) {
775        self.tree.go(To::PreviousSibling);
776        self.window.scroll_to(self.tree.displayable().index());
777    }
778
779    pub fn tree_enter_dir(&mut self, path: std::sync::Arc<path::Path>) -> Result<()> {
780        self.cd(&path)?;
781        self.make_tree(None);
782        self.set_display_mode(Display::Tree);
783        Ok(())
784    }
785
786    /// Move the preview to the top
787    pub fn preview_go_top(&mut self) {
788        self.window.scroll_to(0)
789    }
790
791    /// Move the preview to the bottom
792    pub fn preview_go_bottom(&mut self) {
793        self.window.scroll_to(self.preview.len().saturating_sub(1))
794    }
795
796    fn preview_scroll(&self) -> usize {
797        if matches!(self.menu_mode, Menu::Nothing) {
798            2 * self.height / 3
799        } else {
800            self.height / 3
801        }
802    }
803
804    fn preview_binary_scroll(&self) -> usize {
805        if matches!(self.menu_mode, Menu::Nothing) {
806            self.height / 3
807        } else {
808            self.height / 6
809        }
810    }
811
812    /// Move 30 lines up or an image in Ueberzug.
813    pub fn preview_page_up(&mut self) {
814        match &mut self.preview {
815            Preview::Image(ref mut image) => image.up_one_row(),
816            Preview::Binary(_) => self.window.preview_page_up(self.preview_binary_scroll()),
817            _ => self.window.preview_page_up(self.preview_scroll()),
818        }
819    }
820
821    /// Move down 30 rows except for Ueberzug where it moves 1 image down
822    pub fn preview_page_down(&mut self) {
823        let len = self.preview.len();
824        match &mut self.preview {
825            Preview::Image(ref mut image) => image.down_one_row(),
826            Preview::Binary(_) => self
827                .window
828                .preview_page_down(self.preview_binary_scroll(), len),
829            _ => self.window.preview_page_down(self.preview_scroll(), len),
830        }
831    }
832
833    /// Select a clicked row in display directory
834    pub fn normal_select_row(&mut self, row: u16) {
835        let screen_index = row_to_window_index(row);
836        let index = screen_index + self.window.top;
837        self.directory.select_index(index);
838        self.window.scroll_to(index);
839    }
840
841    /// Select a clicked row in display tree
842    pub fn tree_select_row(&mut self, row: u16) -> Result<()> {
843        let screen_index = row_to_window_index(row);
844        let displayable = self.tree.displayable();
845        let index = screen_index + self.window.top;
846        let path = displayable
847            .lines()
848            .get(index)
849            .context("tree: no selected file")?
850            .path()
851            .to_owned();
852        self.tree.go(To::Path(&path));
853        Ok(())
854    }
855
856    pub fn completion_search_files(&mut self) -> Vec<String> {
857        match self.display_mode {
858            Display::Directory => self.search.matches_from(self.directory.content()),
859            Display::Tree => self.search.matches_from(self.tree.displayable().content()),
860            Display::Preview => vec![],
861            Display::Fuzzy => vec![],
862        }
863    }
864
865    pub fn directory_search_next(&mut self) {
866        if let Some(path) = self.search.select_next() {
867            self.go_to_file(path)
868        } else if let Some(path) = self
869            .search
870            .directory_search_next(self.directory.index_to_index())
871        {
872            self.go_to_file(path);
873        }
874    }
875
876    pub fn dir_enum_skip_take(&self) -> Take<Skip<Enumerate<slice::Iter<'_, FileInfo>>>> {
877        let len = self.directory.content.len();
878        self.directory
879            .enumerate()
880            .skip(self.window.top)
881            .take(min(len, self.window.height))
882    }
883
884    pub fn cd_origin_path(&mut self) -> Result<()> {
885        if let Some(op) = &self.origin_path {
886            self.cd_to_file(op.clone())?;
887        }
888        Ok(())
889    }
890
891    pub fn save_origin_path(&mut self) {
892        self.origin_path = Some(self.current_directory_path().to_owned());
893    }
894
895    pub fn toggle_visual(&mut self) {
896        if matches!(self.display_mode, Display::Directory | Display::Tree) {
897            self.visual = !self.visual;
898        } else {
899            self.reset_visual();
900        }
901    }
902
903    pub fn reset_visual(&mut self) {
904        self.visual = false
905    }
906}