summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorqkzk <qu3nt1n@gmail.com>2023-11-07 21:33:16 +0100
committerqkzk <qu3nt1n@gmail.com>2023-11-07 21:33:16 +0100
commited9c1484827f15c55a6a2baf70d9af5c05129928 (patch)
treefec30927a31ce161225c72e81cb9a870dd50901a
parentc6fd22dbde9b02869867161279fe8df3004f8642 (diff)
tree and users refactor
-rw-r--r--development.md2
-rw-r--r--src/event_exec.rs25
-rw-r--r--src/opener.rs31
-rw-r--r--src/status.rs4
-rw-r--r--src/tab.rs16
-rw-r--r--src/term_manager.rs4
-rw-r--r--src/tree.rs105
7 files changed, 117 insertions, 70 deletions
diff --git a/development.md b/development.md
index 8e55462..ae340f8 100644
--- a/development.md
+++ b/development.md
@@ -620,7 +620,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally.
- [ ] FIX: copying/moving 0B files does nothing
- [ ] test everything
- [ ] refactor
- - [ ] document
+ - [x] document
## TODO
diff --git a/src/event_exec.rs b/src/event_exec.rs
index c1fcbe4..5514ccf 100644
--- a/src/event_exec.rs
+++ b/src/event_exec.rs
@@ -19,8 +19,9 @@ use crate::log_line;
use crate::mocp::Mocp;
use crate::mocp::MOCP;
use crate::mode::{InputSimple, MarkAction, Mode, Navigate, NeedConfirmation};
+use crate::opener::execute_and_capture_output_with_path;
use crate::opener::{
- execute_and_capture_output, execute_and_capture_output_without_check, execute_in_child,
+ execute_and_capture_output_without_check, execute_in_child,
execute_in_child_without_output_with_path,
};
use crate::password::{PasswordKind, PasswordUsage};
@@ -374,7 +375,7 @@ impl EventAction {
/// Basic folders (/, /dev... $HOME) and mount points (even impossible to
/// visit ones) are proposed.
pub fn shortcut(tab: &mut Tab) -> Result<()> {
- std::env::set_current_dir(tab.current_directory_path().context("no parent")?)?;
+ std::env::set_current_dir(tab.directory_of_selected()?)?;
tab.shortcut.update_git_root();
tab.set_mode(Mode::Navigate(Navigate::Shortcut));
Ok(())
@@ -1557,16 +1558,18 @@ impl LeaveMode {
};
let (username, hostname, remote_path) = (strings[0], strings[1], strings[2]);
- let current_path: &str = tab
- .current_directory_path()
- .context("no parent")?
- .to_str()
- .context("couldn't parse the path")?;
+ let current_path: &str = &tab
+ .directory_of_selected_previous_mode()?
+ .display()
+ .to_string();
let first_arg = &format!("{username}@{hostname}:{remote_path}");
- let command_output =
- execute_and_capture_output(SSHFS_EXECUTABLE, &[first_arg, current_path]);
- info!("{SSHFS_EXECUTABLE} output {command_output:?}");
- log_line!("{SSHFS_EXECUTABLE} output {command_output:?}");
+ let command_output = execute_and_capture_output_with_path(
+ SSHFS_EXECUTABLE,
+ current_path,
+ &[first_arg, current_path],
+ );
+ info!("{SSHFS_EXECUTABLE} {strings:?} output {command_output:?}");
+ log_line!("{SSHFS_EXECUTABLE} {strings:?} output {command_output:?}");
Ok(())
}
}
diff --git a/src/opener.rs b/src/opener.rs
index 1b77ad7..7e1e186 100644
--- a/src/opener.rs
+++ b/src/opener.rs
@@ -499,6 +499,37 @@ pub fn execute_and_capture_output<S: AsRef<std::ffi::OsStr> + fmt::Debug>(
}
/// Execute a command with options in a fork.
+/// Wait for termination and return either :
+/// `Ok(stdout)` if the status code is 0
+/// an Error otherwise
+/// Branch stdin and stderr to /dev/null
+pub fn execute_and_capture_output_with_path<
+ S: AsRef<std::ffi::OsStr> + fmt::Debug,
+ P: AsRef<Path>,
+>(
+ exe: S,
+ path: P,
+ args: &[&str],
+) -> Result<String> {
+ info!("execute_and_capture_output. executable: {exe:?}, arguments: {args:?}",);
+ let child = Command::new(exe)
+ .args(args)
+ .stdin(Stdio::null())
+ .stdout(Stdio::piped())
+ .stderr(Stdio::null())
+ .current_dir(path)
+ .spawn()?;
+ let output = child.wait_with_output()?;
+ if output.status.success() {
+ Ok(String::from_utf8(output.stdout)?)
+ } else {
+ Err(anyhow!(
+ "execute_and_capture_output: command didn't finish properly",
+ ))
+ }
+}
+
+/// Execute a command with options in a fork.
/// Wait for termination and return either `Ok(stdout)`.
/// Branch stdin and stderr to /dev/null
pub fn execute_and_capture_output_without_check<S: AsRef<std::ffi::OsStr> + fmt::Debug>(
diff --git a/src/status.rs b/src/status.rs
index 68e940d..e5d9183 100644
--- a/src/status.rs
+++ b/src/status.rs
@@ -582,7 +582,7 @@ impl Status {
/// Drop the current tree, replace it with an empty one.
pub fn remove_tree(&mut self) -> Result<()> {
- self.selected().tree = Tree::empty();
+ self.selected().tree = Tree::default();
Ok(())
}
@@ -617,7 +617,7 @@ impl Status {
if let Mode::Tree = self.selected_non_mut().mode {
{
let tab = self.selected();
- tab.tree = Tree::empty();
+ tab.tree = Tree::default();
tab.refresh_view()
}?;
self.selected().set_mode(Mode::Normal)
diff --git a/src/tab.rs b/src/tab.rs
index e201091..a8edba2 100644
--- a/src/tab.rs
+++ b/src/tab.rs
@@ -17,7 +17,7 @@ use crate::preview::Preview;
use crate::selectable_content::SelectableContent;
use crate::shortcut::Shortcut;
use crate::sort::SortKind;
-use crate::tree::{calculate_tree_window, Go, To, Tree};
+use crate::tree::{calculate_top_bottom, Go, To, Tree};
use crate::users::Users;
use crate::utils::{row_to_window_index, set_clipboard};
@@ -89,7 +89,7 @@ impl Tab {
shortcut.extend_with_mount_points(mount_points);
let searched = None;
let index = path_content.select_file(&path);
- let tree = Tree::empty();
+ let tree = Tree::default();
window.scroll_to(index);
Ok(Self {
mode,
@@ -146,7 +146,7 @@ impl Tab {
self.input.reset();
self.preview = Preview::empty();
self.completion.reset();
- self.tree = Tree::empty();
+ self.tree = Tree::default();
Ok(())
}
@@ -391,10 +391,10 @@ impl Tab {
/// if the selected node is a directory, that's it.
/// else, it is the parent of the selected node.
/// In other modes, it's the current path of pathcontent.
- pub fn current_directory_path(&mut self) -> Option<&path::Path> {
- match self.mode {
- Mode::Tree => return self.tree.directory_of_selected(),
- _ => Some(&self.path_content.path),
+ pub fn directory_of_selected_previous_mode(&mut self) -> Result<&path::Path> {
+ match self.previous_mode {
+ Mode::Tree => return self.tree.directory_of_selected().context("no parent"),
+ _ => Ok(&self.path_content.path),
}
}
@@ -648,7 +648,7 @@ impl Tab {
fn tree_select_row(&mut self, row: u16, term_height: usize) -> Result<()> {
let screen_index = row_to_window_index(row);
let (selected_index, content) = self.tree.into_navigable_content(&self.users);
- let (top, _) = calculate_tree_window(selected_index, term_height - 2);
+ let (top, _) = calculate_top_bottom(selected_index, term_height - 2);
let index = screen_index + top;
let (_, _, colored_path) = content.get(index).context("no selected file")?;
self.tree.go(To::Path(&colored_path.path));
diff --git a/src/term_manager.rs b/src/term_manager.rs
index 035cbcc..06ec467 100644
--- a/src/term_manager.rs
+++ b/src/term_manager.rs
@@ -25,7 +25,7 @@ use crate::selectable_content::SelectableContent;
use crate::status::Status;
use crate::tab::Tab;
use crate::trash::TrashInfo;
-use crate::tree::calculate_tree_window;
+use crate::tree::calculate_top_bottom;
/// Iter over the content, returning a triplet of `(index, line, attr)`.
macro_rules! enumerated_colored_iter {
@@ -416,7 +416,7 @@ impl<'a> WinMain<'a> {
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) = calculate_tree_window(selected_index, height);
+ let (top, bottom) = calculate_top_bottom(selected_index, height);
let length = content.len();
for (i, (metadata, prefix, colored_string)) in content
diff --git a/src/tree.rs b/src/tree.rs
index 828c332..2feebcd 100644
--- a/src/tree.rs
+++ b/src/tree.rs
@@ -1,24 +1,20 @@
-use std::{
- collections::hash_map,
- collections::HashMap,
- ffi::OsStr,
- iter::FilterMap,
- path::{Path, PathBuf},
-};
+use std::collections::hash_map;
+use std::collections::HashMap;
+use std::ffi::OsStr;
+use std::iter::FilterMap;
+use std::path::{Path, PathBuf};
use anyhow::Result;
-use crate::{
- content_window::ContentWindow,
- fileinfo::{files_collection, ColorEffect, FileInfo},
- filter::FilterKind,
- preview::{ColoredTriplet, MakeTriplet},
- sort::SortKind,
- users::Users,
- utils::filename_from_path,
-};
-
-/// Holds a string and its display attributes.
+use crate::content_window::ContentWindow;
+use crate::fileinfo::{files_collection, ColorEffect, FileInfo};
+use crate::filter::FilterKind;
+use crate::preview::{ColoredTriplet, MakeTriplet};
+use crate::sort::SortKind;
+use crate::users::Users;
+use crate::utils::filename_from_path;
+
+/// Holds a string, its display attributes and the associated pathbuf.
#[derive(Clone, Debug)]
pub struct ColoredString {
/// A text to be printed. In most case, it should be a filename.
@@ -30,6 +26,7 @@ pub struct ColoredString {
}
impl ColoredString {
+ /// Creates a new colored string.
pub fn new(text: String, color_effect: ColorEffect, path: std::path::PathBuf) -> Self {
Self {
text,
@@ -39,6 +36,9 @@ impl ColoredString {
}
}
+/// An element of a tree.
+/// It's a file/directory, some optional children.
+/// A Node knows if it's folded or selected.
#[derive(Debug, Clone)]
pub struct Node {
path: PathBuf,
@@ -48,6 +48,8 @@ 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<PathBuf>>) -> Self {
Self {
path: path.to_owned(),
@@ -77,10 +79,12 @@ impl Node {
self.selected = false
}
+ /// Is the node selected ?
pub fn selected(&self) -> bool {
self.selected
}
+ /// Creates a new fileinfo from the node.
pub fn fileinfo(&self, users: &Users) -> Result<FileInfo> {
FileInfo::new(&self.path, users)
}
@@ -97,9 +101,10 @@ impl Node {
/// Describe a movement in a navigable structure
pub trait Go {
- fn go(&mut self, direction: To);
+ fn go(&mut self, to: To);
}
+/// Describes a direction for the next selected tree element.
pub enum To<'a> {
Next,
Prev,
@@ -110,8 +115,9 @@ pub enum To<'a> {
}
impl Go for Tree {
- fn go(&mut self, direction: To) {
- match direction {
+ /// Select another element from a tree.
+ fn go(&mut self, to: To) {
+ match to {
To::Next => self.select_next(),
To::Prev => self.select_prev(),
To::Root => self.select_root(),
@@ -122,7 +128,10 @@ impl Go for Tree {
}
}
-#[derive(Debug, Clone)]
+/// A FileSystem tree of nodes.
+/// Internally it's a wrapper around an `std::collections::HashMap<PathBuf, Node>`
+/// It also holds informations about the required height of the tree.
+#[derive(Debug, Clone, Default)]
pub struct Tree {
root_path: PathBuf,
selected: PathBuf,
@@ -134,6 +143,7 @@ pub struct Tree {
impl Tree {
pub const DEFAULT_REQUIRED_HEIGHT: usize = 80;
+ /// Creates a new tree, exploring every node untill depth is reached.
pub fn new(
root_path: PathBuf,
depth: usize,
@@ -195,58 +205,60 @@ impl Tree {
.collect()
}
- pub fn empty() -> Self {
- Self {
- root_path: PathBuf::default(),
- selected: PathBuf::default(),
- last_path: PathBuf::default(),
- nodes: HashMap::new(),
- required_height: 0,
- }
- }
-
+ /// Root path of the tree.
pub fn root_path(&self) -> &Path {
self.root_path.as_path()
}
+ /// Selected path
pub fn selected_path(&self) -> &Path {
self.selected.as_path()
}
+ /// Selected node
pub fn selected_node(&self) -> Option<&Node> {
self.nodes.get(&self.selected)
}
+ /// The folder containing the selected node.
+ /// Itself if selected is a directory.
pub fn directory_of_selected(&self) -> Option<&Path> {
- if self.selected.is_dir() && !self.selected.is_symlink() {
+ let ret = if self.selected.is_dir() && !self.selected.is_symlink() {
Some(self.selected.as_path())
} else {
self.selected.parent()
- }
+ };
+ log::info!("directory_of_selected {ret:?}");
+ ret
}
+ /// Relative path of selected from rootpath.
pub fn selected_path_relative_to_root(&self) -> Result<&Path> {
Ok(self.selected.strip_prefix(&self.root_path)?)
}
+ /// Number of nodes
pub fn len(&self) -> usize {
self.nodes.len()
}
+ /// True if there's no node.
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
+ /// True if selected is root.
pub fn is_on_root(&self) -> bool {
self.selected == self.root_path
}
+ /// True if selected is the last file
pub fn is_on_last(&self) -> bool {
self.selected == self.last_path
}
/// Select next sibling or the next sibling of the parent
- pub fn select_next(&mut self) {
+ fn select_next(&mut self) {
if self.is_on_last() {
self.select_root();
return;
@@ -317,7 +329,7 @@ impl Tree {
}
// TODO! find the bottom child of parent instead of jumping back 1 level
/// Select previous sibling or the parent
- pub fn select_prev(&mut self) {
+ fn select_prev(&mut self) {
if self.is_on_root() {
self.select_last();
return;
@@ -443,12 +455,14 @@ impl Tree {
}
}
+ /// Fold all node from root to end
pub fn fold_all(&mut self) {
for (_, node) in self.nodes.iter_mut() {
node.fold()
}
}
+ /// Unfold all node from root to end
pub fn unfold_all(&mut self) {
for (_, node) in self.nodes.iter_mut() {
node.unfold()
@@ -456,6 +470,7 @@ impl Tree {
}
// FIX: can only find the first match and nothing else
+ /// Select the first node whose filename match a pattern.
pub fn search_first_match(&mut self, pattern: &str) {
let initial_selected = self.selected.to_owned();
let Some((found_path, found_node)) = self.nodes.iter_mut().find(|(path, _)| {
@@ -474,6 +489,7 @@ impl Tree {
current_node.unselect();
}
+ /// Returns a navigable vector of `ColoredTriplet` and the index of selected file
pub fn into_navigable_content(&self, users: &Users) -> (usize, Vec<ColoredTriplet>) {
let mut stack = vec![("".to_owned(), self.root_path.as_path())];
let mut content = vec![];
@@ -585,18 +601,15 @@ fn filename_format(current_path: &Path, current_node: &Node) -> String {
}
}
-pub fn calculate_tree_window(selected_index: usize, terminal_height: usize) -> (usize, usize) {
- let top: usize;
- let bottom: usize;
+/// Emulate a `ContentWindow`, returning the top and bottom index of displayable files.
+pub fn calculate_top_bottom(selected_index: usize, terminal_height: usize) -> (usize, usize) {
let window_height = terminal_height - ContentWindow::WINDOW_MARGIN_TOP;
- if selected_index < window_height {
- top = 0;
- bottom = window_height;
+ let top = if selected_index < window_height {
+ 0
} else {
- let padding = 10.max(terminal_height / 2);
- top = selected_index - padding;
- bottom = top + window_height;
- }
+ selected_index - 10.max(terminal_height / 2)
+ };
+ let bottom = top + window_height;
(top, bottom)
}