mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
feat: new of open command
This commit is contained in:
parent
11c289229a
commit
521c7271b6
12 changed files with 78 additions and 26 deletions
|
|
@ -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" },
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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>,
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
6
src/core/external/file.rs
vendored
6
src/core/external/file.rs
vendored
|
|
@ -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 {
|
||||||
|
|
|
||||||
2
src/core/external/mod.rs
vendored
2
src/core/external/mod.rs
vendored
|
|
@ -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
25
src/core/external/unar.rs
vendored
Normal 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)
|
||||||
|
}
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue