mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
feat: add decompression to the builtin archive plugin
This commit is contained in:
parent
f024ce03e7
commit
d65a1e2cbc
42 changed files with 715 additions and 357 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -2990,6 +2990,7 @@ dependencies = [
|
||||||
"mlua",
|
"mlua",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
|
"scopeguard",
|
||||||
"shell-escape",
|
"shell-escape",
|
||||||
"shell-words",
|
"shell-words",
|
||||||
"syntect",
|
"syntect",
|
||||||
|
|
|
||||||
|
|
@ -44,9 +44,9 @@ impl Actions {
|
||||||
writeln!(s, "\nVariables")?;
|
writeln!(s, "\nVariables")?;
|
||||||
writeln!(s, " SHELL : {:?}", env::var_os("SHELL"))?;
|
writeln!(s, " SHELL : {:?}", env::var_os("SHELL"))?;
|
||||||
writeln!(s, " EDITOR : {:?}", env::var_os("EDITOR"))?;
|
writeln!(s, " EDITOR : {:?}", env::var_os("EDITOR"))?;
|
||||||
writeln!(s, " ZELLIJ_SESSION_NAME: {:?}", env::var_os("ZELLIJ_SESSION_NAME"))?;
|
|
||||||
writeln!(s, " YAZI_FILE_ONE : {:?}", env::var_os("YAZI_FILE_ONE"))?;
|
writeln!(s, " YAZI_FILE_ONE : {:?}", env::var_os("YAZI_FILE_ONE"))?;
|
||||||
writeln!(s, " YAZI_CONFIG_HOME : {:?}", env::var_os("YAZI_CONFIG_HOME"))?;
|
writeln!(s, " YAZI_CONFIG_HOME : {:?}", env::var_os("YAZI_CONFIG_HOME"))?;
|
||||||
|
writeln!(s, " ZELLIJ_SESSION_NAME: {:?}", env::var_os("ZELLIJ_SESSION_NAME"))?;
|
||||||
|
|
||||||
writeln!(s, "\nText Opener")?;
|
writeln!(s, "\nText Opener")?;
|
||||||
writeln!(
|
writeln!(
|
||||||
|
|
@ -74,7 +74,8 @@ impl Actions {
|
||||||
writeln!(s, " rg : {}", Self::process_output("rg", "--version"))?;
|
writeln!(s, " rg : {}", Self::process_output("rg", "--version"))?;
|
||||||
writeln!(s, " chafa : {}", Self::process_output("chafa", "--version"))?;
|
writeln!(s, " chafa : {}", Self::process_output("chafa", "--version"))?;
|
||||||
writeln!(s, " zoxide : {}", Self::process_output("zoxide", "--version"))?;
|
writeln!(s, " zoxide : {}", Self::process_output("zoxide", "--version"))?;
|
||||||
writeln!(s, " unar : {}", Self::process_output("unar", "--version"))?;
|
writeln!(s, " 7z : {}", Self::process_output("7z", "i"))?;
|
||||||
|
writeln!(s, " 7zz : {}", Self::process_output("7zz", "i"))?;
|
||||||
writeln!(s, " jq : {}", Self::process_output("jq", "--version"))?;
|
writeln!(s, " jq : {}", Self::process_output("jq", "--version"))?;
|
||||||
|
|
||||||
writeln!(s, "\n\n--------------------------------------------------")?;
|
writeln!(s, "\n\n--------------------------------------------------")?;
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,9 @@ tokio = { version = "1.39.1", features = [ "full" ] }
|
||||||
toml_edit = "0.22.16"
|
toml_edit = "0.22.16"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
|
yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
|
||||||
|
|
||||||
|
# External build dependencies
|
||||||
anyhow = "1.0.86"
|
anyhow = "1.0.86"
|
||||||
clap = { version = "4.5.10", features = [ "derive" ] }
|
clap = { version = "4.5.10", features = [ "derive" ] }
|
||||||
clap_complete = "4.5.9"
|
clap_complete = "4.5.9"
|
||||||
|
|
|
||||||
|
|
@ -16,52 +16,67 @@ pub(super) struct Args {
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
pub(super) enum Command {
|
pub(super) enum Command {
|
||||||
/// Publish a message to remote instance(s).
|
/// Publish a message to the current instance.
|
||||||
Pub(CommandPub),
|
Pub(CommandPub),
|
||||||
/// Manage packages.
|
/// Publish a message to the specified instance.
|
||||||
Pack(CommandPack),
|
PubTo(CommandPubTo),
|
||||||
/// Subscribe to messages from all remote instances.
|
/// Subscribe to messages from all remote instances.
|
||||||
Sub(CommandSub),
|
Sub(CommandSub),
|
||||||
|
/// Manage packages.
|
||||||
|
Pack(CommandPack),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(clap::Args)]
|
#[derive(clap::Args)]
|
||||||
pub(super) struct CommandPub {
|
pub(super) struct CommandPub {
|
||||||
/// The kind of message.
|
/// The kind of message.
|
||||||
#[arg(index = 1)]
|
#[arg(index = 1)]
|
||||||
pub(super) kind: String,
|
pub(super) kind: String,
|
||||||
|
/// Send the message with a string body.
|
||||||
|
#[arg(long)]
|
||||||
|
pub(super) str: Option<String>,
|
||||||
|
/// Send the message with a JSON body.
|
||||||
|
#[arg(long)]
|
||||||
|
pub(super) json: Option<String>,
|
||||||
|
/// Send the message as string of list.
|
||||||
|
#[arg(long, num_args = 0..)]
|
||||||
|
pub(super) list: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommandPub {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(super) fn receiver(&self) -> Result<u64> {
|
||||||
|
if let Some(s) = std::env::var("YAZI_PID").ok().filter(|s| !s.is_empty()) {
|
||||||
|
Ok(s.parse()?)
|
||||||
|
} else {
|
||||||
|
bail!("No `YAZI_ID` environment variable found.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(clap::Args)]
|
||||||
|
pub(super) struct CommandPubTo {
|
||||||
/// The receiver ID.
|
/// The receiver ID.
|
||||||
|
#[arg(index = 1)]
|
||||||
|
pub(super) receiver: u64,
|
||||||
|
/// The kind of message.
|
||||||
#[arg(index = 2)]
|
#[arg(index = 2)]
|
||||||
pub(super) receiver: Option<u64>,
|
pub(super) kind: String,
|
||||||
/// Send the message with a string body.
|
/// Send the message with a string body.
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub(super) str: Option<String>,
|
pub(super) str: Option<String>,
|
||||||
/// Send the message with a JSON body.
|
/// Send the message with a JSON body.
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub(super) json: Option<String>,
|
pub(super) json: Option<String>,
|
||||||
|
/// Send the message as string of list.
|
||||||
|
#[arg(long, num_args = 0..)]
|
||||||
|
pub(super) list: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CommandPub {
|
#[derive(clap::Args)]
|
||||||
#[allow(dead_code)]
|
pub(super) struct CommandSub {
|
||||||
pub(super) fn receiver(&self) -> Result<u64> {
|
/// The kind of messages to subscribe to, separated by commas if multiple.
|
||||||
if let Some(receiver) = self.receiver {
|
#[arg(index = 1)]
|
||||||
Ok(receiver)
|
pub(super) kinds: String,
|
||||||
} else if let Some(s) = std::env::var("YAZI_PID").ok().filter(|s| !s.is_empty()) {
|
|
||||||
Ok(s.parse()?)
|
|
||||||
} else {
|
|
||||||
bail!("No receiver ID provided, neither YAZI_ID environment variable found.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub(super) fn body(&self) -> Result<Cow<str>> {
|
|
||||||
if let Some(json) = &self.json {
|
|
||||||
Ok(json.into())
|
|
||||||
} else if let Some(str) = &self.str {
|
|
||||||
Ok(serde_json::to_string(str)?.into())
|
|
||||||
} else {
|
|
||||||
Ok("".into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(clap::Args)]
|
#[derive(clap::Args)]
|
||||||
|
|
@ -81,9 +96,25 @@ pub(super) struct CommandPack {
|
||||||
pub(super) upgrade: bool,
|
pub(super) upgrade: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(clap::Args)]
|
// --- Macros
|
||||||
pub(super) struct CommandSub {
|
macro_rules! impl_body {
|
||||||
/// The kind of messages to subscribe to, separated by commas if multiple.
|
($name:ident) => {
|
||||||
#[arg(index = 1)]
|
impl $name {
|
||||||
pub(super) kinds: String,
|
#[allow(dead_code)]
|
||||||
|
pub(super) fn body(&self) -> Result<Cow<str>> {
|
||||||
|
if let Some(json) = &self.json {
|
||||||
|
Ok(json.into())
|
||||||
|
} else if let Some(str) = &self.str {
|
||||||
|
Ok(serde_json::to_string(str)?.into())
|
||||||
|
} else if !self.list.is_empty() {
|
||||||
|
Ok(serde_json::to_string(&self.list)?.into())
|
||||||
|
} else {
|
||||||
|
Ok("".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl_body!(CommandPub);
|
||||||
|
impl_body!(CommandPubTo);
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,24 @@ async fn main() -> anyhow::Result<()> {
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Command::PubTo(cmd) => {
|
||||||
|
yazi_boot::init_default();
|
||||||
|
yazi_dds::init();
|
||||||
|
if let Err(e) = yazi_dds::Client::shot(&cmd.kind, cmd.receiver, &cmd.body()?).await {
|
||||||
|
eprintln!("Cannot send message: {e}");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Command::Sub(cmd) => {
|
||||||
|
yazi_boot::init_default();
|
||||||
|
yazi_dds::init();
|
||||||
|
yazi_dds::Client::draw(cmd.kinds.split(',').collect()).await?;
|
||||||
|
|
||||||
|
tokio::signal::ctrl_c().await?;
|
||||||
|
}
|
||||||
|
|
||||||
Command::Pack(cmd) => {
|
Command::Pack(cmd) => {
|
||||||
package::init();
|
package::init();
|
||||||
if cmd.install {
|
if cmd.install {
|
||||||
|
|
@ -40,14 +58,6 @@ async fn main() -> anyhow::Result<()> {
|
||||||
package::Package::add_to_config(repo).await?;
|
package::Package::add_to_config(repo).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Command::Sub(cmd) => {
|
|
||||||
yazi_boot::init_default();
|
|
||||||
yazi_dds::init();
|
|
||||||
yazi_dds::Client::draw(cmd.kinds.split(',').collect()).await?;
|
|
||||||
|
|
||||||
tokio::signal::ctrl_c().await?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -38,18 +38,18 @@ open = [
|
||||||
{ run = 'start "" "%1"', orphan = true, desc = "Open", for = "windows" },
|
{ run = 'start "" "%1"', orphan = true, desc = "Open", for = "windows" },
|
||||||
]
|
]
|
||||||
reveal = [
|
reveal = [
|
||||||
{ run = 'xdg-open "$(dirname "$1")"', desc = "Reveal", for = "linux" },
|
{ run = 'xdg-open "$(dirname "$1")"', desc = "Reveal", for = "linux" },
|
||||||
{ run = 'open -R "$1"', desc = "Reveal", for = "macos" },
|
{ run = 'open -R "$1"', desc = "Reveal", for = "macos" },
|
||||||
{ run = 'explorer /select, "%1"', orphan = true, desc = "Reveal", for = "windows" },
|
{ run = 'explorer /select,"%1"', orphan = true, desc = "Reveal", for = "windows" },
|
||||||
{ run = '''exiftool "$1"; echo "Press enter to exit"; read _''', block = true, desc = "Show EXIF", for = "unix" },
|
{ run = '''exiftool "$1"; echo "Press enter to exit"; read _''', block = true, desc = "Show EXIF", for = "unix" },
|
||||||
]
|
]
|
||||||
extract = [
|
extract = [
|
||||||
{ run = 'unar "$1"', desc = "Extract here", for = "unix" },
|
{ run = 'ya pub extract --list "$@"', desc = "Extract here", for = "unix" },
|
||||||
{ run = 'unar "%1"', desc = "Extract here", for = "windows" },
|
{ run = 'ya pub extract --list %*', desc = "Extract here", for = "windows" },
|
||||||
]
|
]
|
||||||
play = [
|
play = [
|
||||||
{ run = 'mpv --force-window "$@"', orphan = true, for = "unix" },
|
{ run = 'mpv --force-window "$@"', orphan = true, for = "unix" },
|
||||||
{ run = 'mpv --force-window "%1"', orphan = true, for = "windows" },
|
{ run = 'mpv --force-window %*', orphan = true, for = "windows" },
|
||||||
{ run = '''mediainfo "$1"; echo "Press enter to exit"; read _''', block = true, desc = "Show media info", for = "unix" },
|
{ run = '''mediainfo "$1"; echo "Press enter to exit"; read _''', block = true, desc = "Show media info", for = "unix" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use yazi_shared::{event::Cmd, Condition};
|
use yazi_shared::{event::Cmd, Condition, MIME_DIR};
|
||||||
|
|
||||||
use crate::{Pattern, Priority};
|
use crate::{Pattern, Priority};
|
||||||
|
|
||||||
|
|
@ -18,6 +20,15 @@ pub struct Fetcher {
|
||||||
pub prio: Priority,
|
pub prio: Priority,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Fetcher {
|
||||||
|
#[inline]
|
||||||
|
pub fn matches(&self, path: &Path, mime: Option<&str>, f: impl Fn(&str) -> bool + Copy) -> bool {
|
||||||
|
self.if_.as_ref().and_then(|c| c.eval(f)) != Some(false)
|
||||||
|
&& (self.mime.as_ref().zip(mime).map_or(false, |(p, m)| p.match_mime(m))
|
||||||
|
|| self.name.as_ref().is_some_and(|p| p.match_path(path, mime == Some(MIME_DIR))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct FetcherProps {
|
pub struct FetcherProps {
|
||||||
pub id: u8,
|
pub id: u8,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
use std::{path::Path, str::FromStr};
|
use std::{collections::HashSet, path::Path, str::FromStr};
|
||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use yazi_shared::MIME_DIR;
|
|
||||||
|
|
||||||
use super::{Fetcher, Preloader, Previewer};
|
use super::{Fetcher, Preloader, Previewer};
|
||||||
use crate::{plugin::MAX_PREWORKERS, Preset};
|
use crate::{plugin::MAX_PREWORKERS, Preset};
|
||||||
|
|
@ -20,39 +19,33 @@ impl Plugin {
|
||||||
mime: Option<&'a str>,
|
mime: Option<&'a str>,
|
||||||
factor: impl Fn(&str) -> bool + Copy,
|
factor: impl Fn(&str) -> bool + Copy,
|
||||||
) -> impl Iterator<Item = &'a Fetcher> {
|
) -> impl Iterator<Item = &'a Fetcher> {
|
||||||
let is_dir = mime == Some(MIME_DIR);
|
let mut seen = HashSet::new();
|
||||||
self.fetchers.iter().filter(move |&f| {
|
self.fetchers.iter().filter(move |&f| {
|
||||||
f.if_.as_ref().and_then(|c| c.eval(factor)) != Some(false)
|
if seen.contains(&f.id) || !f.matches(path, mime, factor) {
|
||||||
&& (f.mime.as_ref().zip(mime).map_or(false, |(p, m)| p.match_mime(m))
|
return false;
|
||||||
|| f.name.as_ref().is_some_and(|p| p.match_path(path, is_dir)))
|
}
|
||||||
|
seen.insert(&f.id);
|
||||||
|
true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn preloaders(&self, path: &Path, mime: Option<&str>) -> Vec<&Preloader> {
|
pub fn preloaders<'a>(
|
||||||
let is_dir = mime == Some(MIME_DIR);
|
&'a self,
|
||||||
let mut preloaders = Vec::with_capacity(1);
|
path: &'a Path,
|
||||||
|
mime: Option<&'a str>,
|
||||||
for p in &self.preloaders {
|
) -> impl Iterator<Item = &'a Preloader> {
|
||||||
if !p.mime.as_ref().zip(mime).map_or(false, |(p, m)| p.match_mime(m))
|
let mut next = true;
|
||||||
&& !p.name.as_ref().is_some_and(|p| p.match_path(path, is_dir))
|
self.preloaders.iter().filter(move |&p| {
|
||||||
{
|
if !next || !p.matches(path, mime) {
|
||||||
continue;
|
return false;
|
||||||
}
|
}
|
||||||
|
next = p.next;
|
||||||
preloaders.push(p);
|
true
|
||||||
if !p.next {
|
})
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
preloaders
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn previewer(&self, path: &Path, mime: &str) -> Option<&Previewer> {
|
pub fn previewer(&self, path: &Path, mime: &str) -> Option<&Previewer> {
|
||||||
let is_dir = mime == MIME_DIR;
|
self.previewers.iter().find(|&p| p.matches(path, mime))
|
||||||
self.previewers.iter().find(|&p| {
|
|
||||||
p.mime.as_ref().is_some_and(|p| p.match_mime(mime))
|
|
||||||
|| p.name.as_ref().is_some_and(|p| p.match_path(path, is_dir))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl FromStr for Plugin {
|
impl FromStr for Plugin {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use yazi_shared::event::Cmd;
|
use yazi_shared::{event::Cmd, MIME_DIR};
|
||||||
|
|
||||||
use crate::{Pattern, Priority};
|
use crate::{Pattern, Priority};
|
||||||
|
|
||||||
|
|
@ -17,6 +19,14 @@ pub struct Preloader {
|
||||||
pub prio: Priority,
|
pub prio: Priority,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Preloader {
|
||||||
|
#[inline]
|
||||||
|
pub fn matches(&self, path: &Path, mime: Option<&str>) -> bool {
|
||||||
|
self.mime.as_ref().zip(mime).map_or(false, |(p, m)| p.match_mime(m))
|
||||||
|
|| self.name.as_ref().is_some_and(|p| p.match_path(path, mime == Some(MIME_DIR)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct PreloaderProps {
|
pub struct PreloaderProps {
|
||||||
pub id: u8,
|
pub id: u8,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use yazi_shared::event::Cmd;
|
use yazi_shared::{event::Cmd, MIME_DIR};
|
||||||
|
|
||||||
use crate::Pattern;
|
use crate::Pattern;
|
||||||
|
|
||||||
|
|
@ -13,6 +15,12 @@ pub struct Previewer {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Previewer {
|
impl Previewer {
|
||||||
|
#[inline]
|
||||||
|
pub fn matches(&self, path: &Path, mime: &str) -> bool {
|
||||||
|
self.mime.as_ref().is_some_and(|p| p.match_mime(mime))
|
||||||
|
|| self.name.as_ref().is_some_and(|p| p.match_path(path, mime == MIME_DIR))
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn any_file(&self) -> bool { self.name.as_ref().is_some_and(|p| p.any_file()) }
|
pub fn any_file(&self) -> bool { self.name.as_ref().is_some_and(|p| p.any_file()) }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use tracing::error;
|
||||||
use yazi_shared::{event::Cmd, fs::Url, render};
|
use yazi_shared::{event::Cmd, fs::Url, render};
|
||||||
|
|
||||||
use crate::{manager::{Manager, LINKED}, tasks::Tasks};
|
use crate::{manager::{Manager, LINKED}, tasks::Tasks};
|
||||||
|
|
@ -12,14 +13,14 @@ impl TryFrom<Cmd> for Opt {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
fn try_from(mut c: Cmd) -> Result<Self, Self::Error> {
|
fn try_from(mut c: Cmd) -> Result<Self, Self::Error> {
|
||||||
Ok(Self { updates: c.take("updates").ok_or(())?.into_table_string() })
|
Ok(Self { updates: c.take("updates").ok_or(())?.into_dict_string() })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Manager {
|
impl Manager {
|
||||||
pub fn update_mimetype(&mut self, opt: impl TryInto<Opt>, tasks: &Tasks) {
|
pub fn update_mimetype(&mut self, opt: impl TryInto<Opt>, tasks: &Tasks) {
|
||||||
let Ok(opt) = opt.try_into() else {
|
let Ok(opt) = opt.try_into() else {
|
||||||
return;
|
return error!("invalid arguments for update_mimetype");
|
||||||
};
|
};
|
||||||
|
|
||||||
let linked = LINKED.read();
|
let linked = LINKED.read();
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use mlua::{ExternalError, Lua, Table, Value, Variadic};
|
use mlua::{ExternalError, Lua, MultiValue, Table, Value};
|
||||||
use yazi_shared::{event::{Data, DataKey}, OrderedFloat};
|
use yazi_shared::{event::{Data, DataKey}, OrderedFloat};
|
||||||
|
|
||||||
pub struct Sendable;
|
pub struct Sendable;
|
||||||
|
|
@ -15,12 +15,22 @@ impl Sendable {
|
||||||
Value::Number(n) => Data::Number(n),
|
Value::Number(n) => Data::Number(n),
|
||||||
Value::String(s) => Data::String(s.to_str()?.to_owned()),
|
Value::String(s) => Data::String(s.to_str()?.to_owned()),
|
||||||
Value::Table(t) => {
|
Value::Table(t) => {
|
||||||
let mut map = HashMap::with_capacity(t.raw_len());
|
let (mut i, mut map) = (0, HashMap::with_capacity(t.raw_len()));
|
||||||
for result in t.pairs::<Value, Value>() {
|
for result in t.pairs::<Value, Value>() {
|
||||||
let (k, v) = result?;
|
let (k, v) = result?;
|
||||||
map.insert(Self::value_to_key(k)?, Self::value_to_data(v)?);
|
let k = Self::value_to_key(k)?;
|
||||||
|
|
||||||
|
if k == DataKey::Integer(i) {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
map.insert(k, Self::value_to_data(v)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
if i as usize == map.len() {
|
||||||
|
Data::List(map.into_values().collect())
|
||||||
|
} else {
|
||||||
|
Data::Dict(map)
|
||||||
}
|
}
|
||||||
Data::Table(map)
|
|
||||||
}
|
}
|
||||||
Value::Function(_) => Err("function is not supported".into_lua_err())?,
|
Value::Function(_) => Err("function is not supported".into_lua_err())?,
|
||||||
Value::Thread(_) => Err("thread is not supported".into_lua_err())?,
|
Value::Thread(_) => Err("thread is not supported".into_lua_err())?,
|
||||||
|
|
@ -44,8 +54,9 @@ impl Sendable {
|
||||||
Data::Integer(v) => Value::Integer(v),
|
Data::Integer(v) => Value::Integer(v),
|
||||||
Data::Number(v) => Value::Number(v),
|
Data::Number(v) => Value::Number(v),
|
||||||
Data::String(v) => Value::String(lua.create_string(v)?),
|
Data::String(v) => Value::String(lua.create_string(v)?),
|
||||||
Data::Table(t) => {
|
Data::List(v) => Value::Table(Self::list_to_table(lua, v)?),
|
||||||
let seq_len = t.keys().filter(|&k| !k.is_numeric()).count();
|
Data::Dict(t) => {
|
||||||
|
let seq_len = t.keys().filter(|&k| !k.is_integer()).count();
|
||||||
let table = lua.create_table_with_capacity(seq_len, t.len() - seq_len)?;
|
let table = lua.create_table_with_capacity(seq_len, t.len() - seq_len)?;
|
||||||
for (k, v) in t {
|
for (k, v) in t {
|
||||||
table.raw_set(Self::key_to_value(lua, k)?, Self::data_to_value(lua, v)?)?;
|
table.raw_set(Self::key_to_value(lua, k)?, Self::data_to_value(lua, v)?)?;
|
||||||
|
|
@ -63,7 +74,7 @@ impl Sendable {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn vec_to_table(lua: &Lua, data: Vec<Data>) -> mlua::Result<Table> {
|
pub fn list_to_table(lua: &Lua, data: Vec<Data>) -> mlua::Result<Table> {
|
||||||
let mut vec = Vec::with_capacity(data.len());
|
let mut vec = Vec::with_capacity(data.len());
|
||||||
for v in data.into_iter() {
|
for v in data.into_iter() {
|
||||||
vec.push(Self::data_to_value(lua, v)?);
|
vec.push(Self::data_to_value(lua, v)?);
|
||||||
|
|
@ -71,15 +82,15 @@ impl Sendable {
|
||||||
lua.create_sequence_from(vec)
|
lua.create_sequence_from(vec)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn vec_to_variadic(lua: &Lua, data: Vec<Data>) -> mlua::Result<Variadic<Value>> {
|
pub fn list_to_values(lua: &Lua, data: Vec<Data>) -> mlua::Result<MultiValue> {
|
||||||
let mut vec = Vec::with_capacity(data.len());
|
let mut vec = Vec::with_capacity(data.len());
|
||||||
for v in data {
|
for v in data {
|
||||||
vec.push(Self::data_to_value(lua, v)?);
|
vec.push(Self::data_to_value(lua, v)?);
|
||||||
}
|
}
|
||||||
Ok(Variadic::from_iter(vec))
|
Ok(MultiValue::from_iter(vec))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn variadic_to_vec(values: Variadic<Value>) -> mlua::Result<Vec<Data>> {
|
pub fn values_to_vec(values: MultiValue) -> mlua::Result<Vec<Data>> {
|
||||||
let mut vec = Vec::with_capacity(values.len());
|
let mut vec = Vec::with_capacity(values.len());
|
||||||
for value in values {
|
for value in values {
|
||||||
vec.push(Self::value_to_data(value)?);
|
vec.push(Self::value_to_data(value)?);
|
||||||
|
|
|
||||||
|
|
@ -44,12 +44,12 @@ impl App {
|
||||||
};
|
};
|
||||||
|
|
||||||
match LUA.named_registry_value::<RtRef>("rt") {
|
match LUA.named_registry_value::<RtRef>("rt") {
|
||||||
Ok(mut r) => r.swap(&opt.id),
|
Ok(mut r) => r.push(&opt.id),
|
||||||
Err(e) => return warn!("{e}"),
|
Err(e) => return warn!("{e}"),
|
||||||
}
|
}
|
||||||
|
defer! { _ = LUA.named_registry_value::<RtRef>("rt").map(|mut r| r.pop()) }
|
||||||
|
|
||||||
defer! { LUA.named_registry_value::<RtRef>("rt").map(|mut r| r.reset()).ok(); };
|
let plugin = match LOADER.load(&LUA, &opt.id) {
|
||||||
let plugin = match LOADER.load(&opt.id) {
|
|
||||||
Ok(plugin) => plugin,
|
Ok(plugin) => plugin,
|
||||||
Err(e) => return warn!("{e}"),
|
Err(e) => return warn!("{e}"),
|
||||||
};
|
};
|
||||||
|
|
@ -58,7 +58,7 @@ impl App {
|
||||||
if let Some(cb) = opt.cb {
|
if let Some(cb) = opt.cb {
|
||||||
cb(&LUA, plugin)
|
cb(&LUA, plugin)
|
||||||
} else {
|
} else {
|
||||||
plugin.call_method("entry", Sendable::vec_to_table(&LUA, opt.args)?)
|
plugin.call_method("entry", Sendable::list_to_table(&LUA, opt.args)?)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ impl Lives {
|
||||||
f: impl FnOnce(&Scope<'a, 'a>) -> mlua::Result<T>,
|
f: impl FnOnce(&Scope<'a, 'a>) -> mlua::Result<T>,
|
||||||
) -> mlua::Result<T> {
|
) -> mlua::Result<T> {
|
||||||
let result = LUA.scope(|scope| {
|
let result = LUA.scope(|scope| {
|
||||||
defer! { SCOPE.drop(); };
|
defer! { SCOPE.drop(); }
|
||||||
SCOPE.init(unsafe {
|
SCOPE.init(unsafe {
|
||||||
mem::transmute::<&mlua::Scope<'a, 'a>, &mlua::Scope<'static, 'static>>(scope)
|
mem::transmute::<&mlua::Scope<'a, 'a>, &mlua::Scope<'static, 'static>>(scope)
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ md-5 = "0.10.6"
|
||||||
mlua = { version = "0.9.9", features = [ "lua54", "serialize", "macros", "async" ] }
|
mlua = { version = "0.9.9", features = [ "lua54", "serialize", "macros", "async" ] }
|
||||||
parking_lot = "0.12.3"
|
parking_lot = "0.12.3"
|
||||||
ratatui = "0.27.0"
|
ratatui = "0.27.0"
|
||||||
|
scopeguard = "1.2.0"
|
||||||
shell-escape = "0.1.5"
|
shell-escape = "0.1.5"
|
||||||
shell-words = "1.1.0"
|
shell-words = "1.1.0"
|
||||||
syntect = { version = "5.2.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
|
syntect = { version = "5.2.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
|
||||||
|
|
|
||||||
|
|
@ -1,62 +1,42 @@
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
function M:peek()
|
function M:peek()
|
||||||
local child
|
|
||||||
if ya.target_os() == "macos" then
|
|
||||||
child = self:try_spawn("7zz") or self:try_spawn("7z")
|
|
||||||
else
|
|
||||||
child = self:try_spawn("7z") or self:try_spawn("7zz")
|
|
||||||
end
|
|
||||||
|
|
||||||
if not child then
|
|
||||||
return ya.err("spawn `7z` and `7zz` both commands failed, error code: " .. tostring(self.last_error))
|
|
||||||
end
|
|
||||||
|
|
||||||
local limit = self.area.h
|
local limit = self.area.h
|
||||||
local i, icon, names, sizes = 0, nil, {}, {}
|
local paths, sizes = {}, {}
|
||||||
repeat
|
|
||||||
local next, event = child:read_line()
|
|
||||||
if event ~= 0 then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
|
|
||||||
local attr, size, name = next:match("^[-%d]+%s+[:%d]+%s+([.%a]+)%s+(%d+)%s+%d+%s+(.+)[\r\n]+")
|
local files, bound, code = self:list_files({ "-p", tostring(self.file.url) }, self.skip, limit)
|
||||||
if not name then
|
if code ~= 0 then
|
||||||
goto continue
|
return ya.preview_widgets(self, {
|
||||||
end
|
ui.Paragraph(self.area, {
|
||||||
|
ui.Line(code == 2 and "File list in this archive is encrypted" or "Spawn `7z` and `7zz` both commands failed"),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
i = i + 1
|
for _, f in ipairs(files) do
|
||||||
if i <= self.skip then
|
local icon = File({
|
||||||
goto continue
|
url = Url(f.path),
|
||||||
end
|
cha = Cha { kind = f.attr:sub(1, 1) == "D" and 1 or 0 },
|
||||||
|
|
||||||
icon = File({
|
|
||||||
url = Url(name),
|
|
||||||
cha = Cha { kind = attr:sub(1, 1) == "D" and 1 or 0 },
|
|
||||||
}):icon()
|
}):icon()
|
||||||
|
|
||||||
if icon then
|
if icon then
|
||||||
names[#names + 1] = ui.Line { ui.Span(" " .. icon.text .. " "):style(icon.style), ui.Span(name) }
|
paths[#paths + 1] = ui.Line { ui.Span(" " .. icon.text .. " "):style(icon.style), ui.Span(f.path) }
|
||||||
else
|
else
|
||||||
names[#names + 1] = ui.Line(name)
|
paths[#paths + 1] = ui.Line(f.path)
|
||||||
end
|
end
|
||||||
|
|
||||||
size = tonumber(size)
|
if f.size > 0 then
|
||||||
if size > 0 then
|
sizes[#sizes + 1] = ui.Line(string.format(" %s ", ya.readable_size(f.size)))
|
||||||
sizes[#sizes + 1] = ui.Line(string.format(" %s ", ya.readable_size(size)))
|
|
||||||
else
|
else
|
||||||
sizes[#sizes + 1] = ui.Line("")
|
sizes[#sizes + 1] = ui.Line("")
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
::continue::
|
if self.skip > 0 and bound < self.skip + limit then
|
||||||
until i >= self.skip + limit
|
ya.manager_emit("peek", { math.max(0, bound - limit), only_if = self.file.url, upper_bound = true })
|
||||||
|
|
||||||
child:start_kill()
|
|
||||||
if self.skip > 0 and i < self.skip + limit then
|
|
||||||
ya.manager_emit("peek", { math.max(0, i - limit), only_if = self.file.url, upper_bound = true })
|
|
||||||
else
|
else
|
||||||
ya.preview_widgets(self, {
|
ya.preview_widgets(self, {
|
||||||
ui.Paragraph(self.area, names),
|
ui.Paragraph(self.area, paths),
|
||||||
ui.Paragraph(self.area, sizes):align(ui.Paragraph.RIGHT),
|
ui.Paragraph(self.area, sizes):align(ui.Paragraph.RIGHT),
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
@ -73,12 +53,93 @@ function M:seek(units)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function M:try_spawn(name)
|
function M:spawn_7z(args)
|
||||||
local child, code = Command(name):args({ "l", "-ba", tostring(self.file.url) }):stdout(Command.PIPED):spawn()
|
local last_error = nil
|
||||||
if not child then
|
local try = function(name)
|
||||||
self.last_error = code
|
local stdout = args[1] == "l" and Command.PIPED or Command.NULL
|
||||||
|
local child, code = Command(name):args(args):stdout(stdout):stderr(Command.PIPED):spawn()
|
||||||
|
if not child then
|
||||||
|
last_error = code
|
||||||
|
end
|
||||||
|
return child
|
||||||
end
|
end
|
||||||
return child
|
|
||||||
|
local child
|
||||||
|
if ya.target_os() == "macos" then
|
||||||
|
child = try("7zz") or try("7z")
|
||||||
|
else
|
||||||
|
child = try("7z") or try("7zz")
|
||||||
|
end
|
||||||
|
|
||||||
|
if not child then
|
||||||
|
return ya.err("spawn `7z` and `7zz` both commands failed, error code: " .. tostring(last_error))
|
||||||
|
end
|
||||||
|
return child, last_error
|
||||||
|
end
|
||||||
|
|
||||||
|
---comment
|
||||||
|
---@param args table
|
||||||
|
---@param skip integer
|
||||||
|
---@param limit integer
|
||||||
|
---@return table
|
||||||
|
---@return integer
|
||||||
|
---@return integer
|
||||||
|
--- 0: success
|
||||||
|
--- 1: failed to spawn
|
||||||
|
--- 2: wrong password
|
||||||
|
--- 3: partial success
|
||||||
|
function M:list_files(args, skip, limit)
|
||||||
|
local child = self:spawn_7z { "l", "-ba", "-slt", table.unpack(args) }
|
||||||
|
if not child then
|
||||||
|
return {}, 0, 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local i, files, code = 0, { { path = "", size = 0, attr = "" } }, 0
|
||||||
|
local key, value = "", ""
|
||||||
|
repeat
|
||||||
|
local next, event = child:read_line()
|
||||||
|
if event == 1 and self:is_encrypted(next) then
|
||||||
|
code = 2
|
||||||
|
break
|
||||||
|
elseif event == 1 then
|
||||||
|
code = 3
|
||||||
|
goto continue
|
||||||
|
elseif event ~= 0 then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
if next == "\n" or next == "\r\n" then
|
||||||
|
i = i + 1
|
||||||
|
if files[#files].path ~= "" then
|
||||||
|
files[#files + 1] = { path = "", size = 0, attr = "" }
|
||||||
|
end
|
||||||
|
goto continue
|
||||||
|
elseif i < skip then
|
||||||
|
goto continue
|
||||||
|
end
|
||||||
|
|
||||||
|
key, value = next:match("^(%u%l+) = (.+)[\r\n]+")
|
||||||
|
if key == "Path" then
|
||||||
|
files[#files].path = value
|
||||||
|
elseif key == "Size" then
|
||||||
|
files[#files].size = tonumber(value) or 0
|
||||||
|
elseif key == "Attributes" then
|
||||||
|
files[#files].attr = value
|
||||||
|
end
|
||||||
|
|
||||||
|
::continue::
|
||||||
|
until i >= skip + limit
|
||||||
|
child:start_kill()
|
||||||
|
|
||||||
|
if files[#files].path == "" then
|
||||||
|
files[#files] = nil
|
||||||
|
end
|
||||||
|
return files, i, code
|
||||||
|
end
|
||||||
|
|
||||||
|
function M:is_encrypted(s)
|
||||||
|
return s:find("Cannot open encrypted archive. Wrong password?", 1, true)
|
||||||
|
or s:find("Data Error in encrypted file. Wrong password?", 1, true)
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|
|
||||||
107
yazi-plugin/preset/plugins/extract.lua
Normal file
107
yazi-plugin/preset/plugins/extract.lua
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
function M:setup()
|
||||||
|
ps.sub_remote("extract", function(args)
|
||||||
|
local noisy = #args == 1 and " --noisy" or ""
|
||||||
|
for _, arg in ipairs(args) do
|
||||||
|
ya.manager_emit("plugin", { self._id, args = ya.quote(arg, true) .. noisy })
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.entry(_, args)
|
||||||
|
if not args[1] then
|
||||||
|
error("No URL provided")
|
||||||
|
end
|
||||||
|
|
||||||
|
local url, pwd = Url(args[1]), ""
|
||||||
|
while true do
|
||||||
|
if not M.try_with(url, pwd) then
|
||||||
|
break
|
||||||
|
elseif args[2] ~= "--noisy" then
|
||||||
|
error(
|
||||||
|
"Failed to extract in batch: this archive is password-protected, please extract it individually and enter the password."
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
local value, event = ya.input {
|
||||||
|
title = string.format('Password for "%s":', url:name()),
|
||||||
|
position = { "center", w = 50 },
|
||||||
|
}
|
||||||
|
if event == 1 then
|
||||||
|
pwd = value
|
||||||
|
else
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.try_with(url, pwd)
|
||||||
|
local actual, assumed = M.output_url(url)
|
||||||
|
if not actual then
|
||||||
|
error("Cannot determine the output directory " .. url)
|
||||||
|
end
|
||||||
|
|
||||||
|
local archive = require("archive")
|
||||||
|
local child, code = archive:spawn_7z { "x", "-aou", "-p" .. pwd, "-o" .. tostring(actual), tostring(url) }
|
||||||
|
if not child then
|
||||||
|
error("Spawn `7z` and `7zz` both commands failed, error code: " .. code)
|
||||||
|
end
|
||||||
|
|
||||||
|
local output, err = child:wait_with_output()
|
||||||
|
if not output then
|
||||||
|
error("7zip failed to output, error code " .. tostring(err))
|
||||||
|
elseif output.status.code == 2 and archive:is_encrypted(output.stderr) then
|
||||||
|
return true -- Needs retry
|
||||||
|
elseif output.status.code ~= 0 then
|
||||||
|
error("7zip exited with error code " .. tostring(output.status.code))
|
||||||
|
end
|
||||||
|
|
||||||
|
if assumed then -- Needs a move
|
||||||
|
local unique = fs.unique_name(assumed)
|
||||||
|
if unique then
|
||||||
|
os.rename(tostring(actual:join(assumed:name())), tostring(unique))
|
||||||
|
os.remove(tostring(actual))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.output_url(url)
|
||||||
|
local parent = url:parent()
|
||||||
|
if not parent then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local files, _, code = require("archive"):list_files({ "-p", "-x!*/*", tostring(url) }, 0, 2)
|
||||||
|
if #files ~= 1 or code ~= 0 then
|
||||||
|
local name = M.trim_ext(url:name())
|
||||||
|
return fs.unique_name(parent:join(name))
|
||||||
|
end
|
||||||
|
|
||||||
|
if files[1].attr:sub(1, 1) == "D" then
|
||||||
|
local assumed = parent:join(files[1].path)
|
||||||
|
if fs.cha(assumed) then
|
||||||
|
local tmp = string.format(".extract_%s", ya.time())
|
||||||
|
return fs.unique_name(parent:join(tmp)), assumed
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return parent
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.trim_ext(name)
|
||||||
|
-- stylua: ignore
|
||||||
|
local exts = { ["7z"] = true, apk = true, bz2 = true, bzip2 = true, exe = true, gz = true, gzip = true, iso = true, jar = true, rar = true, tar = true, tgz = true, xz = true, zip = true, zst = true }
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local s = name:gsub("%.([a-zA-Z0-9]+)$", function(s) return (exts[s] or exts[s:lower()]) and "" end)
|
||||||
|
if s == name or s == "" then
|
||||||
|
break
|
||||||
|
else
|
||||||
|
name = s
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return name
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
|
|
@ -22,7 +22,7 @@ local function entry()
|
||||||
|
|
||||||
local target = output.stdout:gsub("\n$", "")
|
local target = output.stdout:gsub("\n$", "")
|
||||||
if target ~= "" then
|
if target ~= "" then
|
||||||
ya.manager_emit(target:match("[/\\]$") and "cd" or "reveal", { target })
|
ya.manager_emit(target:find("[/\\]$") and "cd" or "reveal", { target })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ local M = {}
|
||||||
|
|
||||||
local function match_mimetype(s)
|
local function match_mimetype(s)
|
||||||
local type, sub = s:match("([-a-z]+/)([+-.a-zA-Z0-9]+)%s*$")
|
local type, sub = s:match("([-a-z]+/)([+-.a-zA-Z0-9]+)%s*$")
|
||||||
if type and sub and string.find(SUPPORTED_TYPES, type, 1, true) then
|
if type and sub and SUPPORTED_TYPES:find(type, 1, true) then
|
||||||
return type .. sub
|
return type .. sub
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -44,7 +44,7 @@ function M:fetch()
|
||||||
end
|
end
|
||||||
|
|
||||||
valid = match_mimetype(line)
|
valid = match_mimetype(line)
|
||||||
if valid and string.find(line, valid, 1, true) ~= 1 then
|
if valid and line:find(valid, 1, true) ~= 1 then
|
||||||
goto continue
|
goto continue
|
||||||
elseif valid then
|
elseif valid then
|
||||||
j, updates[urls[i]] = j + 1, valid
|
j, updates[urls[i]] = j + 1, valid
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,7 @@ end)
|
||||||
|
|
||||||
local set_state = ya.sync(function(st, empty) st.empty = empty end)
|
local set_state = ya.sync(function(st, empty) st.empty = empty end)
|
||||||
|
|
||||||
local function fail(s, ...)
|
local function fail(s, ...) ya.notify { title = "Zoxide", content = s:format(...), timeout = 5, level = "error" } end
|
||||||
ya.notify { title = "Zoxide", content = string.format(s, ...), timeout = 5, level = "error" }
|
|
||||||
end
|
|
||||||
|
|
||||||
local function head(cwd)
|
local function head(cwd)
|
||||||
local child = Command("zoxide"):args({ "query", "-l" }):stdout(Command.PIPED):spawn()
|
local child = Command("zoxide"):args({ "query", "-l" }):stdout(Command.PIPED):spawn()
|
||||||
|
|
|
||||||
|
|
@ -2,3 +2,4 @@ os.setlocale("")
|
||||||
package.path = BOOT.plugin_dir .. "/?.yazi/init.lua;" .. package.path
|
package.path = BOOT.plugin_dir .. "/?.yazi/init.lua;" .. package.path
|
||||||
|
|
||||||
require("dds"):setup()
|
require("dds"):setup()
|
||||||
|
require("extract"):setup()
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ function ya.list_merge(a, b)
|
||||||
return a
|
return a
|
||||||
end
|
end
|
||||||
|
|
||||||
function ya.basename(str) return string.gsub(str, "(.*[/\\])(.*)", "%2") end
|
function ya.basename(s) return s:gsub("(.*[/\\])(.*)", "%2") end
|
||||||
|
|
||||||
function ya.readable_size(size)
|
function ya.readable_size(size)
|
||||||
local units = { "B", "K", "M", "G", "T", "P", "E", "Z", "Y", "R", "Q" }
|
local units = { "B", "K", "M", "G", "T", "P", "E", "Z", "Y", "R", "Q" }
|
||||||
|
|
@ -37,8 +37,8 @@ function ya.readable_path(path)
|
||||||
local home = os.getenv("HOME") or os.getenv("USERPROFILE")
|
local home = os.getenv("HOME") or os.getenv("USERPROFILE")
|
||||||
if not home then
|
if not home then
|
||||||
return path
|
return path
|
||||||
elseif string.sub(path, 1, #home) == home then
|
elseif path:sub(1, #home) == home then
|
||||||
return "~" .. string.sub(path, #home + 1)
|
return "~" .. path:sub(#home + 1)
|
||||||
else
|
else
|
||||||
return path
|
return path
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ pub async fn entry(name: String, args: Vec<Data>) -> mlua::Result<()> {
|
||||||
};
|
};
|
||||||
|
|
||||||
Handle::current()
|
Handle::current()
|
||||||
.block_on(plugin.call_async_method("entry", Sendable::vec_to_table(&lua, args)))
|
.block_on(plugin.call_async_method("entry", Sendable::list_to_table(&lua, args)))
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.into_lua_err()?
|
.into_lua_err()?
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ pub fn slim_lua(name: &str) -> mlua::Result<Lua> {
|
||||||
crate::file::pour(&lua)?;
|
crate::file::pour(&lua)?;
|
||||||
crate::url::pour(&lua)?;
|
crate::url::pour(&lua)?;
|
||||||
|
|
||||||
|
crate::loader::install_isolate(&lua)?;
|
||||||
crate::fs::install(&lua)?;
|
crate::fs::install(&lua)?;
|
||||||
crate::process::install(&lua)?;
|
crate::process::install(&lua)?;
|
||||||
crate::utils::install_isolate(&lua)?;
|
crate::utils::install_isolate(&lua)?;
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,12 @@
|
||||||
use std::{borrow::Cow, collections::HashMap, ops::Deref};
|
use std::{borrow::Cow, collections::HashMap, ops::Deref};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use mlua::{ExternalError, Table};
|
use mlua::{ExternalError, Lua, Table};
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
use yazi_boot::BOOT;
|
use yazi_boot::BOOT;
|
||||||
use yazi_shared::RoCell;
|
use yazi_shared::RoCell;
|
||||||
|
|
||||||
use crate::LUA;
|
|
||||||
|
|
||||||
pub static LOADER: RoCell<Loader> = RoCell::new();
|
pub static LOADER: RoCell<Loader> = RoCell::new();
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
@ -23,11 +21,10 @@ impl Loader {
|
||||||
}
|
}
|
||||||
|
|
||||||
let preset = match name {
|
let preset = match name {
|
||||||
"dds" => &include_bytes!("../../preset/plugins/dds.lua")[..],
|
"archive" => &include_bytes!("../../preset/plugins/archive.lua")[..],
|
||||||
"noop" => include_bytes!("../../preset/plugins/noop.lua"),
|
|
||||||
"session" => include_bytes!("../../preset/plugins/session.lua"),
|
|
||||||
"archive" => include_bytes!("../../preset/plugins/archive.lua"),
|
|
||||||
"code" => include_bytes!("../../preset/plugins/code.lua"),
|
"code" => include_bytes!("../../preset/plugins/code.lua"),
|
||||||
|
"dds" => include_bytes!("../../preset/plugins/dds.lua"),
|
||||||
|
"extract" => include_bytes!("../../preset/plugins/extract.lua"),
|
||||||
"file" => include_bytes!("../../preset/plugins/file.lua"),
|
"file" => include_bytes!("../../preset/plugins/file.lua"),
|
||||||
"folder" => include_bytes!("../../preset/plugins/folder.lua"),
|
"folder" => include_bytes!("../../preset/plugins/folder.lua"),
|
||||||
"font" => include_bytes!("../../preset/plugins/font.lua"),
|
"font" => include_bytes!("../../preset/plugins/font.lua"),
|
||||||
|
|
@ -36,7 +33,9 @@ impl Loader {
|
||||||
"json" => include_bytes!("../../preset/plugins/json.lua"),
|
"json" => include_bytes!("../../preset/plugins/json.lua"),
|
||||||
"magick" => include_bytes!("../../preset/plugins/magick.lua"),
|
"magick" => include_bytes!("../../preset/plugins/magick.lua"),
|
||||||
"mime" => include_bytes!("../../preset/plugins/mime.lua"),
|
"mime" => include_bytes!("../../preset/plugins/mime.lua"),
|
||||||
|
"noop" => include_bytes!("../../preset/plugins/noop.lua"),
|
||||||
"pdf" => include_bytes!("../../preset/plugins/pdf.lua"),
|
"pdf" => include_bytes!("../../preset/plugins/pdf.lua"),
|
||||||
|
"session" => include_bytes!("../../preset/plugins/session.lua"),
|
||||||
"video" => include_bytes!("../../preset/plugins/video.lua"),
|
"video" => include_bytes!("../../preset/plugins/video.lua"),
|
||||||
"zoxide" => include_bytes!("../../preset/plugins/zoxide.lua"),
|
"zoxide" => include_bytes!("../../preset/plugins/zoxide.lua"),
|
||||||
_ => b"",
|
_ => b"",
|
||||||
|
|
@ -52,19 +51,18 @@ impl Loader {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load(&self, id: &str) -> mlua::Result<Table> {
|
pub fn load<'a>(&self, lua: &'a Lua, id: &str) -> mlua::Result<Table<'a>> {
|
||||||
let globals = LUA.globals();
|
let loaded: Table = lua.globals().raw_get::<_, Table>("package")?.raw_get("loaded")?;
|
||||||
let loaded: Table = globals.raw_get::<_, Table>("package")?.raw_get("loaded")?;
|
|
||||||
if let Ok(t) = loaded.raw_get::<_, Table>(id) {
|
if let Ok(t) = loaded.raw_get::<_, Table>(id) {
|
||||||
return Ok(t);
|
return Ok(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
let t: Table = match self.read().get(id) {
|
let t: Table = match self.read().get(id) {
|
||||||
Some(b) => LUA.load(b.as_ref()).set_name(id).call(())?,
|
Some(b) => lua.load(b.as_ref()).set_name(id).call(())?,
|
||||||
None => Err(format!("plugin `{id}` not found").into_lua_err())?,
|
None => Err(format!("plugin `{id}` not found").into_lua_err())?,
|
||||||
};
|
};
|
||||||
|
|
||||||
t.raw_set("_id", LUA.create_string(id)?)?;
|
t.raw_set("_id", lua.create_string(id)?)?;
|
||||||
loaded.raw_set(id, t.clone())?;
|
loaded.raw_set(id, t.clone())?;
|
||||||
Ok(t)
|
Ok(t)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,6 @@ use require::*;
|
||||||
|
|
||||||
pub(super) fn init() { LOADER.with(<_>::default); }
|
pub(super) fn init() { LOADER.with(<_>::default); }
|
||||||
|
|
||||||
pub(super) fn install(lua: &'static mlua::Lua) -> mlua::Result<()> {
|
pub(super) fn install(lua: &'static mlua::Lua) -> mlua::Result<()> { RequireSync::install(lua) }
|
||||||
Require::install(lua)?;
|
|
||||||
|
|
||||||
Ok(())
|
pub(super) fn install_isolate(lua: &mlua::Lua) -> mlua::Result<()> { Require::install(lua) }
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,46 +1,39 @@
|
||||||
use mlua::{ExternalResult, IntoLua, Lua, MetaMethod, Table, TableExt, UserData, Value, Variadic};
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use mlua::{ExternalResult, Function, IntoLua, Lua, MultiValue, Table, Value};
|
||||||
|
|
||||||
use super::LOADER;
|
use super::LOADER;
|
||||||
use crate::RtRef;
|
use crate::RtRef;
|
||||||
|
|
||||||
pub(super) struct Require;
|
pub(crate) struct Require;
|
||||||
|
|
||||||
impl Require {
|
impl Require {
|
||||||
pub(super) fn install(lua: &'static Lua) -> mlua::Result<()> {
|
pub(crate) fn install(lua: &Lua) -> mlua::Result<()> {
|
||||||
let globals = lua.globals();
|
lua.globals().raw_set(
|
||||||
|
|
||||||
globals.raw_set(
|
|
||||||
"require",
|
"require",
|
||||||
lua.create_function(|lua, name: mlua::String| {
|
lua.create_async_function(|lua, id: mlua::String| async move {
|
||||||
let s = name.to_str()?;
|
let s = id.to_str()?;
|
||||||
futures::executor::block_on(LOADER.ensure(s)).into_lua_err()?;
|
LOADER.ensure(s).await.into_lua_err()?;
|
||||||
|
|
||||||
lua.named_registry_value::<RtRef>("rt")?.swap(s);
|
lua.named_registry_value::<RtRef>("rt")?.push(s);
|
||||||
let mod_ = LOADER.load(s)?;
|
let mod_ = LOADER.load(lua, s);
|
||||||
lua.named_registry_value::<RtRef>("rt")?.reset();
|
lua.named_registry_value::<RtRef>("rt")?.pop();
|
||||||
|
|
||||||
Self::create_mt(lua, name, mod_)
|
Self::create_mt(lua, s, mod_?)
|
||||||
})?,
|
})?,
|
||||||
)?;
|
)
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_mt(
|
fn create_mt<'a>(lua: &'a Lua, id: &str, mod_: Table<'a>) -> mlua::Result<Table<'a>> {
|
||||||
lua: &'static Lua,
|
let ts = lua.create_table_from([("_mod", mod_.into_lua(lua)?)])?;
|
||||||
name: mlua::String<'static>,
|
|
||||||
mod_: Table<'static>,
|
|
||||||
) -> mlua::Result<Table<'static>> {
|
|
||||||
let ts =
|
|
||||||
lua.create_table_from([("name", name.into_lua(lua)?), ("mod", mod_.into_lua(lua)?)])?;
|
|
||||||
|
|
||||||
|
let id: Arc<str> = Arc::from(id);
|
||||||
let mt = lua.create_table_from([(
|
let mt = lua.create_table_from([(
|
||||||
"__index",
|
"__index",
|
||||||
lua.create_function(|_, (_, key): (Table, mlua::String)| {
|
lua.create_function(move |lua, (ts, key): (Table, mlua::String)| {
|
||||||
if key.to_str()? == "setup" {
|
match ts.raw_get::<_, Table>("_mod")?.raw_get::<_, Value>(&key)? {
|
||||||
Ok(RequireSetup)
|
Value::Function(_) => Self::create_wrapper(lua, id.clone(), key.to_str()?)?.into_lua(lua),
|
||||||
} else {
|
v => Ok(v),
|
||||||
Err("Only `require():setup()` is supported").into_lua_err()
|
|
||||||
}
|
}
|
||||||
})?,
|
})?,
|
||||||
)])?;
|
)])?;
|
||||||
|
|
@ -48,18 +41,77 @@ impl Require {
|
||||||
ts.set_metatable(Some(mt));
|
ts.set_metatable(Some(mt));
|
||||||
Ok(ts)
|
Ok(ts)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) struct RequireSetup;
|
fn create_wrapper<'a>(lua: &'a Lua, id: Arc<str>, f: &str) -> mlua::Result<Function<'a>> {
|
||||||
|
let f: Arc<str> = Arc::from(f);
|
||||||
|
|
||||||
impl UserData for RequireSetup {
|
lua.create_async_function(move |lua, (ts, args): (Table, MultiValue)| {
|
||||||
fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) {
|
let (id, f) = (id.clone(), f.clone());
|
||||||
methods.add_meta_method(MetaMethod::Call, |lua, _, (ts, args): (Table, Variadic<Value>)| {
|
async move {
|
||||||
let (name, mod_): (mlua::String, Table) = (ts.raw_get("name")?, ts.raw_get("mod")?);
|
let f: Function = ts.raw_get::<_, Table>("_mod")?.raw_get(&*f)?;
|
||||||
lua.named_registry_value::<RtRef>("rt")?.swap(name.to_str()?);
|
let args = MultiValue::from_iter([ts.into_lua(lua)?].into_iter().chain(args));
|
||||||
let result = mod_.call_method::<_, Variadic<Value>>("setup", args);
|
|
||||||
lua.named_registry_value::<RtRef>("rt")?.reset();
|
lua.named_registry_value::<RtRef>("rt")?.push(&id);
|
||||||
result
|
let result = f.call_async::<_, MultiValue>(args).await;
|
||||||
});
|
lua.named_registry_value::<RtRef>("rt")?.pop();
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Sync
|
||||||
|
pub(crate) struct RequireSync;
|
||||||
|
|
||||||
|
impl RequireSync {
|
||||||
|
pub(crate) fn install(lua: &'static Lua) -> mlua::Result<()> {
|
||||||
|
lua.globals().raw_set(
|
||||||
|
"require",
|
||||||
|
lua.create_function(|lua, id: mlua::String| {
|
||||||
|
let s = id.to_str()?;
|
||||||
|
futures::executor::block_on(LOADER.ensure(s)).into_lua_err()?;
|
||||||
|
|
||||||
|
lua.named_registry_value::<RtRef>("rt")?.push(s);
|
||||||
|
let mod_ = LOADER.load(lua, s);
|
||||||
|
lua.named_registry_value::<RtRef>("rt")?.pop();
|
||||||
|
|
||||||
|
Self::create_mt(lua, id, mod_?)
|
||||||
|
})?,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_mt(
|
||||||
|
lua: &'static Lua,
|
||||||
|
id: mlua::String<'static>,
|
||||||
|
mod_: Table<'static>,
|
||||||
|
) -> mlua::Result<Table<'static>> {
|
||||||
|
let ts = lua.create_table_from([("_id", id)])?;
|
||||||
|
|
||||||
|
let mt = lua.create_table_from([(
|
||||||
|
"__index",
|
||||||
|
lua.create_function(move |lua, (_, key): (Table, mlua::String)| {
|
||||||
|
match mod_.raw_get::<_, Value>(key)? {
|
||||||
|
Value::Function(f) => Self::create_wrapper(lua, f)?.into_lua(lua),
|
||||||
|
v => Ok(v),
|
||||||
|
}
|
||||||
|
})?,
|
||||||
|
)])?;
|
||||||
|
|
||||||
|
ts.set_metatable(Some(mt));
|
||||||
|
Ok(ts)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_wrapper(lua: &'static Lua, f: Function<'static>) -> mlua::Result<Function<'static>> {
|
||||||
|
lua.create_function(move |lua, (ts, args): (Table, MultiValue)| {
|
||||||
|
let id: mlua::String = ts.raw_get("_id")?;
|
||||||
|
let args = MultiValue::from_iter([ts.into_lua(lua)?].into_iter().chain(args));
|
||||||
|
|
||||||
|
lua.named_registry_value::<RtRef>("rt")?.push(id.to_str()?);
|
||||||
|
let result = f.call::<_, MultiValue>(args);
|
||||||
|
lua.named_registry_value::<RtRef>("rt")?.pop();
|
||||||
|
|
||||||
|
result
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,8 @@ impl Pubsub {
|
||||||
ps.raw_set(
|
ps.raw_set(
|
||||||
"sub",
|
"sub",
|
||||||
lua.create_function(|lua, (kind, f): (mlua::String, Function)| {
|
lua.create_function(|lua, (kind, f): (mlua::String, Function)| {
|
||||||
let Some(ref cur) = lua.named_registry_value::<RtRef>("rt")?.current else {
|
let rt = lua.named_registry_value::<RtRef>("rt")?;
|
||||||
|
let Some(cur) = rt.current() else {
|
||||||
return Err("`sub()` must be called in a sync plugin").into_lua_err();
|
return Err("`sub()` must be called in a sync plugin").into_lua_err();
|
||||||
};
|
};
|
||||||
if !yazi_dds::Pubsub::sub(cur, kind.to_str()?, f) {
|
if !yazi_dds::Pubsub::sub(cur, kind.to_str()?, f) {
|
||||||
|
|
@ -41,7 +42,8 @@ impl Pubsub {
|
||||||
ps.raw_set(
|
ps.raw_set(
|
||||||
"sub_remote",
|
"sub_remote",
|
||||||
lua.create_function(|_, (kind, f): (mlua::String, Function)| {
|
lua.create_function(|_, (kind, f): (mlua::String, Function)| {
|
||||||
let Some(ref cur) = lua.named_registry_value::<RtRef>("rt")?.current else {
|
let rt = lua.named_registry_value::<RtRef>("rt")?;
|
||||||
|
let Some(cur) = rt.current() else {
|
||||||
return Err("`sub_remote()` must be called in a sync plugin").into_lua_err();
|
return Err("`sub_remote()` must be called in a sync plugin").into_lua_err();
|
||||||
};
|
};
|
||||||
if !yazi_dds::Pubsub::sub_remote(cur, kind.to_str()?, f) {
|
if !yazi_dds::Pubsub::sub_remote(cur, kind.to_str()?, f) {
|
||||||
|
|
@ -54,7 +56,7 @@ impl Pubsub {
|
||||||
ps.raw_set(
|
ps.raw_set(
|
||||||
"unsub",
|
"unsub",
|
||||||
lua.create_function(|_, kind: mlua::String| {
|
lua.create_function(|_, kind: mlua::String| {
|
||||||
if let Some(ref cur) = lua.named_registry_value::<RtRef>("rt")?.current {
|
if let Some(cur) = lua.named_registry_value::<RtRef>("rt")?.current() {
|
||||||
Ok(yazi_dds::Pubsub::unsub(cur, kind.to_str()?))
|
Ok(yazi_dds::Pubsub::unsub(cur, kind.to_str()?))
|
||||||
} else {
|
} else {
|
||||||
Err("`unsub()` must be called in a sync plugin").into_lua_err()
|
Err("`unsub()` must be called in a sync plugin").into_lua_err()
|
||||||
|
|
@ -65,7 +67,7 @@ impl Pubsub {
|
||||||
ps.raw_set(
|
ps.raw_set(
|
||||||
"unsub_remote",
|
"unsub_remote",
|
||||||
lua.create_function(|_, kind: mlua::String| {
|
lua.create_function(|_, kind: mlua::String| {
|
||||||
if let Some(ref cur) = lua.named_registry_value::<RtRef>("rt")?.current {
|
if let Some(cur) = lua.named_registry_value::<RtRef>("rt")?.current() {
|
||||||
Ok(yazi_dds::Pubsub::unsub_remote(cur, kind.to_str()?))
|
Ok(yazi_dds::Pubsub::unsub_remote(cur, kind.to_str()?))
|
||||||
} else {
|
} else {
|
||||||
Err("`unsub_remote()` must be called in a sync plugin").into_lua_err()
|
Err("`unsub_remote()` must be called in a sync plugin").into_lua_err()
|
||||||
|
|
|
||||||
|
|
@ -1,49 +1,53 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::{HashMap, VecDeque};
|
||||||
|
|
||||||
use mlua::{Function, UserData};
|
use mlua::{Function, UserData};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Runtime {
|
pub struct Runtime {
|
||||||
pub current: Option<String>,
|
frames: VecDeque<RuntimeFrame>,
|
||||||
pub calls: usize,
|
blocks: HashMap<String, Vec<Function<'static>>>,
|
||||||
pub blocks: HashMap<String, Vec<Function<'static>>>,
|
}
|
||||||
|
|
||||||
|
struct RuntimeFrame {
|
||||||
|
id: String,
|
||||||
|
calls: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type RtRef<'lua> = mlua::UserDataRefMut<'lua, Runtime>;
|
pub type RtRef<'lua> = mlua::UserDataRefMut<'lua, Runtime>;
|
||||||
|
|
||||||
impl Runtime {
|
impl Runtime {
|
||||||
pub fn new(current: &str) -> Self {
|
pub fn new(id: &str) -> Self {
|
||||||
Self { current: Some(current.to_owned()), ..Default::default() }
|
Self {
|
||||||
|
frames: VecDeque::from([RuntimeFrame { id: id.to_owned(), calls: 0 }]),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn swap(&mut self, name: &str) {
|
pub fn push(&mut self, id: &str) {
|
||||||
self.current = Some(name.to_owned());
|
self.frames.push_back(RuntimeFrame { id: id.to_owned(), calls: 0 });
|
||||||
self.calls = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset(&mut self) {
|
pub fn pop(&mut self) { self.frames.pop_back(); }
|
||||||
self.current = None;
|
|
||||||
self.calls = 0;
|
pub fn current(&self) -> Option<&str> { self.frames.back().map(|f| f.id.as_str()) }
|
||||||
|
|
||||||
|
pub fn next_block(&mut self) -> Option<usize> {
|
||||||
|
self.frames.back_mut().map(|f| {
|
||||||
|
f.calls += 1;
|
||||||
|
f.calls - 1
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next_block(&mut self) -> usize {
|
pub fn get_block(&self, id: &str, calls: usize) -> Option<Function<'static>> {
|
||||||
self.calls += 1;
|
self.blocks.get(id).and_then(|v| v.get(calls)).cloned()
|
||||||
self.calls - 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_block(&self, name: &str, calls: usize) -> Option<Function<'static>> {
|
pub fn put_block(&mut self, f: Function<'static>) -> bool {
|
||||||
self.blocks.get(name).and_then(|v| v.get(calls)).cloned()
|
let Some(cur) = self.frames.back() else { return false };
|
||||||
}
|
if let Some(v) = self.blocks.get_mut(&cur.id) {
|
||||||
|
v.push(f);
|
||||||
pub fn push_block(&mut self, f: Function<'static>) -> bool {
|
|
||||||
let Some(ref cur) = self.current else {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(vec) = self.blocks.get_mut(cur) {
|
|
||||||
vec.push(f);
|
|
||||||
} else {
|
} else {
|
||||||
self.blocks.insert(cur.clone(), vec![f]);
|
self.blocks.insert(cur.id.to_owned(), vec![f]);
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use mlua::{AnyUserData, Lua, MetaMethod, UserDataFields, UserDataMethods, UserDataRef};
|
use mlua::{AnyUserData, ExternalError, Lua, MetaMethod, UserDataFields, UserDataMethods, UserDataRef, Value};
|
||||||
|
|
||||||
use crate::bindings::Cast;
|
use crate::bindings::Cast;
|
||||||
|
|
||||||
|
|
@ -20,7 +20,16 @@ impl Url {
|
||||||
reg.add_method("stem", |lua, me, ()| {
|
reg.add_method("stem", |lua, me, ()| {
|
||||||
me.file_stem().map(|s| lua.create_string(s.as_encoded_bytes())).transpose()
|
me.file_stem().map(|s| lua.create_string(s.as_encoded_bytes())).transpose()
|
||||||
});
|
});
|
||||||
reg.add_method("join", |lua, me, other: UrlRef| Self::cast(lua, me.join(&*other)));
|
reg.add_method("join", |lua, me, other: Value| {
|
||||||
|
Ok(match other {
|
||||||
|
Value::String(s) => Self::cast(lua, me.join(s.to_str()?)),
|
||||||
|
Value::UserData(ud) => {
|
||||||
|
let url = ud.borrow::<yazi_shared::fs::Url>()?;
|
||||||
|
Self::cast(lua, me.join(&*url))
|
||||||
|
}
|
||||||
|
_ => Err("must be a string or a Url".into_lua_err())?,
|
||||||
|
})
|
||||||
|
});
|
||||||
reg.add_method("parent", |lua, me, ()| {
|
reg.add_method("parent", |lua, me, ()| {
|
||||||
me.parent_url().map(|u| Self::cast(lua, u)).transpose()
|
me.parent_url().map(|u| Self::cast(lua, u)).transpose()
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use mlua::{Lua, Table, Value, Variadic};
|
use mlua::{Lua, MultiValue, Table};
|
||||||
use tracing::{debug, error};
|
use tracing::{debug, error};
|
||||||
|
|
||||||
use super::Utils;
|
use super::Utils;
|
||||||
|
|
@ -7,7 +7,7 @@ impl Utils {
|
||||||
pub(super) fn log(lua: &Lua, ya: &Table) -> mlua::Result<()> {
|
pub(super) fn log(lua: &Lua, ya: &Table) -> mlua::Result<()> {
|
||||||
ya.raw_set(
|
ya.raw_set(
|
||||||
"dbg",
|
"dbg",
|
||||||
lua.create_function(|_, values: Variadic<Value>| {
|
lua.create_function(|_, values: MultiValue| {
|
||||||
let s = values.into_iter().map(|v| format!("{v:#?}")).collect::<Vec<_>>().join(" ");
|
let s = values.into_iter().map(|v| format!("{v:#?}")).collect::<Vec<_>>().join(" ");
|
||||||
Ok(debug!("{s}"))
|
Ok(debug!("{s}"))
|
||||||
})?,
|
})?,
|
||||||
|
|
@ -15,7 +15,7 @@ impl Utils {
|
||||||
|
|
||||||
ya.raw_set(
|
ya.raw_set(
|
||||||
"err",
|
"err",
|
||||||
lua.create_function(|_, values: Variadic<Value>| {
|
lua.create_function(|_, values: MultiValue| {
|
||||||
let s = values.into_iter().map(|v| format!("{v:#?}")).collect::<Vec<_>>().join(" ");
|
let s = values.into_iter().map(|v| format!("{v:#?}")).collect::<Vec<_>>().join(" ");
|
||||||
Ok(error!("{s}"))
|
Ok(error!("{s}"))
|
||||||
})?,
|
})?,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use mlua::{ExternalError, ExternalResult, Function, IntoLua, Lua, Table, Value, Variadic};
|
use mlua::{ExternalError, ExternalResult, Function, IntoLua, Lua, MultiValue, Table, Value};
|
||||||
use tokio::sync::oneshot;
|
use tokio::sync::oneshot;
|
||||||
use yazi_dds::Sendable;
|
use yazi_dds::Sendable;
|
||||||
use yazi_shared::{emit, event::{Cmd, Data}, Layer};
|
use yazi_shared::{emit, event::{Cmd, Data}, Layer};
|
||||||
|
|
@ -12,14 +12,15 @@ impl Utils {
|
||||||
"sync",
|
"sync",
|
||||||
lua.create_function(|lua, f: Function<'static>| {
|
lua.create_function(|lua, f: Function<'static>| {
|
||||||
let mut rt = lua.named_registry_value::<RtRef>("rt")?;
|
let mut rt = lua.named_registry_value::<RtRef>("rt")?;
|
||||||
if !rt.push_block(f.clone()) {
|
if !rt.put_block(f.clone()) {
|
||||||
return Err("`ya.sync()` must be called in a plugin").into_lua_err();
|
return Err("`ya.sync()` must be called in a plugin").into_lua_err();
|
||||||
}
|
}
|
||||||
|
|
||||||
let cur = rt.current.clone().unwrap();
|
let cur = rt.current().unwrap().to_owned();
|
||||||
lua.create_function(move |lua, mut args: Variadic<Value>| {
|
lua.create_function(move |lua, args: MultiValue| {
|
||||||
args.insert(0, LOADER.load(&cur)?.into_lua(lua)?);
|
f.call::<_, MultiValue>(MultiValue::from_iter(
|
||||||
f.call::<_, Variadic<Value>>(args)
|
[LOADER.load(lua, &cur)?.into_lua(lua)?].into_iter().chain(args),
|
||||||
|
))
|
||||||
})
|
})
|
||||||
})?,
|
})?,
|
||||||
)?;
|
)?;
|
||||||
|
|
@ -31,13 +32,16 @@ impl Utils {
|
||||||
ya.raw_set(
|
ya.raw_set(
|
||||||
"sync",
|
"sync",
|
||||||
lua.create_function(|lua, ()| {
|
lua.create_function(|lua, ()| {
|
||||||
let block = lua.named_registry_value::<RtRef>("rt")?.next_block();
|
let Some(block) = lua.named_registry_value::<RtRef>("rt")?.next_block() else {
|
||||||
lua.create_async_function(move |lua, args: Variadic<Value>| async move {
|
return Err("`ya.sync()` must be called in a plugin").into_lua_err();
|
||||||
let Some(cur) = lua.named_registry_value::<RtRef>("rt")?.current.clone() else {
|
};
|
||||||
return Err("`ya.sync()` must be called in a plugin").into_lua_err();
|
|
||||||
};
|
|
||||||
|
|
||||||
Sendable::vec_to_variadic(lua, Self::retrieve(cur, block, args).await?)
|
lua.create_async_function(move |lua, args: MultiValue| async move {
|
||||||
|
if let Some(cur) = lua.named_registry_value::<RtRef>("rt")?.current() {
|
||||||
|
Sendable::list_to_values(lua, Self::retrieve(cur, block, args).await?)
|
||||||
|
} else {
|
||||||
|
Err("block spawned by `ya.sync()` must be called in a plugin").into_lua_err()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})?,
|
})?,
|
||||||
)?;
|
)?;
|
||||||
|
|
@ -45,34 +49,29 @@ impl Utils {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn retrieve(
|
async fn retrieve(name: &str, calls: usize, args: MultiValue<'_>) -> mlua::Result<Vec<Data>> {
|
||||||
name: String,
|
let args = Sendable::values_to_vec(args)?;
|
||||||
calls: usize,
|
|
||||||
args: Variadic<Value<'_>>,
|
|
||||||
) -> mlua::Result<Vec<Data>> {
|
|
||||||
let args = Sendable::variadic_to_vec(args)?;
|
|
||||||
let (tx, rx) = oneshot::channel::<Vec<Data>>();
|
let (tx, rx) = oneshot::channel::<Vec<Data>>();
|
||||||
|
|
||||||
let callback: OptCallback = {
|
let callback: OptCallback = {
|
||||||
let name = name.clone();
|
let name = name.to_owned();
|
||||||
Box::new(move |lua, plugin| {
|
Box::new(move |lua, plugin| {
|
||||||
let Some(block) = lua.named_registry_value::<RtRef>("rt")?.get_block(&name, calls) else {
|
let Some(block) = lua.named_registry_value::<RtRef>("rt")?.get_block(&name, calls) else {
|
||||||
return Err("sync block not found".into_lua_err());
|
return Err("sync block not found".into_lua_err());
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut self_args = Vec::with_capacity(args.len() + 1);
|
let args: Vec<_> = [Ok(Value::Table(plugin))]
|
||||||
self_args.push(Value::Table(plugin));
|
.into_iter()
|
||||||
for arg in args {
|
.chain(args.into_iter().map(|d| Sendable::data_to_value(lua, d)))
|
||||||
self_args.push(Sendable::data_to_value(lua, arg)?);
|
.collect::<mlua::Result<_>>()?;
|
||||||
}
|
|
||||||
|
|
||||||
let values = Sendable::variadic_to_vec(block.call(Variadic::from_iter(self_args))?)?;
|
let values = Sendable::values_to_vec(block.call(MultiValue::from_vec(args))?)?;
|
||||||
tx.send(values).map_err(|_| "send failed".into_lua_err())
|
tx.send(values).map_err(|_| "send failed".into_lua_err())
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
emit!(Call(
|
emit!(Call(
|
||||||
Cmd::args("plugin", vec![name.clone()])
|
Cmd::args("plugin", vec![name.to_owned()])
|
||||||
.with_bool("sync", true)
|
.with_bool("sync", true)
|
||||||
.with_any("callback", callback),
|
.with_any("callback", callback),
|
||||||
Layer::App
|
Layer::App
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,9 @@ impl Utils {
|
||||||
"quote",
|
"quote",
|
||||||
lua.create_function(|_, (s, unix): (mlua::String, Option<bool>)| {
|
lua.create_function(|_, (s, unix): (mlua::String, Option<bool>)| {
|
||||||
let s = match unix {
|
let s = match unix {
|
||||||
Some(true) => yazi_shared::escape::unix(s.to_str()?),
|
Some(true) => yazi_shared::shell::escape_unix(s.to_str()?),
|
||||||
Some(false) => yazi_shared::escape::windows(s.to_str()?),
|
Some(false) => yazi_shared::shell::escape_windows(s.to_str()?),
|
||||||
None => yazi_shared::escape::native(s.to_str()?),
|
None => yazi_shared::shell::escape_native(s.to_str()?),
|
||||||
};
|
};
|
||||||
Ok(s.into_owned())
|
Ok(s.into_owned())
|
||||||
})?,
|
})?,
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,7 @@ mod parser {
|
||||||
if let Some(p) = pos {
|
if let Some(p) = pos {
|
||||||
if let Some(arg) = args.get(p.parse::<usize>().unwrap()) {
|
if let Some(arg) = args.get(p.parse::<usize>().unwrap()) {
|
||||||
if quote {
|
if quote {
|
||||||
buf.extend(yazi_shared::escape::os_str(arg).encode_wide());
|
buf.extend(yazi_shared::shell::escape_os_str(arg).encode_wide());
|
||||||
} else {
|
} else {
|
||||||
buf.extend(arg.encode_wide());
|
buf.extend(arg.encode_wide());
|
||||||
}
|
}
|
||||||
|
|
@ -152,13 +152,13 @@ mod parser {
|
||||||
s.push(" ");
|
s.push(" ");
|
||||||
}
|
}
|
||||||
if c == '*' {
|
if c == '*' {
|
||||||
s.push(yazi_shared::escape::os_str(arg));
|
s.push(yazi_shared::shell::escape_os_str(arg));
|
||||||
} else {
|
} else {
|
||||||
s.push(arg);
|
s.push(arg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if quote {
|
if quote {
|
||||||
buf.extend(yazi_shared::escape::os_str(&s).encode_wide());
|
buf.extend(yazi_shared::shell::escape_os_str(&s).encode_wide());
|
||||||
} else {
|
} else {
|
||||||
buf.extend(s.encode_wide());
|
buf.extend(s.encode_wide());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ bitflags = "2.6.0"
|
||||||
crossterm = "0.27.0"
|
crossterm = "0.27.0"
|
||||||
dirs = "5.0.1"
|
dirs = "5.0.1"
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
|
libc = "0.2.155"
|
||||||
parking_lot = "0.12.3"
|
parking_lot = "0.12.3"
|
||||||
percent-encoding = "2.3.1"
|
percent-encoding = "2.3.1"
|
||||||
ratatui = "0.27.0"
|
ratatui = "0.27.0"
|
||||||
|
|
@ -23,11 +24,8 @@ serde = { version = "1.0.204", features = [ "derive" ] }
|
||||||
shell-words = "1.1.0"
|
shell-words = "1.1.0"
|
||||||
tokio = { version = "1.39.1", features = [ "full" ] }
|
tokio = { version = "1.39.1", features = [ "full" ] }
|
||||||
|
|
||||||
[target."cfg(unix)".dependencies]
|
|
||||||
libc = "0.2.155"
|
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
windows-sys = { version = "0.52.0", features = [ "Win32_Storage_FileSystem" ] }
|
windows-sys = { version = "0.52.0", features = [ "Win32_Storage_FileSystem", "Win32_UI_Shell" ] }
|
||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
crossterm = { version = "0.27.0", features = [ "use-dev-tty" ] }
|
crossterm = { version = "0.27.0", features = [ "use-dev-tty" ] }
|
||||||
|
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
//! Escape characters that may have special meaning in a shell, including
|
|
||||||
//! spaces. This is a modified version of the [`shell-escape`] crate and [`this
|
|
||||||
//! PR`].
|
|
||||||
//!
|
|
||||||
//! [`shell-escape`]: https://crates.io/crates/shell-escape
|
|
||||||
//! [`this PR`]: https://github.com/sfackler/shell-escape/pull/9
|
|
||||||
|
|
||||||
use std::{borrow::Cow, ffi::OsStr};
|
|
||||||
|
|
||||||
mod unix;
|
|
||||||
mod windows;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn unix(s: &str) -> Cow<str> { unix::from_str(s) }
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn windows(s: &str) -> Cow<str> { windows::from_str(s) }
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn native(s: &str) -> Cow<str> {
|
|
||||||
#[cfg(unix)]
|
|
||||||
{
|
|
||||||
unix::from_str(s)
|
|
||||||
}
|
|
||||||
#[cfg(windows)]
|
|
||||||
{
|
|
||||||
windows::from_str(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn os_str(s: &OsStr) -> Cow<OsStr> {
|
|
||||||
#[cfg(unix)]
|
|
||||||
{
|
|
||||||
unix::from_os_str(s)
|
|
||||||
}
|
|
||||||
#[cfg(windows)]
|
|
||||||
{
|
|
||||||
windows::from_os_str(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -13,7 +13,8 @@ pub enum Data {
|
||||||
Integer(i64),
|
Integer(i64),
|
||||||
Number(f64),
|
Number(f64),
|
||||||
String(String),
|
String(String),
|
||||||
Table(HashMap<DataKey, Data>),
|
List(Vec<Data>),
|
||||||
|
Dict(HashMap<DataKey, Data>),
|
||||||
#[serde(skip_deserializing)]
|
#[serde(skip_deserializing)]
|
||||||
Url(Url),
|
Url(Url),
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
|
|
@ -64,13 +65,13 @@ impl Data {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_table_string(self) -> HashMap<String, String> {
|
pub fn into_dict_string(self) -> HashMap<String, String> {
|
||||||
let Self::Table(table) = self else {
|
let Self::Dict(dict) = self else {
|
||||||
return Default::default();
|
return Default::default();
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut map = HashMap::with_capacity(table.len());
|
let mut map = HashMap::with_capacity(dict.len());
|
||||||
for pair in table {
|
for pair in dict {
|
||||||
if let (DataKey::String(k), Self::String(v)) = pair {
|
if let (DataKey::String(k), Self::String(v)) = pair {
|
||||||
map.insert(k, v);
|
map.insert(k, v);
|
||||||
}
|
}
|
||||||
|
|
@ -103,7 +104,7 @@ pub enum DataKey {
|
||||||
|
|
||||||
impl DataKey {
|
impl DataKey {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_numeric(&self) -> bool { matches!(self, Self::Integer(_) | Self::Number(_)) }
|
pub fn is_integer(&self) -> bool { matches!(self, Self::Integer(_)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Macros
|
// --- Macros
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ mod condition;
|
||||||
mod debounce;
|
mod debounce;
|
||||||
mod env;
|
mod env;
|
||||||
mod errors;
|
mod errors;
|
||||||
pub mod escape;
|
|
||||||
pub mod event;
|
pub mod event;
|
||||||
pub mod fs;
|
pub mod fs;
|
||||||
mod layer;
|
mod layer;
|
||||||
|
|
@ -14,6 +13,7 @@ mod number;
|
||||||
mod os;
|
mod os;
|
||||||
mod rand;
|
mod rand;
|
||||||
mod ro_cell;
|
mod ro_cell;
|
||||||
|
pub mod shell;
|
||||||
mod terminal;
|
mod terminal;
|
||||||
pub mod theme;
|
pub mod theme;
|
||||||
mod throttle;
|
mod throttle;
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ impl OrderedFloat {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get(&self) -> f64 { self.0 }
|
pub const fn get(&self) -> f64 { self.0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for OrderedFloat {
|
impl Hash for OrderedFloat {
|
||||||
|
|
|
||||||
58
yazi-shared/src/shell/mod.rs
Normal file
58
yazi-shared/src/shell/mod.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
//! Escape characters that may have special meaning in a shell, including
|
||||||
|
//! spaces. This is a modified version of the [`shell-escape`] crate and [`this
|
||||||
|
//! PR`].
|
||||||
|
//!
|
||||||
|
//! [`shell-escape`]: https://crates.io/crates/shell-escape
|
||||||
|
//! [`this PR`]: https://github.com/sfackler/shell-escape/pull/9
|
||||||
|
|
||||||
|
use std::{borrow::Cow, ffi::OsStr};
|
||||||
|
|
||||||
|
mod unix;
|
||||||
|
mod windows;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn escape_unix(s: &str) -> Cow<str> { unix::escape_str(s) }
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn escape_windows(s: &str) -> Cow<str> { windows::escape_str(s) }
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn escape_native(s: &str) -> Cow<str> {
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
escape_unix(s)
|
||||||
|
}
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
escape_windows(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn escape_os_str(s: &OsStr) -> Cow<OsStr> {
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
unix::escape_os_str(s)
|
||||||
|
}
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
windows::escape_os_str(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn split_unix(s: &str) -> anyhow::Result<Vec<String>> { Ok(shell_words::split(s)?) }
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub fn split_windows(s: &str) -> anyhow::Result<Vec<String>> { Ok(windows::split(s)?) }
|
||||||
|
|
||||||
|
pub fn split_native(s: &str) -> anyhow::Result<Vec<String>> {
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
split_unix(s)
|
||||||
|
}
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
split_windows(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,23 +1,23 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
pub fn from_str(s: &str) -> Cow<str> {
|
pub fn escape_str(s: &str) -> Cow<str> {
|
||||||
match from_slice(s.as_bytes()) {
|
match escape_slice(s.as_bytes()) {
|
||||||
Cow::Borrowed(_) => Cow::Borrowed(s),
|
Cow::Borrowed(_) => Cow::Borrowed(s),
|
||||||
Cow::Owned(v) => String::from_utf8(v).expect("Invalid bytes returned from from_slice()").into(),
|
Cow::Owned(v) => String::from_utf8(v).expect("Invalid bytes returned by escape_slice()").into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub fn from_os_str(s: &std::ffi::OsStr) -> Cow<std::ffi::OsStr> {
|
pub fn escape_os_str(s: &std::ffi::OsStr) -> Cow<std::ffi::OsStr> {
|
||||||
use std::os::unix::ffi::{OsStrExt, OsStringExt};
|
use std::os::unix::ffi::{OsStrExt, OsStringExt};
|
||||||
|
|
||||||
match from_slice(s.as_bytes()) {
|
match escape_slice(s.as_bytes()) {
|
||||||
Cow::Borrowed(_) => Cow::Borrowed(s),
|
Cow::Borrowed(_) => Cow::Borrowed(s),
|
||||||
Cow::Owned(v) => std::ffi::OsString::from_vec(v).into(),
|
Cow::Owned(v) => std::ffi::OsString::from_vec(v).into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_slice(s: &[u8]) -> Cow<[u8]> {
|
fn escape_slice(s: &[u8]) -> Cow<[u8]> {
|
||||||
if !s.is_empty() && s.iter().copied().all(allowed) {
|
if !s.is_empty() && s.iter().copied().all(allowed) {
|
||||||
return Cow::Borrowed(s);
|
return Cow::Borrowed(s);
|
||||||
}
|
}
|
||||||
|
|
@ -51,31 +51,31 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_from_str() {
|
fn test_escape_str() {
|
||||||
assert_eq!(from_str(""), r#"''"#);
|
assert_eq!(escape_str(""), r#"''"#);
|
||||||
assert_eq!(from_str(" "), r#"' '"#);
|
assert_eq!(escape_str(" "), r#"' '"#);
|
||||||
assert_eq!(from_str("*"), r#"'*'"#);
|
assert_eq!(escape_str("*"), r#"'*'"#);
|
||||||
|
|
||||||
assert_eq!(from_str("--aaa=bbb-ccc"), "--aaa=bbb-ccc");
|
assert_eq!(escape_str("--aaa=bbb-ccc"), "--aaa=bbb-ccc");
|
||||||
assert_eq!(from_str(r#"--features="default""#), r#"'--features="default"'"#);
|
assert_eq!(escape_str(r#"--features="default""#), r#"'--features="default"'"#);
|
||||||
assert_eq!(from_str("linker=gcc -L/foo -Wl,bar"), r#"'linker=gcc -L/foo -Wl,bar'"#);
|
assert_eq!(escape_str("linker=gcc -L/foo -Wl,bar"), r#"'linker=gcc -L/foo -Wl,bar'"#);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
from_str("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_=/,.+"),
|
escape_str("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_=/,.+"),
|
||||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_=/,.+",
|
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_=/,.+",
|
||||||
);
|
);
|
||||||
assert_eq!(from_str(r#"'!\$`\\\n "#), r#"''\'''\!'\$`\\\n '"#);
|
assert_eq!(escape_str(r#"'!\$`\\\n "#), r#"''\'''\!'\$`\\\n '"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_from_os_str() {
|
fn test_escape_os_str() {
|
||||||
use std::{ffi::OsStr, os::unix::ffi::OsStrExt};
|
use std::{ffi::OsStr, os::unix::ffi::OsStrExt};
|
||||||
|
|
||||||
fn from_str(input: &str, expected: &str) { from_bytes(input.as_bytes(), expected.as_bytes()) }
|
fn from_str(input: &str, expected: &str) { from_bytes(input.as_bytes(), expected.as_bytes()) }
|
||||||
|
|
||||||
fn from_bytes(input: &[u8], expected: &[u8]) {
|
fn from_bytes(input: &[u8], expected: &[u8]) {
|
||||||
assert_eq!(from_os_str(OsStr::from_bytes(input)), OsStr::from_bytes(expected));
|
assert_eq!(escape_os_str(OsStr::from_bytes(input)), OsStr::from_bytes(expected));
|
||||||
}
|
}
|
||||||
|
|
||||||
from_str("", r#"''"#);
|
from_str("", r#"''"#);
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{borrow::Cow, iter::repeat};
|
use std::{borrow::Cow, iter::repeat};
|
||||||
|
|
||||||
pub fn from_str(s: &str) -> Cow<str> {
|
pub fn escape_str(s: &str) -> Cow<str> {
|
||||||
let bytes = s.as_bytes();
|
let bytes = s.as_bytes();
|
||||||
if !bytes.is_empty() && !bytes.iter().any(|&c| matches!(c, b' ' | b'"' | b'\n' | b'\t')) {
|
if !bytes.is_empty() && !bytes.iter().any(|&c| matches!(c, b' ' | b'"' | b'\n' | b'\t')) {
|
||||||
return Cow::Borrowed(s);
|
return Cow::Borrowed(s);
|
||||||
|
|
@ -39,7 +39,7 @@ pub fn from_str(s: &str) -> Cow<str> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
pub fn from_os_str(s: &std::ffi::OsStr) -> Cow<std::ffi::OsStr> {
|
pub fn escape_os_str(s: &std::ffi::OsStr) -> Cow<std::ffi::OsStr> {
|
||||||
use std::os::windows::ffi::{OsStrExt, OsStringExt};
|
use std::os::windows::ffi::{OsStrExt, OsStringExt};
|
||||||
|
|
||||||
let wide = s.encode_wide();
|
let wide = s.encode_wide();
|
||||||
|
|
@ -79,6 +79,37 @@ pub fn from_os_str(s: &std::ffi::OsStr) -> Cow<std::ffi::OsStr> {
|
||||||
std::ffi::OsString::from_wide(&escaped).into()
|
std::ffi::OsString::from_wide(&escaped).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub fn split(s: &str) -> std::io::Result<Vec<String>> {
|
||||||
|
use std::os::windows::ffi::OsStrExt;
|
||||||
|
|
||||||
|
let s: Vec<_> = std::ffi::OsStr::new(s).encode_wide().chain(std::iter::once(0)).collect();
|
||||||
|
split_slice(&s)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn split_slice(s: &[u16]) -> std::io::Result<Vec<String>> {
|
||||||
|
use std::mem::MaybeUninit;
|
||||||
|
|
||||||
|
use windows_sys::Win32::{Foundation::LocalFree, UI::Shell::CommandLineToArgvW};
|
||||||
|
|
||||||
|
let mut argc = MaybeUninit::<i32>::uninit();
|
||||||
|
let argv_p = unsafe { CommandLineToArgvW(s.as_ptr(), argc.as_mut_ptr()) };
|
||||||
|
if argv_p.is_null() {
|
||||||
|
return Err(std::io::Error::last_os_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
let argv = unsafe { std::slice::from_raw_parts(argv_p, argc.assume_init() as usize) };
|
||||||
|
let mut res = vec![];
|
||||||
|
for &arg in argv {
|
||||||
|
let len = unsafe { libc::wcslen(arg) };
|
||||||
|
res.push(String::from_utf16_lossy(unsafe { std::slice::from_raw_parts(arg, len) }));
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe { LocalFree(argv_p as _) };
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn disallowed(b: u16) -> bool {
|
fn disallowed(b: u16) -> bool {
|
||||||
match char::from_u32(b as u32) {
|
match char::from_u32(b as u32) {
|
||||||
|
|
@ -92,33 +123,33 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_from_str() {
|
fn test_escape_str() {
|
||||||
assert_eq!(from_str(""), r#""""#);
|
assert_eq!(escape_str(""), r#""""#);
|
||||||
assert_eq!(from_str(r#""""#), r#""\"\"""#);
|
assert_eq!(escape_str(r#""""#), r#""\"\"""#);
|
||||||
|
|
||||||
assert_eq!(from_str("--aaa=bbb-ccc"), "--aaa=bbb-ccc");
|
assert_eq!(escape_str("--aaa=bbb-ccc"), "--aaa=bbb-ccc");
|
||||||
assert_eq!(from_str(r#"\path\to\my documents\"#), r#""\path\to\my documents\\""#);
|
assert_eq!(escape_str(r#"\path\to\my documents\"#), r#""\path\to\my documents\\""#);
|
||||||
|
|
||||||
assert_eq!(from_str(r#"--features="default""#), r#""--features=\"default\"""#);
|
assert_eq!(escape_str(r#"--features="default""#), r#""--features=\"default\"""#);
|
||||||
assert_eq!(from_str(r#""--features=\"default\"""#), r#""\"--features=\\\"default\\\"\"""#);
|
assert_eq!(escape_str(r#""--features=\"default\"""#), r#""\"--features=\\\"default\\\"\"""#);
|
||||||
assert_eq!(from_str("linker=gcc -L/foo -Wl,bar"), r#""linker=gcc -L/foo -Wl,bar""#);
|
assert_eq!(escape_str("linker=gcc -L/foo -Wl,bar"), r#""linker=gcc -L/foo -Wl,bar""#);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_from_os_str() {
|
fn test_escape_os_str() {
|
||||||
use std::{ffi::OsString, os::windows::ffi::OsStringExt};
|
use std::{ffi::OsString, os::windows::ffi::OsStringExt};
|
||||||
|
|
||||||
fn from_str(input: &str, expected: &str) {
|
fn from_str(input: &str, expected: &str) {
|
||||||
let observed = OsString::from(input);
|
let observed = OsString::from(input);
|
||||||
let expected = OsString::from(expected);
|
let expected = OsString::from(expected);
|
||||||
assert_eq!(from_os_str(observed.as_os_str()), expected.as_os_str());
|
assert_eq!(escape_os_str(observed.as_os_str()), expected.as_os_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_bytes(input: &[u16], expected: &[u16]) {
|
fn from_bytes(input: &[u16], expected: &[u16]) {
|
||||||
let observed = OsString::from_wide(input);
|
let observed = OsString::from_wide(input);
|
||||||
let expected = OsString::from_wide(expected);
|
let expected = OsString::from_wide(expected);
|
||||||
assert_eq!(from_os_str(observed.as_os_str()), expected.as_os_str());
|
assert_eq!(escape_os_str(observed.as_os_str()), expected.as_os_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
from_str("", r#""""#);
|
from_str("", r#""""#);
|
||||||
Loading…
Add table
Reference in a new issue