feat: package manager (#985)

This commit is contained in:
三咲雅 · Misaki Masa 2024-05-07 13:42:45 +08:00 committed by GitHub
parent fdecf629a6
commit faa1d9f37b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 471 additions and 58 deletions

36
Cargo.lock generated
View File

@ -168,9 +168,9 @@ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
[[package]]
name = "base64"
version = "0.22.0"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "better-panic"
@ -1086,9 +1086,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
[[package]]
name = "libc"
version = "0.2.153"
version = "0.2.154"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346"
[[package]]
name = "libredox"
@ -1408,9 +1408,9 @@ checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
[[package]]
name = "parking_lot"
version = "0.12.1"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb"
dependencies = [
"lock_api",
"parking_lot_core",
@ -1695,9 +1695,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.198"
version = "1.0.199"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc"
checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a"
dependencies = [
"serde_derive",
]
@ -1714,9 +1714,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.198"
version = "1.0.199"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9"
checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc"
dependencies = [
"proc-macro2",
"quote",
@ -2257,9 +2257,9 @@ checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
[[package]]
name = "unicode-width"
version = "0.1.11"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6"
[[package]]
name = "url"
@ -2280,9 +2280,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "uzers"
version = "0.11.3"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76d283dc7e8c901e79e32d077866eaf599156cbf427fffa8289aecc52c5c3f63"
checksum = "7d85875e16d59b3b1549efce83ff8251a64923b03bef94add0a1862847448de4"
dependencies = [
"libc",
"log",
@ -2696,7 +2696,7 @@ version = "0.2.5"
dependencies = [
"anyhow",
"arc-swap",
"base64 0.22.0",
"base64 0.22.1",
"color_quant",
"crossterm",
"futures",
@ -2735,10 +2735,14 @@ dependencies = [
"clap_complete",
"clap_complete_fig",
"clap_complete_nushell",
"crossterm",
"md-5",
"serde_json",
"tokio",
"toml_edit",
"vergen",
"yazi-dds",
"yazi-shared",
]
[[package]]
@ -2842,7 +2846,7 @@ version = "0.2.5"
dependencies = [
"ansi-to-tui",
"anyhow",
"base64 0.22.0",
"base64 0.22.1",
"clipboard-win",
"crossterm",
"futures",

View File

@ -14,6 +14,7 @@ Yazi (means "duck") is a terminal file manager written in Rust, based on non-blo
- 🌟 **Built-in Code Highlighting and Image Decoding**: Combined with the pre-loading mechanism, greatly accelerates image and normal file loading.
- 🔌 **Concurrent Plugin System**: UI plugins (rewriting most of the UI), functional plugins, custom previewer, and custom preloader; Just some pieces of Lua.
- 📡 **Data Distribution Service**: Built on a client-server architecture (no additional server process required), integrated with a Lua-based publish-subscribe model, achieving cross-instance communication and state persistence.
- 📦 **Package Manager**: Install plugins and themes with one command, keeping them always up to date, or pin them to a specific version.
- 🧰 Integration with fd, rg, fzf, zoxide
- 💫 Vim-like input/select/which/notify component, auto-completion for cd paths
- 🏷️ Multi-Tab Support, Cross-directory selection, Scrollable Preview (for videos, PDFs, archives, directories, code, etc.)

View File

@ -15,7 +15,7 @@ yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
# External dependencies
anyhow = "1.0.82"
arc-swap = "1.7.1"
base64 = "0.22.0"
base64 = "0.22.1"
color_quant = "1.1.0"
crossterm = "0.27.0"
futures = "0.3.30"

View File

@ -15,7 +15,7 @@ yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
# External dependencies
clap = { version = "4.5.4", features = [ "derive" ] }
serde = { version = "1.0.198", features = [ "derive" ] }
serde = { version = "1.0.199", features = [ "derive" ] }
[build-dependencies]
clap = { version = "4.5.4", features = [ "derive" ] }

View File

@ -148,7 +148,7 @@ impl Default for Boot {
.map(|s| s.split(',').map(|s| s.to_owned()).collect())
.unwrap_or_default();
let boot = Self {
Self {
cwd,
file,
@ -159,11 +159,7 @@ impl Default for Boot {
plugin_dir: config_dir.join("plugins"),
config_dir,
state_dir: Xdg::state_dir(),
};
std::fs::create_dir_all(&boot.flavor_dir).ok();
std::fs::create_dir_all(&boot.plugin_dir).ok();
boot
}
}
}

View File

@ -9,13 +9,17 @@ homepage = "https://yazi-rs.github.io"
repository = "https://github.com/sxyazi/yazi"
[dependencies]
yazi-dds = { path = "../yazi-dds", version = "0.2.5" }
yazi-dds = { path = "../yazi-dds", version = "0.2.5" }
yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
# External dependencies
anyhow = "1.0.82"
clap = { version = "4.5.4", features = [ "derive" ] }
crossterm = "0.27.0"
md-5 = "0.10.6"
serde_json = "1.0.116"
tokio = { version = "1.37.0", features = [ "full" ] }
toml_edit = "0.22.12"
[build-dependencies]
anyhow = "1.0.82"

View File

@ -20,6 +20,8 @@ pub(super) enum Command {
Pub(CommandPub),
/// Publish a static message to all remote instances.
PubStatic(CommandPubStatic),
/// Manage packages.
Pack(CommandPack),
}
#[derive(clap::Args)]
@ -79,3 +81,16 @@ impl CommandPubStatic {
}
}
}
#[derive(clap::Args)]
pub(super) struct CommandPack {
/// Add a package.
#[arg(short = 'a', long)]
pub(super) add: Option<String>,
/// Install all packages.
#[arg(short = 'i', long)]
pub(super) install: bool,
/// Upgrade all packages.
#[arg(short = 'u', long)]
pub(super) upgrade: bool,
}

View File

@ -1,4 +1,5 @@
mod args;
mod package;
use args::*;
use clap::Parser;
@ -30,6 +31,18 @@ async fn main() -> anyhow::Result<()> {
std::process::exit(1);
}
}
Command::Pack(cmd) => {
package::init();
if cmd.install {
package::Package::install_from_config("plugin", false).await?;
package::Package::install_from_config("flavor", false).await?;
} else if cmd.upgrade {
package::Package::install_from_config("plugin", true).await?;
package::Package::install_from_config("flavor", true).await?;
} else if let Some(repo) = &cmd.add {
package::Package::add_to_config(repo).await?;
}
}
}
Ok(())

View File

@ -0,0 +1,20 @@
use anyhow::Result;
use yazi_shared::fs::must_exists;
use super::{Git, Package};
impl Package {
pub(super) async fn add(&mut self) -> Result<()> {
self.output("Upgrading package `{name}`")?;
let path = self.local();
if !must_exists(&path).await {
Git::clone(&self.remote(), &path).await?;
} else {
Git::pull(&path).await?;
};
self.commit = Git::hash(&path).await?;
self.deploy().await
}
}

View File

@ -0,0 +1,50 @@
use anyhow::{bail, Context, Result};
use tokio::fs;
use yazi_shared::{fs::{maybe_exists, must_exists}, Xdg};
use super::Package;
const TRACKER: &str = "DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY";
impl Package {
pub(super) async fn deploy(&mut self) -> Result<()> {
let Some(name) = self.name().map(ToOwned::to_owned) else { bail!("Invalid package url") };
let from = self.local().join(&self.child);
self.output("Deploying package `{name}`")?;
self.is_flavor = maybe_exists(&from.join("flavor.toml")).await;
let to = if self.is_flavor {
Xdg::config_dir().join(format!("flavors/{name}"))
} else {
Xdg::config_dir().join(format!("plugins/{name}"))
};
let tracker = to.join(TRACKER);
if maybe_exists(&to).await && !must_exists(&tracker).await {
bail!(
"A user package with the same name `{name}` already exists.
For safety, please manually delete it from your plugin/flavor directory and re-run the command."
);
}
fs::create_dir_all(&to).await?;
fs::write(tracker, []).await?;
let files = if self.is_flavor {
&["flavor.toml", "tmtheme.xml", "README.md", "preview.png", "LICENSE", "LICENSE-tmtheme"][..]
} else {
&["init.lua", "README.md", "LICENSE"][..]
};
for file in files {
let (from, to) = (from.join(file), to.join(file));
fs::copy(&from, &to)
.await
.with_context(|| format!("Failed to copy `{}` to `{}`", from.display(), to.display()))?;
}
println!("Done!");
Ok(())
}
}

View File

@ -0,0 +1,55 @@
use std::path::Path;
use anyhow::{bail, Context, Result};
use tokio::process::Command;
use yazi_shared::strip_trailing_newline;
pub(super) struct Git;
impl Git {
pub(super) async fn clone(url: &str, path: &Path) -> Result<()> {
Self::exec(|c| c.args(["clone", url]).arg(path)).await
}
pub(super) async fn fetch(path: &Path) -> Result<()> {
Self::exec(|c| c.current_dir(path).arg("fetch")).await
}
pub(super) async fn checkout(path: &Path, commit: &str) -> Result<()> {
Self::exec(|c| c.current_dir(path).args(["checkout", commit])).await
}
pub(super) async fn pull(path: &Path) -> Result<()> {
Self::fetch(path).await?;
Self::checkout(path, "origin/HEAD").await?;
Ok(())
}
pub(super) async fn hash(path: &Path) -> Result<String> {
let output = Command::new("git")
.current_dir(path)
.args(["rev-parse", "--short", "HEAD"])
.output()
.await
.context("Failed to get current commit hash")?;
if !output.status.success() {
bail!("Getting commit hash failed: {}", output.status);
}
Ok(strip_trailing_newline(
String::from_utf8(output.stdout).context("Failed to parse commit hash")?,
))
}
async fn exec(f: impl FnOnce(&mut Command) -> &mut Command) -> Result<()> {
let status =
f(&mut Command::new("git")).status().await.context("Failed to execute `git` command")?;
if !status.success() {
bail!("`git` command failed: {status}");
}
Ok(())
}
}

View File

@ -0,0 +1,25 @@
use anyhow::Result;
use yazi_shared::fs::must_exists;
use super::{Git, Package};
impl Package {
pub(super) async fn install(&mut self) -> Result<()> {
self.output("Installing package `{name}`")?;
let path = self.local();
if !must_exists(&path).await {
Git::clone(&self.remote(), &path).await?;
} else {
Git::fetch(&path).await?;
};
if self.commit.is_empty() {
self.commit = Git::hash(&path).await?;
} else {
Git::checkout(&path, self.commit.trim_start_matches('=')).await?;
}
self.deploy().await
}
}

View File

@ -0,0 +1,17 @@
#![allow(clippy::module_inception)]
mod add;
mod deploy;
mod git;
mod install;
mod package;
mod parser;
mod upgrade;
use git::*;
pub(super) use package::*;
pub(super) fn init() {
let root = yazi_shared::Xdg::state_dir().join("packages");
std::fs::create_dir_all(root).expect("Failed to create packages directory");
}

View File

@ -0,0 +1,79 @@
use std::{borrow::Cow, io::BufWriter, path::PathBuf};
use anyhow::Result;
use md5::{Digest, Md5};
use yazi_shared::Xdg;
pub(crate) struct Package {
pub(crate) repo: String,
pub(crate) child: String,
pub(crate) commit: String,
pub(super) is_flavor: bool,
}
impl Package {
pub(super) fn new(url: &str, commit: Option<&str>) -> Self {
let mut parts = url.splitn(2, '#');
let mut repo = parts.next().unwrap_or_default().to_owned();
let child = if let Some(s) = parts.next() {
format!("{s}.yazi")
} else {
repo.push_str(".yazi");
String::new()
};
Self { repo, child, commit: commit.unwrap_or_default().to_owned(), is_flavor: false }
}
#[inline]
pub(super) fn use_(&self) -> Cow<str> {
if self.child.is_empty() {
self.repo.trim_end_matches(".yazi").into()
} else {
format!("{}#{}", self.repo, self.child.trim_end_matches(".yazi")).into()
}
}
#[inline]
pub(super) fn name(&self) -> Option<&str> {
let s = if self.child.is_empty() {
self.repo.split('/').last().filter(|s| !s.is_empty())
} else {
Some(self.child.as_str())
};
s.filter(|s| s.bytes().all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'z' | b'-' | b'.')))
}
#[inline]
pub(super) fn local(&self) -> PathBuf {
Xdg::state_dir()
.join("packages")
.join(format!("{:x}", Md5::new_with_prefix(self.remote()).finalize()))
}
#[inline]
pub(super) fn remote(&self) -> String {
// Support more Git hosting services in the future
format!("https://github.com/{}.git", self.repo)
}
pub(super) fn output(&self, s: &str) -> Result<()> {
use crossterm::style::{Attribute, Print, SetAttributes};
crossterm::execute!(
BufWriter::new(std::io::stdout()),
Print("\n"),
SetAttributes(Attribute::Reverse.into()),
SetAttributes(Attribute::Bold.into()),
Print(" "),
Print(s.replacen("{name}", self.name().unwrap_or_default(), 1)),
Print(" "),
SetAttributes(Attribute::NoBold.into()),
SetAttributes(Attribute::NoReverse.into()),
Print("\n\n"),
)?;
Ok(())
}
}

