feat: new of open command

This commit is contained in:
sxyazi 2023-07-18 17:50:11 +08:00
parent 11c289229a
commit 521c7271b6
No known key found for this signature in database
12 changed files with 78 additions and 26 deletions

View file

@ -26,6 +26,8 @@ keymap = [
{ on = [ "<C-r>" ], exec = "select_all --state=none" }, { on = [ "<C-r>" ], exec = "select_all --state=none" },
# Operation # Operation
{ on = [ "o" ], exec = "open" },
{ on = [ "O" ], exec = "open --select" },
{ on = [ "y" ], exec = "yank" }, { on = [ "y" ], exec = "yank" },
{ on = [ "x" ], exec = "yank --cut" }, { on = [ "x" ], exec = "yank --cut" },
{ on = [ "p" ], exec = "paste" }, { on = [ "p" ], exec = "paste" },

View file

@ -1,4 +1,4 @@
use std::{collections::BTreeMap, path::Path}; use std::{collections::{BTreeMap, BTreeSet}, path::Path};
use serde::{Deserialize, Deserializer}; use serde::{Deserialize, Deserializer};
@ -22,17 +22,23 @@ struct OpenRule {
impl Open { impl Open {
pub fn new() -> Self { toml::from_str(&MERGED_YAZI).unwrap() } 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<Vec<&Opener>> {
self.rules.iter().find_map(|rule| { self.rules.iter().find_map(|rule| {
if rule.name.as_ref().map_or(false, |e| e.match_path(path, Some(false))) 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)) || 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 { } else {
None 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::<Vec<_>>();
let flat = grouped.iter().flatten().cloned().collect::<BTreeSet<_>>();
flat.into_iter().filter(|o| grouped.iter().all(|g| g.contains(o))).collect()
}
} }
impl<'de> Deserialize<'de> for Open { impl<'de> Deserialize<'de> for Open {

View file

@ -1,6 +1,6 @@
use serde::{Deserialize, Deserializer}; use serde::{Deserialize, Deserializer};
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Opener { pub struct Opener {
pub cmd: String, pub cmd: String,
pub args: Vec<String>, pub args: Vec<String>,

View file

@ -28,7 +28,7 @@ pub enum Event {
Input(InputOpt, oneshot::Sender<Result<String>>), Input(InputOpt, oneshot::Sender<Result<String>>),
// Tasks // Tasks
Open(Vec<PathBuf>), Open(Vec<(PathBuf, String)>),
Progress(u8, u32), Progress(u8, u32),
} }

View file

@ -1,11 +1,11 @@
use std::{collections::BTreeMap, path::PathBuf}; use std::{collections::BTreeMap, ffi::OsStr, path::PathBuf};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use tokio::process::Command; use tokio::process::Command;
use crate::misc::MimeKind; use crate::misc::MimeKind;
pub async fn file(files: &[PathBuf]) -> Result<BTreeMap<PathBuf, String>> { pub async fn file(files: &[impl AsRef<OsStr>]) -> Result<BTreeMap<PathBuf, String>> {
if files.is_empty() { if files.is_empty() {
bail!("no files to get mime types for"); bail!("no files to get mime types for");
} }
@ -23,7 +23,7 @@ pub async fn file(files: &[PathBuf]) -> Result<BTreeMap<PathBuf, String>> {
.into_iter() .into_iter()
.zip(output.trim().lines()) .zip(output.trim().lines())
.filter(|(_, m)| MimeKind::valid(m)) .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 { if mimes.len() == 0 {

View file

@ -5,6 +5,7 @@ mod fzf;
mod jq; mod jq;
mod lsar; mod lsar;
mod rg; mod rg;
mod unar;
mod zoxide; mod zoxide;
pub use fd::*; pub use fd::*;
@ -14,4 +15,5 @@ pub use fzf::*;
pub use jq::*; pub use jq::*;
pub use lsar::*; pub use lsar::*;
pub use rg::*; pub use rg::*;
pub use unar::*;
pub use zoxide::*; pub use zoxide::*;

25
src/core/external/unar.rs vendored Normal file
View file

@ -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<Vec<u8>> {
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)
}

View file

@ -62,7 +62,7 @@ impl Manager {
self.active_mut().preview.go(&hovered.path, &mime); self.active_mut().preview.go(&hovered.path, &mime);
} else { } else {
tokio::spawn(async move { 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)); emit!(Mimetype(mimes));
} }
}); });
@ -111,6 +111,36 @@ impl Manager {
self.quit(tasks) 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::<Vec<_>>();
if selected.is_empty() {
return false;
}
tokio::spawn(async move {
let todo = selected.iter().filter(|(_, m)| m.is_none()).map(|(p, _)| p).collect::<Vec<_>>();
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::<Vec<_>>();
}
emit!(Open(selected.into_iter().filter_map(|(p, m)| m.map(|m| (p, m))).collect::<Vec<_>>()));
});
false
}
pub fn create(&self) -> bool { pub fn create(&self) -> bool {
let cwd = self.current().cwd.clone(); let cwd = self.current().cwd.clone();
tokio::spawn(async move { 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() self.current().selected().or_else(|| self.hovered().map(|h| vec![h.path()])).unwrap_or_default()
} }
pub async fn mimetypes(&mut self, files: &[PathBuf]) -> Vec<Option<String>> {
let todo =
files.iter().filter(|&p| !self.mimetype.contains_key(p)).cloned().collect::<Vec<_>>();
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 { pub fn update_read(&mut self, op: FilesOp) -> bool {
let path = op.path(); let path = op.path();
let cwd = self.current().cwd.clone(); let cwd = self.current().cwd.clone();

View file

@ -120,7 +120,6 @@ impl Tab {
return false; return false;
}; };
if !hovered.meta.is_dir() { if !hovered.meta.is_dir() {
emit!(Open(self.current.selected().unwrap_or(vec![hovered.path])));
return false; return false;
} }

View file

@ -98,9 +98,9 @@ impl Tasks {
pub fn file_open(&self, targets: Vec<(PathBuf, String)>) -> bool { pub fn file_open(&self, targets: Vec<(PathBuf, String)>) -> bool {
let mut openers = BTreeMap::new(); let mut openers = BTreeMap::new();
for target in targets { 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 openers
.entry(opener.clone()) .entry(opener)
.or_insert_with(|| vec![]) .or_insert_with(|| vec![])
.push(target.0.to_string_lossy().into_owned()); .push(target.0.to_string_lossy().into_owned());
} }

View file

@ -127,9 +127,7 @@ impl App {
} }
Event::Open(files) => { Event::Open(files) => {
let mime = manager.mimetypes(&files).await; self.cx.tasks.file_open(files);
let targets = files.into_iter().zip(mime).map_while(|(f, m)| m.map(|m| (f, m))).collect();
self.cx.tasks.file_open(targets);
} }
Event::Progress(percent, left) => { Event::Progress(percent, left) => {
self.cx.tasks.progress = (percent, left); self.cx.tasks.progress = (percent, left);

View file

@ -70,6 +70,7 @@ impl Executor {
} }
// Operation // Operation
"open" => cx.manager.open(exec.named.contains_key("select")),
"yank" => cx.manager.yank(exec.named.contains_key("cut")), "yank" => cx.manager.yank(exec.named.contains_key("cut")),
"paste" => { "paste" => {
let dest = cx.manager.current().cwd.clone(); let dest = cx.manager.current().cwd.clone();