Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions yazi-plugin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,12 @@ unicode-width = { workspace = true }
yazi-prebuilt = "0.1.0"

[target."cfg(unix)".dependencies]
libc = { workspace = true }
uzers = { workspace = true }

[target."cfg(windows)".dependencies]
clipboard-win = "5.4.0"
windows-sys = { version = "0.59.0", features = [ "Win32_Security", "Win32_System_JobObjects", "Win32_System_Threading" ] }

[target.'cfg(target_os = "macos")'.dependencies]
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }
40 changes: 33 additions & 7 deletions yazi-plugin/preset/plugins/svg.lua
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,44 @@ function M:preload(job)
end

-- stylua: ignore
local cmd = require("magick").with_env():args {
"-size", string.format("%dx%d^", rt.preview.max_width, rt.preview.max_height),
string.format("rsvg:%s", job.file.url), "-strip",
"-quality", rt.preview.image_quality,
string.format("JPG:%s", cache),
local cmd = Command("resvg"):args {
"-w", rt.preview.max_width, "-h", rt.preview.max_height,
"--image-rendering", "optimizeSpeed",
tostring(job.file.url), tostring(cache)
}
if rt.tasks.image_alloc > 0 then
cmd = cmd:memory(rt.tasks.image_alloc)
end

local child, err = cmd:spawn()
if not child then
return true, Err("Failed to start `resvg`, error: %s", err)
end

local status, err
while true do
ya.sleep(0.2)

status, err = child:try_wait()
if status or err then
break
end

local id, mem = child:id(), nil
if id then
mem = ya.proc_info(id).mem_resident
end
if mem and mem > rt.tasks.image_alloc then
child:start_kill()
err = Err("memory limit exceeded, pid: %s, memory: %s", id, mem)
break
end
end

local status, err = cmd:status()
if status then
return status.success
else
return true, Err("Failed to start `magick`, error: %s", err)
return true, Err("Error while running `resvg`: %s", err)
end
end

Expand Down
2 changes: 1 addition & 1 deletion yazi-plugin/preset/plugins/video.lua
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ function M:preload(job)
"-skip_frame", "nokey", "-ss", ss,
"-an", "-sn", "-dn",
"-i", tostring(job.file.url),
"-vframes", 1,
"-map", "0:v", "-vframes", 1,
"-q:v", qv,
"-vf", string.format("scale=-1:'min(%d,ih)':flags=fast_bilinear", rt.preview.max_height),
"-f", "image2",
Expand Down
141 changes: 90 additions & 51 deletions yazi-plugin/src/process/child.rs
Original file line number Diff line number Diff line change
@@ -1,48 +1,103 @@
use std::{ops::DerefMut, time::Duration};
use std::{ops::DerefMut, process::ExitStatus, time::Duration};

use futures::future::try_join3;
use mlua::{AnyUserData, ExternalError, IntoLua, IntoLuaMulti, Table, UserData, Value};
use mlua::{AnyUserData, ExternalError, IntoLua, IntoLuaMulti, Table, UserData, UserDataFields, UserDataMethods, Value};
use tokio::{io::{self, AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader, BufWriter}, process::{ChildStderr, ChildStdin, ChildStdout}, select};
use yazi_binding::Error;

use super::Status;
use crate::process::Output;

pub struct Child {
inner: tokio::process::Child,
stdin: Option<BufWriter<ChildStdin>>,
stdout: Option<BufReader<ChildStdout>>,
stderr: Option<BufReader<ChildStderr>>,
inner: tokio::process::Child,
stdin: Option<BufWriter<ChildStdin>>,
stdout: Option<BufReader<ChildStdout>>,
stderr: Option<BufReader<ChildStderr>>,
#[cfg(windows)]
job_handle: Option<std::os::windows::io::RawHandle>,
}

#[cfg(windows)]
impl Drop for Child {
fn drop(&mut self) {
if let Some(h) = self.job_handle.take() {
unsafe { windows_sys::Win32::Foundation::CloseHandle(h) };
}
}
}

impl Child {
pub fn new(mut inner: tokio::process::Child) -> Self {
pub fn new(
mut inner: tokio::process::Child,
#[cfg(windows)] job_handle: Option<std::os::windows::io::RawHandle>,
) -> Self {
let stdin = inner.stdin.take().map(BufWriter::new);
let stdout = inner.stdout.take().map(BufReader::new);
let stderr = inner.stderr.take().map(BufReader::new);
Self { inner, stdin, stdout, stderr }
Self {
inner,
stdin,
stdout,
stderr,
#[cfg(windows)]
job_handle,
}
}
}

impl UserData for Child {
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
#[inline]
async fn read_line(me: &mut Child) -> (Option<Vec<u8>>, u8) {
async fn read(r: Option<impl AsyncBufReadExt + Unpin>) -> Option<Vec<u8>> {
let mut buf = Vec::new();
match r?.read_until(b'\n', &mut buf).await {
Ok(0) | Err(_) => None,
Ok(_) => Some(buf),
}
pub(super) async fn wait(&mut self) -> io::Result<ExitStatus> {
drop(self.stdin.take());
self.inner.wait().await
}

pub(super) async fn status(&mut self) -> io::Result<ExitStatus> {
drop(self.stdin.take());
drop(self.stdout.take());
drop(self.stderr.take());
self.inner.wait().await
}

async fn read_line(&mut self) -> (Option<Vec<u8>>, u8) {
async fn read(r: Option<impl AsyncBufReadExt + Unpin>) -> Option<Vec<u8>> {
let mut buf = Vec::new();
match r?.read_until(b'\n', &mut buf).await {
Ok(0) | Err(_) => None,
Ok(_) => Some(buf),
}
}

select! {
r @ Some(_) = read(self.stdout.as_mut()) => (r, 0u8),
r @ Some(_) = read(self.stderr.as_mut()) => (r, 1u8),
else => (None, 2u8),
}
}

select! {
r @ Some(_) = read(me.stdout.as_mut()) => (r, 0u8),
r @ Some(_) = read(me.stderr.as_mut()) => (r, 1u8),
else => (None, 2u8),
pub(super) async fn wait_with_output(mut self) -> io::Result<std::process::Output> {
async fn read(r: &mut Option<impl AsyncBufReadExt + Unpin>) -> io::Result<Vec<u8>> {
let mut vec = Vec::new();
if let Some(r) = r.as_mut() {
r.read_to_end(&mut vec).await?;
}
Ok(vec)
}

// Ensure stdin is closed so the child isn't stuck waiting on input while the
// parent is waiting for it to exit.
drop(self.stdin.take());

// Drop happens after `try_join` due to <https://github.com/tokio-rs/tokio/issues/4309>
let mut stdout = self.stdout.take();
let mut stderr = self.stderr.take();

let result = try_join3(self.inner.wait(), read(&mut stdout), read(&mut stderr)).await?;
Ok(std::process::Output { status: result.0, stdout: result.1, stderr: result.2 })
}
}

impl UserData for Child {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("id", |_, me, ()| Ok(me.inner.id()));

methods.add_async_method_mut("read", |_, mut me, len: usize| async move {
async fn read(r: Option<impl AsyncBufReadExt + Unpin>, len: usize) -> Option<Vec<u8>> {
let mut r = r?;
Expand All @@ -62,14 +117,15 @@ impl UserData for Child {
})
});
methods.add_async_method_mut("read_line", |lua, mut me, ()| async move {
match read_line(&mut me).await {
match me.read_line().await {
(Some(b), event) => (lua.create_string(b)?, event).into_lua_multi(&lua),
(None, event) => (Value::Nil, event).into_lua_multi(&lua),
}
});
// TODO: deprecate this method
methods.add_async_method_mut("read_line_with", |lua, mut me, options: Table| async move {
let timeout = Duration::from_millis(options.raw_get("timeout")?);
let Ok(result) = tokio::time::timeout(timeout, read_line(&mut me)).await else {
let Ok(result) = tokio::time::timeout(timeout, me.read_line()).await else {
return (Value::Nil, 3u8).into_lua_multi(&lua);
};
match result {
Expand Down Expand Up @@ -98,38 +154,21 @@ impl UserData for Child {
});

methods.add_async_method_mut("wait", |lua, mut me, ()| async move {
drop(me.stdin.take());
match me.inner.wait().await {
match me.wait().await {
Ok(status) => (Status::new(status), Value::Nil).into_lua_multi(&lua),
Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),
}
});
methods.add_async_function("wait_with_output", |lua, ud: AnyUserData| async move {
async fn read_to_end(r: &mut Option<impl AsyncBufReadExt + Unpin>) -> io::Result<Vec<u8>> {
let mut vec = Vec::new();
if let Some(r) = r.as_mut() {
r.read_to_end(&mut vec).await?;
}
Ok(vec)
match ud.take::<Self>()?.wait_with_output().await {
Ok(output) => (Output::new(output), Value::Nil).into_lua_multi(&lua),
Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),
}

let mut me = ud.take::<Self>()?;
let mut stdout_pipe = me.stdout.take();
let mut stderr_pipe = me.stderr.take();

let stdout_fut = read_to_end(&mut stdout_pipe);
let stderr_fut = read_to_end(&mut stderr_pipe);

drop(me.stdin.take());
let result = try_join3(me.inner.wait(), stdout_fut, stderr_fut).await;
drop(stdout_pipe);
drop(stderr_pipe);

match result {
Ok((status, stdout, stderr)) => {
(Output::new(std::process::Output { status, stdout, stderr }), Value::Nil)
.into_lua_multi(&lua)
}
});
methods.add_async_method_mut("try_wait", |lua, mut me, ()| async move {
match me.inner.try_wait() {
Ok(Some(status)) => (Status::new(status), Value::Nil).into_lua_multi(&lua),
Ok(None) => (Value::Nil, Value::Nil).into_lua_multi(&lua),
Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),
}
});
Expand Down
Loading
Loading