diff options
-rw-r--r-- | src/action_map.rs | 6 | ||||
-rw-r--r-- | src/config.rs | 5 | ||||
-rw-r--r-- | src/event_dispatch.rs | 6 | ||||
-rw-r--r-- | src/event_exec.rs | 85 | ||||
-rw-r--r-- | src/fileinfo.rs | 18 | ||||
-rw-r--r-- | src/filter.rs | 7 | ||||
-rw-r--r-- | src/fm_error.rs | 13 | ||||
-rw-r--r-- | src/git.rs | 2 | ||||
-rw-r--r-- | src/help.rs | 5 | ||||
-rw-r--r-- | src/input.rs | 2 | ||||
-rw-r--r-- | src/keybindings.rs | 8 | ||||
-rw-r--r-- | src/log.rs | 3 | ||||
-rw-r--r-- | src/main.rs | 13 | ||||
-rw-r--r-- | src/marks.rs | 9 | ||||
-rw-r--r-- | src/mode.rs | 6 | ||||
-rw-r--r-- | src/opener.rs | 61 | ||||
-rw-r--r-- | src/preview.rs | 87 | ||||
-rw-r--r-- | src/shortcut.rs | 16 | ||||
-rw-r--r-- | src/status.rs | 109 | ||||
-rw-r--r-- | src/tab.rs | 36 | ||||
-rw-r--r-- | src/term_manager.rs | 11 | ||||
-rw-r--r-- | src/utils.rs | 20 | ||||
-rw-r--r-- | src/visited.rs | 16 |
23 files changed, 412 insertions, 132 deletions
diff --git a/src/action_map.rs b/src/action_map.rs index f3614c8..f6abb02 100644 --- a/src/action_map.rs +++ b/src/action_map.rs @@ -95,7 +95,7 @@ impl ActionMap { ActionMap::FlagAll => EventExec::event_flag_all(status), ActionMap::FuzzyFind => EventExec::event_fuzzyfind(status), ActionMap::Goto => EventExec::event_goto(current_tab), - ActionMap::Help => EventExec::event_help(current_tab), + ActionMap::Help => EventExec::event_help(status), ActionMap::History => EventExec::event_history(current_tab), ActionMap::Home => EventExec::event_home(current_tab), ActionMap::Jump => EventExec::event_jump(status), @@ -110,7 +110,7 @@ impl ActionMap { ActionMap::NewDir => EventExec::event_new_dir(current_tab), ActionMap::NewFile => EventExec::event_new_file(current_tab), ActionMap::NvimFilepicker => EventExec::event_nvim_filepicker(current_tab), - ActionMap::OpenFile => EventExec::event_open_file(current_tab), + 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), @@ -120,7 +120,7 @@ impl ActionMap { ActionMap::Rename => EventExec::event_rename(current_tab), ActionMap::ReverseFlags => EventExec::event_reverse_flags(status), ActionMap::Search => EventExec::event_search(current_tab), - ActionMap::Shell => EventExec::event_shell(current_tab), + ActionMap::Shell => EventExec::event_shell(status), ActionMap::Shortcut => EventExec::event_shortcut(current_tab), ActionMap::Sort => EventExec::event_sort(current_tab), ActionMap::Symlink => EventExec::event_symlink(status), diff --git a/src/config.rs b/src/config.rs index 95c71af..a477ea2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -30,6 +30,11 @@ impl Config { } } + /// The terminal name + pub fn terminal(&self) -> &str { + &self.terminal + } + /// Updates the config from a configuration content. fn update_from_config(&mut self, yaml: &serde_yaml::value::Value) -> FmResult<()> { self.colors.update_from_config(&yaml["colors"]); diff --git a/src/event_dispatch.rs b/src/event_dispatch.rs index d725451..2c2d9d8 100644 --- a/src/event_dispatch.rs +++ b/src/event_dispatch.rs @@ -20,8 +20,12 @@ impl EventDispatcher { pub fn new(binds: Bindings) -> Self { Self { binds } } + /// Reaction to received events. - pub fn read_event(&self, status: &mut Status, ev: Event) -> FmResult<()> { + /// Only non keyboard events are dealt here directly. + /// Keyboard events are configurable and are sent to specific functions + /// which needs to know those keybindings. + pub fn dispatch(&self, status: &mut Status, ev: Event) -> FmResult<()> { match ev { Event::Key(Key::WheelUp(_, _, _)) => EventExec::event_move_up(status), Event::Key(Key::WheelDown(_, _, _)) => EventExec::event_move_down(status), diff --git a/src/event_exec.rs b/src/event_exec.rs index 1bda454..53d92e4 100644 --- a/src/event_exec.rs +++ b/src/event_exec.rs @@ -62,7 +62,7 @@ impl EventExec { .for_each(|file| { status.flagged.insert(file.path.clone()); }); - status.reset_statuses() + status.reset_tabs_view() } /// Reverse every flag in _current_ directory. Flagged files in other @@ -79,7 +79,7 @@ impl EventExec { status.flagged.insert(file.path.clone()); } }); - status.reset_statuses() + status.reset_tabs_view() } /// Toggle a single flag and move down one row. @@ -128,7 +128,7 @@ impl EventExec { .clone(), ); }; - status.reset_statuses() + status.reset_tabs_view() } /// Enter JUMP mode, allowing to jump to any flagged file. @@ -191,8 +191,7 @@ impl EventExec { /// can edit the selected filenames. /// Once the temp file is saved, those file names are changed. pub fn event_bulkrename(status: &mut Status) -> FmResult<()> { - Bulkrename::new(status.filtered_flagged_files())? - .rename(&status.selected_non_mut().opener)?; + Bulkrename::new(status.filtered_flagged_files())?.rename(&status.opener)?; status.selected().refresh_view() } @@ -238,7 +237,7 @@ impl EventExec { status.flagged.clear() } status.selected().refresh_view()?; - status.reset_statuses() + status.reset_tabs_view() } fn _exec_last_edition(status: &mut Status) -> FmResult<()> { @@ -462,14 +461,15 @@ impl EventExec { } /// Open the file with configured opener or enter the directory. - pub fn exec_file(tab: &mut Tab) -> FmResult<()> { + pub fn exec_file(status: &mut Status) -> FmResult<()> { + let tab = status.selected(); if tab.path_content.is_empty() { return Ok(()); } if tab.path_content.is_selected_dir()? { tab.go_to_child() } else { - Self::event_open_file(tab) + Self::event_open_file(status) } } @@ -557,9 +557,11 @@ impl EventExec { /// Display the help which can be navigated and displays the configrable /// binds. - pub fn event_help(tab: &mut Tab) -> FmResult<()> { + pub fn event_help(status: &mut Status) -> FmResult<()> { + let help = status.help.clone(); + let tab = status.selected(); tab.mode = Mode::Help; - tab.preview = Preview::help(tab.help.clone()); + tab.preview = Preview::help(help); tab.window.reset(tab.preview.len()); Ok(()) } @@ -649,9 +651,11 @@ impl EventExec { } /// Open a file with custom opener. - pub fn event_open_file(tab: &mut Tab) -> FmResult<()> { - match tab.opener.open( - tab.path_content + pub fn event_open_file(status: &mut Status) -> FmResult<()> { + match status.opener.open( + status + .selected_non_mut() + .path_content .selected_file() .ok_or_else(|| { FmError::new( @@ -665,7 +669,7 @@ impl EventExec { Ok(_) => (), Err(e) => info!( "Error opening {:?}: {:?}", - tab.path_content.selected_file(), + status.selected_non_mut().path_content.selected_file(), e ), } @@ -688,9 +692,10 @@ impl EventExec { /// Open a new terminal in current directory. /// The shell is a fork of current process and will exit if the application /// is terminated first. - pub fn event_shell(tab: &mut Tab) -> FmResult<()> { + pub fn event_shell(status: &mut Status) -> FmResult<()> { + let tab = status.selected_non_mut(); execute_in_child( - &tab.terminal, + &status.opener.terminal.clone(), &vec![ "-d", tab.path_content.path.to_str().ok_or_else(|| { @@ -744,9 +749,9 @@ impl EventExec { })? .file_kind { - Self::exec_file(tab) + Self::exec_file(status) } else { - Self::event_open_file(tab) + Self::event_open_file(status) } } else { Ok(()) @@ -977,6 +982,8 @@ impl EventExec { Ok(()) } + /// Move to the selected shortcut. + /// It may fail if the user has no permission to visit the path. pub fn exec_shortcut(tab: &mut Tab) -> FmResult<()> { tab.input.reset(); let path = tab.shortcut.selected(); @@ -985,6 +992,8 @@ impl EventExec { Self::event_normal(tab) } + /// Move back to a previously visited path. + /// It may fail if the user has no permission to visit the path pub fn exec_history(tab: &mut Tab) -> FmResult<()> { tab.input.reset(); tab.path_content = PathContent::new( @@ -1000,6 +1009,8 @@ impl EventExec { Self::event_normal(tab) } + /// Apply a filter to the displayed files. + /// See `crate::filter` for more details. pub fn exec_filter(tab: &mut Tab) -> FmResult<()> { let filter = FilterKind::from_input(&tab.input.string); tab.path_content.set_filter(filter); @@ -1008,6 +1019,8 @@ impl EventExec { Self::event_normal(tab) } + /// Move up one row in modes allowing movement. + /// Does nothing if the selected item is already the first in list. pub fn event_move_up(status: &mut Status) -> FmResult<()> { match status.selected().mode { Mode::Normal | Mode::Preview | Mode::Help => { @@ -1024,6 +1037,8 @@ impl EventExec { Ok(()) } + /// Move down one row in modes allowing movements. + /// Does nothing if the user is already at the bottom. pub fn event_move_down(status: &mut Status) -> FmResult<()> { match status.selected().mode { Mode::Normal | Mode::Preview | Mode::Help => { @@ -1040,6 +1055,8 @@ impl EventExec { Ok(()) } + /// Move to parent in normal mode, + /// move left one char in mode requiring text input. pub fn event_move_left(status: &mut Status) -> FmResult<()> { match status.selected().mode { Mode::Normal => EventExec::event_move_to_parent(status.selected()), @@ -1060,9 +1077,11 @@ impl EventExec { } } + /// Move to child if any or open a regular file in normal mode. + /// Move the cursor one char to right in mode requiring text input. pub fn event_move_right(status: &mut Status) -> FmResult<()> { match status.selected().mode { - Mode::Normal => EventExec::exec_file(status.selected()), + Mode::Normal => EventExec::exec_file(status), Mode::Rename | Mode::Chmod | Mode::Newdir @@ -1079,6 +1098,7 @@ impl EventExec { } } + /// Delete a char to the left in modes allowing edition. pub fn event_backspace(status: &mut Status) -> FmResult<()> { match status.selected().mode { Mode::Rename @@ -1098,6 +1118,7 @@ impl EventExec { } } + /// Delete all chars to the right in mode allowing edition. pub fn event_delete(status: &mut Status) -> FmResult<()> { match status.selected().mode { Mode::Rename @@ -1116,6 +1137,7 @@ impl EventExec { } } + /// Move to leftmost char in mode allowing edition. pub fn event_key_home(status: &mut Status) -> FmResult<()> { match status.selected().mode { Mode::Normal | Mode::Preview | Mode::Help => EventExec::event_go_top(status.selected()), @@ -1124,6 +1146,7 @@ impl EventExec { Ok(()) } + /// Move to the bottom in any mode. pub fn event_end(status: &mut Status) -> FmResult<()> { match status.selected().mode { Mode::Normal | Mode::Preview | Mode::Help => { @@ -1134,6 +1157,7 @@ impl EventExec { Ok(()) } + /// Move up 10 lines in normal mode and preview. pub fn page_up(status: &mut Status) -> FmResult<()> { match status.selected().mode { Mode::Normal | Mode::Preview | Mode::Help => { @@ -1144,6 +1168,7 @@ impl EventExec { Ok(()) } + /// Move down 10 lines in normal & preview mode. pub fn page_down(status: &mut Status) -> FmResult<()> { match status.selected().mode { Mode::Normal | Mode::Preview | Mode::Help => { @@ -1154,6 +1179,11 @@ impl EventExec { Ok(()) } + /// Execute the mode. + /// In modes requiring confirmation or text input, it will execute the + /// related action. + /// In normal mode, it will open the file. + /// Reset to normal mode afterwards. pub fn enter(status: &mut Status) -> FmResult<()> { match status.selected().mode { Mode::Rename => EventExec::exec_rename(status.selected())?, @@ -1168,7 +1198,7 @@ impl EventExec { Mode::History => EventExec::exec_history(status.selected())?, Mode::Shortcut => EventExec::exec_shortcut(status.selected())?, Mode::Filter => EventExec::exec_filter(status.selected())?, - Mode::Normal => EventExec::exec_file(status.selected())?, + Mode::Normal => EventExec::exec_file(status)?, Mode::NeedConfirmation | Mode::Help | Mode::Sort | Mode::Preview | Mode::Marks(_) => (), }; @@ -1177,6 +1207,8 @@ impl EventExec { Ok(()) } + /// Change tab in normal mode with dual pane displayed, + /// insert a completion in modes allowing completion. pub fn tab(status: &mut Status) -> FmResult<()> { match status.selected().mode { Mode::Goto | Mode::Exec | Mode::Search => { @@ -1188,6 +1220,7 @@ impl EventExec { Ok(()) } + /// Change tab in normal mode. pub fn backtab(status: &mut Status) -> FmResult<()> { if let Mode::Normal = status.selected().mode { status.prev() @@ -1195,10 +1228,13 @@ impl EventExec { Ok(()) } + /// Start a fuzzy find with skim. + /// ATM idk how to avoid using the whole screen. pub fn event_fuzzyfind(status: &mut Status) -> FmResult<()> { status.fill_tabs_with_skim() } + /// Copy the filename of the selected file in normal mode. pub fn event_copy_filename(status: &mut Status) -> FmResult<()> { if let Mode::Normal = status.selected_non_mut().mode { return EventExec::event_filename_to_clipboard(status.selected()); @@ -1206,6 +1242,7 @@ impl EventExec { Ok(()) } + /// Copy the filepath of the selected file in normal mode. pub fn event_copy_filepath(status: &mut Status) -> FmResult<()> { if let Mode::Normal = status.selected_non_mut().mode { return EventExec::event_filepath_to_clipboard(status.selected()); @@ -1213,10 +1250,12 @@ impl EventExec { Ok(()) } + /// Refresh the current view, reloading the files. Move the selection to top. pub fn event_refreshview(status: &mut Status) -> FmResult<()> { Self::refresh_selected_view(status) } + /// Open a thumbnail of an image, scaled up to the whole window. pub fn event_thumbnail(tab: &mut Tab) -> FmResult<()> { if let Mode::Normal = tab.mode { tab.mode = Mode::Preview; @@ -1228,11 +1267,15 @@ impl EventExec { Ok(()) } + /// Toggle between a full display (aka ls -lah) or a simple mode (only the + /// filenames). pub fn event_toggle_display_full(status: &mut Status) -> FmResult<()> { status.display_full = !status.display_full; Ok(()) } + /// Toggle between dualpane and single pane. Does nothing if the width + /// is too low to display both panes. pub fn event_toggle_dualpane(status: &mut Status) -> FmResult<()> { status.dual_pane = !status.dual_pane; status.select_tab(0)?; @@ -1247,5 +1290,5 @@ impl EventExec { fn string_to_path(path_string: String) -> FmResult<path::PathBuf> { let expanded_cow_path = shellexpand::tilde(&path_string); let expanded_target: &str = expanded_cow_path.borrow(); - Ok(std::fs::canonicalize(expanded_target)?.to_path_buf()) + Ok(std::fs::canonicalize(expanded_target)?) } diff --git a/src/fileinfo.rs b/src/fileinfo.rs index d31c739..2ff7335 100644 --- a/src/fileinfo.rs +++ b/src/fileinfo.rs @@ -234,11 +234,17 @@ impl FileInfo { /// the "display all files including hidden" flag and the key to sort files. #[derive(Clone)] pub struct PathContent { + /// The current path pub path: path::PathBuf, + /// A vector of FileInfo with every file in current path pub files: Vec<FileInfo>, + /// The index of the selected file. pub selected: usize, + /// Do we display the hidden files ? pub show_hidden: bool, + /// The kind of sort used to display the files. pub sort_by: SortBy, + /// Is it reversed ? pub reverse: bool, filter: FilterKind, used_space: u64, @@ -272,6 +278,7 @@ impl PathContent { }) } + /// Apply the filter. pub fn set_filter(&mut self, filter: FilterKind) { self.filter = filter } @@ -304,6 +311,8 @@ impl PathContent { } } + /// Convert a path to a &str. + /// It may fails if the path contains non valid utf-8 chars. pub fn path_to_str(&self) -> FmResult<&str> { self.path.to_str().ok_or_else(|| { FmError::new( @@ -400,14 +409,18 @@ impl PathContent { } } + /// Path of the currently selected file. pub fn selected_path_str(&self) -> Option<String> { Some(self.selected_file()?.path.to_str()?.to_owned()) } + /// True if the path starts with a subpath. pub fn contains(&self, path: &path::Path) -> bool { path.starts_with(&self.path) } + /// Is the selected file a directory ? + /// It may fails if the current path is empty, aka if nothing is selected. pub fn is_selected_dir(&self) -> FmResult<bool> { match self .selected_file() @@ -437,14 +450,19 @@ impl PathContent { } } + /// True if the path is empty. pub fn is_empty(&self) -> bool { self.files.is_empty() } + /// Human readable string representation of the space used by _files_ + /// in current path. + /// No recursive exploration of directory. pub fn used_space(&self) -> String { human_size(self.used_space) } + /// A string representation of the git status of the path. pub fn git_string(&self) -> FmResult<String> { Ok(git(&self.path)?) } diff --git a/src/filter.rs b/src/filter.rs index d1c23eb..6e06d80 100644 --- a/src/filter.rs +++ b/src/filter.rs @@ -2,6 +2,8 @@ use regex::Regex; use crate::fileinfo::{FileInfo, FileKind}; +/// Different kinds of filters. +/// By extension, by name, only the directory or all files. #[derive(Clone)] pub enum FilterKind { Extension(String), @@ -11,6 +13,8 @@ pub enum FilterKind { } impl FilterKind { + /// Parse the input string into a filter. + /// It shouldn't fail but use a `Filter::All` if the string isn't parsable; pub fn from_input(input: &str) -> Self { let words = input.split_whitespace().collect::<Vec<&str>>(); if words.len() < 2 { @@ -24,6 +28,9 @@ impl FilterKind { } } + /// Apply the selected filter to the file list. + /// It's a "key" used by the Filter method to hold the files matching this + /// filter. pub fn filter_by(&self, fileinfo: &FileInfo) -> bool { match self { Self::Extension(ext) => Self::filter_by_ext(fileinfo, ext.clone()), diff --git a/src/fm_error.rs b/src/fm_error.rs index 59d2ab2..a7bc39e 100644 --- a/src/fm_error.rs +++ b/src/fm_error.rs @@ -8,6 +8,8 @@ use notify_rust::error::Error as NotifyError; use strfmt::FmtError; use tuikit::error::TuikitError; +/// Different variant of errors, depending on what caused the error. +/// If the error is custom made, a string depicts the problem more precisely. #[derive(Debug)] pub enum ErrorVariant { IO, @@ -25,9 +27,11 @@ pub enum ErrorVariant { STRUM, COMPRESSTOOLS, IMAGEERROR, + SERDEYAML, CUSTOM(String), } +/// Default error used in whole application. #[derive(Debug)] pub struct FmError { variant: ErrorVariant, @@ -35,6 +39,7 @@ pub struct FmError { } impl FmError { + /// Creates a new `FmError` with a variant and a message. pub fn new(variant: ErrorVariant, msg: &str) -> Self { info!("FmError. Variant: {:?} - msg: {}", variant, msg); Self { @@ -150,4 +155,12 @@ impl From<image::error::ImageError> for FmError { Self::new(ErrorVariant::IMAGEERROR, &error.to_string()) } } + +impl From<serde_yaml::Error> for FmError { + fn from(error: serde_yaml::Error) -> Self { + Self::new(ErrorVariant::SERDEYAML, &error.to_string()) + } +} + +/// A Result with type `T` and `FmError`. pub type FmResult<T> = Result<T, FmError>; @@ -75,6 +75,8 @@ fn parse_porcelain2(data: String) -> Option<GitStatus> { Some(status) } +/// Returns a string representation of the git status of this path. +/// Will return an empty string if we're not in a git repository. pub fn git(path: &Path) -> Result<String, Box<dyn Error>> { if std::env::set_current_dir(path).is_err() { // The path may not exist. It should never happen. diff --git a/src/help.rs b/src/help.rs index 87283bc..8a63edc 100644 --- a/src/help.rs +++ b/src/help.rs @@ -70,11 +70,16 @@ static HELP_TO_FORMAT: &str = " {ModeNormal}: NORMAL "; +/// Holds the help string, formated with current keybindings. pub struct Help { + /// The help string, formated with current keybindings. pub help: String, } impl Help { + /// Creates an Help instance from keybindings. + /// If multiple keybindings are bound to the same action, the last one + /// is displayed. pub fn from_keybindings(binds: &Bindings) -> FmResult<Self> { let help = strfmt(HELP_TO_FORMAT, &binds.keybind_reversed())?; Ok(Self { help }) diff --git a/src/input.rs b/src/input.rs index d99e7cb..e4da28b 100644 --- a/src/input.rs +++ b/src/input.rs @@ -2,7 +2,9 @@ /// Methods allow mutation of this string and movement of the cursor. #[derive(Clone)] pub struct Input { + /// The input string typed by the user pub string: String, + /// The index of the cursor in that string pub cursor_index: usize, } diff --git a/src/keybindings.rs b/src/keybindings.rs index 88c7bed..24c187d 100644 --- a/src/keybindings.rs +++ b/src/keybindings.rs @@ -7,8 +7,11 @@ use tuikit::prelude::{from_keyname, Key}; use crate::action_map::ActionMap; use crate::fm_error::FmResult; +/// Holds an hashmap between keys and actions. #[derive(Clone, Debug)] pub struct Bindings { + /// An HashMap of key & Actions. + /// Every binded key is linked to its corresponding action pub binds: HashMap<Key, ActionMap>, } @@ -81,10 +84,13 @@ impl Bindings { Self { binds } } + /// Returns an Option of action. None if the key isn't binded. pub fn get(&self, key: &Key) -> Option<&ActionMap> { self.binds.get(key) } + /// Reverse the hashmap of keys. + /// Used to format the help string. pub fn keybind_reversed(&self) -> HashMap<String, String> { self.binds .clone() @@ -93,6 +99,8 @@ impl Bindings { .collect() } + /// Update the binds from a config file. + /// It may fail (and leave keybinding intact) if the file isn't formated properly. pub fn update_from_config(&mut self, yaml: &serde_yaml::value::Value) -> FmResult<()> { for yaml_key in yaml.as_mapping().unwrap().keys() { if let Some(key_string) = yaml_key.as_str() { @@ -32,6 +32,9 @@ fn create_log_folder(log_path: &str) -> FmResult<String> { Ok(parent.to_string_lossy().to_string()) } +/// Creates a default logger with rotating file logs. +/// 3 files à 5KB each are maintened. +/// The log files are located in $HOME/username/.config/fm pub fn set_logger() -> FmResult<Handle> { let log_path = shellexpand::tilde(LOG_PATH).to_string(); eprintln!("log path: {}", log_path); diff --git a/src/main.rs b/src/main.rs index 74c5799..51710d4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,13 +11,14 @@ use fm::help::Help; use fm::log::set_logger; use fm::status::Status; use fm::term_manager::{Display, EventReader}; -use fm::utils::{init_term, print_on_quit}; +use fm::utils::{drop_everything, init_term, print_on_quit}; static CONFIG_PATH: &str = "~/.config/fm/config.yaml"; /// Main function -/// Init the status and display and listen to events from keyboard and mouse. +/// Init the status and display and listen to events (keyboard, mouse, resize, custom...). /// The application is redrawn after every event. +/// When the user issues a quit event, the main loop is broken and we reset the cursor. fn main() -> FmResult<()> { set_logger()?; info!("fm is starting"); @@ -32,17 +33,19 @@ fn main() -> FmResult<()> { let mut status = Status::new(Args::parse(), config, display.height()?, term.clone(), help)?; while let Ok(event) = event_reader.poll_event() { - event_dispatcher.read_event(&mut status, event)?; + event_dispatcher.dispatch(&mut status, event)?; status.refresh_disks(); - display.display_all(&status)?; if status.selected_non_mut().must_quit() { break; }; } + display.show_cursor()?; - print_on_quit(term, event_dispatcher, event_reader, status, display); + let final_path = status.selected_path_str(); + drop_everything(term, event_dispatcher, event_reader, status, display); + print_on_quit(final_path); info!("fm is shutting down"); Ok(()) } diff --git a/src/marks.rs b/src/marks.rs index 5963b58..ef6b811 100644 --- a/src/marks.rs +++ b/src/marks.rs @@ -7,12 +7,17 @@ use crate::fm_error::{ErrorVariant, FmError, FmResult}; static MARKS_FILEPATH: &str = "~/.config/fm/marks.cfg"; +/// Holds the marks created by the user. +/// It's a map between any char (except :) and a PathBuf. pub struct Marks { save_path: PathBuf, marks: HashMap<char, PathBuf>, } impl Marks { + /// Reads the marks stored in the config file (~/.config/fm/marks.cfg). + /// If an invalid marks is read, only the valid ones are kept + /// and the file is saved again. pub fn read_from_config_file() -> Self { let path = PathBuf::from(shellexpand::tilde(&MARKS_FILEPATH).to_string()); Self::read_from_file(path) @@ -38,6 +43,7 @@ impl Marks { marks } + /// Returns an optional marks associated to a char bind. pub fn get(&self, ch: char) -> Option<&PathBuf> { self.marks.get(&ch) } @@ -62,6 +68,8 @@ impl Marks { } } + /// Store a new mark in the config file. + /// All the marks are saved again. pub fn new_mark(&mut self, ch: char, path: PathBuf) -> FmResult<()> { if ch == ':' { return Err(FmError::new( @@ -94,6 +102,7 @@ impl Marks { .to_owned()) } + /// Returns a vector of strings like "d: / |