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};
13use crate::config::START_FOLDER;
14use crate::io::Args;
15use crate::log_info;
16use crate::modes::{
17 Content, ContentWindow, Directory, Display, FileInfo, FileKind, FilterKind, Go, History,
18 IndexToIndex, Menu, Preview, PreviewBuilder, Search, Selectable, SortKind, To, Tree,
19 TreeBuilder, Users,
20};
21
22pub struct TabSettings {
28 pub show_hidden: bool,
30 pub filter: FilterKind,
32 pub sort_kind: SortKind,
34 pub should_clear_image: bool,
36}
37
38impl TabSettings {
39 fn new(args: &Args) -> Self {
40 let filter = FilterKind::All;
41 let show_hidden = args.all;
42 let sort_kind = SortKind::default();
43 let should_clear_image = false;
44 Self {
45 show_hidden,
46 filter,
47 sort_kind,
48 should_clear_image,
49 }
50 }
51
52 fn toggle_hidden(&mut self) {
53 self.show_hidden = !self.show_hidden;
54 }
55
56 pub fn set_filter(&mut self, filter: FilterKind) {
58 self.filter = filter
59 }
60
61 pub fn reset_filter(&mut self) {
62 self.filter = FilterKind::All;
63 }
64
65 fn update_sort_from_char(&mut self, c: char) {
67 self.sort_kind.update_from_char(c)
68 }
69}
70
71pub struct Tab {
74 pub display_mode: Display,
76
77 pub directory: Directory,
79 pub tree: Tree,
81 pub preview: Preview,
84
85 pub menu_mode: Menu,
88
89 pub window: ContentWindow,
91 pub height: usize,
93
94 pub settings: TabSettings,
99
100 pub search: Search,
102 pub history: History,
105 pub users: Users,
107 pub origin_path: Option<std::path::PathBuf>,
110 pub visual: bool,
111}
112
113impl Tab {
114 pub fn new(args: &Args, height: usize, users: Users) -> Result<Self> {
130 let path = &START_FOLDER.get().context("Startfolder should be set")?;
131 let start_dir = Self::start_dir(path)?;
132 let settings = TabSettings::new(args);
133 let mut directory =
134 Directory::new(start_dir, &users, &settings.filter, settings.show_hidden)?;
135 let display_mode = Display::default();
136 let menu_mode = Menu::Nothing;
137 let mut window = ContentWindow::new(directory.content.len(), height);
138 let preview = Preview::Empty;
139 let history = History::default();
140 let search = Search::empty();
141 let index = directory.select_file(path);
142 let tree = Tree::default();
143 let origin_path = None;
144 let visual = false;
145
146 window.scroll_to(index);
147 Ok(Self {
148 display_mode,
149 menu_mode,
150 window,
151 directory,
152 height,
153 preview,
154 search,
155 history,
156 users,
157 tree,
158 settings,
159 origin_path,
160 visual,
161 })
162 }
163
164 fn start_dir(path: &path::Path) -> Result<&path::Path> {
165 if path.is_dir() {
166 Ok(path)
167 } else {
168 Ok(path.parent().context("Path has no parent")?)
169 }
170 }
171
172 pub fn directory_of_selected(&self) -> Result<&path::Path> {
177 match self.display_mode {
178 Display::Tree => self.tree.directory_of_selected().context("No parent"),
179 _ => Ok(&self.directory.path),
180 }
181 }
182
183 pub fn current_path(&self) -> &path::Path {
185 self.directory.path.borrow()
186 }
187
188 pub fn current_file(&self) -> Result<FileInfo> {
190 match self.display_mode {
191 Display::Tree => {
192 FileInfo::new(self.tree.selected_node_or_parent()?.path(), &self.users)
193 }
194 _ => Ok(self
195 .directory
196 .selected()
197 .context("current_file: no selected file")?
198 .to_owned()),
199 }
200 }
201
202 fn display_len(&self) -> usize {
204 match self.display_mode {
205 Display::Tree => self.tree.display_len(),
206 Display::Preview => self.preview.len(),
207 Display::Directory => self.directory.len(),
208 Display::Fuzzy => 0,
209 }
210 }
211
212 pub fn current_file_string(&self) -> Result<String> {
214 Ok(path_to_string(&self.current_file()?.path))
215 }
216
217 pub fn need_menu_window(&self) -> bool {
220 !matches!(self.menu_mode, Menu::Nothing)
221 }
222
223 pub fn directory_str(&self) -> String {
225 path_to_string(&self.directory.path)
226 }
227
228 pub fn refresh_params(&mut self) {
230 if matches!(self.preview, Preview::Image(_)) {
231 self.settings.should_clear_image = true;
232 }
233 self.preview = PreviewBuilder::empty();
234 if self.display_mode.is_tree() {
235 self.remake_same_tree()
236 } else {
237 self.tree = Tree::default()
238 };
239 }
240
241 fn remake_same_tree(&mut self) {
242 let current_path = self.tree.selected_path().to_owned();
243 self.make_tree(None);
244 self.tree.go(To::Path(¤t_path))
245 }
246
247 pub fn refresh_view(&mut self) -> Result<()> {
251 self.directory.reset_files(&self.settings, &self.users)?;
252 self.window.reset(self.display_len());
253 self.refresh_params();
254 Ok(())
255 }
256
257 pub fn refresh_if_needed(&mut self) -> Result<()> {
262 if match self.display_mode {
263 Display::Preview => false,
264 Display::Directory => {
265 has_last_modification_happened_less_than(&self.directory.path, 10)?
266 }
267 Display::Tree => self.tree.has_modified_dirs(),
268 Display::Fuzzy => false,
269 } {
270 self.refresh_and_reselect_file()
271 } else {
272 Ok(())
273 }
274 }
275
276 pub fn set_display_mode(&mut self, new_display_mode: Display) {
278 self.reset_visual();
279 self.search.reset_paths();
280 self.reset_preview();
281 self.display_mode = new_display_mode
282 }
283
284 pub fn make_tree(&mut self, sort_kind: Option<SortKind>) {
286 let sort_kind = sort_kind.unwrap_or_default();
287 self.settings.sort_kind = sort_kind;
288 let path = self.directory.path.clone();
289 let users = &self.users;
290 self.tree = TreeBuilder::new(path.clone(), users)
291 .with_hidden(self.settings.show_hidden)
292 .with_filter_kind(&self.settings.filter)
293 .with_sort_kind(sort_kind)
294 .build();
295 }
296
297 fn make_tree_for_parent(&mut self) -> Result<()> {
298 let Some(parent) = self.tree.root_path().parent() else {
299 return Ok(());
300 };
301 self.cd(parent.to_owned().as_ref())?;
302 self.make_tree(Some(self.settings.sort_kind));
303 Ok(())
304 }
305
306 pub fn toggle_tree_mode(&mut self) -> Result<()> {
308 let current_file = self.current_file()?;
309 if self.display_mode.is_tree() {
310 {
311 self.tree = Tree::default();
312 self.refresh_view()
313 }?;
314 self.set_display_mode(Display::Directory);
315 } else {
316 self.make_tree(None);
317 self.window.reset(self.tree.displayable().lines().len());
318 self.set_display_mode(Display::Tree);
319 }
320 self.go_to_file(current_file.path);
321 Ok(())
322 }
323
324 pub fn make_preview(&mut self) -> Result<()> {
328 if self.directory.is_empty() {
329 return Ok(());
330 }
331 let Ok(file_info) = self.current_file() else {
332 return Ok(());
333 };
334 match file_info.file_kind {
335 FileKind::Directory => self.toggle_tree_mode()?,
336 _ => self.make_preview_unchecked(file_info),
337 }
338
339 Ok(())
340 }
341
342 fn make_preview_unchecked(&mut self, file_info: FileInfo) {
345 let preview = PreviewBuilder::new(&file_info.path)
346 .build()
347 .unwrap_or_default();
348 self.set_display_mode(Display::Preview);
349 self.window.reset(preview.len());
350 self.preview = preview;
351 }
352
353 fn reset_preview(&mut self) {
355 log_info!(
356 "tab.reset_preview. prev = {prev}",
357 prev = self.preview.kind_display()
358 );
359 if matches!(self.preview, Preview::Image(_)) {
360 log_info!("Clear the image");
361 self.settings.should_clear_image = true;
362 }
363 if self.display_mode.is_preview() {
364 self.preview = PreviewBuilder::empty();
365 }
366 }
367
368 pub fn refresh_and_reselect_file(&mut self) -> Result<()> {
370 let selected_path = self.clone_selected_path()?;
371 self.refresh_view()?;
372 self.select_by_path(selected_path);
373 Ok(())
374 }
375
376 fn clone_selected_path(&self) -> Result<Arc<path::Path>> {
377 Ok(self
378 .current_file()
379 .context("refresh: no selected file")?
380 .path
381 .clone())
382 }
383
384 pub fn select_by_path(&mut self, selected_path: Arc<path::Path>) {
393 match self.display_mode {
394 Display::Directory => {
395 let index = self.directory.select_file(&selected_path);
396 self.scroll_to(index)
397 }
398 Display::Tree => {
399 self.tree.go(To::Path(&selected_path));
400 let index = self.tree.displayable().index();
401 self.scroll_to(index);
402 }
403 Display::Preview | Display::Fuzzy => (),
404 }
405 }
406
407 pub fn reset_display_mode_and_view(&mut self) -> Result<()> {
409 if self.display_mode.is_preview() {
410 self.set_display_mode(Display::Directory);
411 }
412 self.refresh_view()
413 }
414
415 pub fn set_filter(&mut self, filter: FilterKind) -> Result<()> {
416 self.settings.set_filter(filter);
417 self.directory.reset_files(&self.settings, &self.users)?;
418 if self.display_mode.is_tree() {
419 self.make_tree(None);
420 }
421 self.window.reset(self.directory.content.len());
422 Ok(())
423 }
424
425 pub fn set_height(&mut self, height: usize) {
427 self.window.set_height(height);
428 self.height = height;
429 }
430
431 pub fn toggle_hidden(&mut self) -> Result<()> {
433 self.settings.toggle_hidden();
434 self.directory.reset_files(&self.settings, &self.users)?;
435 self.window.reset(self.directory.content.len());
436 if self.display_mode.is_tree() {
437 self.make_tree(None)
438 }
439 Ok(())
440 }
441
442 pub fn scroll_to(&mut self, index: usize) {
444 self.window.scroll_to(index);
445 }
446
447 pub fn sort(&mut self, c: char) -> Result<()> {
457 if self.directory.content.is_empty() {
458 return Ok(());
459 }
460 match self.display_mode {
461 Display::Directory => self.sort_directory(c)?,
462 Display::Tree => self.sort_tree(c),
463 _ => (),
464 }
465 Ok(())
466 }
467
468 fn sort_directory(&mut self, c: char) -> Result<()> {
469 let path = self.current_file()?.path;
470 self.settings.update_sort_from_char(c);
471 self.directory.sort(&self.settings.sort_kind);
472 self.normal_go_top();
473 self.directory.select_file(&path);
474 Ok(())
475 }
476
477 fn sort_tree(&mut self, c: char) {
478 self.settings.update_sort_from_char(c);
479 let selected_path = self.tree.selected_path().to_owned();
480 self.make_tree(Some(self.settings.sort_kind));
481 self.tree.go(To::Path(&selected_path));
482 }
483
484 pub fn set_sortkind_per_mode(&mut self) {
485 self.settings.sort_kind = match self.display_mode {
486 Display::Tree => SortKind::tree_default(),
487 _ => SortKind::default(),
488 };
489 }
490
491 pub fn cd_to_file(&mut self, path: &path::Path) -> Result<()> {
492 crate::log_info!("cd_to_file: {path}", path = path.display());
493 let parent = match path.parent() {
494 Some(parent) => parent,
495 None => std::path::Path::new("/"),
496 };
497 self.cd(parent)?;
498 self.go_to_file(path);
499 Ok(())
500 }
501
502 pub fn try_cd_to_file(&mut self, path_str: String) -> Result<bool> {
503 let path = path::Path::new(&path_str);
504 if path.exists() {
505 self.cd_to_file(path)?;
506 Ok(true)
507 } else {
508 Ok(false)
509 }
510 }
511
512 pub fn cd(&mut self, path: &path::Path) -> Result<()> {
517 if self.display_mode.is_preview() {
518 return Ok(());
519 }
520 self.search.reset_paths();
521 match set_current_dir(path) {
522 Ok(()) => (),
523 Err(error) => {
524 log_info!("can't reach {path}. Error {error}", path = path.display());
525 return Ok(());
526 }
527 }
528 self.history.push(&self.current_file()?.path);
529 self.directory
530 .change_directory(path, &self.settings, &self.users)?;
531 if self.display_mode.is_tree() {
532 self.make_tree(Some(self.settings.sort_kind));
533 self.window.reset(self.tree.displayable().lines().len());
534 } else {
535 self.window.reset(self.directory.content.len());
536 }
537 Ok(())
538 }
539
540 pub fn back(&mut self) -> Result<()> {
541 if self.display_mode.is_preview() {
542 return Ok(());
543 }
544 if self.history.content.is_empty() {
545 return Ok(());
546 }
547 let Some(file) = self.history.content.pop() else {
548 return Ok(());
549 };
550 self.history.content.pop();
551 self.cd_to_file(&file)?;
552 Ok(())
553 }
554
555 pub fn go_to_file<P>(&mut self, file: P)
557 where
558 P: AsRef<path::Path>,
559 {
560 if self.display_mode.is_preview() {
561 self.tree.go(To::Path(file.as_ref()));
562 } else {
563 let index = self.directory.select_file(file.as_ref());
564 self.scroll_to(index);
565 }
566 }
567
568 pub fn jump(&mut self, jump_target: path::PathBuf) -> Result<()> {
572 let target_dir = match jump_target.parent() {
573 Some(parent) => parent,
574 None => &jump_target,
575 };
576 match self.display_mode {
577 Display::Preview => return Ok(()),
578 Display::Directory => self.jump_directory(&jump_target, target_dir)?,
579 Display::Tree => self.jump_tree(&jump_target, target_dir)?,
580 Display::Fuzzy => return Ok(()),
581 }
582 Ok(())
583 }
584
585 fn jump_directory(&mut self, jump_target: &path::Path, target_dir: &path::Path) -> Result<()> {
586 if !self.directory.paths().contains(&jump_target) {
587 self.cd(target_dir)?
588 }
589 let index = self.directory.select_file(jump_target);
590 self.scroll_to(index);
591 Ok(())
592 }
593
594 fn jump_tree(&mut self, jump_target: &path::Path, target_dir: &path::Path) -> Result<()> {
595 if !self.tree.paths().contains(&target_dir) {
596 self.cd(target_dir)?;
597 self.make_tree(None);
598 }
599 self.tree.go(To::Path(jump_target));
600 Ok(())
601 }
602
603 pub fn history_cd_to_last(&mut self) -> Result<()> {
606 let Some(file) = self.history.selected() else {
607 return Ok(());
608 };
609 let file = file.to_owned();
610 self.cd_to_file(&file)?;
611 self.history.drop_queue();
612 Ok(())
613 }
614
615 pub fn move_to_parent(&mut self) -> Result<()> {
617 let path = self.directory.path.clone();
618 let Some(parent) = path.parent() else {
619 return Ok(());
620 };
621 if self.history.is_this_the_last(parent) {
622 self.back()?;
623 return Ok(());
624 }
625 self.cd_to_file(&path)
626 }
627
628 pub fn go_to_index(&mut self, index: usize) {
630 self.directory.select_index(index);
631 self.window.scroll_to(index);
632 }
633
634 pub fn go_to_selected_dir(&mut self) -> Result<()> {
638 self.cd(&self
639 .directory
640 .selected()
641 .context("Empty directory")?
642 .path
643 .clone())?;
644 Ok(())
645 }
646
647 pub fn normal_down_one_row(&mut self) {
649 self.directory.next();
650 self.window.scroll_down_one(self.directory.index)
651 }
652
653 pub fn normal_up_one_row(&mut self) {
655 self.directory.prev();
656 self.window.scroll_up_one(self.directory.index)
657 }
658
659 pub fn normal_go_top(&mut self) {
661 self.directory.select_index(0);
662 self.window.scroll_to(0)
663 }
664
665 pub fn normal_go_bottom(&mut self) {
667 let last_index = self.directory.content.len() - 1;
668 self.directory.select_index(last_index);
669 self.window.scroll_to(last_index)
670 }
671
672 pub fn normal_page_up(&mut self) {
674 let up_index = self.directory.index.saturating_sub(10);
675 self.directory.select_index(up_index);
676 self.window.scroll_to(up_index)
677 }
678
679 pub fn normal_page_down(&mut self) {
681 let down_index = min(self.directory.content.len() - 1, self.directory.index + 10);
682 self.directory.select_index(down_index);
683 self.window.scroll_to(down_index);
684 }
685
686 pub fn tree_go_to_root(&mut self) -> Result<()> {
689 self.tree.go(To::Root);
690 self.window.scroll_to(0);
691 Ok(())
692 }
693
694 pub fn tree_select_parent(&mut self) -> Result<()> {
697 if self.tree.is_on_root() {
698 self.make_tree_for_parent()?;
699 } else {
700 self.tree.go(To::Parent);
701 }
702 self.window.scroll_to(self.tree.displayable().index());
703 Ok(())
704 }
705
706 pub fn tree_page_down(&mut self) {
708 self.tree.page_down();
709 self.window.scroll_to(self.tree.displayable().index());
710 }
711
712 pub fn tree_page_up(&mut self) {
714 self.tree.page_up();
715 self.window.scroll_to(self.tree.displayable().index());
716 }
717
718 pub fn tree_select_next(&mut self) {
720 self.tree.go(To::Next);
721 self.window.scroll_down_one(self.tree.displayable().index());
722 }
723
724 pub fn tree_select_prev(&mut self) {
726 self.tree.go(To::Prev);
727 self.window.scroll_up_one(self.tree.displayable().index());
728 }
729
730 pub fn tree_go_to_bottom_leaf(&mut self) {
732 self.tree.go(To::Last);
733 self.window.scroll_to(self.tree.displayable().index());
734 }
735
736 pub fn tree_next_sibling(&mut self) {
738 self.tree.go(To::NextSibling);
739 self.window.scroll_to(self.tree.displayable().index());
740 }
741
742 pub fn tree_prev_sibling(&mut self) {
744 self.tree.go(To::PreviousSibling);
745 self.window.scroll_to(self.tree.displayable().index());
746 }
747
748 pub fn tree_enter_dir(&mut self, path: std::sync::Arc<path::Path>) -> Result<()> {
749 self.cd(&path)?;
750 self.make_tree(None);
751 self.set_display_mode(Display::Tree);
752 Ok(())
753 }
754
755 pub fn preview_go_top(&mut self) {
757 self.window.scroll_to(0)
758 }
759
760 pub fn preview_go_bottom(&mut self) {
762 self.window.scroll_to(self.preview.len().saturating_sub(1))
763 }
764
765 fn preview_scroll(&self) -> usize {
766 if matches!(self.menu_mode, Menu::Nothing) {
767 2 * self.height / 3
768 } else {
769 self.height / 3
770 }
771 }
772
773 fn preview_binary_scroll(&self) -> usize {
774 if matches!(self.menu_mode, Menu::Nothing) {
775 self.height / 3
776 } else {
777 self.height / 6
778 }
779 }
780
781 pub fn preview_page_up(&mut self) {
783 match &mut self.preview {
784 Preview::Image(ref mut image) => image.up_one_row(),
785 Preview::Binary(_) => self.window.preview_page_up(self.preview_binary_scroll()),
786 _ => self.window.preview_page_up(self.preview_scroll()),
787 }
788 }
789
790 pub fn preview_page_down(&mut self) {
792 let len = self.preview.len();
793 match &mut self.preview {
794 Preview::Image(ref mut image) => image.down_one_row(),
795 Preview::Binary(_) => self
796 .window
797 .preview_page_down(self.preview_binary_scroll(), len),
798 _ => self.window.preview_page_down(self.preview_scroll(), len),
799 }
800 }
801
802 pub fn normal_select_row(&mut self, row: u16) {
804 let screen_index = row_to_window_index(row);
805 let index = screen_index + self.window.top;
806 self.directory.select_index(index);
807 self.window.scroll_to(index);
808 }
809
810 pub fn tree_select_row(&mut self, row: u16) -> Result<()> {
812 let screen_index = row_to_window_index(row);
813 let displayable = self.tree.displayable();
814 let index = screen_index + self.window.top;
815 let path = displayable
816 .lines()
817 .get(index)
818 .context("tree: no selected file")?
819 .path()
820 .to_owned();
821 self.tree.go(To::Path(&path));
822 Ok(())
823 }
824
825 pub fn completion_search_files(&mut self) -> Vec<String> {
826 match self.display_mode {
827 Display::Directory => self.search.matches_from(self.directory.content()),
828 Display::Tree => self.search.matches_from(self.tree.displayable().content()),
829 Display::Preview => vec![],
830 Display::Fuzzy => vec![],
831 }
832 }
833
834 pub fn directory_search_next(&mut self) {
835 if let Some(path) = self.search.select_next() {
836 self.go_to_file(path)
837 } else if let Some(path) = self
838 .search
839 .directory_search_next(self.directory.index_to_index())
840 {
841 self.go_to_file(path);
842 }
843 }
844
845 pub fn dir_enum_skip_take(&self) -> Take<Skip<Enumerate<slice::Iter<FileInfo>>>> {
846 let len = self.directory.content.len();
847 self.directory
848 .enumerate()
849 .skip(self.window.top)
850 .take(min(len, self.window.height))
851 }
852
853 pub fn cd_origin_path(&mut self) -> Result<()> {
854 if let Some(op) = &self.origin_path {
855 self.cd_to_file(&op.to_owned())?;
856 }
857 Ok(())
858 }
859
860 pub fn save_origin_path(&mut self) {
861 self.origin_path = Some(self.current_path().to_owned());
862 }
863
864 pub fn toggle_visual(&mut self) {
865 if matches!(self.display_mode, Display::Directory | Display::Tree) {
866 self.visual = !self.visual;
867 } else {
868 self.reset_visual();
869 }
870 }
871
872 pub fn reset_visual(&mut self) {
873 self.visual = false
874 }
875}