mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
feat: archive preview
This commit is contained in:
parent
4af52c74fb
commit
b438501d2d
9 changed files with 116 additions and 7 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -2374,6 +2374,7 @@ dependencies = [
|
|||
"parking_lot",
|
||||
"ratatui",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"signal-hook-tokio",
|
||||
"syntect",
|
||||
"tokio",
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ once_cell = "1.18.0"
|
|||
parking_lot = "0.12.1"
|
||||
ratatui = "0.21.0"
|
||||
serde = { version = "1.0.164", features = [ "derive" ] }
|
||||
serde_json = "1.0.103"
|
||||
signal-hook-tokio = { version = "0.3.1", features = [ "futures-v0_3" ] }
|
||||
syntect = "5.0.0"
|
||||
tokio = { version = "1.28.2", features = [ "parking_lot", "macros", "rt-multi-thread", "sync", "fs", "process", "io-std", "io-util", "time" ] }
|
||||
|
|
|
|||
12
README.md
12
README.md
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Yazi ("duck" in Chinese) is a terminal file manager written in Rust, based on non-blocking async I/O. It aims to provide an efficient, user-friendly, and configurable file management experience.
|
||||
|
||||
⚠️ Note: Yazi is currently in active development and may be unstable. The API is subject to change without prior notice. Please use it with caution in a non-production environment.
|
||||
⚠️ Note: Yazi is currently in active development and may be unstable. The API is subject to change without prior notice.
|
||||
|
||||
## Installation
|
||||
|
||||
|
|
@ -10,12 +10,22 @@ Before getting started, ensure that the following dependencies are installed on
|
|||
|
||||
- nerd-fonts (required, for icons)
|
||||
- jq (optional, for JSON preview)
|
||||
- unar (optional, for archive preview)
|
||||
- ffmpegthumbnailer (optional, for video thumbnails)
|
||||
- fd (optional, for file searching)
|
||||
- rg (optional, for file content searching)
|
||||
- fzf (optional, for directory jumping)
|
||||
- zoxide (optional, for directory jumping)
|
||||
|
||||
```bash
|
||||
# Arch Linux
|
||||
pacman -S ttf-nerd-fonts-symbols jq unarchiver ffmpegthumbnailer fd ripgrep fzf zoxide
|
||||
|
||||
# macOS
|
||||
brew install jq unar ffmpegthumbnailer fd ripgrep fzf zoxide
|
||||
brew tap homebrew/cask-fonts && brew install --cask font-symbols-only-nerd-font
|
||||
```
|
||||
|
||||
Execute the following commands to clone the project and build Yazi:
|
||||
|
||||
```bash
|
||||
|
|
|
|||
65
src/core/external/lsar.rs
vendored
Normal file
65
src/core/external/lsar.rs
vendored
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
use std::path::Path;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
use tokio::process::Command;
|
||||
use tracing::info;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum LsarAttr {
|
||||
Posix(u16),
|
||||
Windows(u16),
|
||||
Dos(u8),
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct LsarFile {
|
||||
#[serde(rename = "XADFileName")]
|
||||
pub name: String,
|
||||
#[serde(rename = "XADLastModificationDate")]
|
||||
pub last_modified: String,
|
||||
#[serde(rename = "XADFileSize")]
|
||||
pub size: Option<usize>,
|
||||
#[serde(rename = "XADCompressedSize")]
|
||||
pub compressed_size: Option<usize>,
|
||||
#[serde(rename = "XADCompressionName")]
|
||||
pub compression_name: Option<String>,
|
||||
|
||||
#[serde(skip)]
|
||||
pub attributes: Option<LsarAttr>,
|
||||
}
|
||||
|
||||
pub async fn lsar(path: &Path) -> Result<Vec<LsarFile>> {
|
||||
let output =
|
||||
Command::new("lsar").args(["-j", "-jss"]).arg(path).kill_on_drop(true).output().await?;
|
||||
|
||||
if !output.status.success() {
|
||||
bail!("failed to get json: {}", String::from_utf8_lossy(&output.stderr));
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Outer {
|
||||
#[serde(rename = "lsarContents")]
|
||||
contents: Vec<Value>,
|
||||
}
|
||||
|
||||
let output = String::from_utf8_lossy(&output.stdout);
|
||||
info!("lsar output: {}", output);
|
||||
let contents = serde_json::from_str::<Outer>(output.trim())?.contents;
|
||||
|
||||
let mut files = Vec::with_capacity(contents.len());
|
||||
for content in contents {
|
||||
let mut file = serde_json::from_value::<LsarFile>(content.clone())?;
|
||||
if let Some(p) = content.get("XADPosixPermissions").and_then(|p| p.as_u64()) {
|
||||
file.attributes = Some(LsarAttr::Posix(p as u16));
|
||||
} else if let Some(a) = content.get("XADWindowsFileAttributes").and_then(|a| a.as_u64()) {
|
||||
file.attributes = Some(LsarAttr::Windows(a as u16));
|
||||
} else if let Some(a) = content.get("XADDOSFileAttributes").and_then(|a| a.as_u64()) {
|
||||
file.attributes = Some(LsarAttr::Dos(a as u8));
|
||||
}
|
||||
|
||||
files.push(file);
|
||||
}
|
||||
Ok(files)
|
||||
}
|
||||
2
src/core/external/mod.rs
vendored
2
src/core/external/mod.rs
vendored
|
|
@ -3,6 +3,7 @@ mod ffmpegthumbnailer;
|
|||
mod file;
|
||||
mod fzf;
|
||||
mod jq;
|
||||
mod lsar;
|
||||
mod rg;
|
||||
mod zoxide;
|
||||
|
||||
|
|
@ -11,5 +12,6 @@ pub use ffmpegthumbnailer::*;
|
|||
pub use file::*;
|
||||
pub use fzf::*;
|
||||
pub use jq::*;
|
||||
pub use lsar::*;
|
||||
pub use rg::*;
|
||||
pub use zoxide::*;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use std::{collections::{BTreeMap, BTreeSet, HashMap, HashSet}, mem, path::PathBu
|
|||
use tokio::fs;
|
||||
|
||||
use super::{PreviewData, Tab, Tabs, Watcher};
|
||||
use crate::{core::{external::{self}, files::{File, FilesOp}, input::{InputOpt, InputPos}, manager::Folder, tasks::Tasks}, emit};
|
||||
use crate::{core::{external, files::{File, FilesOp}, input::{InputOpt, InputPos}, manager::Folder, tasks::Tasks}, emit};
|
||||
|
||||
pub struct Manager {
|
||||
tabs: Tabs,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use syntect::{easy::HighlightLines, highlighting::{Theme, ThemeSet}, parsing::Sy
|
|||
use tokio::{fs, task::JoinHandle};
|
||||
|
||||
use super::{ALL_RATIO, PREVIEW_BORDER, PREVIEW_PADDING, PREVIEW_RATIO};
|
||||
use crate::{config::{PREVIEW, THEME}, core::{adapter::Kitty, external::{ffmpegthumbnailer, jq}, files::{Files, FilesOp}, tasks::Precache}, emit, misc::{first_n_lines, tty_ratio, tty_size, MimeKind}};
|
||||
use crate::{config::{PREVIEW, THEME}, core::{adapter::Kitty, external, files::{Files, FilesOp}, tasks::Precache}, emit, misc::{first_n_lines, tty_ratio, tty_size, MimeKind}};
|
||||
|
||||
static SYNTECT_SYNTAX: OnceLock<SyntaxSet> = OnceLock::new();
|
||||
static SYNTECT_THEME: OnceLock<Theme> = OnceLock::new();
|
||||
|
|
@ -51,6 +51,7 @@ impl Preview {
|
|||
MimeKind::Text => Self::highlight(&path).await.map(PreviewData::Text),
|
||||
MimeKind::Image => Self::image(&path).await.map(PreviewData::Image),
|
||||
MimeKind::Video => Self::video(&path).await.map(PreviewData::Image),
|
||||
MimeKind::Archive => Self::archive(&path).await.map(PreviewData::Text),
|
||||
MimeKind::Others => Err(anyhow!("Unsupported mimetype: {}", mime)),
|
||||
};
|
||||
|
||||
|
|
@ -105,14 +106,33 @@ impl Preview {
|
|||
pub async fn video(path: &Path) -> Result<Vec<u8>> {
|
||||
let cache = Precache::cache(path);
|
||||
if fs::metadata(&cache).await.is_err() {
|
||||
ffmpegthumbnailer(path, &cache).await?;
|
||||
external::ffmpegthumbnailer(path, &cache).await?;
|
||||
}
|
||||
|
||||
Self::image(&cache).await
|
||||
}
|
||||
|
||||
pub async fn json(path: &Path) -> Result<String> {
|
||||
Ok(jq(path).await?.lines().take(Self::size().1 as usize).collect::<Vec<_>>().join("\n"))
|
||||
Ok(
|
||||
external::jq(path)
|
||||
.await?
|
||||
.lines()
|
||||
.take(Self::size().1 as usize)
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n"),
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn archive(path: &Path) -> Result<String> {
|
||||
Ok(
|
||||
external::lsar(path)
|
||||
.await?
|
||||
.into_iter()
|
||||
.take(Self::size().1 as usize)
|
||||
.map(|f| f.name)
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n"),
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn highlight(path: &Path) -> Result<String> {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use image::{imageops::FilterType, ImageFormat};
|
|||
use tokio::{fs, sync::mpsc};
|
||||
|
||||
use super::TaskOp;
|
||||
use crate::{config::PREVIEW, core::external::{self, ffmpegthumbnailer}, emit};
|
||||
use crate::{config::PREVIEW, core::external, emit};
|
||||
|
||||
pub struct Precache {
|
||||
rx: async_channel::Receiver<PrecacheOp>,
|
||||
|
|
@ -79,7 +79,7 @@ impl Precache {
|
|||
return Ok(self.sch.send(TaskOp::Adv(task.id, 1, 0))?);
|
||||
}
|
||||
|
||||
ffmpegthumbnailer(&task.target, &cache).await.ok();
|
||||
external::ffmpegthumbnailer(&task.target, &cache).await.ok();
|
||||
self.sch.send(TaskOp::Adv(task.id, 1, 0))?;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ pub enum MimeKind {
|
|||
Text,
|
||||
Image,
|
||||
Video,
|
||||
Archive,
|
||||
Others,
|
||||
}
|
||||
|
||||
|
|
@ -42,6 +43,15 @@ impl MimeKind {
|
|||
Self::Image
|
||||
} else if s.starts_with("video/") {
|
||||
Self::Video
|
||||
} else if s == "application/x-bzip"
|
||||
|| s == "application/x-bzip2"
|
||||
|| s == "application/gzip"
|
||||
|| s == "application/vnd.rar"
|
||||
|| s == "application/x-tar"
|
||||
|| s == "application/zip"
|
||||
|| s == "application/x-7z-compressed"
|
||||
{
|
||||
Self::Archive
|
||||
} else {
|
||||
Self::Others
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue