diff options
Diffstat (limited to 'src/status.rs')
-rw-r--r-- | src/status.rs | 123 |
1 files changed, 84 insertions, 39 deletions
diff --git a/src/status.rs b/src/status.rs index a576412..393530e 100644 --- a/src/status.rs +++ b/src/status.rs @@ -1,26 +1,30 @@ +use std::borrow::BorrowMut; use std::fs; use std::os::unix::fs::PermissionsExt; use std::path::Path; use std::sync::Arc; +use anyhow::{anyhow, Context, Result}; use log::info; use regex::Regex; use skim::SkimItem; use sysinfo::{Disk, DiskExt, RefreshKind, System, SystemExt}; +use tuikit::prelude::{from_keyname, Event}; use tuikit::term::Term; use users::UsersCache; use crate::args::Args; use crate::bulkrename::Bulk; +use crate::cli_info::CliInfo; use crate::compress::Compresser; use crate::config::Colors; -use crate::constant_strings_paths::{OPENER_PATH, TUIS_PATH}; +use crate::constant_strings_paths::TUIS_PATH; use crate::copy_move::{copy_move, CopyMove}; -use crate::cryptsetup::DeviceOpener; +use crate::cryptsetup::CryptoDeviceOpener; use crate::flagged::Flagged; -use crate::fm_error::{FmError, FmResult}; +use crate::iso::IsoMounter; use crate::marks::Marks; -use crate::opener::{load_opener, Opener}; +use crate::opener::Opener; use crate::preview::{Directory, Preview}; use crate::shell_menu::{load_shell_menu, ShellMenu}; use crate::skim::Skimer; @@ -66,7 +70,9 @@ pub struct Status { /// The trash pub trash: Trash, /// Encrypted devices opener - pub encrypted_devices: DeviceOpener, + pub encrypted_devices: CryptoDeviceOpener, + /// Iso mounter. Set to None by default, dropped ASAP + pub iso_mounter: Option<IsoMounter>, /// Compression methods pub compression: Compresser, /// NVIM RPC server address @@ -74,6 +80,7 @@ pub struct Status { pub force_clear: bool, pub bulk: Bulk, pub shell_menu: ShellMenu, + pub cli_info: CliInfo, } impl Status { @@ -88,22 +95,18 @@ impl Status { height: usize, term: Arc<Term>, help: String, - terminal: &str, - ) -> FmResult<Self> { - let opener = load_opener(OPENER_PATH, terminal).unwrap_or_else(|_| { - eprintln!("Couldn't read the opener config file at {OPENER_PATH}. See https://raw.githubusercontent.com/qkzk/fm/master/config_files/fm/opener.yaml for an example. Using default."); - info!("Couldn't read opener file at {OPENER_PATH}. Using default."); - Opener::new(terminal) - }); + opener: Opener, + ) -> Result<Self> { let Ok(shell_menu) = load_shell_menu(TUIS_PATH) else { eprintln!("Couldn't load the TUIs config file at {TUIS_PATH}. See https://raw.githubusercontent.com/qkzk/fm/master/config_files/fm/tuis.yaml for an example"); info!("Couldn't read tuis file at {TUIS_PATH}. Exiting"); std::process::exit(1); }; + let cli_info = CliInfo::default(); let sys = System::new_with_specifics(RefreshKind::new().with_disks()); let nvim_server = args.server.clone(); - let encrypted_devices = DeviceOpener::default(); + let encrypted_devices = CryptoDeviceOpener::default(); let trash = Trash::new()?; let compression = Compresser::default(); let force_clear = false; @@ -122,6 +125,7 @@ impl Status { left_tab .shortcut .extend_with_mount_points(&Self::disks_mounts(sys.disks())); + let iso_mounter = None; Ok(Self { tabs: [left_tab, right_tab], @@ -143,6 +147,8 @@ impl Status { force_clear, bulk, shell_menu, + iso_mounter, + cli_info, }) } @@ -170,7 +176,7 @@ impl Status { } /// Reset the view of every tab. - pub fn reset_tabs_view(&mut self) -> FmResult<()> { + pub fn reset_tabs_view(&mut self) -> Result<()> { for tab in self.tabs.iter_mut() { tab.refresh_view()? } @@ -184,14 +190,14 @@ impl Status { /// Replace the tab content with the first result of skim. /// It calls skim, reads its output, then update the tab content. - pub fn skim_output_to_tab(&mut self) -> FmResult<()> { + pub fn skim_output_to_tab(&mut self) -> Result<()> { let skim = self.skimer.search_filename( self.selected_non_mut() .selected() - .ok_or_else(|| FmError::custom("skim", "no selected file"))? + .context("skim: no selected file")? .path .to_str() - .ok_or_else(|| FmError::custom("skim", "skim error"))?, + .context("skim error")?, ); let Some(output) = skim.first() else {return Ok(())}; self._update_tab_from_skim_output(output) @@ -200,28 +206,40 @@ impl Status { /// Replace the tab content with the first result of skim. /// It calls skim, reads its output, then update the tab content. /// The output is splited at `:` since we only care about the path, not the line number. - pub fn skim_line_output_to_tab(&mut self) -> FmResult<()> { + pub fn skim_line_output_to_tab(&mut self) -> Result<()> { let skim = self.skimer.search_line_in_file(); let Some(output) = skim.first() else {return Ok(())}; self._update_tab_from_skim_line_output(output) } - fn _update_tab_from_skim_line_output( - &mut self, - skim_output: &Arc<dyn SkimItem>, - ) -> FmResult<()> { + /// Run a command directly from help. + /// Search a command in skim, if it's a keybinding, run it directly. + /// If the result can't be parsed, nothing is done. + pub fn skim_find_keybinding(&mut self) -> Result<()> { + let skim = self.skimer.search_in_text(self.help.clone()); + let Some(output) = skim.first() else { return Ok(()) }; + let line = output.output().into_owned(); + let Some(keybind) = line.split(':').next() else { return Ok(()) }; + let Some(keyname) = parse_keyname(keybind) else { return Ok(()) }; + let Some(key) = from_keyname(&keyname) else { return Ok(()) }; + let event = Event::Key(key); + let _ = self.term.borrow_mut().send_event(event); + Ok(()) + } + + fn _update_tab_from_skim_line_output(&mut self, skim_output: &Arc<dyn SkimItem>) -> Result<()> { let output_str = skim_output.output().to_string(); let Some(filename) = output_str.split(':').next() else { return Ok(());}; let path = fs::canonicalize(filename)?; self._replace_path_by_skim_output(path) } - fn _update_tab_from_skim_output(&mut self, skim_output: &Arc<dyn SkimItem>) -> FmResult<()> { + fn _update_tab_from_skim_output(&mut self, skim_output: &Arc<dyn SkimItem>) -> Result<()> { let path = fs::canonicalize(skim_output.output().to_string())?; self._replace_path_by_skim_output(path) } - fn _replace_path_by_skim_output(&mut self, path: std::path::PathBuf) -> FmResult<()> { + fn _replace_path_by_skim_output(&mut self, path: std::path::PathBuf) -> Result<()> { let tab = self.selected(); if path.is_file() { let Some(parent) = path.parent() else { return Ok(()) }; @@ -249,25 +267,25 @@ impl Status { /// Execute a move or a copy of the flagged files to current directory. /// A progress bar is displayed (invisible for small files) and a notification /// is sent every time, even for 0 bytes files... - pub fn cut_or_copy_flagged_files(&mut self, cut_or_copy: CopyMove) -> FmResult<()> { + pub fn cut_or_copy_flagged_files(&mut self, cut_or_copy: CopyMove) -> Result<()> { let sources = self.flagged.content.clone(); let dest = self .selected_non_mut() .path_content_str() - .ok_or_else(|| FmError::custom("cut or copy", "unreadable path"))?; + .context("cut or copy: unreadable path")?; copy_move(cut_or_copy, sources, dest, self.term.clone())?; self.clear_flags_and_reset_view() } /// Empty the flagged files, reset the view of every tab. - pub fn clear_flags_and_reset_view(&mut self) -> FmResult<()> { + pub fn clear_flags_and_reset_view(&mut self) -> Result<()> { self.flagged.clear(); self.reset_tabs_view() } /// Set the permissions of the flagged files according to a given permission. /// If the permission are invalid or if the user can't edit them, it may fail. - pub fn set_permissions<P>(path: P, permissions: u32) -> FmResult<()> + pub fn set_permissions<P>(path: P, permissions: u32) -> Result<()> where P: AsRef<Path>, { @@ -296,11 +314,12 @@ impl Status { /// It's deprecated and is left mostly because I'm not sure I want /// tabs & panes... and I haven't fully decided yet. /// Since I'm lazy and don't want to write it twice, it's left here. - pub fn select_tab(&mut self, index: usize) -> FmResult<()> { + pub fn select_tab(&mut self, index: usize) -> Result<()> { if index >= self.tabs.len() { - Err(FmError::custom( - "select tab", - &format!("Only {} tabs. Can't select tab {}", self.tabs.len(), index), + Err(anyhow!( + "Only {} tabs. Can't select tab {}", + self.tabs.len(), + index )) } else { self.index = index; @@ -345,7 +364,7 @@ impl Status { } /// Returns the sice of the terminal (width, height) - pub fn term_size(&self) -> FmResult<(usize, usize)> { + pub fn term_size(&self) -> Result<(usize, usize)> { Ok(self.term.term_size()?) } @@ -357,7 +376,7 @@ impl Status { } /// Refresh the existing users. - pub fn refresh_users(&mut self) -> FmResult<()> { + pub fn refresh_users(&mut self) -> Result<()> { for tab in self.tabs.iter_mut() { let users_cache = unsafe { UsersCache::with_all_users() }; tab.refresh_users(users_cache)?; @@ -366,7 +385,7 @@ impl Status { } /// Drop the current tree, replace it with an empty one. - pub fn remove_tree(&mut self) -> FmResult<()> { + pub fn remove_tree(&mut self) -> Result<()> { let path = self.selected_non_mut().path_content.path.clone(); let users_cache = &self.selected_non_mut().path_content.users_cache; self.selected().directory = Directory::empty(&path, users_cache)?; @@ -374,16 +393,16 @@ impl Status { } /// Updates the encrypted devices - pub fn read_encrypted_devices(&mut self) -> FmResult<()> { + pub fn read_encrypted_devices(&mut self) -> Result<()> { self.encrypted_devices.update()?; Ok(()) } /// Force a preview on the second pane - pub fn force_preview(&mut self, colors: &Colors) -> FmResult<()> { + pub fn force_preview(&mut self, colors: &Colors) -> Result<()> { let fileinfo = &self.tabs[0] .selected() - .ok_or_else(|| FmError::custom("force preview", "No file to select"))?; + .context("force preview: No file to select")?; let users_cache = &self.tabs[0].path_content.users_cache; self.tabs[0].preview = Preview::new(fileinfo, users_cache, self, colors).unwrap_or_default(); @@ -391,7 +410,7 @@ impl Status { } /// Set dual pane if the term is big enough - pub fn set_dual_pane_if_wide_enough(&mut self, width: usize) -> FmResult<()> { + pub fn set_dual_pane_if_wide_enough(&mut self, width: usize) -> Result<()> { if width < MIN_WIDTH_FOR_DUAL_PANE { self.select_tab(0)?; self.dual_pane = false; @@ -406,7 +425,33 @@ impl Status { self.selected_non_mut().must_quit() } + /// Set a "force clear" flag to true, which will reset the display. + /// It's used when some command or whatever may pollute the terminal. + /// We ensure to clear it before displaying again. pub fn force_clear(&mut self) { self.force_clear = true; } } + +fn parse_keyname(keyname: &str) -> Option<String> { + let mut split = keyname.split('('); + let Some(mutator) = split.next() else { return None; }; + let mut mutator = mutator.to_lowercase(); + let Some(param) = split.next() else { return Some(mutator) }; + let mut param = param.to_owned(); + mutator = mutator.replace("char", ""); + param = param.replace([')', '\''], ""); + if param.chars().all(char::is_uppercase) { + if mutator.is_empty() { + mutator = "shift".to_owned(); + } else { + mutator = format!("{mutator}-shift"); + } + } + + if mutator.is_empty() { + Some(param) + } else { + Some(format!("{mutator}-{param}")) + } +} |