fix: avoid appending a newline when reading clipboard contents (#3059)

This commit is contained in:
三咲雅 misaki masa 2025-08-14 11:16:09 +08:00 committed by sxyazi
parent 1bfcbc1664
commit 2ec3a6c645
No known key found for this signature in database
23 changed files with 209 additions and 81 deletions

16
.cargo/config.toml Normal file
View file

@ -0,0 +1,16 @@
[env]
MACOSX_DEPLOYMENT_TARGET = "10.12"
JEMALLOC_SYS_WITH_LG_PAGE = "16"
# environment variable for tikv-jemalloc-sys
#
# https://jemalloc.net/jemalloc.3.html#opt.narenas
# narenas is the maximum number of arenas to use for automatic multiplexing
# of threads and arenas. The default is four times the number of CPUs,
# or one if there is a single CPU.
#
# Improve memory efficiency by reducing fragmentation and ensuring all threads allocate from the same pool
JEMALLOC_SYS_WITH_MALLOC_CONF = "narenas:1"
[target.aarch64-apple-darwin]
rustflags = [ "-Ctarget-cpu=apple-m1" ]

23
Cargo.lock generated
View file

@ -391,9 +391,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.44"
version = "4.5.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c1f056bae57e3e54c3375c41ff79619ddd13460a17d7438712bd0d83fda4ff8"
checksum = "1fc0e74a703892159f5ae7d3aac52c8e6c392f5ae5f359c70b5881d60aaac318"
dependencies = [
"clap_builder",
"clap_derive",
@ -442,9 +442,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.5.41"
version = "4.5.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491"
checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6"
dependencies = [
"heck",
"proc-macro2",
@ -2145,9 +2145,9 @@ dependencies = [
[[package]]
name = "rayon-core"
version = "1.12.1"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
@ -2514,9 +2514,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.104"
version = "2.0.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
checksum = "7bc3fcb250e53458e712715cf74285c1f889686520d79294a9ef3bd7aa1fc619"
dependencies = [
"proc-macro2",
"quote",
@ -3567,6 +3567,13 @@ dependencies = [
"yazi-shared",
]
[[package]]
name = "yazi-build"
version = "0.1.8"
dependencies = [
"yazi-term",
]
[[package]]
name = "yazi-cli"
version = "25.6.11"

View file

@ -24,7 +24,7 @@ ansi-to-tui = "7.0.0"
anyhow = "1.0.99"
base64 = "0.22.1"
bitflags = { version = "2.9.1", features = [ "serde" ] }
clap = { version = "4.5.44", features = [ "derive" ] }
clap = { version = "4.5.45", features = [ "derive" ] }
core-foundation-sys = "0.8.7"
crossterm = { version = "0.29.0", features = [ "event-stream" ] }
dirs = "6.0.0"

View file

@ -1 +1 @@
{"language":"en","flagWords":[],"version":"0.2","words":["Punct","KEYMAP","splitn","crossterm","YAZI","peekable","ratatui","syntect","pbpaste","pbcopy","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","Konsole","Überzug","pkgs","pdftoppm","poppler","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt","realpath","realname","REPARSE","hardlink","hardlinking","nlink","nlink","linemodes","SIGSTOP","sevenzip","rsplitn","replacen","DECSET","DECRQM","repeek","cwds","tcsi","Hyprland","Wayfire","SWAYSOCK","btime","nsec","codegen","gethostname","fchmod","fdfind","Rustc","rustc","ffprobe","vframes","luma","obase","outln","errln","tmtheme","twox","cfgs","fstype","objc","rdev","runloop","exfat","rclone","DECRQSS","DECSCUSR","libvterm","Uninit","lockin","rposition","resvg","foldhash","tilded","futs"]}
{"words":["Punct","KEYMAP","splitn","crossterm","YAZI","peekable","ratatui","syntect","pbpaste","pbcopy","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","Konsole","Überzug","pkgs","pdftoppm","poppler","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt","realpath","realname","REPARSE","hardlink","hardlinking","nlink","nlink","linemodes","SIGSTOP","sevenzip","rsplitn","replacen","DECSET","DECRQM","repeek","cwds","tcsi","Hyprland","Wayfire","SWAYSOCK","btime","nsec","codegen","gethostname","fchmod","fdfind","Rustc","rustc","ffprobe","vframes","luma","obase","outln","errln","tmtheme","twox","cfgs","fstype","objc","rdev","runloop","exfat","rclone","DECRQSS","DECSCUSR","libvterm","Uninit","lockin","rposition","resvg","foldhash","tilded","futs","chdir"],"language":"en","flagWords":[],"version":"0.2"}

View file

@ -16,7 +16,7 @@ impl Actor for UpdateFiles {
fn act(cx: &mut Ctx, opt: Self::Options) -> Result<Data> {
let revision = cx.current().files.revision;
let linked: Vec<_> = LINKED.read().from_dir(opt.op.cwd()).map(|u| opt.op.rebase(u)).collect();
let linked: Vec<_> = LINKED.read().from_dir(opt.op.cwd()).map(|u| opt.op.chdir(u)).collect();
for op in [opt.op].into_iter().chain(linked) {
cx.mgr.yanked.apply_op(&op);
Self::update_tab(cx, op).ok();

22
yazi-build/Cargo.toml Normal file
View file

@ -0,0 +1,22 @@
[package]
name = "yazi-build"
version = "0.1.8"
edition = "2024"
license = "MIT"
authors = [ "sxyazi <sxyazi@gmail.com>" ]
description = "Yazi build system"
homepage = "https://yazi-rs.github.io"
repository = "https://github.com/sxyazi/yazi"
[profile.release]
codegen-units = 1
lto = true
panic = "abort"
strip = true
[build-dependencies]
yazi-term = { path = "../yazi-term", version = "25.5.31" }
[[bin]]
name = "yazi-build"
path = "src/main.rs"

69
yazi-build/build.rs Normal file
View file

@ -0,0 +1,69 @@
use std::{env, error::Error, io::{BufRead, BufReader, Read, Write}, process::{Command, Stdio}, thread};
use yazi_term::tty::TTY;
fn main() -> Result<(), Box<dyn Error>> {
yazi_term::init();
let manifest = env::var_os("CARGO_MANIFEST_DIR").unwrap().to_string_lossy().replace(r"\", "/");
let crates = if manifest.contains("/git/checkouts/yazi-") {
&["--git", "https://github.com/sxyazi/yazi.git", "yazi-fm", "yazi-cli"]
} else if manifest.contains("/registry/src/index.crates.io-") {
&["yazi-fm", "yazi-cli"][..]
} else {
return Ok(());
};
let target = env::var("TARGET").unwrap();
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
unsafe {
env::set_var("VERGEN_GIT_SHA", "Crates.io");
env::set_var("YAZI_CRATE_BUILD", "1");
env::set_var("JEMALLOC_SYS_WITH_LG_PAGE", "16");
env::set_var("JEMALLOC_SYS_WITH_MALLOC_CONF", "narenas:1");
env::set_var(
"MACOSX_DEPLOYMENT_TARGET",
if target == "aarch64-apple-darwin" { "11.0" } else { "10.12" },
);
if target == "aarch64-apple-darwin" {
env::set_var("RUSTFLAGS", "-Ctarget-cpu=apple-m1");
}
};
let profile = if target_os == "windows" { &["--profile", "release-windows"][..] } else { &[] };
let mut child = Command::new(env::var_os("CARGO").unwrap())
.args(&["install", "--force", "--locked"])
.args(profile)
.args(crates)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
let out = flash(child.stdout.take().unwrap());
let err = flash(child.stderr.take().unwrap());
child.wait()?;
out.join().ok();
err.join().ok();
Ok(())
}
fn flash<R: Read + Send + 'static>(src: R) -> thread::JoinHandle<()> {
thread::spawn(move || {
let reader = BufReader::new(src);
for part in reader.split(b'\n') {
match part {
Ok(mut bytes) => {
bytes.push(b'\n');
let mut out = TTY.lockout();
out.write_all(&bytes).ok();
out.flush().ok();
}
Err(_) => break,
}
}
})
}

3
yazi-build/src/main.rs Normal file
View file

@ -0,0 +1,3 @@
fn main() {
println!("See https://yazi-rs.github.io/docs/installation#crates on how to install Yazi.");
}

View file

@ -47,15 +47,9 @@ clap_complete_nushell = "4.5.8"
serde_json = { workspace = true }
vergen-gitcl = { version = "1.0.8", features = [ "build" ] }
[target.aarch64-apple-darwin]
rustflags = [ "-Ctarget-cpu=apple-m1" ]
[target.'cfg(target_os = "macos")'.dependencies]
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }
[env]
MACOSX_DEPLOYMENT_TARGET = "10.11"
[[bin]]
name = "ya"
path = "src/main.rs"

View file

@ -8,6 +8,20 @@ use clap_complete::{Shell, generate_to};
use vergen_gitcl::{BuildBuilder, Emitter, GitclBuilder};
fn main() -> Result<(), Box<dyn Error>> {
let manifest = env::var_os("CARGO_MANIFEST_DIR").unwrap().to_string_lossy().replace(r"\", "/");
if env::var_os("YAZI_CRATE_BUILD").is_none()
&& (manifest.contains("/git/checkouts/yazi-")
|| manifest.contains("/registry/src/index.crates.io-"))
{
panic!(
"Due to Cargo's limitations, the `yazi-fm` and `yazi-cli` crates on crates.io must be built with `cargo install --force yazi-build`"
);
}
generate()
}
fn generate() -> Result<(), Box<dyn Error>> {
Emitter::default()
.add_instructions(&BuildBuilder::default().build_date(true).build()?)?
.add_instructions(&GitclBuilder::default().commit_date(true).sha(true).build()?)?
@ -28,6 +42,5 @@ fn main() -> Result<(), Box<dyn Error>> {
generate_to(clap_complete_nushell::Nushell, cmd, bin, out)?;
generate_to(clap_complete_fig::Fig, cmd, bin, out)?;
Ok(())
}

View file

@ -13,5 +13,5 @@ proc-macro = true
[dependencies]
# External dependencies
syn = { version = "2.0.104", features = [ "full" ] }
syn = { version = "2.0.105", features = [ "full" ] }
quote = "1.0.40"

View file

@ -1,4 +1,4 @@
use std::{collections::HashMap, ops::{Deref, DerefMut}};
use std::{collections::HashMap, iter, ops::{Deref, DerefMut}};
use yazi_shared::url::Url;
@ -20,7 +20,9 @@ impl Linked {
where
'a: 'b,
{
if let Some(to) = self.get(url) {
if url.scheme.is_virtual() {
Box::new(iter::empty())
} else if let Some(to) = self.get(url) {
Box::new(self.iter().filter(move |(k, v)| *v == to && *k != url).map(|(k, _)| k))
} else {
Box::new(self.iter().filter(move |(_, v)| *v == url).map(|(k, _)| k))
@ -28,15 +30,12 @@ impl Linked {
}
pub fn from_file(&self, url: &Url) -> Vec<Url> {
if self.is_empty() {
return vec![];
if url.scheme.is_virtual() {
vec![]
} else if let Some((parent, urn)) = url.pair() {
self.from_dir(&parent).map(|u| u.join(&urn)).collect()
} else {
vec![]
}
let Some(p) = url.parent_url() else {
return vec![];
};
let name = url.file_name().unwrap();
self.from_dir(&p).map(|u| u.join(name)).collect()
}
}

View file

@ -88,7 +88,7 @@ impl Watcher {
let futs: Vec<_> = folders
.iter()
.filter(|&f| f.url.scheme.is_builtin())
.filter(|&f| f.url.is_internal())
.map(|&f| go(f.url.to_owned(), f.cha))
.collect();

View file

@ -38,10 +38,10 @@ impl Preview {
pub fn go_folder(&mut self, file: File, dir: Option<Cha>, force: bool) {
let same = self.same_file(&file, MIME_DIR);
let (wd, cha, builtin) = (file.url_owned(), file.cha, file.url.scheme.is_builtin());
let (wd, cha, internal) = (file.url_owned(), file.cha, file.url.is_internal());
self.go(file, Cow::Borrowed(MIME_DIR), force);
if same || !builtin {
if same || !internal {
return;
}

View file

@ -4,7 +4,7 @@ version = "25.6.11"
edition = "2024"
license = "MIT"
authors = [ "sxyazi <sxyazi@gmail.com>" ]
description = "Yazi File Manager"
description = "Yazi file manager"
homepage = "https://yazi-rs.github.io"
repository = "https://github.com/sxyazi/yazi"
@ -58,9 +58,6 @@ tracing = { workspace = true }
tracing-appender = "0.2.3"
tracing-subscriber = { version = "0.3.19", features = [ "env-filter" ] }
[target.aarch64-apple-darwin]
rustflags = [ "-Ctarget-cpu=apple-m1" ]
[target."cfg(unix)".dependencies]
libc = { workspace = true }
signal-hook-tokio = { version = "0.3.1", features = [ "futures-v0_3" ] }
@ -71,11 +68,6 @@ crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }
[target.'cfg(all(not(target_os = "macos"), not(target_os = "windows")))'.dependencies]
tikv-jemallocator = "0.6.0"
[env]
MACOSX_DEPLOYMENT_TARGET = "10.11"
JEMALLOC_SYS_WITH_LG_PAGE = "16"
JEMALLOC_SYS_WITH_MALLOC_CONF = "narenas:1"
[[bin]]
name = "yazi"
path = "src/main.rs"

View file

@ -15,5 +15,15 @@ fn main() -> Result<(), Box<dyn Error>> {
);
}
let manifest = env::var_os("CARGO_MANIFEST_DIR").unwrap().to_string_lossy().replace(r"\", "/");
if env::var_os("YAZI_CRATE_BUILD").is_none()
&& (manifest.contains("/git/checkouts/yazi-")
|| manifest.contains("/registry/src/index.crates.io-"))
{
panic!(
"Due to Cargo's limitations, the `yazi-fm` and `yazi-cli` crates on crates.io must be built with `cargo install --force yazi-build`"
);
}
Ok(())
}

View file

@ -45,8 +45,8 @@ impl File {
pub fn hash_u64(&self) -> u64 { foldhash::fast::FixedState::default().hash_one(self) }
#[inline]
pub fn rebase(&self, parent: &Url) -> Self {
Self { url: self.url.rebase(parent), cha: self.cha, link_to: self.link_to.clone() }
pub fn chdir(&self, wd: &Url) -> Self {
Self { url: self.url.rebase(wd), cha: self.cha, link_to: self.link_to.clone() }
}
}

View file

@ -95,26 +95,26 @@ impl FilesOp {
}
}
pub fn rebase(&self, new: &Url) -> Self {
pub fn chdir(&self, wd: &Url) -> Self {
macro_rules! files {
($files:expr) => {{ $files.iter().map(|f| f.rebase(new)).collect() }};
($files:expr) => {{ $files.iter().map(|file| file.chdir(wd)).collect() }};
}
macro_rules! map {
($map:expr) => {{ $map.iter().map(|(u, f)| (u.clone(), f.rebase(new))).collect() }};
($map:expr) => {{ $map.iter().map(|(urn, file)| (urn.clone(), file.chdir(wd))).collect() }};
}
let n = new.clone();
let w = wd.clone();
match self {
Self::Full(_, files, cha) => Self::Full(n, files!(files), *cha),
Self::Part(_, files, ticket) => Self::Part(n, files!(files), *ticket),
Self::Done(_, cha, ticket) => Self::Done(n, *cha, *ticket),
Self::Size(_, map) => Self::Size(n, map.iter().map(|(u, &s)| (u.clone(), s)).collect()),
Self::IOErr(_, err) => Self::IOErr(n, *err),
Self::Full(_, files, cha) => Self::Full(w, files!(files), *cha),
Self::Part(_, files, ticket) => Self::Part(w, files!(files), *ticket),
Self::Done(_, cha, ticket) => Self::Done(w, *cha, *ticket),
Self::Size(_, map) => Self::Size(w, map.iter().map(|(urn, &s)| (urn.clone(), s)).collect()),
Self::IOErr(_, err) => Self::IOErr(w, *err),
Self::Creating(_, files) => Self::Creating(n, files!(files)),
Self::Deleting(_, urns) => Self::Deleting(n, urns.clone()),
Self::Updating(_, map) => Self::Updating(n, map!(map)),
Self::Upserting(_, map) => Self::Upserting(n, map!(map)),
Self::Creating(_, files) => Self::Creating(w, files!(files)),
Self::Deleting(_, urns) => Self::Deleting(w, urns.clone()),
Self::Updating(_, map) => Self::Updating(w, map!(map)),
Self::Upserting(_, map) => Self::Upserting(w, map!(map)),
}
}

View file

@ -200,6 +200,13 @@ impl Loc {
})
}
#[inline]
pub fn rebase(&self, base: &Path) -> Self {
let mut loc: Self = base.join(self.uri()).into();
(loc.uri, loc.urn) = (self.uri, self.urn);
loc
}
#[inline]
pub fn has_base(&self) -> bool { self.bytes().len() != self.uri }
@ -215,15 +222,6 @@ impl Loc {
#[inline]
pub fn has_trail(&self) -> bool { self.bytes().len() != self.urn }
#[inline]
pub fn rebase(&self, parent: &Path) -> Self {
debug_assert!(self.uri == self.name().len());
let path = parent.join(self.name());
debug_assert!(path.file_name().is_some_and(|s| s.len() == self.name().len()));
Self { inner: path, uri: self.uri, urn: self.uri }
}
#[inline]
pub fn to_path(&self) -> PathBuf { self.inner.clone() }

View file

@ -89,14 +89,6 @@ impl Scheme {
if self.is_virtual() || other.is_virtual() { self == other } else { true }
}
#[inline]
pub fn is_builtin(&self) -> bool {
match self {
Self::Regular | Self::Search(_) | Self::Sftp(_) => true,
Self::Archive(_) => false,
}
}
#[inline]
pub fn is_virtual(&self) -> bool {
match self {

View file

@ -25,3 +25,7 @@ impl Deref for Uri {
fn deref(&self) -> &Self::Target { &self.0 }
}
impl AsRef<Path> for Uri {
fn as_ref(&self) -> &Path { &self.0 }
}

View file

@ -205,18 +205,17 @@ impl Url {
#[inline]
pub fn set_name(&mut self, name: impl AsRef<OsStr>) { self.loc.set_name(name); }
#[inline]
pub fn rebase(&self, base: &Path) -> Self {
Self { loc: self.loc.rebase(base), scheme: self.scheme.clone() }
}
#[inline]
pub fn pair(&self) -> Option<(Self, UrnBuf)> { Some((self.parent_url()?, self.loc.urn_owned())) }
#[inline]
pub fn hash_u64(&self) -> u64 { foldhash::fast::FixedState::default().hash_one(self) }
#[inline]
pub fn rebase(&self, parent: &Path) -> Self {
debug_assert!(self.is_regular());
self.loc.rebase(parent).into()
}
pub fn parse(bytes: &[u8]) -> Result<(Scheme, PathBuf, Option<(usize, usize)>)> {
let mut skip = 0;
let (scheme, tilde, uri, urn) = Scheme::parse(bytes, &mut skip)?;
@ -278,6 +277,16 @@ impl Url {
#[inline]
pub fn is_archive(&self) -> bool { matches!(self.scheme, Scheme::Archive(_)) }
// --- Internal
#[inline]
pub fn is_internal(&self) -> bool {
match self.scheme {
Scheme::Regular | Scheme::Sftp(_) => true,
Scheme::Search(_) => !self.loc.uri().is_empty(),
Scheme::Archive(_) => false,
}
}
// FIXME: remove
#[inline]
pub fn into_path2(self) -> PathBuf { self.loc.into_path() }

View file

@ -25,7 +25,7 @@ impl Clipboard {
let all = [
("pbpaste", &[][..]),
("termux-clipboard-get", &[]),
("wl-paste", &[]),
("wl-paste", &["-n"]),
("xclip", &["-o", "-selection", "clipboard"]),
("xsel", &["-ob"]),
];