summaryrefslogtreecommitdiffstats
path: root/src/modes/display/tree.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/modes/display/tree.rs')
-rw-r--r--src/modes/display/tree.rs391
1 files changed, 240 insertions, 151 deletions
diff --git a/src/modes/display/tree.rs b/src/modes/display/tree.rs
index 5df5c4b..9457898 100644
--- a/src/modes/display/tree.rs
+++ b/src/modes/display/tree.rs
@@ -2,15 +2,15 @@ use std::borrow::Borrow;
use std::collections::HashMap;
use std::ffi::OsStr;
use std::path::Path;
-use std::rc::Rc;
+use std::sync::Arc;
use anyhow::Result;
use crate::common::filename_from_path;
use crate::common::has_last_modification_happened_less_than;
use crate::modes::files_collection;
-use crate::modes::ContentWindow;
use crate::modes::FilterKind;
+use crate::modes::Flagged;
use crate::modes::SortKind;
use crate::modes::Users;
use crate::modes::{ColorEffect, FileInfo};
@@ -23,12 +23,12 @@ pub struct ColoredString {
/// A pair of [`tuikit::attr::Color`] and [`tuikit::attr::Effect`] used to enhance the text.
pub color_effect: ColorEffect,
/// The complete path of this string.
- pub path: Rc<Path>,
+ pub path: Arc<Path>,
}
impl ColoredString {
/// Creates a new colored string.
- pub fn new(text: String, color_effect: ColorEffect, path: Rc<Path>) -> Self {
+ pub fn new(text: String, color_effect: ColorEffect, path: Arc<Path>) -> Self {
Self {
text,
color_effect,
@@ -42,11 +42,11 @@ impl ColoredString {
/// A Node knows if it's folded or selected.
#[derive(Debug, Clone)]
pub struct Node {
- path: Rc<Path>,
- prev: Rc<Path>,
- next: Rc<Path>,
+ path: Arc<Path>,
+ prev: Arc<Path>,
+ next: Arc<Path>,
index: usize,
- children: Option<Vec<Rc<Path>>>,
+ children: Option<Vec<Arc<Path>>>,
folded: bool,
selected: bool,
reachable: bool,
@@ -55,11 +55,11 @@ pub struct Node {
impl Node {
/// Creates a new Node from a path and its children.
/// By default it's not selected nor folded.
- fn new(path: &Path, children: Option<Vec<Rc<Path>>>, prev: &Path, index: usize) -> Self {
+ fn new(path: &Path, children: Option<Vec<Arc<Path>>>, prev: &Path, index: usize) -> Self {
Self {
- path: Rc::from(path),
- prev: Rc::from(prev),
- next: Rc::from(Path::new("")),
+ path: Arc::from(path),
+ prev: Arc::from(prev),
+ next: Arc::from(Path::new("")),
index,
children,
folded: false,
@@ -117,6 +117,8 @@ pub enum To<'a> {
Root,
Last,
Parent,
+ NextSibling,
+ PreviousSibling,
Path(&'a Path),
}
@@ -132,7 +134,9 @@ impl Go for Tree {
To::Root => self.select_root(),
To::Last => self.select_last(),
To::Parent => self.select_parent(),
- To::Path(path) => self.select_path(path, true),
+ To::NextSibling => self.select_next_sibling(),
+ To::PreviousSibling => self.select_previous_sibling(),
+ To::Path(path) => self.select_path(path),
}
}
}
@@ -142,7 +146,7 @@ trait Depth {
fn depth(&self) -> usize;
}
-impl Depth for Rc<Path> {
+impl Depth for Arc<Path> {
/// Measure the number of components of a `PathBuf`.
/// For absolute paths, it's the number of folders plus one for / and one for the file itself.
#[inline]
@@ -156,29 +160,27 @@ impl Depth for Rc<Path> {
/// It also holds informations about the required height of the tree.
#[derive(Debug, Clone)]
pub struct Tree {
- root_path: Rc<Path>,
- selected: Rc<Path>,
- nodes: HashMap<Rc<Path>, Node>,
- required_height: usize,
+ root_path: Arc<Path>,
+ selected: Arc<Path>,
+ nodes: HashMap<Arc<Path>, Node>,
+ displayable_lines: TreeLines,
}
impl Default for Tree {
fn default() -> Self {
Self {
- root_path: Rc::from(Path::new("")),
- selected: Rc::from(Path::new("")),
+ root_path: Arc::from(Path::new("")),
+ selected: Arc::from(Path::new("")),
nodes: HashMap::new(),
- required_height: 0,
+ displayable_lines: TreeLines::default(),
}
}
}
impl Tree {
- pub const DEFAULT_REQUIRED_HEIGHT: usize = 80;
-
/// Creates a new tree, exploring every node until depth is reached.
pub fn new(
- root_path: Rc<Path>,
+ root_path: Arc<Path>,
depth: usize,
sort_kind: SortKind,
users: &Users,
@@ -194,27 +196,29 @@ impl Tree {
filter_kind,
);
+ let content = Self::make_displayable(users, &root_path, &nodes);
+
Self {
selected: root_path.clone(),
root_path,
nodes,
- required_height: Self::DEFAULT_REQUIRED_HEIGHT,
+ displayable_lines: content,
}
}
#[inline]
fn make_nodes(
- root_path: Rc<Path>,
+ root_path: Arc<Path>,
depth: usize,
sort_kind: SortKind,
users: &Users,
show_hidden: bool,
filter_kind: &FilterKind,
- ) -> HashMap<Rc<Path>, Node> {
+ ) -> HashMap<Arc<Path>, Node> {
// keep track of the depth
let root_depth = root_path.depth();
let mut stack = vec![root_path.to_owned()];
- let mut nodes: HashMap<Rc<Path>, Node> = HashMap::new();
+ let mut nodes: HashMap<Arc<Path>, Node> = HashMap::new();
let mut last_path = root_path.to_owned();
let mut index = 0;
@@ -249,30 +253,30 @@ impl Tree {
}
#[inline]
- fn set_prev_for_root(nodes: &mut HashMap<Rc<Path>, Node>, root_path: &Path, last_path: &Path) {
+ fn set_prev_for_root(nodes: &mut HashMap<Arc<Path>, Node>, root_path: &Path, last_path: &Path) {
let Some(root_node) = nodes.get_mut(root_path) else {
unreachable!("root_path should be in nodes");
};
- root_node.prev = Rc::from(last_path);
+ root_node.prev = Arc::from(last_path);
root_node.select();
}
#[inline]
- fn set_next_for_last(nodes: &mut HashMap<Rc<Path>, Node>, root_path: &Path, last_path: &Path) {
+ fn set_next_for_last(nodes: &mut HashMap<Arc<Path>, Node>, root_path: &Path, last_path: &Path) {
if let Some(last_node) = nodes.get_mut(last_path) {
- last_node.next = Rc::from(root_path);
+ last_node.next = Arc::from(root_path);
};
}
#[inline]
fn create_children(
- stack: &mut Vec<Rc<Path>>,
+ stack: &mut Vec<Arc<Path>>,
current_path: &Path,
users: &Users,
show_hidden: bool,
filter_kind: &FilterKind,
sort_kind: SortKind,
- ) -> Option<Vec<Rc<Path>>> {
+ ) -> Option<Vec<Arc<Path>>> {
if let Some(mut files) =
files_collection(current_path, users, show_hidden, filter_kind, true)
{
@@ -287,9 +291,9 @@ impl Tree {
#[inline]
fn make_children_and_stack_them(
- stack: &mut Vec<Rc<Path>>,
+ stack: &mut Vec<Arc<Path>>,
files: &[FileInfo],
- ) -> Vec<Rc<Path>> {
+ ) -> Vec<Arc<Path>> {
files
.iter()
.map(|fileinfo| fileinfo.path.clone())
@@ -335,6 +339,10 @@ impl Tree {
self.nodes.len()
}
+ pub fn display_len(&self) -> usize {
+ self.displayable().lines().len()
+ }
+
/// True if there's no node.
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
@@ -350,23 +358,43 @@ impl Tree {
self.find_next_path() == self.root_path
}
+ /// True if the node has a parent which is in `self.nodes` and is folded.
+ /// This hacky and inefficient solution is necessary to know if we can select
+ /// this node in `select_prev` `select_next`.
+ /// It will construct all parent path from `/` to `node.path` except for the last one.
+ fn node_has_parent_folded(&self, node: &Node) -> bool {
+ let path = &node.path;
+ let mut current_path = std::path::PathBuf::from("/");
+ for part in path.components() {
+ current_path = current_path.join(part.as_os_str());
+ if current_path == <Arc<std::path::Path> as Borrow<Path>>::borrow(path) {
+ continue;
+ }
+ if let Some(current_node) = self.nodes.get(current_path.as_path()) {
+ if current_node.folded {
+ return true;
+ }
+ }
+ }
+ false
+ }
+
/// Select next sibling or the next sibling of the parent
fn select_next(&mut self) {
let next_path = self.find_next_path();
- self.select_path(&next_path, false);
+ self.select_path(&next_path);
drop(next_path);
- self.increment_required_height()
}
- fn find_next_path(&self) -> Rc<Path> {
- let mut current_path: Rc<Path> = self.selected.clone();
+ fn find_next_path(&self) -> Arc<Path> {
+ let mut current_path: Arc<Path> = self.selected.clone();
loop {
if let Some(current_node) = self.nodes.get(&current_path) {
let next_path = &current_node.next;
let Some(next_node) = self.nodes.get(next_path) else {
return self.root_path.clone();
};
- if next_node.reachable {
+ if next_node.reachable && !self.node_has_parent_folded(next_node) {
return next_path.to_owned();
} else {
current_path = next_path.clone();
@@ -377,18 +405,12 @@ impl Tree {
/// Select previous sibling or the parent
fn select_prev(&mut self) {
- let must_increase = self.is_on_root();
let previous_path = self.find_prev_path();
- self.select_path(&previous_path, false);
+ self.select_path(&previous_path);
drop(previous_path);
- if must_increase {
- self.set_required_height_to_max()
- } else {
- self.decrement_required_height()
- }
}
- fn find_prev_path(&self) -> Rc<Path> {
+ fn find_prev_path(&self) -> Arc<Path> {
let mut current_path = self.selected.to_owned();
loop {
if let Some(current_node) = self.nodes.get(&current_path) {
@@ -396,7 +418,7 @@ impl Tree {
let Some(prev_node) = self.nodes.get(prev_path) else {
unreachable!("");
};
- if prev_node.reachable {
+ if prev_node.reachable && !self.node_has_parent_folded(prev_node) {
return prev_path.to_owned();
} else {
current_path = prev_path.to_owned();
@@ -425,8 +447,7 @@ impl Tree {
fn select_root(&mut self) {
let root_path = self.root_path.to_owned();
- self.select_path(&root_path, false);
- self.reset_required_height()
+ self.select_path(&root_path);
}
fn select_last(&mut self) {
@@ -436,13 +457,52 @@ impl Tree {
fn select_parent(&mut self) {
if let Some(parent_path) = self.selected.parent() {
- self.select_path(parent_path.to_owned().as_path(), false);
- self.decrement_required_height()
+ self.select_path(parent_path.to_owned().as_path());
}
}
- fn select_path(&mut self, dest_path: &Path, set_height: bool) {
- if Rc::from(dest_path) == self.selected {
+ fn find_siblings(&self) -> &Option<Vec<Arc<Path>>> {
+ let Some(parent_path) = self.selected.parent() else {
+ return &None;
+ };
+ let Some(parent_node) = self.nodes.get(parent_path) else {
+ return &None;
+ };
+ &parent_node.children
+ }
+
+ fn select_next_sibling(&mut self) {
+ let Some(children) = self.find_siblings() else {
+ self.select_next();
+ return;
+ };
+ let Some(curr_index) = children.iter().position(|p| p == &self.selected) else {
+ return;
+ };
+ let next_index = if curr_index > 0 {
+ curr_index - 1
+ } else {
+ children.len().checked_sub(1).unwrap_or_default()
+ };
+ let sibling_path = children[next_index].clone();
+ self.select_path(&sibling_path);
+ }
+
+ fn select_previous_sibling(&mut self) {
+ let Some(children) = self.find_siblings() else {
+ self.select_prev();
+ return;
+ };
+ let Some(curr_index) = children.iter().position(|p| p == &self.selected) else {
+ return;
+ };
+ let next_index = (curr_index + 1) % children.len();
+ let sibling_path = children[next_index].clone();
+ self.select_path(&sibling_path);
+ }
+
+ fn select_path(&mut self, dest_path: &Path) {
+ if Arc::from(dest_path) == self.selected {
return;
}
let Some(dest_node) = self.nodes.get_mut(dest_path) else {
@@ -453,34 +513,15 @@ impl Tree {
unreachable!("current_node should be in nodes");
};
selected_node.unselect();
- self.selected = Rc::from(dest_path);
- if set_height {
- self.set_required_height_to_max()
- }
- }
-
- fn increment_required_height(&mut self) {
- if self.required_height < usize::MAX {
- self.required_height += 1
+ self.selected = Arc::from(dest_path);
+ self.displayable_lines.unselect();
+ if let Some(index) = self.displayable_lines.find_by_path(dest_path) {
+ self.displayable_lines.select(index);
}
}
- fn decrement_required_height(&mut self) {
- if self.required_height > Self::DEFAULT_REQUIRED_HEIGHT {
- self.required_height -= 1
- }
- }
-
- fn set_required_height_to_max(&mut self) {
- self.required_height = usize::MAX
- }
-
- fn reset_required_height(&mut self) {
- self.required_height = Self::DEFAULT_REQUIRED_HEIGHT
- }
-
/// Fold selected node
- pub fn toggle_fold(&mut self) {
+ pub fn toggle_fold(&mut self, users: &Users) {
if let Some(node) = self.nodes.get_mut(&self.selected) {
if node.folded {
node.unfold();
@@ -490,9 +531,10 @@ impl Tree {
self.make_children_unreachable()
}
}
+ self.remake_displayable(users);
}
- fn children_of_selected(&self) -> Vec<Rc<Path>> {
+ fn children_of_selected(&self) -> Vec<Arc<Path>> {
self.nodes
.keys()
.filter(|p| p.starts_with(&self.selected) && p != &&self.selected)
@@ -504,7 +546,6 @@ impl Tree {
for path in self.children_of_selected().iter() {
if let Some(child_node) = self.nodes.get_mut(path) {
child_node.reachable = true;
- child_node.unfold();
};
}
}
@@ -518,18 +559,20 @@ impl Tree {
}
/// Fold all node from root to end
- pub fn fold_all(&mut self) {
+ pub fn fold_all(&mut self, users: &Users) {
for (_, node) in self.nodes.iter_mut() {
node.fold()
}
- self.select_root()
+ self.select_root();
+ self.remake_displayable(users);
}
/// Unfold all node from root to end
- pub fn unfold_all(&mut self) {
+ pub fn unfold_all(&mut self, users: &Users) {
for (_, node) in self.nodes.iter_mut() {
node.unfold()
}
+ self.remake_displayable(users);
}
/// Select the first node whose filename match a pattern.
@@ -538,10 +581,10 @@ impl Tree {
let Some(found_path) = self.deep_first_search(pattern) else {
return;
};
- self.select_path(&found_path, true);
+ self.select_path(&found_path);
}
- fn deep_first_search(&self, pattern: &str) -> Option<Rc<Path>> {
+ fn deep_first_search(&self, pattern: &str) -> Option<Arc<Path>> {
let mut stack = vec![self.root_path.clone()];
let mut found = vec![];
@@ -565,7 +608,7 @@ impl Tree {
self.pick_best_match(&found)
}
- fn pick_best_match(&self, found: &[Rc<Path>]) -> Option<Rc<Path>> {
+ fn pick_best_match(&self, found: &[Arc<Path>]) -> Option<Arc<Path>> {
if found.is_empty() {
return None;
}
@@ -598,46 +641,48 @@ impl Tree {
/// an access to the user list.
/// The prefix (straight lines displaying targets) must also be calcuated immediatly.
/// Name format is calculated on the fly.
- pub fn content(&self, users: &Users, display_metadata: bool) -> (usize, Vec<TreeLineMaker>) {
- let mut stack = vec![("".to_owned(), self.root_path.borrow())];
- let mut content = vec![];
- let mut selected_index = 0;
+ fn make_displayable(
+ users: &Users,
+ root_path: &Path,
+ nodes: &HashMap<Arc<Path>, Node>,
+ ) -> TreeLines {
+ let mut stack = vec![("".to_owned(), root_path.borrow())];
+ let mut lines = vec![];
+ let mut index = 0;
while let Some((prefix, path)) = stack.pop() {
- let Some(node) = self.nodes.get(path) else {
+ let Some(node) = nodes.get(path) else {
continue;
};
if node.selected {
- selected_index = content.len();
+ index = lines.len();
}
let Ok(fileinfo) = FileInfo::new(path, users) else {
continue;
};
- content.push(TreeLineMaker::new(
- &fileinfo,
- &prefix,
- node,
- path,
- display_metadata,
- ));
+ lines.push(TreeLineBuilder::new(&fileinfo, &prefix, node, path));
if node.have_children() {
Self::stack_children(&mut stack, prefix, node);
}
-
- if content.len() > self.required_height {
- break;
- }
}
- (selected_index, content)
+ TreeLines::new(lines, index)
+ }
+
+ fn remake_displayable(&mut self, users: &Users) {
+ self.displayable_lines = Self::make_displayable(users, &self.root_path, &self.nodes);
+ }
+
+ pub fn displayable(&self) -> &TreeLines {
+ &self.displayable_lines
}
#[inline]
pub fn filenames_containing(&self, input_string: &str) -> Vec<String> {
- let to_filename: fn(&Rc<Path>) -> Option<&OsStr> = |path| path.file_name();
+ let to_filename: fn(&Arc<Path>) -> Option<&OsStr> = |path| path.file_name();
let to_str: fn(&OsStr) -> Option<&str> = |filename| filename.to_str();
self.nodes
.keys()
@@ -653,6 +698,12 @@ impl Tree {
self.nodes.keys().map(|p| p.borrow()).collect()
}
+ pub fn flag_all(&self, flagged: &mut Flagged) {
+ self.nodes
+ .keys()
+ .for_each(|p| flagged.push(p.to_path_buf()))
+ }
+
/// True if any directory (not symlink to a directory)
/// has been modified less than 10 seconds ago.
#[inline]
@@ -708,13 +759,13 @@ fn other_prefix(prefix: &str) -> String {
}
#[inline]
-fn filename_format(current_path: &Path, current_node: &Node) -> String {
+fn filename_format(current_path: &Path, folded: bool) -> String {
let filename = filename_from_path(current_path)
.unwrap_or_default()
.to_owned();
if current_path.is_dir() && !current_path.is_symlink() {
- if current_node.folded {
+ if folded {
format!("▸ {}", filename)
} else {
format!("▾ {}", filename)
@@ -724,20 +775,6 @@ fn filename_format(current_path: &Path, current_node: &Node) -> String {
}
}
-/// Emulate a `ContentWindow`, returning the top and bottom index of displayable files.
-#[inline]
-pub fn calculate_top_bottom(selected_index: usize, terminal_height: usize) -> (usize, usize) {
- let window_height = terminal_height - ContentWindow::WINDOW_MARGIN_TOP;
- let top = if selected_index < window_height {
- 0
- } else {
- selected_index - 10.max(terminal_height / 2)
- };
- let bottom = top + window_height;
-
- (top, bottom)
-}
-
fn path_filename_contains(path: &Path, pattern: &str) -> bool {
path.file_name()
.unwrap_or_default()
@@ -745,40 +782,73 @@ fn path_filename_contains(path: &Path, pattern: &str) -> bool {
.contains(pattern)
}
+/// A vector of displayable lines used to draw a tree content.
+/// We use the index to follow the user movements in the tree.
+#[derive(Clone, Debug, Default)]
+pub struct TreeLines {
+ pub content: Vec<TreeLineBuilder>,
+ index: usize,
+}
+
+impl TreeLines {
+ fn new(content: Vec<TreeLineBuilder>, index: usize) -> Self {
+ Self { content, index }
+ }
+
+ /// Index of the currently selected file.
+ pub fn index(&self) -> usize {
+ self.index
+ }
+
+ /// A reference to the displayable lines.
+ pub fn lines(&self) -> &Vec<TreeLineBuilder> {
+ &self.content
+ }
+
+ fn find_by_path(&self, path: &Path) -> Option<usize> {
+ self.content
+ .iter()
+ .position(|tlm| <Arc<std::path::Path> as Borrow<Path>>::borrow(&tlm.path) == path)
+ }
+
+ fn unselect(&mut self) {
+ if !self.content.is_empty() {
+ self.content[self.index].unselect()
+ }
+ }
+
+ fn select(&mut self, index: usize) {
+ if !self.content.is_empty() {
+ self.index = index;
+ self.content[self.index].select()
+ }
+ }
+}
+
/// Holds a few references used to display a tree line
/// Only the metadata info is hold.
-pub struct TreeLineMaker<'a> {
- node: &'a Node,
- prefix: std::rc::Rc<str>,
- path: std::rc::Rc<Path>,
+#[derive(Clone, Debug)]
+pub struct TreeLineBuilder {
+ folded: bool,
+ prefix: Arc<str>,
+ path: Arc<Path>,
color_effect: ColorEffect,
- metadata: Option<String>,
+ metadata: String,
}
-impl<'a> TreeLineMaker<'a> {
- /// Uses references to fileinfo, prefix, node & path to create a `TreeLineMaker`.
- fn new(
- fileinfo: &FileInfo,
- prefix: &str,
- node: &'a Node,
- path: &Path,
- display_metadata: bool,
- ) -> Self {
+impl TreeLineBuilder {
+ /// Uses references to fileinfo, prefix, node & path to create an instance.
+ fn new(fileinfo: &FileInfo, prefix: &str, node: &Node, path: &Path) -> Self {
let color_effect = ColorEffect::node(fileinfo, node.selected());
- let prefix = Rc::from(prefix);
- let path = Rc::from(path);
- let metadata = if display_metadata {
- Some(
- fileinfo
- .format_no_filename()
- .unwrap_or_else(|_| "?".repeat(19)),
- )
- } else {
- None
- };
+ let prefix = Arc::from(prefix);
+ let path = Arc::from(path);
+ let metadata = fileinfo
+ .format_no_filename()
+ .unwrap_or_else(|_| "?".repeat(19));
+ let folded = node.folded;
Self {
- node,
+ folded,
prefix,
path,
color_effect,
@@ -786,23 +856,42 @@ impl<'a> TreeLineMaker<'a> {
}
}
+ /// Formated filename
pub fn filename(&self) -> String {
- filename_format(&self.path, self.node)
+ filename_format(&self.path, self.folded)
}
+ /// `tuikit::attr::Attr` of the line
pub fn attr(&self) -> tuikit::attr::Attr {
self.color_effect.attr()
}
+ /// Vertical bar displayed before the filename to show
+ /// the adress of the file
pub fn prefix(&self) -> &str {
self.prefix.borrow()
}
+ /// Path of the file
pub fn path(&self) -> &Path {
self.path.borrow()
}
- pub fn metadata(&self) -> &Option<String> {
+ /// Metadata string representation
+ /// permission, size, owner, groupe, modification date
+ pub fn metadata(&self) -> &str {
&self.metadata
}
+
+ /// Change the current effect to Empty, displaying
+ /// the file as not selected
+ pub fn unselect(&mut self) {
+ self.color_effect.effect = tuikit::attr::Effect::empty();
+ }
+
+ /// Change the current effect to `REVERSE`, displaying
+ /// the file as selected.
+ pub fn select(&mut self) {
+ self.color_effect.effect = tuikit::attr::Effect::REVERSE;
+ }
}