View File

@ -0,0 +1,113 @@
use anyhow::{bail, Context, Result};
use tokio::fs;
use toml_edit::{Array, DocumentMut, InlineTable, Item, Value};
use yazi_shared::Xdg;
use super::Package;
impl Package {
pub(crate) async fn add_to_config(use_: &str) -> Result<()> {
let mut package = Self::new(use_, None);
let Some(name) = package.name() else { bail!("Invalid package `use`") };
let path = Xdg::config_dir().join("package.toml");
let mut doc = Self::ensure_config(&fs::read_to_string(&path).await.unwrap_or_default())?;
Self::ensure_unique(&doc, name)?;
package.add().await?;
let mut table = InlineTable::new();
table.insert("use", package.use_().as_ref().into());
if !package.commit.is_empty() {
table.insert("commit", package.commit.into());
}
if package.is_flavor {
doc["flavor"]["deps"].as_array_mut().unwrap().push(table);
} else {
doc["plugin"]["deps"].as_array_mut().unwrap().push(table);
}
fs::write(path, doc.to_string()).await?;
Ok(())
}
pub(crate) async fn install_from_config(section: &str, upgrade: bool) -> Result<()> {
let path = Xdg::config_dir().join("package.toml");
let Ok(s) = fs::read_to_string(&path).await else {
return Ok(());
};
let mut doc = s.parse::<DocumentMut>().context("Failed to parse package.toml")?;
let Some(deps) = doc.get_mut(section).and_then(|d| d.get_mut("deps")) else {
return Ok(());
};
let deps = deps.as_array_mut().context("`deps` must be an array")?;
for dep in deps.iter_mut() {
let dep = dep.as_inline_table_mut().context("Dependency must be an inline table")?;
let use_ = dep.get("use").and_then(|d| d.as_str()).context("Missing `use` field")?;
let commit = dep.get("commit").and_then(|d| d.as_str());
let mut package = Package::new(use_, commit);
if upgrade {
package.upgrade().await?;
} else {
package.install().await?;
}
if package.commit.is_empty() {
dep.remove("commit");
} else {
dep.insert("commit", package.commit.into());
}
}
fs::write(path, doc.to_string()).await.context("Failed to write package.toml")
}
fn ensure_config(s: &str) -> Result<DocumentMut> {
let mut doc = s.parse::<DocumentMut>().context("Failed to parse package.toml")?;
doc
.entry("plugin")
.or_insert(toml_edit::table())
.as_table_mut()
.context("Failed to get `plugin` table")?
.entry("deps")
.or_insert(Item::Value(Array::new().into()))
.as_array()
.context("Failed to get `deps` array")?;
doc
.entry("flavor")
.or_insert(toml_edit::table())
.as_table_mut()
.context("Failed to get `flavor` table")?
.entry("deps")
.or_insert(Item::Value(Array::new().into()))
.as_array()
.context("Failed to get `deps` array")?;
Ok(doc)
}
fn ensure_unique(doc: &DocumentMut, name: &str) -> Result<()> {
#[inline]
fn same(v: &Value, name: &str) -> bool {
v.as_inline_table()
.and_then(|t| t.get("use"))
.and_then(|v| v.as_str())
.is_some_and(|s| Package::new(s, None).name() == Some(name))
}
if doc["plugin"]["deps"].as_array().unwrap().into_iter().any(|v| same(v, name)) {
bail!("Plugin `{name}` already exists in package.toml");
}
if doc["flavor"]["deps"].as_array().unwrap().into_iter().any(|v| same(v, name)) {
bail!("Flavor `{name}` already exists in package.toml");
}
Ok(())
}
}

