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
23pub struct TabSettings {
29 pub show_hidden: bool,
31 pub filter: FilterKind,
33 pub sort_kind: SortKind,
35 pub should_clear_image: bool,
37 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 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 fn update_sort_from_char(&mut self, c: char) {
72 self.sort_kind.update_from_char(c)
73 }
74}
75
76pub struct Tab {
79 pub display_mode: Display,
81
82 pub directory: Directory,
84 pub tree: Tree,
86 pub preview: Preview,
89
90 pub menu_mode: Menu,
93
94 pub window: ContentWindow,
96 pub height: usize,
98
99 pub settings: TabSettings,
104
105 pub search: Search,
107 pub history: History,
110 pub users: Users,
112 pub origin_path: Option<std::path::PathBuf>,
115 pub visual: bool,
116}
117
118impl Tab {
119 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 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 pub fn current_directory_path(&self) -> &path::Path {
190 self.directory.path.borrow()
191 }
192
193 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 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 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 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 pub fn need_menu_window(&self) -> bool {
248 !matches!(self.menu_mode, Menu::Nothing)
249 }
250
251 pub fn directory_str(&self) -> String {
253 path_to_string(&self.directory.path)
254 }
255
256 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(¤t_path))
273 }
274
275 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 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 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 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 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 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 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 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 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 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 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 pub fn set_height(&mut self, height: usize) {
448 self.window.set_height(height);
449 self.height = height;
450 }
451
452 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 pub fn scroll_to(&mut self, index: usize) {
465 self.window.scroll_to(index);
466 }
467
468 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 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 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 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 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 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 pub fn go_to_index(&mut self, index: usize) {
660 self.directory.select_index(index);
661 self.window.scroll_to(index);
662 }
663
664 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 pub fn normal_down_one_row(&mut self) {
679 self.directory.next();
680 self.window.scroll_down_one(self.directory.index)
681 }
682
683 pub fn normal_up_one_row(&mut self) {
685 self.directory.prev();
686 self.window.scroll_up_one(self.directory.index)
687 }
688
689 pub fn normal_go_top(&mut self) {
691 self.directory.select_index(0);
692 self.window.scroll_to(0)
693 }
694
695 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 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 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 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 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 pub fn tree_page_down(&mut self) {
738 self.tree.page_down();
739 self.window.scroll_to(self.tree.displayable().index());
740 }
741
742 pub fn tree_page_up(&mut self) {
744 self.tree.page_up();
745 self.window.scroll_to(self.tree.displayable().index());
746 }
747
748 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 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 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 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 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 pub fn preview_go_top(&mut self) {
787 self.window.scroll_to(0)
788 }
789
790 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 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 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 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 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}