feat: add hovered as $0 for shell and opener (#738)

This commit is contained in:
rrveex 2024-02-28 21:12:09 +02:00 committed by GitHub
parent b6fb02fda3
commit 2efda755f1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 63 additions and 53 deletions

View File

@ -9,37 +9,42 @@ use yazi_shared::{emit, event::{Cmd, EventQuit}, fs::{File, Url}, Layer, MIME_DI
use crate::{folder::Folder, manager::Manager, select::Select, tasks::Tasks};
pub struct Opt {
targets: Vec<(Url, String)>,
interactive: bool,
hovered: bool,
}
impl From<Cmd> for Opt {
fn from(mut c: Cmd) -> Self {
fn from(c: Cmd) -> Self {
Self {
targets: c.take_data().unwrap_or_default(),
interactive: c.named.contains_key("interactive"),
hovered: c.named.contains_key("hovered"),
}
}
}
#[derive(Default)]
pub struct OptDo {
hovered: Url,
targets: Vec<(Url, String)>,
interactive: bool,
}
impl From<Cmd> for OptDo {
fn from(mut c: Cmd) -> Self { c.take_data().unwrap_or_default() }
}
impl Manager {
pub fn open(&mut self, opt: impl Into<Opt>, tasks: &Tasks) {
if !self.active_mut().try_escape_visual() {
return;
}
let mut opt = opt.into() as Opt;
let selected = if opt.hovered {
self.hovered().map(|h| vec![&h.url]).unwrap_or_default()
} else {
self.selected_or_hovered()
let Some(hovered) = self.hovered().map(|h| h.url()) else {
return;
};
if selected.is_empty() {
return;
} else if Self::quit_with_selected(&selected) {
let opt = opt.into() as Opt;
let selected = if opt.hovered { vec![&hovered] } else { self.selected_or_hovered() };
if Self::quit_with_selected(&selected) {
return;
}
@ -55,8 +60,7 @@ impl Manager {
}
if todo.is_empty() {
opt.targets = done;
return self.open_do(opt, tasks);
return self.open_do(OptDo { hovered, targets: done, interactive: opt.interactive }, tasks);
}
tokio::spawn(async move {
@ -69,27 +73,20 @@ impl Manager {
done.extend(files.iter().map(|f| (f.url(), String::new())));
if let Err(e) = isolate::preload("mime", files, true).await {
error!("preload in watcher failed: {e}");
error!("preload in open failed: {e}");
}
Self::_open_do(done, opt.interactive);
Self::_open_do(OptDo { hovered, targets: done, interactive: opt.interactive });
});
}
#[inline]
pub fn _open_do(targets: Vec<(Url, String)>, interactive: bool) {
emit!(Call(
Cmd::new("open_do").with_bool("interactive", interactive).with_data(targets),
Layer::Manager
));
pub fn _open_do(opt: OptDo) {
emit!(Call(Cmd::new("open_do").with_data(opt), Layer::Manager));
}
pub fn open_do(&mut self, opt: impl Into<Opt>, tasks: &Tasks) {
let opt = opt.into() as Opt;
if opt.targets.is_empty() {
return;
}
pub fn open_do(&mut self, opt: impl Into<OptDo>, tasks: &Tasks) {
let opt = opt.into() as OptDo;
let targets: Vec<_> = opt
.targets
.into_iter()
@ -101,8 +98,7 @@ impl Manager {
if targets.is_empty() {
return;
} else if !opt.interactive {
tasks.file_open(&targets);
return;
return tasks.file_open(&opt.hovered, &targets);
}
let openers: Vec<_> = OPEN.common_openers(&targets).into_iter().cloned().collect();
@ -110,11 +106,11 @@ impl Manager {
return;
}
let urls = targets.into_iter().map(|(u, _)| u).collect();
let urls = [opt.hovered].into_iter().chain(targets.into_iter().map(|(u, _)| u)).collect();
tokio::spawn(async move {
let result = Select::_show(SelectCfg::open(openers.iter().map(|o| o.desc.clone()).collect()));
if let Ok(choice) = result.await {
Tasks::_open(urls, openers[choice].clone());
Tasks::_open_with(urls, openers[choice].clone());
}
});
}

View File

@ -1,4 +1,4 @@
use std::{collections::BTreeMap, ffi::OsStr, io::{stdout, BufWriter, Write}, path::PathBuf};
use std::{collections::BTreeMap, ffi::{OsStr, OsString}, io::{stdout, BufWriter, Write}, path::PathBuf};
use anyhow::{anyhow, bail, Result};
use tokio::{fs::{self, OpenOptions}, io::{stdin, AsyncReadExt, AsyncWriteExt}};
@ -129,7 +129,7 @@ impl Manager {
let mut child = external::shell(ShellOpt {
cmd: (*opener.exec).into(),
args: vec![tmp.to_owned().into()],
args: vec![OsString::new(), tmp.to_owned().into()],
piped: false,
orphan: false,
})?;

View File

@ -52,4 +52,7 @@ impl Manager {
#[inline]
pub fn selected_or_hovered(&self) -> Vec<&Url> { self.tabs.active().selected_or_hovered() }
#[inline]
pub fn hovered_and_selected(&self) -> Vec<&Url> { self.tabs.active().hovered_and_selected() }
}

View File

@ -26,7 +26,7 @@ impl Tab {
}
let mut opt = opt.into() as Opt;
let selected: Vec<_> = self.selected_or_hovered().into_iter().cloned().collect();
let selected = self.hovered_and_selected().into_iter().cloned().collect();
tokio::spawn(async move {
if !opt.confirm || opt.exec.is_empty() {
@ -37,7 +37,7 @@ impl Tab {
}
}
Tasks::_open(selected, Opener {
Tasks::_open_with(selected, Opener {
exec: opt.exec,
block: opt.block,
orphan: false,

View File

@ -58,6 +58,18 @@ impl Tab {
}
}
pub fn hovered_and_selected(&self) -> Vec<&Url> {
let Some(h) = self.current.hovered() else {
return vec![];
};
if self.selected.is_empty() {
vec![&h.url, &h.url]
} else {
[&h.url].into_iter().chain(self.selected.iter()).collect()
}
}
// --- History
#[inline]
pub fn history_new(&mut self, url: &Url) -> Folder {

View File

@ -1,5 +1,5 @@
mod arrow;
mod cancel;
mod inspect;
mod open;
mod open_with;
mod toggle;

View File

@ -15,11 +15,11 @@ impl TryFrom<Cmd> for Opt {
}
impl Tasks {
pub fn _open(targets: Vec<Url>, opener: Opener) {
emit!(Call(Cmd::new("open").with_data(Opt { targets, opener }), Layer::Tasks));
pub fn _open_with(targets: Vec<Url>, opener: Opener) {
emit!(Call(Cmd::new("open_with").with_data(Opt { targets, opener }), Layer::Tasks));
}
pub fn open(&mut self, opt: impl TryInto<Opt>) {
pub fn open_with(&mut self, opt: impl TryInto<Opt>) {
if let Ok(opt) = opt.try_into() {
self.file_open_with(&opt.opener, &opt.targets);
}

View File

@ -1,4 +1,4 @@
use std::{collections::{BTreeMap, HashMap, HashSet}, ffi::OsStr, mem, path::Path, sync::Arc, time::Duration};
use std::{collections::{BTreeMap, HashMap, HashSet}, ffi::OsStr, mem, sync::Arc, time::Duration};
use tokio::time::sleep;
use tracing::debug;
@ -56,28 +56,28 @@ impl Tasks {
running.values().take(Self::limit()).map(Into::into).collect()
}
pub fn file_open(&self, targets: &[(impl AsRef<Path>, impl AsRef<str>)]) -> bool {
pub fn file_open(&self, hovered: &Url, targets: &[(Url, String)]) {
let mut openers = BTreeMap::new();
for (path, mime) in targets {
if let Some(opener) = OPEN.openers(path, mime).and_then(|o| o.first().copied()) {
openers.entry(opener).or_insert_with(Vec::new).push(path.as_ref().as_os_str());
for (url, mime) in targets {
if let Some(opener) = OPEN.openers(url, mime).and_then(|o| o.first().copied()) {
openers.entry(opener).or_insert_with(|| vec![hovered]).push(url);
}
}
for (opener, args) in openers {
self.file_open_with(opener, &args);
}
false
}
pub fn file_open_with(&self, opener: &Opener, args: &[impl AsRef<OsStr>]) -> bool {
if opener.spread {
pub fn file_open_with(&self, opener: &Opener, args: &[impl AsRef<OsStr>]) {
if args.len() < 2 {
return;
} else if opener.spread {
self.scheduler.process_open(opener, args);
return false;
return;
}
for target in args {
self.scheduler.process_open(opener, &[target]);
for target in args.iter().skip(1) {
self.scheduler.process_open(opener, &[&args[0], target]);
}
false
}
pub fn file_cut(&self, src: &HashSet<Url>, dest: &Url, force: bool) {

View File

@ -151,11 +151,11 @@ impl<'a> Executor<'a> {
};
}
on!(open);
on!(toggle, "close");
on!(arrow);
on!(inspect);
on!(cancel);
on!(open_with);
#[allow(clippy::single_match)]
match cmd.name.as_str() {

View File

@ -40,7 +40,6 @@ pub fn shell(opt: ShellOpt) -> Result<Child> {
.stdout(opt.stdio())
.stderr(opt.stdio())
.arg(opt.cmd)
.arg("") // $0 is the command name
.args(opt.args)
.kill_on_drop(!opt.orphan)
.pre_exec(move || {