View File

@ -0,0 +1,9 @@
use anyhow::Result;
use super::Package;
impl Package {
pub(super) async fn upgrade(&mut self) -> Result<()> {
if self.commit.starts_with('=') { Ok(()) } else { self.add().await }
}
}

View File

@ -18,7 +18,7 @@ crossterm = "0.27.0"
globset = "0.4.14"
indexmap = "2.2.6"
ratatui = "=0.26.1"
serde = { version = "1.0.198", features = [ "derive" ] }
serde = { version = "1.0.199", features = [ "derive" ] }
shell-words = "1.1.0"
toml = { version = "0.8.12", features = [ "preserve_order" ] }
validator = { version = "0.18.1", features = [ "derive" ] }

View File

@ -24,18 +24,18 @@ bitflags = "2.5.0"
crossterm = "0.27.0"
futures = "0.3.30"
notify = { version = "6.1.1", default-features = false, features = [ "macos_fsevent" ] }
parking_lot = "0.12.1"
parking_lot = "0.12.2"
ratatui = "=0.26.1"
regex = "1.10.4"
scopeguard = "1.2.0"
serde = "1.0.198"
serde = "1.0.199"
tokio = { version = "1.37.0", features = [ "full" ] }
tokio-stream = "0.1.15"
tokio-util = "0.7.10"
unicode-width = "0.1.11"
unicode-width = "0.1.12"
# Logging
tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_warn" ] }
[target."cfg(unix)".dependencies]
libc = "0.2.153"
libc = "0.2.154"

