This commit is contained in:
sxyazi 2023-07-29 14:20:34 +08:00
parent 7488cb0eee
commit 3219297ffd
No known key found for this signature in database
12 changed files with 198 additions and 86 deletions

18
Cargo.lock generated
View file

@ -961,9 +961,9 @@ dependencies = [
[[package]] [[package]]
name = "matchit" name = "matchit"
version = "0.7.0" version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" checksum = "67827e6ea8ee8a7c4a72227ef4fc08957040acffdb5f122733b24fa12daff41b"
[[package]] [[package]]
name = "md5" name = "md5"
@ -1425,7 +1425,7 @@ checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
"regex-automata 0.3.3", "regex-automata 0.3.4",
"regex-syntax 0.7.4", "regex-syntax 0.7.4",
] ]
@ -1440,9 +1440,9 @@ dependencies = [
[[package]] [[package]]
name = "regex-automata" name = "regex-automata"
version = "0.3.3" version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@ -1502,18 +1502,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.176" version = "1.0.178"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76dc28c9523c5d70816e393136b86d48909cfb27cecaa902d338c19ed47164dc" checksum = "60363bdd39a7be0266a520dab25fdc9241d2f987b08a01e01f0ec6d06a981348"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.176" version = "1.0.178"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4e7b8c5dc823e3b90651ff1d3808419cd14e5ad76de04feaf37da114e7a306f" checksum = "f28482318d6641454cb273da158647922d1be6b5a2fcc6165cd89ebdd7ed576b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View file

@ -4,7 +4,6 @@ sort_reverse = true
show_hidden = false show_hidden = false
[preview] [preview]
adaptor = "kitty"
tab_size = 2 tab_size = 2
max_width = 600 max_width = 600
max_height = 900 max_height = 900

View file

@ -1 +1 @@
{"flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","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","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug"],"version":"0.2","language":"en"} {"flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","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","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp"],"language":"en","version":"0.2"}

View file

@ -1,21 +1,55 @@
use anyhow::bail; use std::env;
use serde::Deserialize;
#[derive(Debug, Deserialize, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
#[serde(try_from = "String")]
pub enum PreviewAdaptor { pub enum PreviewAdaptor {
Kitty, Kitty,
Ueberzug, Iterm2,
// Supported by Überzug++
X11,
Wayland,
Sixel,
Chafa,
} }
impl TryFrom<String> for PreviewAdaptor { impl Default for PreviewAdaptor {
type Error = anyhow::Error; fn default() -> Self {
match env::var("TERM").unwrap_or_default().as_str() {
fn try_from(value: String) -> Result<Self, Self::Error> { "wezterm" => return Self::Kitty,
Ok(match value.to_lowercase().as_str() { "xterm-kitty" => return Self::Kitty,
"kitty" => Self::Kitty, "iterm2" => return Self::Iterm2,
"ueberzug" => Self::Ueberzug, "foot" => return Self::Sixel,
_ => bail!("invalid preview adaptor: {}", value), _ => {}
}) }
match env::var("XDG_SESSION_TYPE").unwrap_or_default().as_str() {
"x11" => return Self::X11,
"wayland" => return Self::Wayland,
_ => Self::Chafa,
}
}
}
impl ToString for PreviewAdaptor {
fn to_string(&self) -> String {
match self {
PreviewAdaptor::Kitty => "kitty",
PreviewAdaptor::Iterm2 => "iterm2",
PreviewAdaptor::X11 => "x11",
PreviewAdaptor::Wayland => "wayland",
PreviewAdaptor::Sixel => "sixel",
PreviewAdaptor::Chafa => "chafa",
}
.to_string()
}
}
impl PreviewAdaptor {
#[inline]
pub fn needs_ueberzug(&self) -> bool {
match self {
PreviewAdaptor::Kitty => false,
PreviewAdaptor::Iterm2 => false,
_ => true,
}
} }
} }

View file

