diff --git a/Cargo.lock b/Cargo.lock index 5c03f312..ec370fff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2810,7 +2810,6 @@ dependencies = [ "anyhow", "crossterm", "futures", - "libc", "md-5", "mlua", "parking_lot", @@ -2859,10 +2858,12 @@ dependencies = [ "base64 0.22.0", "crossterm", "futures", + "libc", "parking_lot", "regex", "tokio", "tokio-stream", + "tokio-util", "tracing", "trash", "yazi-adaptor", diff --git a/yazi-adaptor/src/ueberzug.rs b/yazi-adaptor/src/ueberzug.rs index c2f9a6c1..3bcd905c 100644 --- a/yazi-adaptor/src/ueberzug.rs +++ b/yazi-adaptor/src/ueberzug.rs @@ -46,7 +46,7 @@ impl Ueberzug { tx.send(Some((path.to_path_buf(), rect)))?; Adaptor::shown_store(rect, (0, 0)); } else { - bail!("uninitialized ueberzug"); + bail!("uninitialized ueberzugpp"); } let path = path.to_owned(); @@ -66,12 +66,13 @@ impl Ueberzug { if let Some(tx) = &*DEMON { Ok(tx.send(None)?) } else { - bail!("uninitialized ueberzug"); + bail!("uninitialized ueberzugpp"); } } fn create_demon(adaptor: Adaptor) -> Result { - let result = Command::new("ueberzug") + // TODO: demon + let result = Command::new("ueberzugpp") .args(["layer", "-so", &adaptor.to_string()]) .kill_on_drop(true) .stdin(Stdio::piped()) @@ -79,7 +80,7 @@ impl Ueberzug { .spawn(); if let Err(ref e) = result { - warn!("ueberzug spawning failed: {}", e); + warn!("ueberzugpp spawning failed: {e}"); } Ok(result?) } @@ -98,9 +99,9 @@ impl Ueberzug { async fn send_command(child: &mut Child, cmd: Option<(PathBuf, Rect)>) -> Result<()> { let stdin = child.stdin.as_mut().unwrap(); if let Some((path, rect)) = cmd { - debug!("ueberzug rect before adjustment: {:?}", rect); + debug!("ueberzugpp rect before adjustment: {:?}", rect); let rect = Self::adjust_rect(rect); - debug!("ueberzug rect after adjustment: {:?}", rect); + debug!("ueberzugpp rect after adjustment: {:?}", rect); let s = format!( r#"{{"action":"add","identifier":"yazi","x":{},"y":{},"max_width":{},"max_height":{},"path":"{}"}}{}"#, @@ -111,10 +112,10 @@ impl Ueberzug { path.to_string_lossy(), "\n" ); - debug!("ueberzug command: {}", s); + debug!("ueberzugpp command: {}", s); stdin.write_all(s.as_bytes()).await?; } else { - debug!("ueberzug command: remove"); + debug!("ueberzugpp command: remove"); stdin .write_all(format!(r#"{{"action":"remove","identifier":"yazi"}}{}"#, "\n").as_bytes()) .await?; diff --git a/yazi-boot/src/boot.rs b/yazi-boot/src/boot.rs index 6380445c..d4f1f530 100644 --- a/yazi-boot/src/boot.rs +++ b/yazi-boot/src/boot.rs @@ -74,8 +74,11 @@ impl Boot { println!(" WAYLAND_DISPLAY: {:?}", env::var_os("WAYLAND_DISPLAY")); println!(" DISPLAY: {:?}", env::var_os("DISPLAY")); - println!("\nUeberzug"); - println!(" Version: {:?}", std::process::Command::new("ueberzug").arg("--version").output()); + println!("\nUeberzug++"); + println!( + " Version: {:?}", + std::process::Command::new("ueberzugpp").arg("--version").output() + ); println!("\nWSL"); println!( diff --git a/yazi-config/preset/keymap.toml b/yazi-config/preset/keymap.toml index a803ae7b..eb363ac0 100644 --- a/yazi-config/preset/keymap.toml +++ b/yazi-config/preset/keymap.toml @@ -106,18 +106,18 @@ keymap = [ { on = [ "N" ], run = "find_arrow --previous", desc = "Go to previous found file" }, # Sorting - { on = [ ",", "m" ], run = "sort modified --dir-first", desc = "Sort by modified time" }, - { on = [ ",", "M" ], run = "sort modified --reverse --dir-first", desc = "Sort by modified time (reverse)" }, - { on = [ ",", "c" ], run = "sort created --dir-first", desc = "Sort by created time" }, - { on = [ ",", "C" ], run = "sort created --reverse --dir-first", desc = "Sort by created time (reverse)" }, - { on = [ ",", "e" ], run = "sort extension --dir-first", desc = "Sort by extension" }, - { on = [ ",", "E" ], run = "sort extension --reverse --dir-first", desc = "Sort by extension (reverse)" }, - { on = [ ",", "a" ], run = "sort alphabetical --dir-first", desc = "Sort alphabetically" }, - { on = [ ",", "A" ], run = "sort alphabetical --reverse --dir-first", desc = "Sort alphabetically (reverse)" }, - { on = [ ",", "n" ], run = "sort natural --dir-first", desc = "Sort naturally" }, - { on = [ ",", "N" ], run = "sort natural --reverse --dir-first", desc = "Sort naturally (reverse)" }, - { on = [ ",", "s" ], run = "sort size --dir-first", desc = "Sort by size" }, - { on = [ ",", "S" ], run = "sort size --reverse --dir-first", desc = "Sort by size (reverse)" }, + { on = [ ",", "m" ], run = "sort modified", desc = "Sort by modified time" }, + { on = [ ",", "M" ], run = "sort modified --reverse", desc = "Sort by modified time (reverse)" }, + { on = [ ",", "c" ], run = "sort created", desc = "Sort by created time" }, + { on = [ ",", "C" ], run = "sort created --reverse", desc = "Sort by created time (reverse)" }, + { on = [ ",", "e" ], run = "sort extension", desc = "Sort by extension" }, + { on = [ ",", "E" ], run = "sort extension --reverse", desc = "Sort by extension (reverse)" }, + { on = [ ",", "a" ], run = "sort alphabetical", desc = "Sort alphabetically" }, + { on = [ ",", "A" ], run = "sort alphabetical --reverse", desc = "Sort alphabetically (reverse)" }, + { on = [ ",", "n" ], run = "sort natural", desc = "Sort naturally" }, + { on = [ ",", "N" ], run = "sort natural --reverse", desc = "Sort naturally (reverse)" }, + { on = [ ",", "s" ], run = "sort size", desc = "Sort by size" }, + { on = [ ",", "S" ], run = "sort size --reverse", desc = "Sort by size (reverse)" }, # Tabs { on = [ "t" ], run = "tab_create --current", desc = "Create a new tab using the current path" }, diff --git a/yazi-core/src/manager/commands/bulk_rename.rs b/yazi-core/src/manager/commands/bulk_rename.rs index ca4bf92b..23f6be96 100644 --- a/yazi-core/src/manager/commands/bulk_rename.rs +++ b/yazi-core/src/manager/commands/bulk_rename.rs @@ -1,10 +1,9 @@ -use std::{collections::HashMap, ffi::{OsStr, OsString}, io::{stdout, BufWriter, Write}, path::PathBuf}; +use std::{borrow::Cow, collections::HashMap, ffi::{OsStr, OsString}, io::{stdout, BufWriter, Write}, path::PathBuf}; use anyhow::{anyhow, Result}; use tokio::{fs::{self, OpenOptions}, io::{stdin, AsyncReadExt, AsyncWriteExt}}; use yazi_config::{OPEN, PREVIEW}; -use yazi_plugin::external::{self, ShellOpt}; -use yazi_proxy::{AppProxy, HIDER, WATCHER}; +use yazi_proxy::{AppProxy, TasksProxy, HIDER, WATCHER}; use yazi_shared::{fs::{accessible, max_common_root, File, FilesOp, Url}, term::Term, Defer}; use crate::manager::Manager; @@ -32,18 +31,13 @@ impl Manager { .write_all(s.as_encoded_bytes()) .await?; - let _permit = HIDER.acquire().await.unwrap(); - let _defer1 = Defer::new(AppProxy::resume); - let _defer2 = Defer::new(|| tokio::spawn(fs::remove_file(tmp.clone()))); - AppProxy::stop().await; + let _defer1 = Defer::new(|| tokio::spawn(fs::remove_file(tmp.clone()))); + TasksProxy::process_exec(vec![OsString::new(), tmp.to_owned().into()], Cow::Borrowed(opener)) + .await; - let mut child = external::shell(ShellOpt { - cmd: (*opener.run).into(), - args: vec![OsString::new(), tmp.to_owned().into()], - piped: false, - orphan: false, - })?; - child.wait().await?; + let _permit = HIDER.acquire().await.unwrap(); + let _defer2 = Defer::new(AppProxy::resume); + AppProxy::stop().await; let new: Vec<_> = fs::read_to_string(&tmp).await?.lines().map(PathBuf::from).collect(); Self::bulk_rename_do(cwd, root, old, new).await diff --git a/yazi-core/src/manager/commands/open.rs b/yazi-core/src/manager/commands/open.rs index e4d1862a..926ca773 100644 --- a/yazi-core/src/manager/commands/open.rs +++ b/yazi-core/src/manager/commands/open.rs @@ -1,4 +1,4 @@ -use std::ffi::OsString; +use std::{borrow::Cow, ffi::OsString}; use tracing::error; use yazi_boot::ARGS; @@ -84,10 +84,10 @@ impl Manager { if targets.is_empty() { return; } else if !opt.interactive { - return tasks.file_open(&opt.hovered, &targets); + return tasks.process_from_files(opt.hovered, targets); } - let openers: Vec<_> = OPEN.common_openers(&targets).into_iter().cloned().collect(); + let openers: Vec<_> = OPEN.common_openers(&targets); if openers.is_empty() { return; } @@ -98,7 +98,7 @@ impl Manager { openers.iter().map(|o| o.desc.clone()).collect(), )); if let Ok(choice) = result.await { - TasksProxy::open_with(urls, openers[choice].clone()); + TasksProxy::open_with(urls, Cow::Borrowed(openers[choice])); } }); } diff --git a/yazi-core/src/tab/commands/shell.rs b/yazi-core/src/tab/commands/shell.rs index 2547a694..3be29cd8 100644 --- a/yazi-core/src/tab/commands/shell.rs +++ b/yazi-core/src/tab/commands/shell.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use yazi_config::{open::Opener, popup::InputCfg}; use yazi_proxy::{InputProxy, TasksProxy}; use yazi_shared::event::Cmd; @@ -38,14 +40,17 @@ impl Tab { } } - TasksProxy::open_with(selected, Opener { - run: opt.run, - block: opt.block, - orphan: false, - desc: Default::default(), - for_: None, - spread: true, - }); + TasksProxy::open_with( + selected, + Cow::Owned(Opener { + run: opt.run, + block: opt.block, + orphan: false, + desc: Default::default(), + for_: None, + spread: true, + }), + ); }); } } diff --git a/yazi-core/src/tasks/commands/mod.rs b/yazi-core/src/tasks/commands/mod.rs index 0fce8d2b..9456f9da 100644 --- a/yazi-core/src/tasks/commands/mod.rs +++ b/yazi-core/src/tasks/commands/mod.rs @@ -2,4 +2,5 @@ mod arrow; mod cancel; mod inspect; mod open_with; +mod process_exec; mod toggle; diff --git a/yazi-core/src/tasks/commands/open_with.rs b/yazi-core/src/tasks/commands/open_with.rs index ff874532..4712740f 100644 --- a/yazi-core/src/tasks/commands/open_with.rs +++ b/yazi-core/src/tasks/commands/open_with.rs @@ -5,7 +5,10 @@ use crate::tasks::Tasks; impl Tasks { pub fn open_with(&mut self, opt: impl TryInto) { if let Ok(opt) = opt.try_into() { - self.file_open_with(&opt.opener, &opt.targets); + self.process_from_opener( + opt.opener, + opt.targets.into_iter().map(|u| u.into_os_string()).collect(), + ); } } } diff --git a/yazi-core/src/tasks/commands/process_exec.rs b/yazi-core/src/tasks/commands/process_exec.rs new file mode 100644 index 00000000..e53ba8ce --- /dev/null +++ b/yazi-core/src/tasks/commands/process_exec.rs @@ -0,0 +1,11 @@ +use yazi_proxy::options::ProcessExecOpt; + +use crate::tasks::Tasks; + +impl Tasks { + pub fn process_exec(&mut self, opt: impl TryInto) { + if let Ok(opt) = opt.try_into() { + self.scheduler.process_open(opt.opener, opt.args, Some(opt.done)); + } + } +} diff --git a/yazi-core/src/tasks/file.rs b/yazi-core/src/tasks/file.rs new file mode 100644 index 00000000..7e2e2fe3 --- /dev/null +++ b/yazi-core/src/tasks/file.rs @@ -0,0 +1,51 @@ +use std::collections::HashSet; + +use tracing::debug; +use yazi_shared::fs::Url; + +use super::Tasks; + +impl Tasks { + pub fn file_cut(&self, src: &[&Url], dest: &Url, force: bool) { + for &u in src { + let to = dest.join(u.file_name().unwrap()); + if force && *u == to { + debug!("file_cut: same file, skipping {:?}", to); + } else { + self.scheduler.file_cut(u.clone(), to, force); + } + } + } + + pub fn file_copy(&self, src: &[&Url], dest: &Url, force: bool, follow: bool) { + for &u in src { + let to = dest.join(u.file_name().unwrap()); + if force && *u == to { + debug!("file_copy: same file, skipping {:?}", to); + } else { + self.scheduler.file_copy(u.clone(), to, force, follow); + } + } + } + + pub fn file_link(&self, src: &HashSet, dest: &Url, relative: bool, force: bool) { + for u in src { + let to = dest.join(u.file_name().unwrap()); + if force && *u == to { + debug!("file_link: same file, skipping {:?}", to); + } else { + self.scheduler.file_link(u.clone(), to, relative, force); + } + } + } + + pub fn file_remove(&self, targets: Vec, permanently: bool) { + for u in targets { + if permanently { + self.scheduler.file_delete(u); + } else { + self.scheduler.file_trash(u); + } + } + } +} diff --git a/yazi-core/src/tasks/mod.rs b/yazi-core/src/tasks/mod.rs index d8475836..32abaac5 100644 --- a/yazi-core/src/tasks/mod.rs +++ b/yazi-core/src/tasks/mod.rs @@ -1,4 +1,8 @@ mod commands; +mod file; +mod plugin; +mod preload; +mod process; mod progress; mod tasks; diff --git a/yazi-core/src/tasks/plugin.rs b/yazi-core/src/tasks/plugin.rs new file mode 100644 index 00000000..665dddda --- /dev/null +++ b/yazi-core/src/tasks/plugin.rs @@ -0,0 +1,15 @@ +use yazi_plugin::ValueSendable; + +use super::Tasks; + +impl Tasks { + #[inline] + pub fn plugin_micro(&self, name: String, args: Vec) { + self.scheduler.plugin_micro(name, args); + } + + #[inline] + pub fn plugin_macro(&self, name: String, args: Vec) { + self.scheduler.plugin_macro(name, args); + } +} diff --git a/yazi-core/src/tasks/preload.rs b/yazi-core/src/tasks/preload.rs new file mode 100644 index 00000000..8f1915df --- /dev/null +++ b/yazi-core/src/tasks/preload.rs @@ -0,0 +1,94 @@ +use std::{collections::HashMap, mem}; + +use yazi_config::{manager::SortBy, plugin::{PluginRule, MAX_PRELOADERS}, PLUGIN}; +use yazi_shared::{fs::{File, Url}, MIME_DIR}; + +use super::Tasks; +use crate::folder::Files; + +impl Tasks { + pub fn preload_paged(&self, paged: &[File], mimetype: &HashMap) { + let mut single_tasks = Vec::with_capacity(paged.len()); + let mut multi_tasks: [Vec<_>; MAX_PRELOADERS as usize] = Default::default(); + + let loaded = self.scheduler.preload.rule_loaded.read(); + for f in paged { + let mime = if f.is_dir() { Some(MIME_DIR) } else { mimetype.get(&f.url).map(|s| &**s) }; + let factors = |s: &str| match s { + "mime" => mime.is_some(), + _ => false, + }; + + for rule in PLUGIN.preloaders(&f.url, mime, factors) { + if loaded.get(&f.url).is_some_and(|x| x & (1 << rule.id) != 0) { + continue; + } + if rule.multi { + multi_tasks[rule.id as usize].push(f); + } else { + single_tasks.push((rule, f)); + } + } + } + + drop(loaded); + let mut loaded = self.scheduler.preload.rule_loaded.write(); + + let mut go = |rule: &PluginRule, targets: Vec<&File>| { + for &f in &targets { + if let Some(n) = loaded.get_mut(&f.url) { + *n |= 1 << rule.id; + } else { + loaded.insert(f.url.clone(), 1 << rule.id); + } + } + self.scheduler.preload_paged(rule, targets); + }; + + #[allow(clippy::needless_range_loop)] + for i in 0..PLUGIN.preloaders.len() { + if !multi_tasks[i].is_empty() { + go(&PLUGIN.preloaders[i], mem::take(&mut multi_tasks[i])); + } + } + for (rule, target) in single_tasks { + go(rule, vec![target]); + } + } + + pub fn preload_affected(&self, affected: &[File], mimetype: &HashMap) { + { + let mut loaded = self.scheduler.preload.rule_loaded.write(); + for f in affected { + loaded.remove(&f.url); + } + } + + self.preload_paged(affected, mimetype); + } + + pub fn preload_sorted(&self, targets: &Files) { + if targets.sorter().by != SortBy::Size { + return; + } + + let targets: Vec<_> = { + let loading = self.scheduler.preload.size_loading.read(); + targets + .iter() + .filter(|f| f.is_dir() && !targets.sizes.contains_key(&f.url) && !loading.contains(&f.url)) + .map(|f| &f.url) + .collect() + }; + if targets.is_empty() { + return; + } + + let mut loading = self.scheduler.preload.size_loading.write(); + for &target in &targets { + loading.insert(target.clone()); + } + + self.scheduler.preload_size(targets); + } +} diff --git a/yazi-core/src/tasks/process.rs b/yazi-core/src/tasks/process.rs new file mode 100644 index 00000000..17beaa1a --- /dev/null +++ b/yazi-core/src/tasks/process.rs @@ -0,0 +1,41 @@ +use std::{borrow::Cow, collections::HashMap, ffi::OsString, mem}; + +use yazi_config::{open::Opener, OPEN}; +use yazi_shared::fs::Url; + +use super::Tasks; + +impl Tasks { + pub fn process_from_files(&self, hovered: Url, targets: Vec<(Url, String)>) { + let mut openers = HashMap::new(); + 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.clone()]).push(url); + } + } + for (opener, args) in openers { + self.process_from_opener( + Cow::Borrowed(opener), + args.into_iter().map(|u| u.into_os_string()).collect(), + ); + } + } + + pub fn process_from_opener(&self, opener: Cow<'static, Opener>, mut args: Vec) { + if opener.spread { + self.scheduler.process_open(opener, args, None); + return; + } + if args.is_empty() { + return; + } + if args.len() == 2 { + self.scheduler.process_open(opener, args, None); + return; + } + let hovered = mem::take(&mut args[0]); + for target in args.into_iter().skip(1) { + self.scheduler.process_open(opener.clone(), vec![hovered.clone(), target], None); + } + } +} diff --git a/yazi-core/src/tasks/tasks.rs b/yazi-core/src/tasks/tasks.rs index f2684c1b..69b01178 100644 --- a/yazi-core/src/tasks/tasks.rs +++ b/yazi-core/src/tasks/tasks.rs @@ -1,14 +1,10 @@ -use std::{collections::{HashMap, HashSet}, ffi::OsStr, mem, sync::Arc, time::Duration}; +use std::{sync::Arc, time::Duration}; use tokio::time::sleep; -use tracing::debug; -use yazi_config::{manager::SortBy, open::Opener, plugin::{PluginRule, MAX_PRELOADERS}, OPEN, PLUGIN}; -use yazi_plugin::ValueSendable; use yazi_scheduler::{Scheduler, TaskSummary}; -use yazi_shared::{emit, event::Cmd, fs::{File, Url}, term::Term, Layer, MIME_DIR}; +use yazi_shared::{emit, event::Cmd, term::Term, Layer}; use super::{TasksProgress, TASKS_BORDER, TASKS_PADDING, TASKS_PERCENT}; -use crate::folder::Files; pub struct Tasks { pub(super) scheduler: Arc, @@ -56,168 +52,6 @@ impl Tasks { ongoing.values().take(Self::limit()).map(Into::into).collect() } - pub fn file_open(&self, hovered: &Url, targets: &[(Url, String)]) { - let mut openers = HashMap::new(); - 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); - } - } - - pub fn file_open_with(&self, opener: &Opener, args: &[impl AsRef]) { - if opener.spread { - self.scheduler.process_open(opener, args); - return; - } - for target in args.iter().skip(1) { - self.scheduler.process_open(opener, &[&args[0], target]); - } - } - - pub fn file_cut(&self, src: &[&Url], dest: &Url, force: bool) { - for &u in src { - let to = dest.join(u.file_name().unwrap()); - if force && *u == to { - debug!("file_cut: same file, skipping {:?}", to); - } else { - self.scheduler.file_cut(u.clone(), to, force); - } - } - } - - pub fn file_copy(&self, src: &[&Url], dest: &Url, force: bool, follow: bool) { - for &u in src { - let to = dest.join(u.file_name().unwrap()); - if force && *u == to { - debug!("file_copy: same file, skipping {:?}", to); - } else { - self.scheduler.file_copy(u.clone(), to, force, follow); - } - } - } - - pub fn file_link(&self, src: &HashSet, dest: &Url, relative: bool, force: bool) { - for u in src { - let to = dest.join(u.file_name().unwrap()); - if force && *u == to { - debug!("file_link: same file, skipping {:?}", to); - } else { - self.scheduler.file_link(u.clone(), to, relative, force); - } - } - } - - pub fn file_remove(&self, targets: Vec, permanently: bool) { - for u in targets { - if permanently { - self.scheduler.file_delete(u); - } else { - self.scheduler.file_trash(u); - } - } - } - - #[inline] - pub fn plugin_micro(&self, name: String, args: Vec) { - self.scheduler.plugin_micro(name, args); - } - - #[inline] - pub fn plugin_macro(&self, name: String, args: Vec) { - self.scheduler.plugin_macro(name, args); - } - - pub fn preload_paged(&self, paged: &[File], mimetype: &HashMap) { - let mut single_tasks = Vec::with_capacity(paged.len()); - let mut multi_tasks: [Vec<_>; MAX_PRELOADERS as usize] = Default::default(); - - let loaded = self.scheduler.preload.rule_loaded.read(); - for f in paged { - let mime = if f.is_dir() { Some(MIME_DIR) } else { mimetype.get(&f.url).map(|s| &**s) }; - let factors = |s: &str| match s { - "mime" => mime.is_some(), - _ => false, - }; - - for rule in PLUGIN.preloaders(&f.url, mime, factors) { - if loaded.get(&f.url).is_some_and(|x| x & (1 << rule.id) != 0) { - continue; - } - if rule.multi { - multi_tasks[rule.id as usize].push(f); - } else { - single_tasks.push((rule, f)); - } - } - } - - drop(loaded); - let mut loaded = self.scheduler.preload.rule_loaded.write(); - - let mut go = |rule: &PluginRule, targets: Vec<&File>| { - for &f in &targets { - if let Some(n) = loaded.get_mut(&f.url) { - *n |= 1 << rule.id; - } else { - loaded.insert(f.url.clone(), 1 << rule.id); - } - } - self.scheduler.preload_paged(rule, targets); - }; - - #[allow(clippy::needless_range_loop)] - for i in 0..PLUGIN.preloaders.len() { - if !multi_tasks[i].is_empty() { - go(&PLUGIN.preloaders[i], mem::take(&mut multi_tasks[i])); - } - } - for (rule, target) in single_tasks { - go(rule, vec![target]); - } - } - - pub fn preload_affected(&self, affected: &[File], mimetype: &HashMap) { - { - let mut loaded = self.scheduler.preload.rule_loaded.write(); - for f in affected { - loaded.remove(&f.url); - } - } - - self.preload_paged(affected, mimetype); - } - - pub fn preload_sorted(&self, targets: &Files) { - if targets.sorter().by != SortBy::Size { - return; - } - - let targets: Vec<_> = { - let loading = self.scheduler.preload.size_loading.read(); - targets - .iter() - .filter(|f| f.is_dir() && !targets.sizes.contains_key(&f.url) && !loading.contains(&f.url)) - .map(|f| &f.url) - .collect() - }; - if targets.is_empty() { - return; - } - - let mut loading = self.scheduler.preload.size_loading.write(); - for &target in &targets { - loading.insert(target.clone()); - } - - self.scheduler.preload_size(targets); - } -} - -impl Tasks { #[inline] pub fn len(&self) -> usize { self.scheduler.ongoing.lock().len() } } diff --git a/yazi-core/src/which/commands/callback.rs b/yazi-core/src/which/commands/callback.rs index 7dd4100c..ef0fc5fe 100644 --- a/yazi-core/src/which/commands/callback.rs +++ b/yazi-core/src/which/commands/callback.rs @@ -27,7 +27,7 @@ impl Which { }; if opt.tx.try_send(opt.idx).is_err() { - error!("callback: send error"); + error!("which callback: send error"); } } } diff --git a/yazi-fm/src/executor.rs b/yazi-fm/src/executor.rs index e3163577..97eb60f1 100644 --- a/yazi-fm/src/executor.rs +++ b/yazi-fm/src/executor.rs @@ -156,6 +156,7 @@ impl<'a> Executor<'a> { on!(inspect); on!(cancel); on!(open_with); + on!(process_exec); #[allow(clippy::single_match)] match cmd.name.as_str() { diff --git a/yazi-plugin/Cargo.toml b/yazi-plugin/Cargo.toml index 1727184b..6aa277ad 100644 --- a/yazi-plugin/Cargo.toml +++ b/yazi-plugin/Cargo.toml @@ -36,5 +36,4 @@ unicode-width = "^0" yazi-prebuild = "0.1.2" [target."cfg(unix)".dependencies] -libc = "^0" uzers = "^0" diff --git a/yazi-plugin/src/external/mod.rs b/yazi-plugin/src/external/mod.rs index 92cb58b1..9400cf83 100644 --- a/yazi-plugin/src/external/mod.rs +++ b/yazi-plugin/src/external/mod.rs @@ -3,7 +3,6 @@ mod fzf; mod highlighter; mod lsar; mod rg; -mod shell; mod zoxide; pub use fd::*; @@ -11,5 +10,4 @@ pub use fzf::*; pub use highlighter::*; pub use lsar::*; pub use rg::*; -pub use shell::*; pub use zoxide::*; diff --git a/yazi-proxy/src/options/mod.rs b/yazi-proxy/src/options/mod.rs index e787fc53..d8b2feb1 100644 --- a/yazi-proxy/src/options/mod.rs +++ b/yazi-proxy/src/options/mod.rs @@ -1,9 +1,11 @@ mod input; mod notify; mod open; +mod process; mod select; pub use input::*; pub use notify::*; pub use open::*; +pub use process::*; pub use select::*; diff --git a/yazi-proxy/src/options/open.rs b/yazi-proxy/src/options/open.rs index 65a6cd5d..77ff707d 100644 --- a/yazi-proxy/src/options/open.rs +++ b/yazi-proxy/src/options/open.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use yazi_config::open::Opener; use yazi_shared::{event::Cmd, fs::Url}; @@ -16,7 +18,7 @@ impl From for OpenDoOpt { // --- Open with pub struct OpenWithOpt { pub targets: Vec, - pub opener: Opener, + pub opener: Cow<'static, Opener>, } impl TryFrom for OpenWithOpt { diff --git a/yazi-proxy/src/options/process.rs b/yazi-proxy/src/options/process.rs new file mode 100644 index 00000000..00daf667 --- /dev/null +++ b/yazi-proxy/src/options/process.rs @@ -0,0 +1,18 @@ +use std::{borrow::Cow, ffi::OsString}; + +use tokio::sync::oneshot; +use yazi_config::open::Opener; +use yazi_shared::event::Cmd; + +// --- Exec +pub struct ProcessExecOpt { + pub args: Vec, + pub opener: Cow<'static, Opener>, + pub done: oneshot::Sender<()>, +} + +impl TryFrom for ProcessExecOpt { + type Error = (); + + fn try_from(mut c: Cmd) -> Result { c.take_data().ok_or(()) } +} diff --git a/yazi-proxy/src/tasks.rs b/yazi-proxy/src/tasks.rs index 24ce61f6..b07bdf2c 100644 --- a/yazi-proxy/src/tasks.rs +++ b/yazi-proxy/src/tasks.rs @@ -1,13 +1,26 @@ +use std::{borrow::Cow, ffi::OsString}; + +use tokio::sync::oneshot; use yazi_config::open::Opener; use yazi_shared::{emit, event::Cmd, fs::Url, Layer}; -use crate::options::OpenWithOpt; +use crate::options::{OpenWithOpt, ProcessExecOpt}; pub struct TasksProxy; impl TasksProxy { #[inline] - pub fn open_with(targets: Vec, opener: Opener) { + pub fn open_with(targets: Vec, opener: Cow<'static, Opener>) { emit!(Call(Cmd::new("open_with").with_data(OpenWithOpt { targets, opener }), Layer::Tasks)); } + + #[inline] + pub async fn process_exec(args: Vec, opener: Cow<'static, Opener>) { + let (tx, rx) = oneshot::channel(); + emit!(Call( + Cmd::new("process_exec").with_data(ProcessExecOpt { args, opener, done: tx }), + Layer::Tasks + )); + rx.await.ok(); + } } diff --git a/yazi-scheduler/Cargo.toml b/yazi-scheduler/Cargo.toml index 7c760a29..ed4c29db 100644 --- a/yazi-scheduler/Cargo.toml +++ b/yazi-scheduler/Cargo.toml @@ -25,9 +25,13 @@ parking_lot = "^0" regex = "^1" tokio = { version = "^1", features = [ "parking_lot", "rt-multi-thread" ] } tokio-stream = "^0" +tokio-util = "^0" # Logging tracing = { version = "^0", features = [ "max_level_debug", "release_max_level_warn" ] } +[target."cfg(unix)".dependencies] +libc = "^0" + [target.'cfg(not(target_os = "android"))'.dependencies] trash = "^3" diff --git a/yazi-scheduler/src/process/mod.rs b/yazi-scheduler/src/process/mod.rs index 1d72a7b1..e78ef646 100644 --- a/yazi-scheduler/src/process/mod.rs +++ b/yazi-scheduler/src/process/mod.rs @@ -2,6 +2,8 @@ mod op; mod process; +mod shell; pub use op::*; pub use process::*; +pub use shell::*; diff --git a/yazi-scheduler/src/process/op.rs b/yazi-scheduler/src/process/op.rs index 5275b3fa..b740e071 100644 --- a/yazi-scheduler/src/process/op.rs +++ b/yazi-scheduler/src/process/op.rs @@ -1,20 +1,45 @@ use std::ffi::OsString; -use tokio::sync::oneshot; -use yazi_plugin::external::ShellOpt; +use tokio_util::sync::CancellationToken; + +use super::ShellOpt; #[derive(Debug)] -pub struct ProcessOpOpen { - pub id: usize, - pub cmd: OsString, - pub args: Vec, - pub block: bool, - pub orphan: bool, - pub cancel: oneshot::Sender<()>, +pub struct ProcessOpBlock { + pub id: usize, + pub cmd: OsString, + pub args: Vec, } -impl From for ShellOpt { - fn from(op: ProcessOpOpen) -> Self { - Self { cmd: op.cmd, args: op.args, piped: false, orphan: op.orphan } +impl From for ShellOpt { + fn from(op: ProcessOpBlock) -> Self { + Self { cmd: op.cmd, args: op.args, piped: false, orphan: false } + } +} + +#[derive(Debug)] +pub struct ProcessOpOrphan { + pub id: usize, + pub cmd: OsString, + pub args: Vec, +} + +impl From for ShellOpt { + fn from(op: ProcessOpOrphan) -> Self { + Self { cmd: op.cmd, args: op.args, piped: false, orphan: true } + } +} + +#[derive(Debug)] +pub struct ProcessOpBg { + pub id: usize, + pub cmd: OsString, + pub args: Vec, + pub ct: CancellationToken, +} + +impl From for ShellOpt { + fn from(op: ProcessOpBg) -> Self { + Self { cmd: op.cmd, args: op.args, piped: true, orphan: false } } } diff --git a/yazi-scheduler/src/process/process.rs b/yazi-scheduler/src/process/process.rs index e1b0e930..e868f3ae 100644 --- a/yazi-scheduler/src/process/process.rs +++ b/yazi-scheduler/src/process/process.rs @@ -1,10 +1,9 @@ use anyhow::Result; use tokio::{io::{AsyncBufReadExt, BufReader}, select, sync::mpsc}; -use yazi_plugin::external::{self, ShellOpt}; use yazi_proxy::{AppProxy, HIDER}; use yazi_shared::Defer; -use super::ProcessOpOpen; +use super::{ProcessOpBg, ProcessOpBlock, ProcessOpOrphan, ShellOpt}; use crate::TaskProg; pub struct Process { @@ -14,28 +13,53 @@ pub struct Process { impl Process { pub fn new(prog: mpsc::UnboundedSender) -> Self { Self { prog } } - pub async fn open(&self, mut task: ProcessOpOpen) -> Result<()> { - if task.block { - return self.open_block(task).await; + pub async fn block(&self, task: ProcessOpBlock) -> Result<()> { + let _permit = HIDER.acquire().await.unwrap(); + let _defer = Defer::new(AppProxy::resume); + AppProxy::stop().await; + + let (id, cmd) = (task.id, task.cmd.clone()); + let result = super::shell(task.into()); + if let Err(e) = result { + AppProxy::notify_warn(&cmd.to_string_lossy(), &format!("Failed to spawn process: {e}")); + return self.succ(id); } - if task.orphan { - return self.open_orphan(task).await; + let status = result.unwrap().wait().await?; + if !status.success() { + let content = match status.code() { + Some(code) => format!("Process exited with status code: {code}"), + None => "Process terminated by signal".to_string(), + }; + AppProxy::notify_warn(&cmd.to_string_lossy(), &content); } + self.succ(id) + } + + pub async fn orphan(&self, task: ProcessOpOrphan) -> Result<()> { + let id = task.id; + match super::shell(task.into()) { + Ok(_) => self.succ(id)?, + Err(e) => { + self.prog.send(TaskProg::New(id, 0))?; + self.fail(id, format!("Failed to spawn process: {e}"))?; + } + } + + Ok(()) + } + + pub async fn bg(&self, task: ProcessOpBg) -> Result<()> { self.prog.send(TaskProg::New(task.id, 0))?; - let mut child = external::shell(ShellOpt { - cmd: task.cmd, - args: task.args, - piped: true, - ..Default::default() - })?; + let mut child = + super::shell(ShellOpt { cmd: task.cmd, args: task.args, piped: true, ..Default::default() })?; let mut stdout = BufReader::new(child.stdout.take().unwrap()).lines(); let mut stderr = BufReader::new(child.stderr.take().unwrap()).lines(); loop { select! { - _ = task.cancel.closed() => { + _ = task.ct.cancelled() => { child.start_kill().ok(); break; } @@ -61,43 +85,6 @@ impl Process { self.prog.send(TaskProg::Adv(task.id, 1, 0))?; self.succ(task.id) } - - async fn open_block(&self, task: ProcessOpOpen) -> Result<()> { - let _permit = HIDER.acquire().await.unwrap(); - let _defer = Defer::new(AppProxy::resume); - AppProxy::stop().await; - - let (id, cmd) = (task.id, task.cmd.clone()); - let result = external::shell(task.into()); - if let Err(e) = result { - AppProxy::notify_warn(&cmd.to_string_lossy(), &format!("Failed to spawn process: {e}")); - return self.succ(id); - } - - let status = result.unwrap().wait().await?; - if !status.success() { - let content = match status.code() { - Some(code) => format!("Process exited with status code: {code}"), - None => "Process terminated by signal".to_string(), - }; - AppProxy::notify_warn(&cmd.to_string_lossy(), &content); - } - - self.succ(id) - } - - async fn open_orphan(&self, task: ProcessOpOpen) -> Result<()> { - let id = task.id; - match external::shell(task.into()) { - Ok(_) => self.succ(id)?, - Err(e) => { - self.prog.send(TaskProg::New(id, 0))?; - self.fail(id, format!("Failed to spawn process: {e}"))?; - } - } - - Ok(()) - } } impl Process { diff --git a/yazi-plugin/src/external/shell.rs b/yazi-scheduler/src/process/shell.rs similarity index 98% rename from yazi-plugin/src/external/shell.rs rename to yazi-scheduler/src/process/shell.rs index cc7039b7..2be7f2fc 100644 --- a/yazi-plugin/src/external/shell.rs +++ b/yazi-scheduler/src/process/shell.rs @@ -12,11 +12,6 @@ pub struct ShellOpt { } impl ShellOpt { - pub fn with_piped(mut self) -> Self { - self.piped = true; - self - } - #[inline] fn stdio(&self) -> Stdio { if self.orphan { diff --git a/yazi-scheduler/src/scheduler.rs b/yazi-scheduler/src/scheduler.rs index 2fdb3764..8cff33eb 100644 --- a/yazi-scheduler/src/scheduler.rs +++ b/yazi-scheduler/src/scheduler.rs @@ -1,14 +1,15 @@ -use std::{ffi::OsStr, sync::Arc, time::Duration}; +use std::{borrow::Cow, ffi::OsString, sync::Arc, time::Duration}; use futures::{future::BoxFuture, FutureExt}; use parking_lot::Mutex; use tokio::{fs, select, sync::{mpsc::{self, UnboundedReceiver}, oneshot}}; +use tokio_util::sync::CancellationToken; use yazi_config::{open::Opener, plugin::PluginRule, TASKS}; use yazi_plugin::ValueSendable; use yazi_shared::{fs::{unique_path, Url}, Throttle}; use super::{Ongoing, TaskProg, TaskStage}; -use crate::{file::{File, FileOpDelete, FileOpLink, FileOpPaste, FileOpTrash}, plugin::{Plugin, PluginOpEntry}, preload::{Preload, PreloadOpRule, PreloadOpSize}, process::{Process, ProcessOpOpen}, TaskKind, TaskOp, HIGH, LOW, NORMAL}; +use crate::{file::{File, FileOpDelete, FileOpLink, FileOpPaste, FileOpTrash}, plugin::{Plugin, PluginOpEntry}, preload::{Preload, PreloadOpRule, PreloadOpSize}, process::{Process, ProcessOpBg, ProcessOpBlock, ProcessOpOrphan}, TaskKind, TaskOp, HIGH, LOW, NORMAL}; pub struct Scheduler { pub file: Arc, @@ -332,47 +333,55 @@ impl Scheduler { } } - pub fn process_open(&self, opener: &Opener, args: &[impl AsRef]) { + pub fn process_open( + &self, + opener: Cow<'static, Opener>, + args: Vec, + done: Option>, + ) { let name = { - let s = format!("Run `{}`", opener.run); - let args = args.iter().map(|a| a.as_ref().to_string_lossy()).collect::>().join(" "); - if args.is_empty() { s } else { format!("{s} with `{args}`") } + let args = args.iter().map(|a| a.to_string_lossy()).collect::>().join(" "); + if args.is_empty() { + format!("Run {:?}", opener.run) + } else { + format!("Run {:?} with `{args}`", opener.run) + } }; + let ct = CancellationToken::new(); let mut ongoing = self.ongoing.lock(); - let id = ongoing.add(TaskKind::User, name); - let (cancel_tx, mut cancel_rx) = oneshot::channel(); + let id = ongoing.add(TaskKind::User, name); ongoing.hooks.insert(id, { + let ct = ct.clone(); let ongoing = self.ongoing.clone(); Box::new(move |canceled: bool| { async move { - if canceled { - cancel_rx.close(); - } ongoing.lock().try_remove(id, TaskStage::Hooked); + if canceled { + ct.cancel(); + } + if let Some(tx) = done { + tx.send(()).ok(); + } } .boxed() }) }); - let args = args.iter().map(|a| a.as_ref().to_os_string()).collect::>(); - tokio::spawn({ - let process = self.process.clone(); - let opener = opener.clone(); + let process = self.process.clone(); + _ = self.micro.try_send( async move { - process - .open(ProcessOpOpen { - id, - cmd: opener.run.into(), - args, - block: opener.block, - orphan: opener.orphan, - cancel: cancel_tx, - }) - .await - .ok(); + if opener.block { + process.block(ProcessOpBlock { id, cmd: OsString::from(&opener.run), args }).await.ok(); + } else if opener.orphan { + process.orphan(ProcessOpOrphan { id, cmd: OsString::from(&opener.run), args }).await.ok(); + } else { + process.bg(ProcessOpBg { id, cmd: OsString::from(&opener.run), args, ct }).await.ok(); + } } - }); + .boxed(), + HIGH, + ); } }