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" },
# Operation
{ on = [ "o" ], exec = "open" },
{ on = [ "O" ], exec = "open --select" },
{ on = [ "y" ], exec = "yank" },
{ on = [ "x" ], exec = "yank --cut" },
{ 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};
@ -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<Vec<&Opener>> {
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::<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 {

View file

@ -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<String>,

View file

@ -28,7 +28,7 @@ pub enum Event {
Input(InputOpt, oneshot::Sender<Result<String>>),
// Tasks
Open(Vec<PathBuf>),
Open(Vec<(PathBuf, String)>),
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 tokio::process::Command;
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() {
bail!("no files to get mime types for");
}
@ -23,7 +23,7 @@ pub async fn file(files: &[PathBuf]) -> Result<BTreeMap<PathBuf, String>> {
.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 {

View file

@ -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::*;

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);
} 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::<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 {
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<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 {
let path = op.path();
let cwd = self.current().cwd.clone();

View file

@ -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;
}

View file

@ -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());
}

View file

@ -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);

View file

@ -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();