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,
117}
118
119impl Tab {
120 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 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 pub fn current_directory_path(&self) -> &path::Path {
191 self.directory.path.borrow()
192 }
193
194 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 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 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 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 pub fn need_menu_window(&self) -> bool {
249 !matches!(self.menu_mode, Menu::Nothing)
250 }
251
252 pub fn directory_str(&self) -> String {
254 path_to_string(&self.directory.path)
255 }
256
257 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(¤t_path))
274 }
275
276 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 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 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 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 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 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 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 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 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 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 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 pub fn set_height(&mut self, height: usize) {
449 self.window.set_height(height);
450 self.height = height;
451 }
452
453 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 pub fn scroll_to(&mut self, index: usize) {
466 self.window.scroll_to(index);
467 }
468
469 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 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 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 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 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 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 pub fn go_to_index(&mut self, index: usize) {
661 self.directory.select_index(index);
662 self.window.scroll_to(index);
663 }
664
665 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 pub fn normal_down_one_row(&mut self) {
680 self.directory.next();
681 self.window.scroll_down_one(self.directory.index)
682 }
683
684 pub fn normal_up_one_row(&mut self) {
686 self.directory.prev();
687 self.window.scroll_up_one(self.directory.index)
688 }
689
690 pub fn normal_go_top(&mut self) {
692 self.directory.select_index(0);
693 self.window.scroll_to(0)
694 }
695
696 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 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 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 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 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 pub fn tree_page_down(&mut self) {
739 self.tree.page_down();
740 self.window.scroll_to(self.tree.displayable().index());
741 }
742
743 pub fn tree_page_up(&mut self) {
745 self.tree.page_up();
746 self.window.scroll_to(self.tree.displayable().index());
747 }
748
749 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 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 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 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 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 pub fn preview_go_top(&mut self) {
788 self.window.scroll_to(0)
789 }
790
791 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 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 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 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 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}