diff options
-rw-r--r-- | Cargo.lock | 9 | ||||
-rw-r--r-- | Cargo.toml | 5 | ||||
-rw-r--r-- | config_files/fm/config.yaml | 1 | ||||
-rw-r--r-- | development.md | 37 | ||||
-rw-r--r-- | src/action_map.rs | 12 | ||||
-rw-r--r-- | src/color_cache.rs | 2 | ||||
-rw-r--r-- | src/completion.rs | 10 | ||||
-rw-r--r-- | src/config.rs | 3 | ||||
-rw-r--r-- | src/event_dispatch.rs | 2 | ||||
-rw-r--r-- | src/event_exec.rs | 448 | ||||
-rw-r--r-- | src/fileinfo.rs | 145 | ||||
-rw-r--r-- | src/fm_error.rs | 3 | ||||
-rw-r--r-- | src/help.rs | 8 | ||||
-rw-r--r-- | src/keybindings.rs | 4 | ||||
-rw-r--r-- | src/lib.rs | 1 | ||||
-rw-r--r-- | src/log.rs | 2 | ||||
-rw-r--r-- | src/main.rs | 2 | ||||
-rw-r--r-- | src/mode.rs | 32 | ||||
-rw-r--r-- | src/preview.rs | 185 | ||||
-rw-r--r-- | src/status.rs | 30 | ||||
-rw-r--r-- | src/tab.rs | 134 | ||||
-rw-r--r-- | src/term_manager.rs | 63 | ||||
-rw-r--r-- | src/trash.rs | 4 | ||||
-rw-r--r-- | src/tree.rs | 422 |
24 files changed, 1246 insertions, 318 deletions
@@ -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" @@ -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_index); + Ok(()) + } + } } pub fn event_search_next(tab: &mut Tab) -> FmResult<()> { - if let Some(searched) = tab.searched.clone() { - let next_index = (tab.path_content.index + 1) % tab.path_content.content.len(); - tab.search_from(&searched, next_index); + match tab.mode { + Mode::Tree => (), + _ => { + if let Some(searched) = tab.searched.clone() { + let next_index = (tab.path_content.index + 1) % tab.path_content.content.len(); + tab.search_from(&searched, next_index); + } + } } Ok(()) } @@ -999,9 +1068,9 @@ impl EventExec { /// See `crate::filter` for more details. pub fn exec_filter(tab: &mut Tab) -> FmResult<()> { let filter = FilterKind::from_input(&tab.input.string()); - tab. |