View File

@ -2,7 +2,7 @@ use std::{collections::{HashMap, HashSet}, fs::Metadata, mem, ops::Deref, sync::
use tokio::{fs::{self, DirEntry}, select, sync::mpsc::{self, UnboundedReceiver}};
use yazi_config::{manager::SortBy, MANAGER};
use yazi_shared::fs::{accessible, File, FilesOp, Url, FILES_TICKET};
use yazi_shared::fs::{maybe_exists, File, FilesOp, Url, FILES_TICKET};
use super::{FilesSorter, Filter};
@ -101,7 +101,7 @@ impl Files {
Ok(m) if mtime == m.modified().ok() => {}
Ok(m) => return Some(m),
Err(e) => {
if accessible(url).await {
if maybe_exists(url).await {
FilesOp::IOErr(url.clone(), e.kind()).emit();
} else if let Some(p) = url.parent_url() {
FilesOp::Deleting(p, vec![url.clone()]).emit();

View File

@ -6,7 +6,7 @@ use tokio::{fs::{self, OpenOptions}, io::{stdin, AsyncReadExt, AsyncWriteExt}};
use yazi_config::{OPEN, PREVIEW};
use yazi_dds::Pubsub;
use yazi_proxy::{AppProxy, TasksProxy, HIDER, WATCHER};
use yazi_shared::{fs::{accessible, max_common_root, File, FilesOp, Url}, term::Term};
use yazi_shared::{fs::{max_common_root, maybe_exists, File, FilesOp, Url}, term::Term};
use crate::manager::Manager;
@ -84,7 +84,7 @@ impl Manager {
for (o, n) in todo {
let (old, new) = (root.join(&o), root.join(&n));
if accessible(&new).await {
if maybe_exists(&new).await {
failed.push((o, n, anyhow!("Destination already exists")));
} else if let Err(e) = fs::rename(&old, &new).await {
failed.push((o, n, e.into()));

View File

@ -3,7 +3,7 @@ use std::path::PathBuf;
use tokio::fs;
use yazi_config::popup::InputCfg;
use yazi_proxy::{InputProxy, ManagerProxy};
use yazi_shared::{event::Cmd, fs::{accessible, File, FilesOp, Url}};
use yazi_shared::{event::Cmd, fs::{maybe_exists, File, FilesOp, Url}};
use crate::manager::Manager;
@ -26,7 +26,7 @@ impl Manager {
};
let path = cwd.join(&name);
if !opt.force && accessible(&path).await {
if !opt.force && maybe_exists(&path).await {
match InputProxy::show(InputCfg::overwrite()).recv().await {
Some(Ok(c)) if c == "y" || c == "Y" => (),
_ => return Ok(()),

View File

@ -5,7 +5,7 @@ use tokio::fs;
use yazi_config::popup::InputCfg;
use yazi_dds::Pubsub;
use yazi_proxy::{InputProxy, ManagerProxy, WATCHER};
use yazi_shared::{event::Cmd, fs::{accessible, File, FilesOp, Url}};
use yazi_shared::{event::Cmd, fs::{maybe_exists, File, FilesOp, Url}};
use crate::manager::Manager;
@ -62,7 +62,7 @@ impl Manager {
}
let new = hovered.parent().unwrap().join(name);
if opt.force || !accessible(&new).await {
if opt.force || !maybe_exists(&new).await {
Self::rename_do(tab, hovered, Url::from(new)).await.ok();
return;
}

View File

@ -19,12 +19,12 @@ yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
# External dependencies
anyhow = "1.0.82"
mlua = { version = "0.9.7", features = [ "lua54" ] }
parking_lot = "0.12.1"
serde = { version = "1.0.198", features = [ "derive" ] }
parking_lot = "0.12.2"
serde = { version = "1.0.199", features = [ "derive" ] }
serde_json = "1.0.116"
tokio = { version = "1.37.0", features = [ "full" ] }
tokio-stream = "0.1.15"
tokio-util = "0.7.10"
[target."cfg(unix)".dependencies]
uzers = "0.11.3"
uzers = "0.12.0"

View File

@ -41,7 +41,7 @@ tracing-appender = "0.2.3"
tracing-subscriber = "0.3.18"
[target."cfg(unix)".dependencies]
libc = "0.2.153"
libc = "0.2.154"
signal-hook-tokio = { version = "0.3.1", features = [ "futures-v0_3" ] }
[target.'cfg(all(not(target_os = "macos"), not(target_os = "windows")))'.dependencies]

View File

@ -23,28 +23,28 @@ yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
# External dependencies
ansi-to-tui = "3.1.0"
anyhow = "1.0.82"
base64 = "0.22.0"
base64 = "0.22.1"
crossterm = "0.27.0"
futures = "0.3.30"
md-5 = "0.10.6"
mlua = { version = "0.9.7", features = [ "lua54", "serialize", "macros", "async" ] }
parking_lot = "0.12.1"
parking_lot = "0.12.2"
ratatui = "=0.26.1"
serde = "1.0.198"
serde = "1.0.199"
serde_json = "1.0.116"
shell-escape = "0.1.5"
shell-words = "1.1.0"
syntect = { version = "5.2.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
tokio = { version = "1.37.0", features = [ "full" ] }
tokio-util = "0.7.10"
unicode-width = "0.1.11"
unicode-width = "0.1.12"
yazi-prebuild = "0.1.2"
# Logging
tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_warn" ] }
[target."cfg(unix)".dependencies]
uzers = "0.11.3"
uzers = "0.12.0"
[target."cfg(windows)".dependencies]
clipboard-win = "5.3.1"

View File

@ -19,7 +19,7 @@ yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
anyhow = "1.0.82"
async-priority-channel = "0.2.0"
futures = "0.3.30"
parking_lot = "0.12.1"
parking_lot = "0.12.2"
scopeguard = "1.2.0"
tokio = { version = "1.37.0", features = [ "full" ] }
@ -27,7 +27,7 @@ tokio = { version = "1.37.0", features = [ "full" ] }
tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_warn" ] }
[target."cfg(unix)".dependencies]
libc = "0.2.153"
libc = "0.2.154"
[target.'cfg(not(target_os = "android"))'.dependencies]
trash = "4.1.0"

View File

@ -5,7 +5,7 @@ use futures::{future::BoxFuture, FutureExt};
use tokio::{fs, io::{self, ErrorKind::{AlreadyExists, NotFound}}, sync::mpsc};
use tracing::warn;
use yazi_config::TASKS;
use yazi_shared::fs::{accessible, calculate_size, copy_with_progress, path_relative_to, Url};
use yazi_shared::fs::{calculate_size, copy_with_progress, maybe_exists, path_relative_to, Url};
use super::{FileOp, FileOpDelete, FileOpLink, FileOpPaste, FileOpTrash};
use crate::{TaskOp, TaskProg, LOW, NORMAL};
@ -108,7 +108,7 @@ impl File {
}
FileOp::Delete(task) => {
if let Err(e) = fs::remove_file(&task.target).await {
if e.kind() != NotFound && accessible(&task.target).await {
if e.kind() != NotFound && maybe_exists(&task.target).await {
self.fail(task.id, format!("Delete task failed: {:?}, {e}", task))?;
Err(e)?
}

View File

@ -15,15 +15,15 @@ crossterm = "0.27.0"
dirs = "5.0.1"
filetime = "0.2.23"
futures = "0.3.30"
parking_lot = "0.12.1"
parking_lot = "0.12.2"
percent-encoding = "2.3.1"
ratatui = "=0.26.1"
regex = "1.10.4"
serde = { version = "1.0.198", features = [ "derive" ] }
serde = { version = "1.0.199", features = [ "derive" ] }
tokio = { version = "1.37.0", features = [ "full" ] }
# Logging
tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_warn" ] }
[target."cfg(unix)".dependencies]
libc = "0.2.153"
libc = "0.2.154"

View File

@ -18,3 +18,13 @@ impl CharKind {
}
}
}
pub fn strip_trailing_newline(mut s: String) -> String {
if s.ends_with('\n') {
s.pop();
}
if s.ends_with('\r') {
s.pop();
}
s
}

View File

@ -4,8 +4,10 @@ use anyhow::Result;
use filetime::{set_file_mtime, FileTime};
use tokio::{fs, io, select, sync::{mpsc, oneshot}, time};
pub async fn accessible(path: &Path) -> bool {
match fs::symlink_metadata(path).await {
pub async fn must_exists(p: impl AsRef<Path>) -> bool { fs::symlink_metadata(p).await.is_ok() }
pub async fn maybe_exists(p: impl AsRef<Path>) -> bool {
match fs::symlink_metadata(p).await {
Ok(_) => true,
Err(e) => e.kind() != io::ErrorKind::NotFound,
}

View File

@ -1,6 +1,6 @@
use std::{borrow::Cow, env, ffi::OsString, path::{Component, Path, PathBuf, MAIN_SEPARATOR}};
use super::accessible;
use super::maybe_exists;
use crate::fs::Url;
#[inline]
@ -76,7 +76,7 @@ pub async fn unique_path(mut p: Url) -> Url {
.unwrap_or_default();
let mut i = 0;
while accessible(&p).await {
while maybe_exists(&p).await {
i += 1;
let mut name = OsString::with_capacity(stem.len() + ext.len() + 5);