mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
..
This commit is contained in:
parent
7488cb0eee
commit
3219297ffd
12 changed files with 198 additions and 86 deletions
18
Cargo.lock
generated
18
Cargo.lock
generated
|
|
@ -961,9 +961,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.7.0"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40"
|
||||
checksum = "67827e6ea8ee8a7c4a72227ef4fc08957040acffdb5f122733b24fa12daff41b"
|
||||
|
||||
[[package]]
|
||||
name = "md5"
|
||||
|
|
@ -1425,7 +1425,7 @@ checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575"
|
|||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata 0.3.3",
|
||||
"regex-automata 0.3.4",
|
||||
"regex-syntax 0.7.4",
|
||||
]
|
||||
|
||||
|
|
@ -1440,9 +1440,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.3.3"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310"
|
||||
checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
|
|
@ -1502,18 +1502,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.176"
|
||||
version = "1.0.178"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76dc28c9523c5d70816e393136b86d48909cfb27cecaa902d338c19ed47164dc"
|
||||
checksum = "60363bdd39a7be0266a520dab25fdc9241d2f987b08a01e01f0ec6d06a981348"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.176"
|
||||
version = "1.0.178"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4e7b8c5dc823e3b90651ff1d3808419cd14e5ad76de04feaf37da114e7a306f"
|
||||
checksum = "f28482318d6641454cb273da158647922d1be6b5a2fcc6165cd89ebdd7ed576b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ sort_reverse = true
|
|||
show_hidden = false
|
||||
|
||||
[preview]
|
||||
adaptor = "kitty"
|
||||
tab_size = 2
|
||||
max_width = 600
|
||||
max_height = 900
|
||||
|
|
|
|||
|
|
@ -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"}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,55 @@
|
|||
use anyhow::bail;
|
||||
use serde::Deserialize;
|
||||
use std::env;
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq)]
|
||||
#[serde(try_from = "String")]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum PreviewAdaptor {
|
||||
Kitty,
|
||||
Ueberzug,
|
||||
Iterm2,
|
||||
|
||||
// Supported by Überzug++
|
||||
X11,
|
||||
Wayland,
|
||||
Sixel,
|
||||
Chafa,
|
||||
}
|
||||
|
||||
impl TryFrom<String> for PreviewAdaptor {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
Ok(match value.to_lowercase().as_str() {
|
||||
"kitty" => Self::Kitty,
|
||||
"ueberzug" => Self::Ueberzug,
|
||||
_ => bail!("invalid preview adaptor: {}", value),
|
||||
})
|
||||
impl Default for PreviewAdaptor {
|
||||
fn default() -> Self {
|
||||
match env::var("TERM").unwrap_or_default().as_str() {
|
||||
"wezterm" => return Self::Kitty,
|
||||
"xterm-kitty" => return Self::Kitty,
|
||||
"iterm2" => return Self::Iterm2,
|
||||
"foot" => return Self::Sixel,
|
||||
_ => {}
|
||||
}
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ use crate::config::MERGED_YAZI;
|
|||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Preview {
|
||||
#[serde(skip)]
|
||||
pub adaptor: PreviewAdaptor,
|
||||
pub tab_size: u32,
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use anyhow::Result;
|
|||
use ratatui::prelude::Rect;
|
||||
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};
|
||||
|
||||
pub struct Adaptor {
|
||||
|
|
@ -15,7 +15,7 @@ impl Adaptor {
|
|||
pub fn new() -> Self {
|
||||
let mut adaptor = Self { ueberzug: None };
|
||||
|
||||
if PREVIEW.adaptor == PreviewAdaptor::Ueberzug {
|
||||
if PREVIEW.adaptor.needs_ueberzug() {
|
||||
adaptor.ueberzug = Ueberzug::init().ok();
|
||||
}
|
||||
|
||||
|
|
@ -25,7 +25,8 @@ impl Adaptor {
|
|||
pub async fn image_show(&self, path: &Path, rect: Rect) -> Result<()> {
|
||||
match PREVIEW.adaptor {
|
||||
PreviewAdaptor::Kitty => Kitty::image_show(path, rect).await,
|
||||
PreviewAdaptor::Ueberzug => {
|
||||
PreviewAdaptor::Iterm2 => Iterm2::image_show(path, rect).await,
|
||||
_ => {
|
||||
if let Some(tx) = &self.ueberzug {
|
||||
tx.send(Some((path.to_path_buf(), rect))).ok();
|
||||
}
|
||||
|
|
@ -37,7 +38,8 @@ impl Adaptor {
|
|||
pub fn image_hide(&self) {
|
||||
match PREVIEW.adaptor {
|
||||
PreviewAdaptor::Kitty => Kitty::image_hide(),
|
||||
PreviewAdaptor::Ueberzug => {
|
||||
PreviewAdaptor::Iterm2 => {}
|
||||
_ => {
|
||||
if let Some(tx) = &self.ueberzug {
|
||||
tx.send(None).ok();
|
||||
}
|
||||
|
|
|
|||
31
src/core/adaptor/image.rs
Normal file
31
src/core/adaptor/image.rs
Normal 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??)
|
||||
}
|
||||
}
|
||||
50
src/core/adaptor/iterm2.rs
Normal file
50
src/core/adaptor/iterm2.rs
Normal 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?
|
||||
}
|
||||
}
|
||||
|
|
@ -2,32 +2,19 @@ use std::{io::Write, path::Path};
|
|||
|
||||
use anyhow::Result;
|
||||
use base64::{engine::general_purpose, Engine};
|
||||
use image::{imageops::FilterType, DynamicImage};
|
||||
use image::DynamicImage;
|
||||
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 {
|
||||
pub async fn image_show(path: &Path, rect: Rect) -> Result<()> {
|
||||
let (w, h) = {
|
||||
let r = tty_ratio();
|
||||
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??;
|
||||
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();
|
||||
|
|
@ -35,9 +22,9 @@ impl Kitty {
|
|||
}
|
||||
|
||||
#[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>> {
|
||||
let b64 = general_purpose::STANDARD.encode(raw).chars().collect::<Vec<_>>();
|
||||
|
||||
|
|
@ -67,10 +54,11 @@ impl Kitty {
|
|||
}
|
||||
|
||||
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::ImageRgba8(v) => output(v.into_raw(), 32, size),
|
||||
v => output(v.to_rgb8().into_raw(), 24, size),
|
||||
}
|
||||
})
|
||||
.await?
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
mod adaptor;
|
||||
mod image;
|
||||
mod iterm2;
|
||||
mod kitty;
|
||||
mod ueberzug;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,36 +1,29 @@
|
|||
use std::{path::PathBuf, process::Stdio};
|
||||
|
||||
use anyhow::{Ok, Result};
|
||||
use anyhow::Result;
|
||||
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 {
|
||||
pub fn init() -> Result<UnboundedSender<Option<(PathBuf, Rect)>>> {
|
||||
let mut child = Some(Self::create_demon()?);
|
||||
pub(super) fn init() -> Result<UnboundedSender<Option<(PathBuf, Rect)>>> {
|
||||
let mut child = Self::create_demon().ok();
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
if let Some(c) = &mut child {
|
||||
select! {
|
||||
_ = c.wait() => child = None,
|
||||
data = rx.recv() => {
|
||||
if let Some(img) = data {
|
||||
Self::send_command(c, img).await.ok();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
},
|
||||
}
|
||||
} else if let Some(img) = rx.recv().await {
|
||||
while let Some(img) = rx.recv().await {
|
||||
let exit = child.as_mut().and_then(|c| c.try_wait().ok());
|
||||
if exit != Some(None) {
|
||||
child = None;
|
||||
}
|
||||
if child.is_none() {
|
||||
child = Self::create_demon().ok();
|
||||
if let Some(c) = &mut child {
|
||||
Self::send_command(c, img).await.ok();
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
if let Some(c) = &mut child {
|
||||
Self::send_command(c, img).await.ok();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -41,21 +34,31 @@ impl Ueberzug {
|
|||
fn create_demon() -> Result<Child> {
|
||||
Ok(
|
||||
Command::new("ueberzug")
|
||||
.args(["layer", "-s", "--use-escape-codes"])
|
||||
.kill_on_drop(true)
|
||||
.stdin(Stdio::piped())
|
||||
// .stdout(Stdio::piped())
|
||||
.stderr(Stdio::null())
|
||||
.spawn()?,
|
||||
.args(["layer", "-so", &PREVIEW.adaptor.to_string()])
|
||||
.kill_on_drop(true)
|
||||
.stdin(Stdio::piped())
|
||||
.stderr(Stdio::null())
|
||||
.spawn()?,
|
||||
)
|
||||
}
|
||||
|
||||
async fn send_command(child: &mut Child, img: Option<(PathBuf, Rect)>) -> Result<()> {
|
||||
let stdin = child.stdin.as_mut().unwrap();
|
||||
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 {
|
||||
stdin.write_all(br#"{"action":"remove","identifier":"preview"}\n"#).await?;
|
||||
stdin
|
||||
.write_all(format!(r#"{{"action":"remove","identifier":"yazi"}}{}"#, "\n").as_bytes())
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
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 crate::{core::manager::PreviewData, ui::Ctx};
|
||||
|
|
@ -14,6 +14,8 @@ impl<'a> Preview<'a> {
|
|||
|
||||
impl<'a> Widget for Preview<'a> {
|
||||
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
|
||||
// if self.cx.input.visible || self.cx.select.visible || self.cx.tasks.visible {
|
||||
// }
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue