summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorqkzk <qu3nt1n@gmail.com>2023-10-16 17:59:42 +0200
committerqkzk <qu3nt1n@gmail.com>2023-10-16 18:00:19 +0200
commit087bf81a88d58236b28072c53163d85479a2dfd7 (patch)
tree6bfb187ec161b98ba5ba4c3bed649d8c4a38b134
parentf2568d1b7e6c932e0a7172d7bc24fb5ab3ddba92 (diff)
regroup openers when opening multiple files
-rw-r--r--development.md2
-rw-r--r--src/event_exec.rs40
-rw-r--r--src/opener.rs46
-rw-r--r--src/status.rs31
4 files changed, 80 insertions, 39 deletions
diff --git a/development.md b/development.md
index 8a7c2be..916450e 100644
--- a/development.md
+++ b/development.md
@@ -573,7 +573,7 @@ New view: Tree ! Toggle with 't', fold with 'z'. Navigate normally.
- [x] binary preview also display parsed ASCII strings
- [x] skim fuzzy find (ctrl-f) starts from current dir, not current selected file
- [x] open/file pick flagged files if there are, use selected file instead
-- [ ] regroup openers when opening multiple files. Requires a lot of change...
+- [x] regroup openers when opening multiple files.
## TODO
diff --git a/src/event_exec.rs b/src/event_exec.rs
index 561a5d8..fc29493 100644
--- a/src/event_exec.rs
+++ b/src/event_exec.rs
@@ -27,7 +27,7 @@ use crate::mocp::Mocp;
use crate::mode::{InputSimple, MarkAction, Mode, Navigate, NeedConfirmation};
use crate::opener::{
execute_and_capture_output, execute_and_capture_output_without_check, execute_in_child,
- execute_in_child_without_output_with_path, InternalVariant,
+ execute_in_child_without_output_with_path,
};
use crate::password::{PasswordKind, PasswordUsage};
use crate::preview::ExtensionKind;
@@ -344,45 +344,13 @@ impl EventAction {
/// their respective opener.
/// Directories aren't opened since it will lead nowhere, it would only replace the
/// current tab multiple times. It may change in the future.
- /// Another strange behavior, it doesn't regroup files by opener : opening multiple
- /// text file will create a process per file.
- /// This may also change in the future.
+ /// Only files which use an external opener are supported.
pub fn open_file(status: &mut Status) -> Result<()> {
if status.flagged.is_empty() {
- let filepath = &status
- .selected_non_mut()
- .selected()
- .context("event open file, Empty directory")?
- .path
- .clone();
- Self::open_filepath(status, filepath)?;
+ status.open_selected_file()
} else {
- let content = status.flagged.content().clone();
- for flagged in content.iter() {
- if !flagged.is_dir() {
- Self::open_filepath(status, flagged)?
- }
- }
+ status.open_flagged_files()
}
- Ok(())
- }
-
- /// Open a single file with its own opener
- fn open_filepath(status: &mut Status, filepath: &path::Path) -> Result<()> {
- let opener = status.opener.open_info(filepath);
- if let Some(InternalVariant::NotSupported) = opener.internal_variant.as_ref() {
- status.mount_iso_drive()?;
- } else {
- match status.opener.open(filepath) {
- Ok(_) => (),
- Err(e) => info!(
- "Error opening {:?}: {:?}",
- status.selected_non_mut().path_content.selected(),
- e
- ),
- }
- }
- Ok(())
}
/// Enter the rename mode.
diff --git a/src/opener.rs b/src/opener.rs
index a81d5d4..f6d5760 100644
--- a/src/opener.rs
+++ b/src/opener.rs
@@ -229,7 +229,7 @@ pub enum InternalVariant {
/// A way to open one kind of files.
/// It's either an internal method or an external program.
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, Hash, Eq, PartialEq)]
pub struct OpenerInfo {
/// The external program used to open the file.
pub external_program: Option<String>,
@@ -321,6 +321,46 @@ impl Opener {
}
}
+ /// Open multiple files.
+ /// Files sharing an opener are opened in a single command ie.: `nvim a.txt b.rs c.py`.
+ /// Only files opened with an external opener are supported.
+ pub fn open_multiple(&self, file_paths: &[PathBuf]) -> Result<()> {
+ let openers = self.regroup_openers(file_paths);
+ for (open_info, file_paths) in openers.iter() {
+ let file_paths_str = Self::collect_paths_as_str(file_paths);
+ let mut args: Vec<&str> = vec![open_info.external_program.as_ref().unwrap()];
+ args.extend(&file_paths_str);
+ self.open_with_args(args, open_info.use_term)?;
+ }
+ Ok(())
+ }
+
+ /// Create an hashmap of openers -> [files].
+ /// Each file in the collection share the same opener.
+ fn regroup_openers(&self, file_paths: &[PathBuf]) -> HashMap<OpenerInfo, Vec<PathBuf>> {
+ let mut openers: HashMap<OpenerInfo, Vec<PathBuf>> = HashMap::new();
+ for file_path in file_paths.iter() {
+ let open_info = self.get_opener(extract_extension(file_path));
+ if open_info.external_program.is_some() {
+ openers
+ .entry(open_info.to_owned())
+ .and_modify(|files| files.push((*file_path).to_owned()))
+ .or_insert(vec![(*file_path).to_owned()]);
+ }
+ }
+ openers
+ }
+
+ /// Convert a slice of `PathBuf` into their string representation.
+ /// Files which are directory are skipped.
+ fn collect_paths_as_str(file_paths: &[PathBuf]) -> Vec<&str> {
+ file_paths
+ .iter()
+ .filter(|fp| !fp.is_dir())
+ .filter_map(|fp| fp.to_str())
+ .collect()
+ }
+
/// Open a file, using the configured method.
/// It may fail if the program changed after reading the config file.
/// It may also fail if the program can't handle this kind of files.
@@ -370,6 +410,10 @@ impl Opener {
.to_str()
.context("open with: can't parse filepath to str")?;
let args = vec![program, strpath];
+ self.open_with_args(args, use_term)
+ }
+
+ fn open_with_args(&self, args: Vec<&str>, use_term: bool) -> Result<std::process::Child> {
if use_term {
self.open_terminal(args)
} else {
diff --git a/src/status.rs b/src/status.rs
index 5a7ccdd..aa48a50 100644
--- a/src/status.rs
+++ b/src/status.rs
@@ -28,7 +28,7 @@ use crate::log::write_log_line;
use crate::marks::Marks;
use crate::mode::{InputSimple, Mode, NeedConfirmation};
use crate::mount_help::MountHelper;
-use crate::opener::Opener;
+use crate::opener::{InternalVariant, Opener};
use crate::password::{
drop_sudo_privileges, execute_sudo_command_with_password, reset_sudo_faillock, PasswordHolder,
PasswordKind, PasswordUsage,
@@ -508,6 +508,35 @@ impl Status {
self.force_clear = true;
}
+ /// Open a the selected file with its opener
+ pub fn open_selected_file(&mut self) -> Result<()> {
+ let filepath = &self
+ .selected_non_mut()
+ .selected()
+ .context("Empty directory")?
+ .path
+ .clone();
+ let opener = self.opener.open_info(filepath);
+ if let Some(InternalVariant::NotSupported) = opener.internal_variant.as_ref() {
+ self.mount_iso_drive()?;
+ } else {
+ match self.opener.open(filepath) {
+ Ok(_) => (),
+ Err(e) => info!(
+ "Error opening {:?}: {:?}",
+ self.selected_non_mut().path_content.selected(),
+ e
+ ),
+ }
+ }
+ Ok(())
+ }
+
+ /// Open every flagged file with their respective opener.
+ pub fn open_flagged_files(&mut self) -> Result<()> {
+ self.opener.open_multiple(self.flagged.content())
+ }
+
/// Mount the currently selected file (which should be an .iso file) to
/// `/run/media/$CURRENT_USER/fm_iso`
/// Ask a sudo password first if needed. It should always be the case.