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