@ -5,6 +5,7 @@ use crate::config::MERGED_YAZI;
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Preview { pub struct Preview {
#[serde(skip)]
pub adaptor: PreviewAdaptor, pub adaptor: PreviewAdaptor,
pub tab_size: u32, pub tab_size: u32,

View file

@ -4,7 +4,7 @@ use anyhow::Result;
use ratatui::prelude::Rect; use ratatui::prelude::Rect;
use tokio::sync::mpsc::UnboundedSender; use tokio::sync::mpsc::UnboundedSender;
use super::{kitty::Kitty, ueberzug::Ueberzug}; use super::{iterm2::Iterm2, kitty::Kitty, ueberzug::Ueberzug};
use crate::config::{preview::PreviewAdaptor, PREVIEW}; use crate::config::{preview::PreviewAdaptor, PREVIEW};
pub struct Adaptor { pub struct Adaptor {
@ -15,7 +15,7 @@ impl Adaptor {
pub fn new() -> Self { pub fn new() -> Self {
let mut adaptor = Self { ueberzug: None }; let mut adaptor = Self { ueberzug: None };
if PREVIEW.adaptor == PreviewAdaptor::Ueberzug { if PREVIEW.adaptor.needs_ueberzug() {
adaptor.ueberzug = Ueberzug::init().ok(); adaptor.ueberzug = Ueberzug::init().ok();
} }
@ -25,7 +25,8 @@ impl Adaptor {
pub async fn image_show(&self, path: &Path, rect: Rect) -> Result<()> { pub async fn image_show(&self, path: &Path, rect: Rect) -> Result<()> {
match PREVIEW.adaptor { match PREVIEW.adaptor {
PreviewAdaptor::Kitty => Kitty::image_show(path, rect).await, PreviewAdaptor::Kitty => Kitty::image_show(path, rect).await,
PreviewAdaptor::Ueberzug => { PreviewAdaptor::Iterm2 => Iterm2::image_show(path, rect).await,
_ => {
if let Some(tx) = &self.ueberzug { if let Some(tx) = &self.ueberzug {
tx.send(Some((path.to_path_buf(), rect))).ok(); tx.send(Some((path.to_path_buf(), rect))).ok();
} }
@ -37,7 +38,8 @@ impl Adaptor {
pub fn image_hide(&self) { pub fn image_hide(&self) {
match PREVIEW.adaptor { match PREVIEW.adaptor {
PreviewAdaptor::Kitty => Kitty::image_hide(), PreviewAdaptor::Kitty => Kitty::image_hide(),
PreviewAdaptor::Ueberzug => { PreviewAdaptor::Iterm2 => {}
_ => {
if let Some(tx) = &self.ueberzug { if let Some(tx) = &self.ueberzug {
tx.send(None).ok(); tx.send(None).ok();
} }

31
src/core/adaptor/image.rs Normal file
View file

@ -0,0 +1,31 @@
use std::path::Path;
use anyhow::Result;
use image::{imageops::FilterType, DynamicImage};
use tokio::fs;
use crate::{config::PREVIEW, misc::tty_ratio};
pub(super) struct Image;
impl Image {
pub(super) async fn resize(path: &Path, size: (u16, u16)) -> Result<DynamicImage> {
let (w, h) = {
let r = tty_ratio();
let (w, h) = ((size.0 as f64 * r.0) as u32, (size.1 as f64 * r.1) as u32);
(w.min(PREVIEW.max_width), h.min(PREVIEW.max_height))
};
let img = fs::read(path).await?;
let img = tokio::task::spawn_blocking(move || -> Result<DynamicImage> {
let img = image::load_from_memory(&img)?;
Ok(if img.width() > w || img.height() > h {
img.resize(w, h, FilterType::Triangle)
} else {
img
})
});
Ok(img.await??)
}
}

View file

@ -0,0 +1,50 @@
use std::{io::Write, path::Path};
use anyhow::Result;
use base64::{engine::general_purpose, Engine};
use image::DynamicImage;
use ratatui::prelude::Rect;
use tokio::io::AsyncWriteExt;
use super::image::Image;
use crate::ui::Term;
pub(super) struct Iterm2;
impl Iterm2 {
pub(super) async fn image_show(path: &Path, rect: Rect) -> Result<()> {
let img = Image::resize(path, (rect.width, rect.height)).await?;
let b = Self::encode(img).await?;
Term::move_to(rect.x, rect.y).ok();
tokio::io::stdout().write_all(&b).await.ok();
Ok(())
}
async fn encode(img: DynamicImage) -> Result<Vec<u8>> {
fn output(raw: Vec<u8>, size: (u32, u32)) -> Result<Vec<u8>> {
let mut buf = Vec::with_capacity(raw.len() * 4 / 3 + 4 + 200);
write!(
buf,
"\x1b]1337;File=inline=1;size={};width={}px;height={}px:",
raw.len(),
size.0,
size.1,
)?;
let len = buf.len();
let written = general_purpose::STANDARD.encode_slice(raw, &mut buf[len..])?;
buf.truncate(len + written);
Ok(buf)
}
let size = (img.width(), img.height());
tokio::task::spawn_blocking(move || match img {
DynamicImage::ImageRgb8(v) => output(v.into_raw(), size),
DynamicImage::ImageRgba8(v) => output(v.into_raw(), size),
v => output(v.to_rgb8().into_raw(), size),
})
.await?
}
}

View file

@ -2,32 +2,19 @@ use std::{io::Write, path::Path};
use anyhow::Result; use anyhow::Result;
use base64::{engine::general_purpose, Engine}; use base64::{engine::general_purpose, Engine};
use image::{imageops::FilterType, DynamicImage}; use image::DynamicImage;
use ratatui::prelude::Rect; use ratatui::prelude::Rect;
use tokio::{fs, io::AsyncWriteExt}; use tokio::io::AsyncWriteExt;
use crate::{config::PREVIEW, misc::tty_ratio, ui::Term}; use super::image::Image;
use crate::ui::Term;
pub struct Kitty; pub(super) struct Kitty;
impl Kitty { impl Kitty {
pub async fn image_show(path: &Path, rect: Rect) -> Result<()> { pub(super) async fn image_show(path: &Path, rect: Rect) -> Result<()> {
let (w, h) = { let img = Image::resize(path, (rect.width, rect.height)).await?;
let r = tty_ratio(); let b = Self::encode(img).await?;
let (w, h) = ((rect.width as f64 * r.0) as u32, (rect.height as f64 * r.1) as u32);
(w.min(PREVIEW.max_width), h.min(PREVIEW.max_height))
};
let img = fs::read(path).await?;
let b = tokio::task::spawn_blocking(move || -> Result<Vec<u8>> {
let img = image::load_from_memory(&img)?;
Self::encode(if img.width() > w || img.height() > h {
img.resize(w, h, FilterType::Triangle)
} else {
img
})
})
.await??;
Term::move_to(rect.x, rect.y).ok(); Term::move_to(rect.x, rect.y).ok();
tokio::io::stdout().write_all(&b).await.ok(); tokio::io::stdout().write_all(&b).await.ok();
@ -35,9 +22,9 @@ impl Kitty {
} }
#[inline] #[inline]
pub fn image_hide() { std::io::stdout().write_all(b"\x1b\\\x1b_Ga=d\x1b\\").ok(); } pub(super) fn image_hide() { std::io::stdout().write_all(b"\x1b\\\x1b_Ga=d\x1b\\").ok(); }
fn encode(img: DynamicImage) -> Result<Vec<u8>> { async fn encode(img: DynamicImage) -> Result<Vec<u8>> {
fn output(raw: Vec<u8>, format: u8, size: (u32, u32)) -> Result<Vec<u8>> { fn output(raw: Vec<u8>, format: u8, size: (u32, u32)) -> Result<Vec<u8>> {
let b64 = general_purpose::STANDARD.encode(raw).chars().collect::<Vec<_>>(); let b64 = general_purpose::STANDARD.encode(raw).chars().collect::<Vec<_>>();
@ -67,10 +54,11 @@ impl Kitty {
} }
let size = (img.width(), img.height()); let size = (img.width(), img.height());
match img { tokio::task::spawn_blocking(move || match img {
DynamicImage::ImageRgb8(v) => output(v.into_raw(), 24, size), DynamicImage::ImageRgb8(v) => output(v.into_raw(), 24, size),
DynamicImage::ImageRgba8(v) => output(v.into_raw(), 32, size), DynamicImage::ImageRgba8(v) => output(v.into_raw(), 32, size),
v => output(v.to_rgb8().into_raw(), 24, size), v => output(v.to_rgb8().into_raw(), 24, size),
} })
.await?
} }
} }

View file

@ -1,4 +1,6 @@
mod adaptor; mod adaptor;
mod image;
mod iterm2;
mod kitty; mod kitty;
mod ueberzug; mod ueberzug;

View file

@ -1,36 +1,29 @@
use std::{path::PathBuf, process::Stdio}; use std::{path::PathBuf, process::Stdio};
use anyhow::{Ok, Result}; use anyhow::Result;
use ratatui::prelude::Rect; use ratatui::prelude::Rect;
use tokio::{io::AsyncWriteExt, process::{Child, Command}, select, sync::mpsc::{self, UnboundedSender}}; use tokio::{io::AsyncWriteExt, process::{Child, Command}, sync::mpsc::{self, UnboundedSender}};
pub struct Ueberzug; use crate::config::PREVIEW;
pub(super) struct Ueberzug;
impl Ueberzug { impl Ueberzug {
pub fn init() -> Result<UnboundedSender<Option<(PathBuf, Rect)>>> { pub(super) fn init() -> Result<UnboundedSender<Option<(PathBuf, Rect)>>> {
let mut child = Some(Self::create_demon()?); let mut child = Self::create_demon().ok();
let (tx, mut rx) = mpsc::unbounded_channel(); let (tx, mut rx) = mpsc::unbounded_channel();
tokio::spawn(async move { tokio::spawn(async move {
loop { while let Some(img) = rx.recv().await {
if let Some(c) = &mut child { let exit = child.as_mut().and_then(|c| c.try_wait().ok());
select! { if exit != Some(None) {
_ = c.wait() => child = None, child = None;
data = rx.recv() => { }
if let Some(img) = data { if child.is_none() {
Self::send_command(c, img).await.ok();
} else {
break;
}
},
}
} else if let Some(img) = rx.recv().await {
child = Self::create_demon().ok(); child = Self::create_demon().ok();
if let Some(c) = &mut child { }
Self::send_command(c, img).await.ok(); if let Some(c) = &mut child {
} Self::send_command(c, img).await.ok();
} else {
break;
} }
} }
}); });
@ -41,21 +34,31 @@ impl Ueberzug {
fn create_demon() -> Result<Child> { fn create_demon() -> Result<Child> {
Ok( Ok(
Command::new("ueberzug") Command::new("ueberzug")
.args(["layer", "-s", "--use-escape-codes"]) .args(["layer", "-so", &PREVIEW.adaptor.to_string()])
.kill_on_drop(true) .kill_on_drop(true)
.stdin(Stdio::piped()) .stdin(Stdio::piped())
// .stdout(Stdio::piped()) .stderr(Stdio::null())
.stderr(Stdio::null()) .spawn()?,
.spawn()?,
) )
} }
async fn send_command(child: &mut Child, img: Option<(PathBuf, Rect)>) -> Result<()> { async fn send_command(child: &mut Child, img: Option<(PathBuf, Rect)>) -> Result<()> {
let stdin = child.stdin.as_mut().unwrap(); let stdin = child.stdin.as_mut().unwrap();
if let Some((path, rect)) = img { if let Some((path, rect)) = img {
stdin.write_all(br#"{"action":"add","identifier":"preview","max_height":0,"max_width":0,"path":"/Users/ika/Downloads/photo_2023-06-28 07.23.33.jpeg","x":0,"y":0}\n"#).await?; let cmd = format!(
r#"{{"action":"add","identifier":"yazi","x":{},"y":{},"max_width":{},"max_height":{},"path":"{}"}}{}"#,
rect.x,
rect.y,
rect.width,
rect.height,
path.to_string_lossy(),
"\n"
);
stdin.write_all(cmd.as_bytes()).await?;
} else { } else {
stdin.write_all(br#"{"action":"remove","identifier":"preview"}\n"#).await?; stdin
.write_all(format!(r#"{{"action":"remove","identifier":"yazi"}}{}"#, "\n").as_bytes())
.await?;
} }
Ok(()) Ok(())
} }

View file

@ -1,5 +1,5 @@
use ansi_to_tui::IntoText; use ansi_to_tui::IntoText;
use ratatui::{buffer::Buffer, layout::Rect, widgets::{Paragraph, Widget}}; use ratatui::{buffer::Buffer, layout::Rect, widgets::{Clear, Paragraph, Widget}};
use super::Folder; use super::Folder;
use crate::{core::manager::PreviewData, ui::Ctx}; use crate::{core::manager::PreviewData, ui::Ctx};
@ -14,6 +14,8 @@ impl<'a> Preview<'a> {
impl<'a> Widget for Preview<'a> { impl<'a> Widget for Preview<'a> {
fn render(self, area: Rect, buf: &mut Buffer) { fn render(self, area: Rect, buf: &mut Buffer) {
Clear.render(Rect { x: area.x, y: area.y, width: area.width + 1, height: area.height }, buf);
// TODO: image // TODO: image
// if self.cx.input.visible || self.cx.select.visible || self.cx.tasks.visible { // if self.cx.input.visible || self.cx.select.visible || self.cx.tasks.visible {
// } // }