refactor: eliminate the old kitty graphics protocol

This commit is contained in:
sxyazi 2023-11-18 13:05:16 +08:00
parent 31ee5ab546
commit d2963b23a9
No known key found for this signature in database
8 changed files with 188 additions and 264 deletions

View file

@ -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<Option<UnboundedSender<Option<(PathBuf, Rect)>>>> = 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)
}
}

View file

@ -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?
}
}

View file

@ -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};

View file

@ -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>) -> (PathBuf, Option<OsString>) {
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();

View file

@ -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: &regex::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<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");
}
}
pub fn env_exists(name: &str) -> bool { std::env::var_os(name).is_some_and(|s| !s.is_empty()) }

View file

@ -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()]);

View file

@ -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::*;

179
yazi-shared/src/path.rs Normal file
View 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: &regex::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");
}
}