mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
refactor: eliminate the old kitty graphics protocol
This commit is contained in:
parent
31ee5ab546
commit
d2963b23a9
8 changed files with 188 additions and 264 deletions
|
|
@ -7,7 +7,7 @@ use tracing::warn;
|
||||||
use yazi_config::PREVIEW;
|
use yazi_config::PREVIEW;
|
||||||
use yazi_shared::{env_exists, RoCell};
|
use yazi_shared::{env_exists, RoCell};
|
||||||
|
|
||||||
use super::{Iterm2, Kitty, KittyOld};
|
use super::{Iterm2, Kitty};
|
||||||
use crate::{ueberzug::Ueberzug, Sixel, TMUX};
|
use crate::{ueberzug::Ueberzug, Sixel, TMUX};
|
||||||
|
|
||||||
static IMAGE_SHOWN: AtomicBool = AtomicBool::new(false);
|
static IMAGE_SHOWN: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
@ -18,7 +18,6 @@ static UEBERZUG: RoCell<Option<UnboundedSender<Option<(PathBuf, Rect)>>>> = RoCe
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
pub enum Adaptor {
|
pub enum Adaptor {
|
||||||
Kitty,
|
Kitty,
|
||||||
KittyOld,
|
|
||||||
Iterm2,
|
Iterm2,
|
||||||
Sixel,
|
Sixel,
|
||||||
|
|
||||||
|
|
@ -86,7 +85,7 @@ impl Adaptor {
|
||||||
Emulator::Kitty => vec![Self::Kitty],
|
Emulator::Kitty => vec![Self::Kitty],
|
||||||
Emulator::Konsole => vec![Self::Kitty, Self::Iterm2, Self::Sixel],
|
Emulator::Konsole => vec![Self::Kitty, Self::Iterm2, Self::Sixel],
|
||||||
Emulator::Iterm2 => vec![Self::Iterm2, Self::Sixel],
|
Emulator::Iterm2 => vec![Self::Iterm2, Self::Sixel],
|
||||||
Emulator::WezTerm => vec![Self::KittyOld, Self::Iterm2, Self::Sixel],
|
Emulator::WezTerm => vec![Self::Iterm2, Self::Sixel],
|
||||||
Emulator::Foot => vec![Self::Sixel],
|
Emulator::Foot => vec![Self::Sixel],
|
||||||
Emulator::BlackBox => vec![Self::Sixel],
|
Emulator::BlackBox => vec![Self::Sixel],
|
||||||
Emulator::VSCode => vec![Self::Sixel],
|
Emulator::VSCode => vec![Self::Sixel],
|
||||||
|
|
@ -100,9 +99,6 @@ impl Adaptor {
|
||||||
if env_exists("ZELLIJ_SESSION_NAME") {
|
if env_exists("ZELLIJ_SESSION_NAME") {
|
||||||
protocols.retain(|p| *p == Self::Sixel);
|
protocols.retain(|p| *p == Self::Sixel);
|
||||||
}
|
}
|
||||||
if *TMUX && protocols.len() > 1 {
|
|
||||||
protocols.retain(|p| *p != Self::KittyOld);
|
|
||||||
}
|
|
||||||
if let Some(p) = protocols.first() {
|
if let Some(p) = protocols.first() {
|
||||||
return *p;
|
return *p;
|
||||||
}
|
}
|
||||||
|
|
@ -152,7 +148,6 @@ impl ToString for Adaptor {
|
||||||
fn to_string(&self) -> String {
|
fn to_string(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
Self::Kitty => "kitty",
|
Self::Kitty => "kitty",
|
||||||
Self::KittyOld => "kitty",
|
|
||||||
Self::Iterm2 => "iterm2",
|
Self::Iterm2 => "iterm2",
|
||||||
Self::Sixel => "sixel",
|
Self::Sixel => "sixel",
|
||||||
Self::X11 => "x11",
|
Self::X11 => "x11",
|
||||||
|
|
@ -179,7 +174,6 @@ impl Adaptor {
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Self::Kitty => Kitty::image_show(path, rect).await,
|
Self::Kitty => Kitty::image_show(path, rect).await,
|
||||||
Self::KittyOld => KittyOld::image_show(path, rect).await,
|
|
||||||
Self::Iterm2 => Iterm2::image_show(path, rect).await,
|
Self::Iterm2 => Iterm2::image_show(path, rect).await,
|
||||||
Self::Sixel => Sixel::image_show(path, rect).await,
|
Self::Sixel => Sixel::image_show(path, rect).await,
|
||||||
_ => Ok(if let Some(tx) = &*UEBERZUG {
|
_ => Ok(if let Some(tx) = &*UEBERZUG {
|
||||||
|
|
@ -195,7 +189,6 @@ impl Adaptor {
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Self::Kitty => Kitty::image_hide(rect),
|
Self::Kitty => Kitty::image_hide(rect),
|
||||||
Self::KittyOld => KittyOld::image_hide(),
|
|
||||||
Self::Iterm2 => Iterm2::image_hide(rect),
|
Self::Iterm2 => Iterm2::image_hide(rect),
|
||||||
Self::Sixel => Sixel::image_hide(rect),
|
Self::Sixel => Sixel::image_hide(rect),
|
||||||
_ => Ok(if let Some(tx) = &*UEBERZUG {
|
_ => Ok(if let Some(tx) = &*UEBERZUG {
|
||||||
|
|
@ -206,6 +199,6 @@ impl Adaptor {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(super) fn needs_ueberzug(self) -> bool {
|
pub(super) fn needs_ueberzug(self) -> bool {
|
||||||
!matches!(self, Self::Kitty | Self::KittyOld | Self::Iterm2 | Self::Sixel)
|
!matches!(self, Self::Kitty | Self::Iterm2 | Self::Sixel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,76 +0,0 @@
|
||||||
use std::{io::{stdout, Write}, path::Path};
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use base64::{engine::general_purpose, Engine};
|
|
||||||
use image::DynamicImage;
|
|
||||||
use ratatui::prelude::Rect;
|
|
||||||
use yazi_shared::Term;
|
|
||||||
|
|
||||||
use super::image::Image;
|
|
||||||
use crate::{CLOSE, ESCAPE, START};
|
|
||||||
|
|
||||||
pub(super) struct KittyOld;
|
|
||||||
|
|
||||||
impl KittyOld {
|
|
||||||
pub(super) async fn image_show(path: &Path, rect: Rect) -> Result<()> {
|
|
||||||
let img = Image::downscale(path, (rect.width, rect.height)).await?;
|
|
||||||
let b = Self::encode(img).await?;
|
|
||||||
|
|
||||||
Self::image_hide()?;
|
|
||||||
Term::move_lock(stdout().lock(), (rect.x, rect.y), |stdout| Ok(stdout.write_all(&b)?))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(super) fn image_hide() -> Result<()> {
|
|
||||||
let mut stdout = stdout().lock();
|
|
||||||
stdout.write_all(format!("{}_Gq=1,a=d,d=A{}\\{}", START, ESCAPE, CLOSE).as_bytes())?;
|
|
||||||
stdout.flush()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn encode(img: DynamicImage) -> Result<Vec<u8>> {
|
|
||||||
fn output(raw: &[u8], format: u8, size: (u32, u32)) -> Result<Vec<u8>> {
|
|
||||||
let b64 = general_purpose::STANDARD.encode(raw).chars().collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let mut it = b64.chunks(4096).peekable();
|
|
||||||
let mut buf = Vec::with_capacity(b64.len() + it.len() * 50);
|
|
||||||
if let Some(first) = it.next() {
|
|
||||||
write!(
|
|
||||||
buf,
|
|
||||||
"{}_Gq=1,a=T,z=-1,C=1,f={},s={},v={},m={};{}{}\\{}",
|
|
||||||
START,
|
|
||||||
format,
|
|
||||||
size.0,
|
|
||||||
size.1,
|
|
||||||
it.peek().is_some() as u8,
|
|
||||||
first.iter().collect::<String>(),
|
|
||||||
ESCAPE,
|
|
||||||
CLOSE
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
while let Some(chunk) = it.next() {
|
|
||||||
write!(
|
|
||||||
buf,
|
|
||||||
"{}_Gm={};{}{}\\{}",
|
|
||||||
START,
|
|
||||||
it.peek().is_some() as u8,
|
|
||||||
chunk.iter().collect::<String>(),
|
|
||||||
ESCAPE,
|
|
||||||
CLOSE
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.write_all(CLOSE.as_bytes())?;
|
|
||||||
Ok(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
let size = (img.width(), img.height());
|
|
||||||
tokio::task::spawn_blocking(move || match img {
|
|
||||||
DynamicImage::ImageRgb8(v) => output(v.as_raw(), 24, size),
|
|
||||||
DynamicImage::ImageRgba8(v) => output(v.as_raw(), 32, size),
|
|
||||||
v => output(v.to_rgb8().as_raw(), 24, size),
|
|
||||||
})
|
|
||||||
.await?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -4,14 +4,12 @@ mod adaptor;
|
||||||
mod image;
|
mod image;
|
||||||
mod iterm2;
|
mod iterm2;
|
||||||
mod kitty;
|
mod kitty;
|
||||||
mod kitty_old;
|
|
||||||
mod sixel;
|
mod sixel;
|
||||||
mod ueberzug;
|
mod ueberzug;
|
||||||
|
|
||||||
use adaptor::*;
|
use adaptor::*;
|
||||||
use iterm2::*;
|
use iterm2::*;
|
||||||
use kitty::*;
|
use kitty::*;
|
||||||
use kitty_old::*;
|
|
||||||
use sixel::*;
|
use sixel::*;
|
||||||
use yazi_shared::{env_exists, RoCell};
|
use yazi_shared::{env_exists, RoCell};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use std::{env, ffi::OsString, fs, path::PathBuf, process};
|
use std::{ffi::OsString, fs, path::PathBuf, process};
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use yazi_shared::expand_path;
|
use yazi_shared::{current_cwd, expand_path};
|
||||||
|
|
||||||
use super::cli::Args;
|
use super::cli::Args;
|
||||||
use crate::{Xdg, PREVIEW};
|
use crate::{Xdg, PREVIEW};
|
||||||
|
|
@ -21,7 +21,7 @@ impl Boot {
|
||||||
fn parse_entry(entry: Option<PathBuf>) -> (PathBuf, Option<OsString>) {
|
fn parse_entry(entry: Option<PathBuf>) -> (PathBuf, Option<OsString>) {
|
||||||
let entry = match entry {
|
let entry = match entry {
|
||||||
Some(p) => expand_path(p),
|
Some(p) => expand_path(p),
|
||||||
None => return (env::current_dir().unwrap(), None),
|
None => return (current_cwd().unwrap(), None),
|
||||||
};
|
};
|
||||||
|
|
||||||
let parent = entry.parent();
|
let parent = entry.parent();
|
||||||
|
|
|
||||||
|
|
@ -1,171 +1,2 @@
|
||||||
use std::{borrow::Cow, env, ffi::OsString, path::{Component, Path, PathBuf, MAIN_SEPARATOR}};
|
|
||||||
|
|
||||||
use tokio::fs;
|
|
||||||
|
|
||||||
use crate::Url;
|
|
||||||
|
|
||||||
fn _expand_path(p: &Path) -> PathBuf {
|
|
||||||
// ${HOME} or $HOME
|
|
||||||
#[cfg(unix)]
|
|
||||||
let re = regex::Regex::new(r"\$(?:\{([^}]+)\}|([a-zA-Z\d_]+))").unwrap();
|
|
||||||
|
|
||||||
// %USERPROFILE%
|
|
||||||
#[cfg(windows)]
|
|
||||||
let re = regex::Regex::new(r"%([^%]+)%").unwrap();
|
|
||||||
|
|
||||||
let s = p.to_string_lossy();
|
|
||||||
let s = re.replace_all(&s, |caps: ®ex::Captures| {
|
|
||||||
let name = caps.get(2).or_else(|| caps.get(1)).unwrap();
|
|
||||||
env::var(name.as_str()).unwrap_or_else(|_| caps.get(0).unwrap().as_str().to_owned())
|
|
||||||
});
|
|
||||||
|
|
||||||
let p = Path::new(s.as_ref());
|
|
||||||
if let Ok(rest) = p.strip_prefix("~") {
|
|
||||||
#[cfg(unix)]
|
|
||||||
let home = env::var_os("HOME");
|
|
||||||
#[cfg(windows)]
|
|
||||||
let home = env::var_os("USERPROFILE");
|
|
||||||
|
|
||||||
return if let Some(p) = home { PathBuf::from(p).join(rest) } else { rest.to_path_buf() };
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.is_absolute() {
|
|
||||||
return p.to_path_buf();
|
|
||||||
}
|
|
||||||
env::current_dir().map_or_else(|_| p.to_path_buf(), |c| c.join(p))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn expand_path(p: impl AsRef<Path>) -> PathBuf { _expand_path(p.as_ref()) }
|
pub fn env_exists(name: &str) -> bool { std::env::var_os(name).is_some_and(|s| !s.is_empty()) }
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn ends_with_slash(p: &Path) -> bool {
|
|
||||||
// TODO: uncomment this when Rust 1.74 is released
|
|
||||||
// let b = p.as_os_str().as_encoded_bytes();
|
|
||||||
// if let [.., last] = b { *last == MAIN_SEPARATOR as u8 } else { false }
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
{
|
|
||||||
use std::os::unix::ffi::OsStrExt;
|
|
||||||
let b = p.as_os_str().as_bytes();
|
|
||||||
if let [.., last] = b { *last == MAIN_SEPARATOR as u8 } else { false }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
{
|
|
||||||
let s = p.to_string_lossy();
|
|
||||||
let b = s.as_bytes();
|
|
||||||
if let [.., last] = b { *last == MAIN_SEPARATOR as u8 } else { false }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn unique_path(mut p: Url) -> Url {
|
|
||||||
let Some(stem) = p.file_stem().map(|s| s.to_owned()) else {
|
|
||||||
return p;
|
|
||||||
};
|
|
||||||
|
|
||||||
let ext = p
|
|
||||||
.extension()
|
|
||||||
.map(|s| {
|
|
||||||
let mut n = OsString::with_capacity(s.len() + 1);
|
|
||||||
n.push(".");
|
|
||||||
n.push(s);
|
|
||||||
n
|
|
||||||
})
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let mut i = 0;
|
|
||||||
while fs::symlink_metadata(&p).await.is_ok() {
|
|
||||||
i += 1;
|
|
||||||
|
|
||||||
let mut name = OsString::with_capacity(stem.len() + ext.len() + 5);
|
|
||||||
name.push(&stem);
|
|
||||||
name.push(format!("_{i}"));
|
|
||||||
if !ext.is_empty() {
|
|
||||||
name.push(&ext);
|
|
||||||
}
|
|
||||||
|
|
||||||
p.set_file_name(name);
|
|
||||||
}
|
|
||||||
p
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parameters
|
|
||||||
// * `path`: The absolute path(contains no `/./`) to get relative path.
|
|
||||||
// * `root`: The absolute path(contains no `/./`) to be compared.
|
|
||||||
//
|
|
||||||
// Return
|
|
||||||
// * Unix: The relative format to `root` of `path`.
|
|
||||||
// * Windows: The relative format to `root` of `path`; or `path` itself when
|
|
||||||
// `path` and `root` are both under different disk drives.
|
|
||||||
pub fn path_relative_to<'a>(path: &'a Path, root: &Path) -> Cow<'a, Path> {
|
|
||||||
assert!(path.is_absolute());
|
|
||||||
assert!(root.is_absolute());
|
|
||||||
let mut p_comps = path.components();
|
|
||||||
let mut r_comps = root.components();
|
|
||||||
|
|
||||||
// 1. Ensure that the two paths have the same prefix.
|
|
||||||
// 2. Strips any common prefix the two paths do have.
|
|
||||||
//
|
|
||||||
// NOTE:
|
|
||||||
// Prefixes are platform dependent,
|
|
||||||
// but different prefixes would for example indicate paths for different drives
|
|
||||||
// on Windows.
|
|
||||||
let (p_head, r_head) = loop {
|
|
||||||
use std::path::Component::*;
|
|
||||||
match (p_comps.next(), r_comps.next()) {
|
|
||||||
(Some(RootDir), Some(RootDir)) => (),
|
|
||||||
(Some(Prefix(a)), Some(Prefix(b))) if a == b => (),
|
|
||||||
(Some(Prefix(_) | RootDir), _) | (_, Some(Prefix(_) | RootDir)) => {
|
|
||||||
return Cow::from(path);
|
|
||||||
}
|
|
||||||
(None, None) => break (None, None),
|
|
||||||
(a, b) if a != b => break (a, b),
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let p_comps = p_head.into_iter().chain(p_comps);
|
|
||||||
let walk_up = r_head.into_iter().chain(r_comps).map(|_| Component::ParentDir);
|
|
||||||
|
|
||||||
let mut buf = PathBuf::new();
|
|
||||||
buf.extend(walk_up);
|
|
||||||
buf.extend(p_comps);
|
|
||||||
|
|
||||||
Cow::from(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use std::{borrow::Cow, path::Path};
|
|
||||||
|
|
||||||
use super::path_relative_to;
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
#[test]
|
|
||||||
fn test_path_relative_to() {
|
|
||||||
fn assert(path: &str, root: &str, res: &str) {
|
|
||||||
assert_eq!(path_relative_to(Path::new(path), Path::new(root)), Cow::Borrowed(Path::new(res)));
|
|
||||||
}
|
|
||||||
|
|
||||||
assert("/a/b", "/a/b/c", "../");
|
|
||||||
assert("/a/b/c", "/a/b", "c");
|
|
||||||
assert("/a/b/c", "/a/b/d", "../c");
|
|
||||||
assert("/a", "/a/b/c", "../../");
|
|
||||||
assert("/a/a/b", "/a/b/b", "../../a/b");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
#[test]
|
|
||||||
fn test_path_relative_to() {
|
|
||||||
fn assert(path: &str, root: &str, res: &str) {
|
|
||||||
assert_eq!(path_relative_to(Path::new(path), Path::new(root)), Cow::Borrowed(Path::new(res)));
|
|
||||||
}
|
|
||||||
|
|
||||||
assert("C:\\a\\b", "C:\\a\\b\\c", "..\\");
|
|
||||||
assert("C:\\a\\b\\c", "C:\\a\\b", "c");
|
|
||||||
assert("C:\\a\\b\\c", "C:\\a\\b\\d", "..\\c");
|
|
||||||
assert("C:\\a", "C:\\a\\b\\c", "..\\..\\");
|
|
||||||
assert("C:\\a\\a\\b", "C:\\a\\b\\b", "..\\..\\a\\b");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,6 @@ use std::{collections::VecDeque, path::{Path, PathBuf}};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tokio::{fs, io, select, sync::{mpsc, oneshot}, time};
|
use tokio::{fs, io, select, sync::{mpsc, oneshot}, time};
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn env_exists(name: &str) -> bool { std::env::var_os(name).is_some_and(|s| !s.is_empty()) }
|
|
||||||
|
|
||||||
pub async fn calculate_size(path: &Path) -> u64 {
|
pub async fn calculate_size(path: &Path) -> u64 {
|
||||||
let mut total = 0;
|
let mut total = 0;
|
||||||
let mut stack = VecDeque::from([path.to_path_buf()]);
|
let mut stack = VecDeque::from([path.to_path_buf()]);
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ mod fns;
|
||||||
mod fs;
|
mod fs;
|
||||||
mod mime;
|
mod mime;
|
||||||
mod natsort;
|
mod natsort;
|
||||||
|
mod path;
|
||||||
mod ro_cell;
|
mod ro_cell;
|
||||||
mod term;
|
mod term;
|
||||||
mod throttle;
|
mod throttle;
|
||||||
|
|
@ -24,6 +25,7 @@ pub use fns::*;
|
||||||
pub use fs::*;
|
pub use fs::*;
|
||||||
pub use mime::*;
|
pub use mime::*;
|
||||||
pub use natsort::*;
|
pub use natsort::*;
|
||||||
|
pub use path::*;
|
||||||
pub use ro_cell::*;
|
pub use ro_cell::*;
|
||||||
pub use term::*;
|
pub use term::*;
|
||||||
pub use throttle::*;
|
pub use throttle::*;
|
||||||
|
|
|
||||||
179
yazi-shared/src/path.rs
Normal file
179
yazi-shared/src/path.rs
Normal file
|
|
@ -0,0 +1,179 @@
|
||||||
|
use std::{borrow::Cow, env, ffi::OsString, path::{Component, Path, PathBuf, MAIN_SEPARATOR}};
|
||||||
|
|
||||||
|
use tokio::fs;
|
||||||
|
|
||||||
|
use crate::Url;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn current_cwd() -> Option<PathBuf> {
|
||||||
|
env::var_os("PWD")
|
||||||
|
.map(PathBuf::from)
|
||||||
|
.filter(|p| p.is_absolute())
|
||||||
|
.or_else(|| env::current_dir().ok())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _expand_path(p: &Path) -> PathBuf {
|
||||||
|
// ${HOME} or $HOME
|
||||||
|
#[cfg(unix)]
|
||||||
|
let re = regex::Regex::new(r"\$(?:\{([^}]+)\}|([a-zA-Z\d_]+))").unwrap();
|
||||||
|
|
||||||
|
// %USERPROFILE%
|
||||||
|
#[cfg(windows)]
|
||||||
|
let re = regex::Regex::new(r"%([^%]+)%").unwrap();
|
||||||
|
|
||||||
|
let s = p.to_string_lossy();
|
||||||
|
let s = re.replace_all(&s, |caps: ®ex::Captures| {
|
||||||
|
let name = caps.get(2).or_else(|| caps.get(1)).unwrap();
|
||||||
|
env::var(name.as_str()).unwrap_or_else(|_| caps.get(0).unwrap().as_str().to_owned())
|
||||||
|
});
|
||||||
|
|
||||||
|
let p = Path::new(s.as_ref());
|
||||||
|
if let Ok(rest) = p.strip_prefix("~") {
|
||||||
|
#[cfg(unix)]
|
||||||
|
let home = env::var_os("HOME");
|
||||||
|
#[cfg(windows)]
|
||||||
|
let home = env::var_os("USERPROFILE");
|
||||||
|
|
||||||
|
return if let Some(p) = home { PathBuf::from(p).join(rest) } else { rest.to_path_buf() };
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.is_absolute() {
|
||||||
|
return p.to_path_buf();
|
||||||
|
}
|
||||||
|
current_cwd().map_or_else(|| p.to_path_buf(), |c| c.join(p))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn expand_path(p: impl AsRef<Path>) -> PathBuf { _expand_path(p.as_ref()) }
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn ends_with_slash(p: &Path) -> bool {
|
||||||
|
// TODO: uncomment this when Rust 1.74 is released
|
||||||
|
// let b = p.as_os_str().as_encoded_bytes();
|
||||||
|
// if let [.., last] = b { *last == MAIN_SEPARATOR as u8 } else { false }
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
let b = p.as_os_str().as_bytes();
|
||||||
|
if let [.., last] = b { *last == MAIN_SEPARATOR as u8 } else { false }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
let s = p.to_string_lossy();
|
||||||
|
let b = s.as_bytes();
|
||||||
|
if let [.., last] = b { *last == MAIN_SEPARATOR as u8 } else { false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn unique_path(mut p: Url) -> Url {
|
||||||
|
let Some(stem) = p.file_stem().map(|s| s.to_owned()) else {
|
||||||
|
return p;
|
||||||
|
};
|
||||||
|
|
||||||
|
let ext = p
|
||||||
|
.extension()
|
||||||
|
.map(|s| {
|
||||||
|
let mut n = OsString::with_capacity(s.len() + 1);
|
||||||
|
n.push(".");
|
||||||
|
n.push(s);
|
||||||
|
n
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let mut i = 0;
|
||||||
|
while fs::symlink_metadata(&p).await.is_ok() {
|
||||||
|
i += 1;
|
||||||
|
|
||||||
|
let mut name = OsString::with_capacity(stem.len() + ext.len() + 5);
|
||||||
|
name.push(&stem);
|
||||||
|
name.push(format!("_{i}"));
|
||||||
|
if !ext.is_empty() {
|
||||||
|
name.push(&ext);
|
||||||
|
}
|
||||||
|
|
||||||
|
p.set_file_name(name);
|
||||||
|
}
|
||||||
|
p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parameters
|
||||||
|
// * `path`: The absolute path(contains no `/./`) to get relative path.
|
||||||
|
// * `root`: The absolute path(contains no `/./`) to be compared.
|
||||||
|
//
|
||||||
|
// Return
|
||||||
|
// * Unix: The relative format to `root` of `path`.
|
||||||
|
// * Windows: The relative format to `root` of `path`; or `path` itself when
|
||||||
|
// `path` and `root` are both under different disk drives.
|
||||||
|
pub fn path_relative_to<'a>(path: &'a Path, root: &Path) -> Cow<'a, Path> {
|
||||||
|
assert!(path.is_absolute());
|
||||||
|
assert!(root.is_absolute());
|
||||||
|
let mut p_comps = path.components();
|
||||||
|
let mut r_comps = root.components();
|
||||||
|
|
||||||
|
// 1. Ensure that the two paths have the same prefix.
|
||||||
|
// 2. Strips any common prefix the two paths do have.
|
||||||
|
//
|
||||||
|
// NOTE:
|
||||||
|
// Prefixes are platform dependent,
|
||||||
|
// but different prefixes would for example indicate paths for different drives
|
||||||
|
// on Windows.
|
||||||
|
let (p_head, r_head) = loop {
|
||||||
|
use std::path::Component::*;
|
||||||
|
match (p_comps.next(), r_comps.next()) {
|
||||||
|
(Some(RootDir), Some(RootDir)) => (),
|
||||||
|
(Some(Prefix(a)), Some(Prefix(b))) if a == b => (),
|
||||||
|
(Some(Prefix(_) | RootDir), _) | (_, Some(Prefix(_) | RootDir)) => {
|
||||||
|
return Cow::from(path);
|
||||||
|
}
|
||||||
|
(None, None) => break (None, None),
|
||||||
|
(a, b) if a != b => break (a, b),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let p_comps = p_head.into_iter().chain(p_comps);
|
||||||
|
let walk_up = r_head.into_iter().chain(r_comps).map(|_| Component::ParentDir);
|
||||||
|
|
||||||
|
let mut buf = PathBuf::new();
|
||||||
|
buf.extend(walk_up);
|
||||||
|
buf.extend(p_comps);
|
||||||
|
|
||||||
|
Cow::from(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::{borrow::Cow, path::Path};
|
||||||
|
|
||||||
|
use super::path_relative_to;
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
#[test]
|
||||||
|
fn test_path_relative_to() {
|
||||||
|
fn assert(path: &str, root: &str, res: &str) {
|
||||||
|
assert_eq!(path_relative_to(Path::new(path), Path::new(root)), Cow::Borrowed(Path::new(res)));
|
||||||
|
}
|
||||||
|
|
||||||
|
assert("/a/b", "/a/b/c", "../");
|
||||||
|
assert("/a/b/c", "/a/b", "c");
|
||||||
|
assert("/a/b/c", "/a/b/d", "../c");
|
||||||
|
assert("/a", "/a/b/c", "../../");
|
||||||
|
assert("/a/a/b", "/a/b/b", "../../a/b");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
#[test]
|
||||||
|
fn test_path_relative_to() {
|
||||||
|
fn assert(path: &str, root: &str, res: &str) {
|
||||||
|
assert_eq!(path_relative_to(Path::new(path), Path::new(root)), Cow::Borrowed(Path::new(res)));
|
||||||
|
}
|
||||||
|
|
||||||
|
assert("C:\\a\\b", "C:\\a\\b\\c", "..\\");
|
||||||
|
assert("C:\\a\\b\\c", "C:\\a\\b", "c");
|
||||||
|
assert("C:\\a\\b\\c", "C:\\a\\b\\d", "..\\c");
|
||||||
|
assert("C:\\a", "C:\\a\\b\\c", "..\\..\\");
|
||||||
|
assert("C:\\a\\a\\b", "C:\\a\\b\\b", "..\\..\\a\\b");
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue