feat: archive preview

This commit is contained in:
sxyazi 2023-07-17 18:06:07 +08:00
parent 4af52c74fb
commit b438501d2d
No known key found for this signature in database
9 changed files with 116 additions and 7 deletions

1
Cargo.lock generated
View file

@ -2374,6 +2374,7 @@ dependencies = [
"parking_lot",
"ratatui",
"serde",
"serde_json",
"signal-hook-tokio",
"syntect",
"tokio",

View file

@ -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" ] }

View file

@ -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
View 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)
}

View file

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

View file

@ -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,

View file

@ -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> {

View file

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

View file

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