From d2963b23a9d6d398e6c14b13f6ae87cf4c82293f Mon Sep 17 00:00:00 2001 From: sxyazi Date: Sat, 18 Nov 2023 13:05:16 +0800 Subject: [PATCH] refactor: eliminate the old kitty graphics protocol --- yazi-adaptor/src/adaptor.rs | 13 +-- yazi-adaptor/src/kitty_old.rs | 76 --------------- yazi-adaptor/src/lib.rs | 2 - yazi-config/src/boot/boot.rs | 6 +- yazi-shared/src/fns.rs | 171 +------------------------------- yazi-shared/src/fs.rs | 3 - yazi-shared/src/lib.rs | 2 + yazi-shared/src/path.rs | 179 ++++++++++++++++++++++++++++++++++ 8 files changed, 188 insertions(+), 264 deletions(-) delete mode 100644 yazi-adaptor/src/kitty_old.rs create mode 100644 yazi-shared/src/path.rs diff --git a/yazi-adaptor/src/adaptor.rs b/yazi-adaptor/src/adaptor.rs index a00bbaf9..8dbb5a2b 100644 --- a/yazi-adaptor/src/adaptor.rs +++ b/yazi-adaptor/src/adaptor.rs @@ -7,7 +7,7 @@ use tracing::warn; use yazi_config::PREVIEW; use yazi_shared::{env_exists, RoCell}; -use super::{Iterm2, Kitty, KittyOld}; +use super::{Iterm2, Kitty}; use crate::{ueberzug::Ueberzug, Sixel, TMUX}; static IMAGE_SHOWN: AtomicBool = AtomicBool::new(false); @@ -18,7 +18,6 @@ static UEBERZUG: RoCell>>> = RoCe #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum Adaptor { Kitty, - KittyOld, Iterm2, Sixel, @@ -86,7 +85,7 @@ impl Adaptor { Emulator::Kitty => vec![Self::Kitty], Emulator::Konsole => vec![Self::Kitty, 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::BlackBox => vec![Self::Sixel], Emulator::VSCode => vec![Self::Sixel], @@ -100,9 +99,6 @@ impl Adaptor { if env_exists("ZELLIJ_SESSION_NAME") { protocols.retain(|p| *p == Self::Sixel); } - if *TMUX && protocols.len() > 1 { - protocols.retain(|p| *p != Self::KittyOld); - } if let Some(p) = protocols.first() { return *p; } @@ -152,7 +148,6 @@ impl ToString for Adaptor { fn to_string(&self) -> String { match self { Self::Kitty => "kitty", - Self::KittyOld => "kitty", Self::Iterm2 => "iterm2", Self::Sixel => "sixel", Self::X11 => "x11", @@ -179,7 +174,6 @@ impl Adaptor { match self { 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::Sixel => Sixel::image_show(path, rect).await, _ => Ok(if let Some(tx) = &*UEBERZUG { @@ -195,7 +189,6 @@ impl Adaptor { match self { Self::Kitty => Kitty::image_hide(rect), - Self::KittyOld => KittyOld::image_hide(), Self::Iterm2 => Iterm2::image_hide(rect), Self::Sixel => Sixel::image_hide(rect), _ => Ok(if let Some(tx) = &*UEBERZUG { @@ -206,6 +199,6 @@ impl Adaptor { #[inline] 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) } } diff --git a/yazi-adaptor/src/kitty_old.rs b/yazi-adaptor/src/kitty_old.rs deleted file mode 100644 index 72e9f2fa..00000000 --- a/yazi-adaptor/src/kitty_old.rs +++ /dev/null @@ -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> { - fn output(raw: &[u8], format: u8, size: (u32, u32)) -> Result> { - let b64 = general_purpose::STANDARD.encode(raw).chars().collect::>(); - - 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::(), - ESCAPE, - CLOSE - )?; - } - - while let Some(chunk) = it.next() { - write!( - buf, - "{}_Gm={};{}{}\\{}", - START, - it.peek().is_some() as u8, - chunk.iter().collect::(), - 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? - } -} diff --git a/yazi-adaptor/src/lib.rs b/yazi-adaptor/src/lib.rs index de261e47..0f3929a8 100644 --- a/yazi-adaptor/src/lib.rs +++ b/yazi-adaptor/src/lib.rs @@ -4,14 +4,12 @@ mod adaptor; mod image; mod iterm2; mod kitty; -mod kitty_old; mod sixel; mod ueberzug; use adaptor::*; use iterm2::*; use kitty::*; -use kitty_old::*; use sixel::*; use yazi_shared::{env_exists, RoCell}; diff --git a/yazi-config/src/boot/boot.rs b/yazi-config/src/boot/boot.rs index ce21ecd1..f207d350 100644 --- a/yazi-config/src/boot/boot.rs +++ b/yazi-config/src/boot/boot.rs @@ -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 yazi_shared::expand_path; +use yazi_shared::{current_cwd, expand_path}; use super::cli::Args; use crate::{Xdg, PREVIEW}; @@ -21,7 +21,7 @@ impl Boot { fn parse_entry(entry: Option) -> (PathBuf, Option) { let entry = match entry { Some(p) => expand_path(p), - None => return (env::current_dir().unwrap(), None), + None => return (current_cwd().unwrap(), None), }; let parent = entry.parent(); diff --git a/yazi-shared/src/fns.rs b/yazi-shared/src/fns.rs index f5d4eb90..ea959508 100644 --- a/yazi-shared/src/fns.rs +++ b/yazi-shared/src/fns.rs @@ -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] -pub fn expand_path(p: impl AsRef) -> 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"); - } -} +pub fn env_exists(name: &str) -> bool { std::env::var_os(name).is_some_and(|s| !s.is_empty()) } diff --git a/yazi-shared/src/fs.rs b/yazi-shared/src/fs.rs index 72e01be0..cfd1f6d7 100644 --- a/yazi-shared/src/fs.rs +++ b/yazi-shared/src/fs.rs @@ -3,9 +3,6 @@ use std::{collections::VecDeque, path::{Path, PathBuf}}; use anyhow::Result; 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 { let mut total = 0; let mut stack = VecDeque::from([path.to_path_buf()]); diff --git a/yazi-shared/src/lib.rs b/yazi-shared/src/lib.rs index 215001a3..1edcaaa2 100644 --- a/yazi-shared/src/lib.rs +++ b/yazi-shared/src/lib.rs @@ -9,6 +9,7 @@ mod fns; mod fs; mod mime; mod natsort; +mod path; mod ro_cell; mod term; mod throttle; @@ -24,6 +25,7 @@ pub use fns::*; pub use fs::*; pub use mime::*; pub use natsort::*; +pub use path::*; pub use ro_cell::*; pub use term::*; pub use throttle::*; diff --git a/yazi-shared/src/path.rs b/yazi-shared/src/path.rs new file mode 100644 index 00000000..1d197c8f --- /dev/null +++ b/yazi-shared/src/path.rs @@ -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 { + 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) -> 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"); + } +}