summaryrefslogtreecommitdiffstats
path: root/src/status.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/status.rs')
-rw-r--r--src/status.rs123
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}"))
+ }
+}