From 521c7271b6410c63bb3ff8d4e9145e926afa9725 Mon Sep 17 00:00:00 2001 From: sxyazi Date: Tue, 18 Jul 2023 17:50:11 +0800 Subject: [PATCH] feat: new of `open` command --- config/keymap.toml | 2 ++ src/config/open/open.rs | 12 ++++++++--- src/config/open/opener.rs | 2 +- src/core/event.rs | 2 +- src/core/external/file.rs | 6 +++--- src/core/external/mod.rs | 2 ++ src/core/external/unar.rs | 25 +++++++++++++++++++++ src/core/manager/manager.rs | 43 ++++++++++++++++++++++++++----------- src/core/manager/tab.rs | 1 - src/core/tasks/tasks.rs | 4 ++-- src/ui/app.rs | 4 +--- src/ui/dispatcher.rs | 1 + 12 files changed, 78 insertions(+), 26 deletions(-) create mode 100644 src/core/external/unar.rs diff --git a/config/keymap.toml b/config/keymap.toml index 44efb0c2..ae976740 100644 --- a/config/keymap.toml +++ b/config/keymap.toml @@ -26,6 +26,8 @@ keymap = [ { on = [ "" ], exec = "select_all --state=none" }, # Operation + { on = [ "o" ], exec = "open" }, + { on = [ "O" ], exec = "open --select" }, { on = [ "y" ], exec = "yank" }, { on = [ "x" ], exec = "yank --cut" }, { on = [ "p" ], exec = "paste" }, diff --git a/src/config/open/open.rs b/src/config/open/open.rs index 4d8119ff..b97c97e5 100644 --- a/src/config/open/open.rs +++ b/src/config/open/open.rs @@ -1,4 +1,4 @@ -use std::{collections::BTreeMap, path::Path}; +use std::{collections::{BTreeMap, BTreeSet}, path::Path}; use serde::{Deserialize, Deserializer}; @@ -22,17 +22,23 @@ struct OpenRule { impl Open { pub fn new() -> Self { toml::from_str(&MERGED_YAZI).unwrap() } - pub fn opener(&self, path: &Path, mime: &str) -> Option<&Opener> { + pub fn openers(&self, path: &Path, mime: &str) -> Option> { self.rules.iter().find_map(|rule| { if rule.name.as_ref().map_or(false, |e| e.match_path(path, Some(false))) || rule.mime.as_ref().map_or(false, |m| m.matches(mime)) { - self.openers.get(&rule.use_).and_then(|v| v.first()) + self.openers.get(&rule.use_).map(|v| v.iter().collect()) } else { None } }) } + + pub fn common_openers(&self, targets: Vec<(&Path, &str)>) -> Vec<&Opener> { + let grouped = targets.into_iter().filter_map(|(p, m)| self.openers(p, m)).collect::>(); + let flat = grouped.iter().flatten().cloned().collect::>(); + flat.into_iter().filter(|o| grouped.iter().all(|g| g.contains(o))).collect() + } } impl<'de> Deserialize<'de> for Open { diff --git a/src/config/open/opener.rs b/src/config/open/opener.rs index 1c4ef77d..568fec03 100644 --- a/src/config/open/opener.rs +++ b/src/config/open/opener.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Deserializer}; -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Opener { pub cmd: String, pub args: Vec, diff --git a/src/core/event.rs b/src/core/event.rs index 6ebfd549..58693cc4 100644 --- a/src/core/event.rs +++ b/src/core/event.rs @@ -28,7 +28,7 @@ pub enum Event { Input(InputOpt, oneshot::Sender>), // Tasks - Open(Vec), + Open(Vec<(PathBuf, String)>), Progress(u8, u32), } diff --git a/src/core/external/file.rs b/src/core/external/file.rs index ad5a1259..f878e280 100644 --- a/src/core/external/file.rs +++ b/src/core/external/file.rs @@ -1,11 +1,11 @@ -use std::{collections::BTreeMap, path::PathBuf}; +use std::{collections::BTreeMap, ffi::OsStr, path::PathBuf}; use anyhow::{bail, Result}; use tokio::process::Command; use crate::misc::MimeKind; -pub async fn file(files: &[PathBuf]) -> Result> { +pub async fn file(files: &[impl AsRef]) -> Result> { if files.is_empty() { bail!("no files to get mime types for"); } @@ -23,7 +23,7 @@ pub async fn file(files: &[PathBuf]) -> Result> { .into_iter() .zip(output.trim().lines()) .filter(|(_, m)| MimeKind::valid(m)) - .map(|(f, m)| (f.clone(), m.to_string())), + .map(|(f, m)| (f.as_ref().into(), m.to_string())), ); if mimes.len() == 0 { diff --git a/src/core/external/mod.rs b/src/core/external/mod.rs index 2a07cf73..aa73bfde 100644 --- a/src/core/external/mod.rs +++ b/src/core/external/mod.rs @@ -5,6 +5,7 @@ mod fzf; mod jq; mod lsar; mod rg; +mod unar; mod zoxide; pub use fd::*; @@ -14,4 +15,5 @@ pub use fzf::*; pub use jq::*; pub use lsar::*; pub use rg::*; +pub use unar::*; pub use zoxide::*; diff --git a/src/core/external/unar.rs b/src/core/external/unar.rs new file mode 100644 index 00000000..b893b982 --- /dev/null +++ b/src/core/external/unar.rs @@ -0,0 +1,25 @@ +use std::{path::Path, process::Stdio}; + +use anyhow::{bail, Result}; +use tokio::{io::{AsyncReadExt, BufReader}, process::Command}; + +pub async fn unar_head(path: &Path, target: &Path) -> Result> { + let mut child = Command::new("unar") + .args(&[path, target]) + .args(&["-o", "-"]) + .stdout(Stdio::piped()) + .stderr(Stdio::null()) + .kill_on_drop(true) + .spawn()?; + + let mut buf = vec![0; 1024]; + let mut reader = BufReader::new(child.stdout.take().unwrap()); + + reader.read(&mut buf).await.ok(); + child.kill().await.ok(); + + if buf.is_empty() { + bail!("failed to get head of unar"); + } + Ok(buf) +} diff --git a/src/core/manager/manager.rs b/src/core/manager/manager.rs index 462b9a88..49d95b7f 100644 --- a/src/core/manager/manager.rs +++ b/src/core/manager/manager.rs @@ -62,7 +62,7 @@ impl Manager { self.active_mut().preview.go(&hovered.path, &mime); } else { tokio::spawn(async move { - if let Ok(mimes) = external::file(&[hovered.path()]).await { + if let Ok(mimes) = external::file(&[hovered.path]).await { emit!(Mimetype(mimes)); } }); @@ -111,6 +111,36 @@ impl Manager { self.quit(tasks) } + pub fn open(&mut self, select: bool) -> bool { + let mut selected = self + .selected() + .into_iter() + .map(|p| { + let mime = self.mimetype.get(&p).cloned(); + (p, mime) + }) + .collect::>(); + + if selected.is_empty() { + return false; + } + + tokio::spawn(async move { + let todo = selected.iter().filter(|(_, m)| m.is_none()).map(|(p, _)| p).collect::>(); + if let Ok(mut mimes) = external::file(&todo).await { + selected = selected + .into_iter() + .map(|(p, m)| { + let mime = m.or_else(|| mimes.remove(&p)); + (p, mime) + }) + .collect::>(); + } + emit!(Open(selected.into_iter().filter_map(|(p, m)| m.map(|m| (p, m))).collect::>())); + }); + false + } + pub fn create(&self) -> bool { let cwd = self.current().cwd.clone(); tokio::spawn(async move { @@ -167,17 +197,6 @@ impl Manager { self.current().selected().or_else(|| self.hovered().map(|h| vec![h.path()])).unwrap_or_default() } - pub async fn mimetypes(&mut self, files: &[PathBuf]) -> Vec> { - let todo = - files.iter().filter(|&p| !self.mimetype.contains_key(p)).cloned().collect::>(); - - if let Ok(mimes) = external::file(&todo).await { - self.mimetype.extend(mimes); - } - - files.into_iter().map(|p| self.mimetype.get(p).cloned()).collect() - } - pub fn update_read(&mut self, op: FilesOp) -> bool { let path = op.path(); let cwd = self.current().cwd.clone(); diff --git a/src/core/manager/tab.rs b/src/core/manager/tab.rs index b5484802..ec8d6d78 100644 --- a/src/core/manager/tab.rs +++ b/src/core/manager/tab.rs @@ -120,7 +120,6 @@ impl Tab { return false; }; if !hovered.meta.is_dir() { - emit!(Open(self.current.selected().unwrap_or(vec![hovered.path]))); return false; } diff --git a/src/core/tasks/tasks.rs b/src/core/tasks/tasks.rs index 777c6fe9..6c5d2c79 100644 --- a/src/core/tasks/tasks.rs +++ b/src/core/tasks/tasks.rs @@ -98,9 +98,9 @@ impl Tasks { pub fn file_open(&self, targets: Vec<(PathBuf, String)>) -> bool { let mut openers = BTreeMap::new(); for target in targets { - if let Some(opener) = OPEN.opener(&target.0, &target.1) { + if let Some(opener) = OPEN.openers(&target.0, &target.1).and_then(|o| o.first().cloned()) { openers - .entry(opener.clone()) + .entry(opener) .or_insert_with(|| vec![]) .push(target.0.to_string_lossy().into_owned()); } diff --git a/src/ui/app.rs b/src/ui/app.rs index d753f1ca..33505f7c 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -127,9 +127,7 @@ impl App { } Event::Open(files) => { - let mime = manager.mimetypes(&files).await; - let targets = files.into_iter().zip(mime).map_while(|(f, m)| m.map(|m| (f, m))).collect(); - self.cx.tasks.file_open(targets); + self.cx.tasks.file_open(files); } Event::Progress(percent, left) => { self.cx.tasks.progress = (percent, left); diff --git a/src/ui/dispatcher.rs b/src/ui/dispatcher.rs index 9c0f753d..2b79a77d 100644 --- a/src/ui/dispatcher.rs +++ b/src/ui/dispatcher.rs @@ -70,6 +70,7 @@ impl Executor { } // Operation + "open" => cx.manager.open(exec.named.contains_key("select")), "yank" => cx.manager.yank(exec.named.contains_key("cut")), "paste" => { let dest = cx.manager.current().cwd.clone();