summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorqkzk <qkzk@users.noreply.github.com>2023-01-08 16:26:53 +0100
committerGitHub <noreply@github.com>2023-01-08 16:26:53 +0100
commit2c05d3f1e0f87eb06000135bf8feb3edeb1a6f6d (patch)
tree810a2bb362638db5d2ed8f0d40ce95ea81b773cf
parent40511c485cb590370c00f9ac8a7b3e8b1583a28c (diff)
parentc3440b4456c0e36f8560a9b85e022f7c20045332 (diff)
Merge pull request #65 from qkzk/colored-tree-viewv0.1.9
Colored tree view
-rw-r--r--Cargo.lock9
-rw-r--r--Cargo.toml5
-rw-r--r--config_files/fm/config.yaml1
-rw-r--r--development.md37
-rw-r--r--src/action_map.rs12
-rw-r--r--src/color_cache.rs2
-rw-r--r--src/completion.rs10
-rw-r--r--src/config.rs3
-rw-r--r--src/event_dispatch.rs2
-rw-r--r--src/event_exec.rs448
-rw-r--r--src/fileinfo.rs145
-rw-r--r--src/fm_error.rs3
-rw-r--r--src/help.rs8
-rw-r--r--src/keybindings.rs4
-rw-r--r--src/lib.rs1
-rw-r--r--src/log.rs2
-rw-r--r--src/main.rs2
-rw-r--r--src/mode.rs32
-rw-r--r--src/preview.rs185
-rw-r--r--src/status.rs30
-rw-r--r--src/tab.rs134
-rw-r--r--src/term_manager.rs63
-rw-r--r--src/trash.rs4
-rw-r--r--src/tree.rs422
24 files changed, 1246 insertions, 318 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 614a5ed..0e92138 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -935,7 +935,7 @@ dependencies = [
[[package]]
name = "fm-tui"
-version = "0.1.8"
+version = "0.1.9"
dependencies = [
"chrono",
"clap 4.0.32",
@@ -961,7 +961,6 @@ dependencies = [
"strum_macros 0.24.3",
"syntect",
"sysinfo",
- "termtree",
"tuikit",
"url-escape",
"users",
@@ -2568,12 +2567,6 @@ dependencies = [
]
[[package]]
-name = "termtree"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95059e91184749cb66be6dc994f67f182b6d897cb3df74a5bf66b5e709295fd8"
-
-[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 44919fd..550dd43 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "fm-tui"
-version = "0.1.8"
+version = "0.1.9"
authors = ["Quentin Konieczko <qu3nt1n@gmail.com>"]
edition = "2021"
license-file = "LICENSE.txt"
@@ -9,7 +9,7 @@ readme = "readme.md"
repository = "https://github.com/qkzk/fm"
keywords = ["tui", "file-manager", "file", "manager", "tuikit"]
categories = ["command-line-utilities", "filesystem", "os::unix-apis"]
-documentation = "https://docs.serde.rs/fm-tui/"
+documentation = "https://docs.rs/fm-tui/latest/"
[profile.release]
strip = true # Automatically strip symbols from the binary.
@@ -53,7 +53,6 @@ strum = {version = "0.24.1", features = ["derive"]}
strum_macros = "0.24.3"
syntect = "5.0.0"
sysinfo = "0.26.7"
-termtree = "0.4.0"
tuikit = "0.5.0"
url-escape = "0.1.1"
users = "0.11.0"
diff --git a/config_files/fm/config.yaml b/config_files/fm/config.yaml
index 107b752..d58bcf4 100644
--- a/config_files/fm/config.yaml
+++ b/config_files/fm/config.yaml
@@ -45,6 +45,7 @@ keys:
'q': Quit
'r': Rename
's': Shell
+ 't': Tree
'u': ClearFlags
'v': ReverseFlags
'w': RegexMatch
diff --git a/development.md b/development.md
index ddb9b34..069b398 100644
--- a/development.md
+++ b/development.md
@@ -263,6 +263,39 @@
- [x] improve fuzzy finding by moving to the selected file
- [x] use latest version of skim-qkzk
+### Version 0.1.9 : tree view
+
+New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally.
+
+- [x] display a tree view with T
+- [x] navigate in the tree
+- [x] enter a directory from the tree
+- [x] enter a file:
+ - [x] enter parent
+ - [x] select the file
+- [x] scrolling
+ - [x] last file can't be at top
+- [x] enable most modes
+ - [x] copy, cut, delete, trash, search
+- [x] enable most actions
+ - [x] END move to last leaf
+ - [x] toggle flag and display flagged files
+ - [x] copy filename, filepath
+ - [x] toggle hidden
+ - [x] drag n drop
+ - [x] symlink
+- [x] disabled:
+ - [x] regex match: would only in current root path
+ - [x] sort: would require recursive sort of every directory
+- [x] replace preview::directory by mode::tree
+- [x] filter
+ - [x] force display full in tree mode
+- [x] search simple: first result
+- [x] fold
+ - [x] fold a single directory
+ - [x] display a triangle to display folded status
+ - [x] unfold all, fold all
+
## TODO
- [ ] remote control
@@ -286,10 +319,6 @@
- [x] print in term
- [x] print in raw mode
- [ ] print with term: term...
-- [ ] navigable tree view [termtree](https://crates.io/crates/termtree)
- - [x] preview directory with tree view
- - [x] use as another display
- - [ ] navigation
- [ ] zoxide support
- [ ] Version 0.2.0 : tests
diff --git a/src/action_map.rs b/src/action_map.rs
index deadfd1..aad13e6 100644
--- a/src/action_map.rs
+++ b/src/action_map.rs
@@ -71,6 +71,10 @@ pub enum ActionMap {
TrashRestoreFile,
TrashEmpty,
TrashOpen,
+ Tree,
+ TreeFold,
+ TreeUnFoldAll,
+ TreeFoldAll,
}
impl ActionMap {
@@ -118,7 +122,7 @@ impl ActionMap {
ActionMap::OpenFile => EventExec::event_open_file(status),
ActionMap::PageDown => EventExec::page_down(status),
ActionMap::PageUp => EventExec::page_up(status),
- ActionMap::Preview => EventExec::event_preview(current_tab),
+ ActionMap::Preview => EventExec::event_preview(status),
ActionMap::Quit => EventExec::event_quit(current_tab),
ActionMap::RefreshView => EventExec::event_refreshview(status),
ActionMap::RegexMatch => EventExec::event_regex_match(current_tab),
@@ -134,11 +138,15 @@ impl ActionMap {
ActionMap::Thumbnail => EventExec::event_thumbnail(current_tab),
ActionMap::ToggleDualPane => EventExec::event_toggle_dualpane(status),
ActionMap::ToggleFlag => EventExec::event_toggle_flag(status),
- ActionMap::ToggleHidden => EventExec::event_toggle_hidden(current_tab),
+ ActionMap::ToggleHidden => EventExec::event_toggle_hidden(status),
ActionMap::TrashMoveFile => EventExec::event_trash_move_file(status),
ActionMap::TrashRestoreFile => EventExec::event_trash_restore_file(status),
ActionMap::TrashEmpty => EventExec::exec_trash_empty(status),
ActionMap::TrashOpen => EventExec::event_trash_open(status),
+ ActionMap::Tree => EventExec::event_tree(status),
+ ActionMap::TreeFold => EventExec::event_tree_fold(status),
+ ActionMap::TreeFoldAll => EventExec::event_tree_fold_all(status),
+ ActionMap::TreeUnFoldAll => EventExec::event_tree_unfold_all(status),
ActionMap::Nothing => Ok(()),
}
diff --git a/src/color_cache.rs b/src/color_cache.rs
index 53339d3..9b09439 100644
--- a/src/color_cache.rs
+++ b/src/color_cache.rs
@@ -7,7 +7,7 @@ use tuikit::attr::Color;
/// Holds a map of extension name to color.
/// Every extension is associated to a color which is only computed once
/// per run. This trades a bit of memory for a bit of CPU.
-#[derive(Default)]
+#[derive(Default, Clone, Debug)]
pub struct ColorCache {
cache: RefCell<HashMap<String, Color>>,
}
diff --git a/src/completion.rs b/src/completion.rs
index 2d44c0a..7f88bba 100644
--- a/src/completion.rs
+++ b/src/completion.rs
@@ -2,10 +2,10 @@ use std::fs::{self, ReadDir};
use crate::fileinfo::PathContent;
use crate::fm_error::FmResult;
-use crate::mode::Mode;
+use crate::mode::{LastMode, Mode};
/// Different kind of completions
-#[derive(Clone, Default)]
+#[derive(Clone, Default, Copy)]
pub enum InputCompleted {
/// No completion needed
#[default]
@@ -13,7 +13,7 @@ pub enum InputCompleted {
/// Complete a directory path in filesystem
Goto,
/// Complete a filename from current directory
- Search,
+ Search(LastMode),
/// Complete an executable name from $PATH
Exec,
}
@@ -32,7 +32,7 @@ pub struct Completion {
impl Completion {
pub fn set_kind(&mut self, mode: &Mode) {
if let Mode::InputCompleted(completion_kind) = mode {
- self.kind = completion_kind.clone()
+ self.kind = *completion_kind
} else {
self.kind = InputCompleted::Nothing
}
@@ -106,7 +106,7 @@ impl Completion {
match self.kind {
InputCompleted::Exec => self.exec(input_string),
InputCompleted::Goto => self.goto(input_string, current_path),
- InputCompleted::Search => self.search(input_string, path_content),
+ InputCompleted::Search(_) => self.search(input_string, path_content),
InputCompleted::Nothing => Ok(()),
}
}
diff --git a/src/config.rs b/src/config.rs
index 09c9041..74e9167 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -3,6 +3,7 @@ use std::{fs::File, path};
use serde_yaml;
use tuikit::attr::Color;
+use crate::color_cache::ColorCache;
use crate::constant_strings_paths::DEFAULT_TERMINAL_APPLICATION;
use crate::fm_error::FmResult;
use crate::keybindings::Bindings;
@@ -70,6 +71,7 @@ pub struct Colors {
pub socket: String,
/// Color for `symlink` files.
pub symlink: String,
+ pub color_cache: ColorCache,
}
impl Colors {
@@ -102,6 +104,7 @@ impl Colors {
fifo: "blue".to_owned(),
socket: "cyan".to_owned(),
symlink: "magenta".to_owned(),
+ color_cache: ColorCache::default(),
}
}
}
diff --git a/src/event_dispatch.rs b/src/event_dispatch.rs
index 8ace282..4f2bb33 100644
--- a/src/event_dispatch.rs
+++ b/src/event_dispatch.rs
@@ -75,7 +75,7 @@ impl EventDispatcher {
Mode::InputCompleted(_) => {
EventExec::event_text_insert_and_complete(status.selected(), c)
}
- Mode::Normal => match self.binds.get(&key_char) {
+ Mode::Normal | Mode::Tree => match self.binds.get(&key_char) {
Some(event_char) => event_char.matcher(status),
None => Ok(()),
},
diff --git a/src/event_exec.rs b/src/event_exec.rs
index db65a35..170d20b 100644
--- a/src/event_exec.rs
+++ b/src/event_exec.rs
@@ -9,6 +9,7 @@ use sysinfo::SystemExt;
use crate::bulkrename::Bulkrename;
use crate::completion::InputCompleted;
+use crate::config::Colors;
use crate::constant_strings_paths::DEFAULT_DRAGNDROP;
use crate::constant_strings_paths::NVIM_RPC_SENDER;
use crate::content_window::RESERVED_ROWS;
@@ -16,6 +17,7 @@ use crate::copy_move::CopyMove;
use crate::fileinfo::FileKind;
use crate::filter::FilterKind;
use crate::fm_error::{FmError, FmResult};
+use crate::mode::LastMode;
use crate::mode::Navigate;
use crate::mode::{InputSimple, MarkAction, Mode, NeedConfirmation};
use crate::opener::execute_in_child;
@@ -34,7 +36,12 @@ impl EventExec {
/// Reset the selected tab view to the default.
pub fn refresh_status(status: &mut Status) -> FmResult<()> {
status.refresh_users()?;
- status.selected().refresh_view()
+ status.selected().refresh_view()?;
+ if let Mode::Tree = status.selected_non_mut().mode {
+ let colors = &status.config_colors.clone();
+ status.selected().make_tree(colors)?
+ }
+ Ok(())
}
/// When a rezise event occurs, we may hide the second panel if the width
@@ -84,10 +91,21 @@ impl EventExec {
/// Toggle a single flag and move down one row.
pub fn event_toggle_flag(status: &mut Status) -> FmResult<()> {
- if let Some(file) = status.selected().path_content.selected() {
- let path = file.path.clone();
- status.toggle_flag_on_path(&path);
- Self::event_down_one_row(status.selected());
+ let tab = status.selected_non_mut();
+
+ match tab.mode {
+ Mode::Normal => {
+ if let Some(file) = tab.path_content.selected() {
+ let path = file.path.clone();
+ status.toggle_flag_on_path(&path);
+ Self::event_down_one_row(status.selected());
+ }
+ }
+ Mode::Tree => {
+ let path = tab.directory.tree.current_node.filepath();
+ status.toggle_flag_on_path(&path);
+ }
+ _ => (),
}
Ok(())
}
@@ -109,14 +127,9 @@ impl EventExec {
}
status.selected().mode = Mode::InputSimple(InputSimple::Chmod);
if status.flagged.is_empty() {
- status.flagged.push(
- status.tabs[status.index]
- .path_content
- .selected()
- .unwrap()
- .path
- .clone(),
- );
+ status
+ .flagged
+ .push(status.tabs[status.index].selected().unwrap().path.clone());
};
status.reset_tabs_view()
}
@@ -170,10 +183,9 @@ impl EventExec {
.as_path()
.file_name()
.ok_or_else(|| FmError::custom("event symlink", "File not found"))?;
- let newpath = status.tabs[status.index]
- .path_content
- .path
- .clone()
+ let newpath = status
+ .selected_non_mut()
+ .directory_of_selected()?
.join(filename);
std::os::unix::fs::symlink(oldpath, newpath)?;
}
@@ -246,7 +258,8 @@ impl EventExec {
let tab = status.selected();
tab.input.clear();
tab.history.push(target_dir);
- tab.path_content.change_directory(target_dir)?;
+ tab.path_content
+ .change_directory(target_dir, &tab.filter, tab.show_hidden)?;
let index = tab.find_jump_index(&jump_target).unwrap_or_default();
tab.path_content.select_index(index);
tab.set_window();
@@ -289,7 +302,7 @@ impl EventExec {
pub fn event_normal(tab: &mut Tab) -> FmResult<()> {
tab.input.reset();
tab.completion.reset();
- tab.path_content.reset_files()?;
+ tab.path_content.reset_files(&tab.filter, tab.show_hidden)?;
tab.window.reset(tab.path_content.content.len());
tab.mode = Mode::Normal;
tab.preview = Preview::new_empty();
@@ -446,11 +459,7 @@ impl EventExec {
/// Does
/// Add the starting directory to history.
pub fn event_move_to_parent(tab: &mut Tab) -> FmResult<()> {
- let path = tab.path_content.path.clone();
- if let Some(parent) = path.parent() {
- tab.set_pathcontent(parent)?;
- }
- Ok(())
+ tab.move_to_parent()
}
/// Move the cursor left one block.
@@ -539,17 +548,21 @@ impl EventExec {
/// Every file can be previewed. See the `crate::enum::Preview` for
/// more details on previewinga file.
/// Does nothing if the directory is empty.
- pub fn event_preview(tab: &mut Tab) -> FmResult<()> {
- if tab.path_content.content.is_empty() {
+ pub fn event_preview(status: &mut Status) -> FmResult<()> {
+ if status.selected_non_mut().path_content.is_empty() {
return Ok(());
}
- if let Some(file_info) = tab.path_content.selected() {
+ let unmutable_tab = status.selected_non_mut();
+ if let Some(file_info) = unmutable_tab.selected() {
match file_info.file_kind {
- FileKind::Directory | FileKind::NormalFile => {
- tab.mode = Mode::Preview;
- tab.preview = Preview::new(file_info)?;
- tab.window.reset(tab.preview.len());
+ FileKind::NormalFile => {
+ let preview =
+ Preview::new(file_info, &unmutable_tab.path_content.users_cache, status)?;
+ status.selected().mode = Mode::Preview;
+ status.selected().window.reset(preview.len());
+ status.selected().preview = preview;
}
+ FileKind::Directory => Self::event_tree(status)?,
_ => (),
}
}
@@ -582,28 +595,38 @@ impl EventExec {
/// Matching items are displayed as you type them.
pub fn event_search(tab: &mut Tab) -> FmResult<()> {
tab.searched = None;
- tab.mode = Mode::InputCompleted(InputCompleted::Search);
+ tab.mode = Mode::InputCompleted(InputCompleted::Search(LastMode::from_mode(tab.mode)));
Ok(())
}
/// Enter the regex mode.
/// Every file matching the typed regex will be flagged.
pub fn event_regex_match(tab: &mut Tab) -> FmResult<()> {
- tab.mode = Mode::InputSimple(InputSimple::RegexMatch);
+ match tab.mode {
+ Mode::Tree => (),
+ _ => tab.mode = Mode::InputSimple(InputSimple::RegexMatch),
+ }
Ok(())
}
/// Enter the sort mode, allowing the user to select a sort method.
pub fn event_sort(tab: &mut Tab) -> FmResult<()> {
- tab.mode = Mode::InputSimple(InputSimple::Sort);
+ match tab.mode {
+ Mode::Tree => (),
+ _ => tab.mode = Mode::InputSimple(InputSimple::Sort),
+ }
Ok(())
}
/// Once a quit event is received, we change a flag and break the main loop.
/// It's usefull to reset the cursor before leaving the application.
pub fn event_quit(tab: &mut Tab) -> FmResult<()> {
- tab.must_quit = true;
- Ok(())
+ if let Mode::Tree = tab.mode {
+ Self::event_normal(tab)
+ } else {
+ tab.must_quit = true;
+ Ok(())
+ }
}
/// Reset the mode to normal.
@@ -638,11 +661,15 @@ impl EventExec {
}
/// Toggle the display of hidden files.
- pub fn event_toggle_hidden(tab: &mut Tab) -> FmResult<()> {
+ pub fn event_toggle_hidden(status: &mut Status) -> FmResult<()> {
+ let colors = &status.config_colors.clone();
+ let tab = status.selected();
tab.show_hidden = !tab.show_hidden;
- tab.path_content.show_hidden = !tab.path_content.show_hidden;
- tab.path_content.reset_files()?;
+ tab.path_content.reset_files(&tab.filter, tab.show_hidden)?;
tab.window.reset(tab.path_content.content.len());
+ if let Mode::Tree = tab.mode {
+ tab.make_tree(colors)?
+ }
Ok(())
}
@@ -651,7 +678,6 @@ impl EventExec {
match status.opener.open(
&status
.selected_non_mut()
- .path_content
.selected()
.ok_or_else(|| FmError::custom("event open file", "Empty directory"))?
.path,
@@ -667,8 +693,13 @@ impl EventExec {
}
/// Enter the rename mode.
+ /// Keep a track of the current mode to ensure we rename the correct file.
+ /// When we enter rename from a "tree" mode, we'll need to rename the selected file in the tree,
+ /// not the selected file in the pathcontent.
pub fn event_rename(tab: &mut Tab) -> FmResult<()> {
- tab.mode = Mode::InputSimple(InputSimple::Rename);
+ if tab.selected().is_some() {
+ tab.mode = Mode::InputSimple(InputSimple::Rename(LastMode::from_mode(tab.mode)));
+ }
Ok(())
}
@@ -684,15 +715,11 @@ impl EventExec {
/// is terminated first.
pub fn event_shell(status: &mut Status) -> FmResult<()> {
let tab = status.selected_non_mut();
- execute_in_child(
- &status.opener.terminal.clone(),
- &vec![
- "-d",
- tab.path_content.path.to_str().ok_or_else(|| {
- FmError::custom("event_shell", "Couldn't parse the path name")
- })?,
- ],
- )?;
+ let path = tab
+ .directory_of_selected()?
+ .to_str()
+ .ok_or_else(|| FmError::custom("event_shell", "Couldn't parse the directory"))?;
+ execute_in_child(&status.opener.terminal.clone(), &vec!["-d", path])?;
Ok(())
}
@@ -748,49 +775,53 @@ impl EventExec {
/// reasons unknow to me - it does nothing.
/// It requires the "nvim-send" application to be in $PATH.
pub fn event_nvim_filepicker(tab: &mut Tab) -> FmResult<()> {
- if tab.path_content.content.is_empty() {
- info!("Called nvim filepicker in an empty directory.");
- return Ok(());
- }
- // "nvim-send --remote-send '<esc>:e readme.md<cr>' --servername 127.0.0.1:8888"
if let Ok(nvim_listen_address) = Self::nvim_listen_address(tab) {
- if let Some(path_str) = tab.path_content.selected_path_string() {
- let _ = execute_in_child(
- NVIM_RPC_SENDER,
- &vec![
- "--remote-send",
- &format!("<esc>:e {}<cr><esc>:close<cr>", path_str),
- "--servername",
- &nvim_listen_address,
- ],
- );
+ if let Some(fileinfo) = tab.selected() {
+ if let Some(path_str) = fileinfo.path.to_str() {
+ let _ = execute_in_child(
+ NVIM_RPC_SENDER,
+ &vec![
+ "--remote-send",
+ &format!("<esc>:e {}<cr><esc>:close<cr>", path_str),
+ "--servername",
+ &nvim_listen_address,
+ ],
+ );
+ }
}
- } else {
- info!("Nvim server not defined");
}
Ok(())
}
/// Copy the selected filename to the clipboard. Only the filename.
pub fn event_filename_to_clipboard(tab: &Tab) -> FmResult<()> {
- if let Some(file) = tab.path_content.selected() {
- let filename = file.filename.clone();
- let mut ctx = ClipboardContext::new()?;
- ctx.set_contents(filename)?;
- // For some reason, it's not writen if you don't read it back...
- let _ = ctx.get_contents();
- }
+ let filename = tab
+ .selected()
+ .ok_or_else(|| FmError::custom("event_filename_to_clipboard", "no selected file"))?
+ .filename
+ .clone();
+ let mut ctx = ClipboardContext::new()?;
+ ctx.set_contents(filename)?;
+ // For some reason, it's not writen if you don't read it back...
+ let _ = ctx.get_contents();
+
Ok(())
}
/// Copy the selected filepath to the clipboard. The absolute path.
pub fn event_filepath_to_clipboard(tab: &Tab) -> FmResult<()> {
- if let Some(filepath) = tab.path_content.selected_path_string() {
- let mut ctx = ClipboardContext::new()?;
- ctx.set_contents(filepath)?;
- // For some reason, it's not writen if you don't read it back...
- let _ = ctx.get_contents();
- }
+ let filepath = tab
+ .selected()
+ .ok_or_else(|| FmError::custom("event_filepath_to_clipboard", "no selected file"))?
+ .path
+ .to_str()
+ .ok_or_else(|| FmError::custom("event_filepath_to_clipboard", "no selected file"))?
+ .to_owned();
+ info!("filepath: {}", filepath);
+ let mut ctx = ClipboardContext::new()?;
+ ctx.set_contents(filepath)?;
+ // For some reason, it's not writen if you don't read it back...
+ let _ = ctx.get_contents();
Ok(())
}
@@ -836,19 +867,27 @@ impl EventExec {
/// It uses the `fs::rename` function and has the same limitations.
/// We only tries to rename in the same directory, so it shouldn't be a problem.
/// Filename is sanitized before processing.
- pub fn exec_rename(tab: &mut Tab) -> FmResult<()> {
- if tab.path_content.content.is_empty() {
- return Err(FmError::custom("event rename", "Empty directory"));
+ pub fn exec_rename(tab: &mut Tab, lastmode: LastMode) -> FmResult<()> {
+ let fileinfo = match lastmode {
+ LastMode::Tree => &tab.directory.tree.current_node.fileinfo,
+ LastMode::Other => tab
+ .path_content
+ .selected()
+ .ok_or_else(|| FmError::custom("rename", "couldnt parse selected"))?,
+ };
+
+ info!("fileinfo {:?}", fileinfo);
+ let original_path = &fileinfo.path;
+ if let Some(parent) = original_path.parent() {
+ let new_path = parent.join(sanitize_filename::sanitize(tab.input.string()));
+ info!(
+ "original: {} - new: {}",
+ original_path.display(),
+ new_path.display()
+ );
+ fs::rename(original_path, new_path)?;
}
- fs::rename(
- tab.path_content
- .selected_path_string()
- .ok_or_else(|| FmError::custom("exec rename", "File not found"))?,
- tab.path_content
- .path
- .to_path_buf()
- .join(&sanitize_filename::sanitize(tab.input.string())),
- )?;
+
tab.refresh_view()
}
@@ -898,9 +937,19 @@ impl EventExec {
let mut args: Vec<&str> = exec_command.split(' ').collect();
let command = args.remove(0);
if std::path::Path::new(command).exists() {
- let path = &tab.path_content.selected_path_string().ok_or_else(|| {
- FmError::custom("exec exec", &format!("can't find command {}", command))
- })?;
+ let path = &tab
+ .selected()
+ .ok_or_else(|| {
+ FmError::custom("exec exec", &format!("can't find command {}", command))
+ })?
+ .path
+ .to_str()
+ .ok_or_else(|| {
+ FmError::custom("exec exec", &format!("can't find command {}", command))
+ })?;
+ // let path = &tab.path_content.selected_path_string().ok_or_else(|| {
+ // FmError::custom("exec exec", &format!("can't find command {}", command))
+ // })?;
args.push(path);
execute_in_child(command, &args)?;
tab.completion.reset();
@@ -913,15 +962,14 @@ impl EventExec {
/// It obviously requires the `dragon-drop` command to be installed.
pub fn event_drag_n_drop(status: &mut Status) -> FmResult<()> {
let tab = status.selected_non_mut();
- execute_in_child(
- DEFAULT_DRAGNDROP,
- &vec![&tab.path_content.selected_path_string().ok_or_else(|| {
- FmError::custom(
- "event drag n drop",
- "can't find dragon-drop in the system. Is the application installed?",
- )
- })?],
- )?;
+ if let Some(file) = tab.selected() {
+ let path_str = file
+ .path
+ .to_str()
+ .ok_or_else(|| FmError::custom("event drag n drop", "Couldn't read path"))?;
+
+ execute_in_child(DEFAULT_DRAGNDROP, &vec![path_str])?;
+ }
Ok(())
}
@@ -930,22 +978,43 @@ impl EventExec {
/// ie. If you typed `"jpg"` before, it will move to the first file
/// whose filename contains `"jpg"`.
/// The current order of files is used.
- pub fn exec_search(tab: &mut Tab) {
+ pub fn exec_search(tab: &mut Tab, lastmode: LastMode, colors: &Colors) -> FmResult<()> {
let searched = tab.input.string();
tab.input.reset();
if searched.is_empty() {
tab.searched = None;
- return;
+ return Ok(());
}
tab.searched = Some(searched.clone());
- let next_index = tab.path_content.index;
- tab.search_from(&searched, next_index);
+ match lastmode {
+ LastMode::Tree => {
+ tab.directory.tree.unselect_children();
+ if let Some(position) = tab.directory.tree.select_first_match(&searched) {
+ tab.directory.tree.position = position;
+ tab.directory.tree.select_from_position()?;
+ } else {
+ tab.directory.tree.select_root()
+ };
+ tab.directory.make_preview(colors);
+ Ok(())
+ }
+ LastMode::Other => {
+ let next_index = tab.path_content.index;
+ tab.search_from(&searched, next_inde