diff options
author | qkzk <qu3nt1n@gmail.com> | 2023-11-05 02:09:54 +0100 |
---|---|---|
committer | qkzk <qu3nt1n@gmail.com> | 2023-11-05 02:09:54 +0100 |
commit | bd1cb2b3f325ddaa019972762aa32fd4db286eaf (patch) | |
tree | 93bc7c0761d88e783974fa5c8dd5c1e3d520befe /src | |
parent | efe46e9abb71c6cfc6a4c56a15920ec73183f0a0 (diff) |
doesn't work yet, is way quicker
Diffstat (limited to 'src')
-rw-r--r-- | src/fileinfo.rs | 2 | ||||
-rw-r--r-- | src/lib.rs | 1 | ||||
-rw-r--r-- | src/status.rs | 1 | ||||
-rw-r--r-- | src/tab.rs | 16 | ||||
-rw-r--r-- | src/term_manager.rs | 38 | ||||
-rw-r--r-- | src/tree.rs | 2 | ||||
-rw-r--r-- | src/trees.rs | 368 |
7 files changed, 424 insertions, 4 deletions
diff --git a/src/fileinfo.rs b/src/fileinfo.rs index f4156c3..335ae9e 100644 --- a/src/fileinfo.rs +++ b/src/fileinfo.rs @@ -158,7 +158,7 @@ pub struct FileInfo { } impl FileInfo { - fn new(path: &path::Path, users: &Users) -> Result<Self> { + pub fn new(path: &path::Path, users: &Users) -> Result<Self> { let filename = extract_filename(path)?; let metadata = symlink_metadata(path)?; let path = path.to_owned(); @@ -45,5 +45,6 @@ pub mod tab; pub mod term_manager; pub mod trash; pub mod tree; +pub mod trees; pub mod users; pub mod utils; diff --git a/src/status.rs b/src/status.rs index 9aae2de..132ea1a 100644 --- a/src/status.rs +++ b/src/status.rs @@ -623,6 +623,7 @@ impl Status { self.selected().set_mode(Mode::Normal) } else { self.display_full = true; + // self.selected().make_tree()?; self.selected().make_tree()?; self.selected().set_mode(Mode::Tree); let len = self.selected_non_mut().directory.len(); @@ -16,6 +16,7 @@ use crate::opener::execute_in_child; use crate::preview::{Directory, Preview}; use crate::selectable_content::SelectableContent; use crate::shortcut::Shortcut; +use crate::trees::FileSystem; use crate::users::Users; use crate::utils::{filename_from_path, row_to_window_index, set_clipboard}; @@ -56,6 +57,7 @@ pub struct Tab { pub history: History, /// Users & groups pub users: Users, + pub tree: FileSystem, } impl Tab { @@ -89,6 +91,7 @@ impl Tab { shortcut.extend_with_mount_points(mount_points); let searched = None; let index = path_content.select_file(&path); + let tree = FileSystem::empty(); window.scroll_to(index); Ok(Self { mode, @@ -107,6 +110,7 @@ impl Tab { show_hidden, history, users, + tree, }) } @@ -427,7 +431,15 @@ impl Tab { pub fn make_tree(&mut self) -> Result<()> { let path = self.path_content.path.clone(); let users = &self.users; - self.directory = Directory::new(&path, users, &self.filter, self.show_hidden, None)?; + // self.directory = Directory::new(&path, users, &self.filter, self.show_hidden, None)?; + self.tree = FileSystem::new( + path, + 5, + self.path_content.sort_kind.clone(), + users, + self.show_hidden, + &self.filter, + ); Ok(()) } @@ -530,11 +542,13 @@ impl Tab { /// Select the next sibling of the current node. pub fn select_next(&mut self) -> Result<()> { + self.tree.next(); self.tree_select_next() } /// Select the previous sibling of the current node. pub fn select_prev(&mut self) -> Result<()> { + self.tree.prev(); self.tree_select_prev() } diff --git a/src/term_manager.rs b/src/term_manager.rs index ce3c8c1..945745f 100644 --- a/src/term_manager.rs +++ b/src/term_manager.rs @@ -25,6 +25,7 @@ use crate::selectable_content::SelectableContent; use crate::status::Status; use crate::tab::Tab; use crate::trash::TrashInfo; +use crate::trees::calculate_tree_window; /// Iter over the content, returning a triplet of `(index, line, attr)`. macro_rules! enumerated_colored_iter { @@ -138,7 +139,7 @@ impl<'a> Draw for WinMain<'a> { } match self.tab.mode { Mode::Preview => self.preview(self.tab, &self.tab.window, canvas), - Mode::Tree => self.tree(self.status, self.tab, canvas), + Mode::Tree => self.trees(self.status, self.tab, canvas), Mode::Normal => self.files(self.status, self.tab, canvas), _ => match self.tab.previous_mode { Mode::Tree => self.tree(self.status, self.tab, canvas), @@ -423,6 +424,41 @@ impl<'a> WinMain<'a> { Ok(()) } + fn trees(&self, status: &Status, tab: &Tab, canvas: &mut dyn Canvas) -> Result<()> { + let left_margin = if status.display_full { 1 } else { 3 }; + let (_, height) = canvas.size()?; + let (selected_index, content) = tab.tree.into_navigable_content(&tab.users); + let (top, bottom, len) = calculate_tree_window(selected_index, canvas.size()?.1, height); + + for (i, (metadata, prefix, colored_string)) in content + .iter() + .enumerate() + .skip(top) + .take(min(len, bottom + 1)) + { + let row = i + ContentWindow::WINDOW_MARGIN_TOP - top; + let mut attr = colored_string.color_effect.attr(); + if status.flagged.contains(&colored_string.path) { + attr.effect |= Effect::BOLD; + canvas.print_with_attr(row, 0, "█", ATTR_YELLOW_BOLD)?; + } + + let col_metadata = if status.display_full { + canvas.print_with_attr(row, left_margin, metadata, attr)? + } else { + 0 + }; + let col_tree_prefix = canvas.print(row, left_margin + col_metadata, prefix)?; + canvas.print_with_attr( + row, + left_margin + col_metadata + col_tree_prefix, + &colored_string.text, + attr, + )?; + } + self.second_line(status, tab, canvas)?; + Ok(()) + } fn print_line_number( row_position_in_canvas: usize, line_number_to_print: usize, diff --git a/src/tree.rs b/src/tree.rs index a659191..cb63eba 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -21,7 +21,7 @@ pub struct ColoredString { } impl ColoredString { - fn new(text: String, color_effect: ColorEffect, path: std::path::PathBuf) -> Self { + pub fn new(text: String, color_effect: ColorEffect, path: std::path::PathBuf) -> Self { Self { text, color_effect, diff --git a/src/trees.rs b/src/trees.rs new file mode 100644 index 0000000..9cf5f16 --- /dev/null +++ b/src/trees.rs @@ -0,0 +1,368 @@ +use std::{ + collections::HashMap, + path::{Path, PathBuf}, +}; + +use anyhow::Result; + +use crate::{ + content_window::ContentWindow, + fileinfo::{files_collection, ColorEffect, FileInfo}, + filter::FilterKind, + preview::ColoredTriplet, + sort::SortKind, + tree::ColoredString, + users::Users, + utils::filename_from_path, +}; +#[derive(Debug, Clone)] +pub struct Node { + pub path: PathBuf, + pub children: Option<Vec<PathBuf>>, + pub folded: bool, + pub selected: bool, +} + +impl Node { + pub fn new(path: &Path, children: Option<Vec<PathBuf>>) -> Self { + Self { + path: path.to_owned(), + children, + folded: false, + selected: false, + } + } + + pub fn fold(&mut self) { + self.folded = true + } + + pub fn unfold(&mut self) { + self.folded = false + } + + pub fn toggle_fold(&mut self) { + self.folded = !self.folded + } + + pub fn select(&mut self) { + self.selected = true + } + + pub fn unselect(&mut self) { + self.selected = false + } + + pub fn fileinfo(&self, users: &Users) -> Result<FileInfo> { + FileInfo::new(&self.path, users) + } + + pub fn color_effect(&self, users: &Users) -> Result<ColorEffect> { + Ok(ColorEffect::new(&self.fileinfo(users)?)) + } + + pub fn set_children(&mut self, children: Option<Vec<PathBuf>>) { + self.children = children + } +} + +#[derive(Debug, Clone)] +pub struct FileSystem { + root_path: PathBuf, + selected: PathBuf, + nodes: HashMap<PathBuf, Node>, + required_height: usize, +} + +impl FileSystem { + pub const REQUIRED_HEIGHT: usize = 80; + + pub fn new( + root_path: PathBuf, + depth: usize, + sort_kind: SortKind, + users: &Users, + show_hidden: bool, + filter_kind: &FilterKind, + ) -> Self { + // keep track of the depth + let start_depth = root_path.components().collect::<Vec<_>>().len(); + let mut stack = vec![root_path.to_owned()]; + let mut nodes: HashMap<PathBuf, Node> = HashMap::new(); + + while let Some(path) = stack.pop() { + let reached_depth = path.components().collect::<Vec<_>>().len(); + if reached_depth >= depth + start_depth { + continue; + } + let mut node = Node::new(&path, None); + if let Ok(fileinfo) = node.fileinfo(users) { + if path.is_dir() && !path.is_symlink() { + if let Some(mut files) = + files_collection(&fileinfo, users, show_hidden, filter_kind, true) + { + sort_kind.sort(&mut files); + let children = files + .iter() + .map(|fileinfo| { + stack.push(fileinfo.path.to_owned()); + fileinfo + }) + .map(|fileinfo| fileinfo.path.to_owned()) + .collect(); + node.set_children(Some(children)); + }; + } + } + nodes.insert(node.path.to_owned(), node); + } + + if let Some(node) = nodes.get_mut(&root_path) { + node.select() + } + + Self { + selected: root_path.clone(), + root_path, + nodes, + required_height: Self::REQUIRED_HEIGHT, + } + } + + pub fn empty() -> Self { + Self { + root_path: PathBuf::default(), + selected: PathBuf::default(), + nodes: HashMap::new(), + required_height: 0, + } + } + + pub fn selected(&self) -> &Path { + self.selected.as_path() + } + + pub fn root_path(&self) -> &Path { + self.root_path.as_path() + } + + pub fn selected_node(&self) -> Option<&Node> { + self.nodes.get(self.selected()) + } + + pub fn sort(&mut self, sort_kind: SortKind) -> Result<()> { + todo!() + } + + pub fn select<P>(&mut self, path: P) + where + P: AsRef<Path> + Into<PathBuf>, + { + self.unselect_current(); + self.selected = path.into(); + self.select_current(); + } + + fn unselect_current(&mut self) { + if let Some(node) = self.nodes.get_mut(&self.selected) { + node.unselect() + } + } + + fn select_current(&mut self) { + if let Some(node) = self.nodes.get_mut(&self.selected) { + node.select() + } + } + + // TODO: remove indentation with let ... else + /// Select next sibling or the next sibling of the parent + pub fn next(&mut self) { + if let Some(current) = self.selected_node() { + if let Some(children) = ¤t.children { + if let Some(child_path) = children.get(0) { + log::info!("first child {sel}", sel = self.selected.display()); + self.select(child_path) + } + } + } + + if let Some(parent) = self.selected.parent() { + if let Some(parent_node) = self.nodes.get(parent) { + if let Some(siblings) = &parent_node.children { + if let Some(index_selected) = + siblings.iter().position(|path| path == &self.selected) + { + if let Some(next_sibling) = siblings.get(index_selected + 1) { + self.selected = next_sibling.to_owned(); + log::info!("next sibling {sel}", sel = self.selected.display()); + self.select_current() + } else { + if let Some(parent_parent) = parent.parent() { + if let Some(parent_parent_node) = self.nodes.get(parent_parent) { + if let Some(parent_siblings) = &parent_parent_node.children { + if let Some(index_parent) = + parent_siblings.iter().position(|path| path == &parent) + { + if let Some(next_parent_sibling) = + parent_siblings.get(index_parent + 1) + { + self.selected = next_parent_sibling.to_owned(); + log::info!( + "parent sibling {sel}", + sel = self.selected.display() + ); + self.select_current() + } + } + } + } + } + } + } + } + } + } + } + + // TODO: remove indentation with let ... else + /// Select previous sibling or parent if it's the first. + pub fn prev(&mut self) { + if let Some(parent) = self.selected.parent() { + if let Some(parent_node) = self.nodes.get(parent) { + if let Some(siblings) = &parent_node.children { + if let Some(index_selected) = + siblings.iter().position(|path| path == &self.selected) + { + if index_selected == 0 { + self.selected = parent.to_owned(); + } else if let Some(prev_sibling) = siblings.get(index_selected - 1) { + self.selected = prev_sibling.to_owned(); + } + self.select_current() + } + } + } + } + } + + /// Fold selected node + pub fn toggle_fold(&mut self) { + if let Some(node) = self.nodes.get_mut(&self.selected) { + node.toggle_fold(); + } + } + + pub fn toggle_fold_all(&mut self) { + for (_, node) in self.nodes.iter_mut() { + node.toggle_fold() + } + } + + pub fn search_first_match(&mut self, pattern: &str) { + if let Some(filename) = self.selected.file_name() { + let filename = filename.to_string_lossy(); + if filename.contains(pattern) { + return; + } + } + todo!() + } + + pub fn len(&mut self) -> usize { + self.nodes.len() + } + + pub fn is_empty(self) -> bool { + self.nodes.is_empty() + } + + pub fn into_navigable_content(&self, users: &Users) -> (usize, Vec<ColoredTriplet>) { + let required_height = self.required_height; + let mut stack = vec![("".to_owned(), self.root_path())]; + let mut content = vec![]; + let mut selected_index = 0; + + while let Some((prefix, current)) = stack.pop() { + let Some(node) = &self.nodes.get(current) else { + continue; + }; + + if node.selected { + selected_index = content.len(); + } + + let Ok(fileinfo) = FileInfo::new(current, users) else { + continue; + }; + let filename = filename_from_path(current).unwrap_or_default().to_owned(); + + let mut color_effect = ColorEffect::new(&fileinfo); + if node.selected { + color_effect.effect |= tuikit::attr::Effect::REVERSE; + } + content.push(( + fileinfo.format_no_filename().unwrap_or_default(), + prefix.to_owned(), + ColoredString::new(filename, color_effect, current.to_owned()), + )); + + if !node.folded { + let first_prefix = first_prefix(prefix.clone()); + let other_prefix = other_prefix(prefix); + + if let Some(children) = &node.children { + let mut leaves = children.iter(); + let Some(first_leaf) = leaves.next() else { + continue; + }; + stack.push((first_prefix.clone(), first_leaf)); + + for leaf in leaves { + stack.push((other_prefix.clone(), leaf)); + } + } + } + if content.len() > required_height { + break; + } + } + (selected_index, content) + } +} + +fn first_prefix(mut prefix: String) -> String { + prefix.push(' '); + prefix = prefix.replace("└──", " "); + prefix = prefix.replace("├──", "│ "); + prefix.push_str("└──"); + prefix +} + +fn other_prefix(mut prefix: String) -> String { + prefix.push(' '); + prefix = prefix.replace("└──", " "); + prefix = prefix.replace("├──", "│ "); + prefix.push_str("├──"); + prefix +} + +pub fn calculate_tree_window( + selected_index: usize, + terminal_height: usize, + length: usize, +) -> (usize, usize, usize) { + let top: usize; + let bottom: usize; + let window_height = terminal_height - ContentWindow::WINDOW_MARGIN_TOP; + if selected_index < terminal_height - 1 { + top = 0; + bottom = window_height; + } else { + let padding = std::cmp::max(10, terminal_height / 2); + top = selected_index - padding; + bottom = top + window_height; + } + + (top, bottom, length) +} |