diff options
-rw-r--r-- | development.md | 2 | ||||
-rw-r--r-- | src/event_exec.rs | 25 | ||||
-rw-r--r-- | src/opener.rs | 31 | ||||
-rw-r--r-- | src/status.rs | 4 | ||||
-rw-r--r-- | src/tab.rs | 16 | ||||
-rw-r--r-- | src/term_manager.rs | 4 | ||||
-rw-r--r-- | src/tree.rs | 105 |
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) @@ -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) } |