Compare commits

..

4 Commits

Author SHA1 Message Date
uncenter
c09437d55f
Merge 8ac8ffa236 into 52681b19e4 2024-08-05 17:45:28 +08:00
Julian Chen
52681b19e4
chore: update snap to core24 (#1415) 2024-08-05 17:17:10 +08:00
P_Lee
cda7d3f2ea
feat: add support portrait orientation preview for EXIF image (#1412)
Co-authored-by: sxyazi <sxyazi@gmail.com>
2024-08-05 17:15:24 +08:00
Xerxes-2
cc50f94de6
fix: preview files containing special \x1b characters as plain text and escape (#1395)
Co-authored-by: sxyazi <sxyazi@gmail.com>
2024-08-05 13:21:28 +08:00
14 changed files with 70 additions and 75 deletions

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","iterm","wezterm","sixel","chafa","ueberzugpp"," Überzug"," Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","imagesize","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt","realpath","realname","REPARSE","hardlink","hardlinking","nlink","nlink","linemodes","SIGSTOP","sevenzip","rsplitn","replacen","DECSET","DECRQM"],"language":"en","version":"0.2"} {"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"," Überzug"," Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","imagesize","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt","realpath","realname","REPARSE","hardlink","hardlinking","nlink","nlink","linemodes","SIGSTOP","sevenzip","rsplitn","replacen","DECSET","DECRQM","repeek"],"version":"0.2","language":"en","flagWords":[]}

View File

@ -1,5 +1,5 @@
name: yazi name: yazi
base: core22 base: core24
adopt-info: yazi adopt-info: yazi
summary: Blazing fast terminal file manager written in Rust, based on async I/O. summary: Blazing fast terminal file manager written in Rust, based on async I/O.
description: | description: |
@ -9,9 +9,9 @@ license: MIT
grade: stable grade: stable
confinement: classic confinement: classic
architectures: platforms:
- amd64 amd64:
# - arm64 # arm64:
apps: apps:
yazi: yazi:

View File

@ -199,10 +199,10 @@ impl Emulator {
bail!("unexpected EOF"); bail!("unexpected EOF");
} }
buf.push(c[0]); buf.push(c[0]);
if c[0] != b'c' || !buf.contains(&b'\x1b') { if c[0] != b'c' || !buf.contains(&0x1b) {
continue; continue;
} }
if buf.rsplitn(2, |&b| b == b'\x1b').next().is_some_and(|s| s.starts_with(b"[?")) { if buf.rsplitn(2, |&b| b == 0x1b).next().is_some_and(|s| s.starts_with(b"[?")) {
break; break;
} }
} }

View File

@ -92,7 +92,7 @@ fetchers = [
] ]
preloaders = [ preloaders = [
# Image # Image
{ mime = "image/{avif,heic,jxl,svg+xml}", run = "magick" }, { mime = "image/{avif,hei?,jxl,svg+xml}", run = "magick" },
{ mime = "image/*", run = "image" }, { mime = "image/*", run = "image" },
# Video # Video
{ mime = "video/*", run = "video" }, { mime = "video/*", run = "video" },
@ -110,7 +110,7 @@ previewers = [
# JSON # JSON
{ mime = "application/{json,x-ndjson}", run = "json" }, { mime = "application/{json,x-ndjson}", run = "json" },
# Image # Image
{ mime = "image/{avif,heic,jxl,svg+xml}", run = "magick" }, { mime = "image/{avif,hei?,jxl,svg+xml}", run = "magick" },
{ mime = "image/*", run = "image" }, { mime = "image/*", run = "image" },
# Video # Video
{ mime = "video/*", run = "video" }, { mime = "video/*", run = "video" },

View File

@ -28,7 +28,7 @@ impl Manager {
self.current_mut().tracing = true; self.current_mut().tracing = true;
} }
// Re-peek // Repeek
self.peek(false); self.peek(false);
// Refresh watcher // Refresh watcher

View File

@ -50,9 +50,12 @@ impl Manager {
.cloned() .cloned()
.collect(); .collect();
let repeek = self.hovered().is_some_and(|f| updates.contains_key(&f.url));
self.mimetype.extend(updates); self.mimetype.extend(updates);
self.peek(false);
if repeek {
self.peek(false);
}
tasks.prework_affected(&affected, &self.mimetype); tasks.prework_affected(&affected, &self.mimetype);
render!(); render!();
} }

View File

@ -111,7 +111,7 @@ impl Watcher {
async fn fan_out(rx: UnboundedReceiver<Url>) { async fn fan_out(rx: UnboundedReceiver<Url>) {
// TODO: revert this once a new notification is implemented // TODO: revert this once a new notification is implemented
let rx = UnboundedReceiverStream::new(rx).chunks_timeout(1000, Duration::from_millis(50)); let rx = UnboundedReceiverStream::new(rx).chunks_timeout(1000, Duration::from_millis(100));
pin!(rx); pin!(rx);
while let Some(chunk) = rx.next().await { while let Some(chunk) = rx.next().await {

View File

@ -89,25 +89,11 @@ impl Preview {
matches!(self.lock, Some(ref lock) if lock.url == *url) matches!(self.lock, Some(ref lock) if lock.url == *url)
} }
#[inline]
fn content_unchanged(&self, url: &Url, cha: &Cha) -> bool { fn content_unchanged(&self, url: &Url, cha: &Cha) -> bool {
let Some(lock) = &self.lock else { match &self.lock {
return false; Some(l) => *url == l.url && self.skip == l.skip && cha.hits(l.cha),
}; None => false,
}
*url == lock.url
&& self.skip == lock.skip
&& cha.len == lock.cha.len
&& cha.mtime == lock.cha.mtime
&& cha.kind == lock.cha.kind
&& {
#[cfg(unix)]
{
cha.perm == lock.cha.perm
}
#[cfg(windows)]
{
true
}
}
} }
} }

View File

@ -3,7 +3,7 @@ use std::ops::Range;
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use ratatui::{buffer::Buffer, layout::Rect, text::Line, widgets::{Block, BorderType, Paragraph, Widget}}; use ratatui::{buffer::Buffer, layout::Rect, text::Line, widgets::{Block, BorderType, Paragraph, Widget}};
use syntect::easy::HighlightLines; use syntect::easy::HighlightLines;
use yazi_config::THEME; use yazi_config::{PREVIEW, THEME};
use yazi_core::input::InputMode; use yazi_core::input::InputMode;
use yazi_plugin::external::Highlighter; use yazi_plugin::external::Highlighter;
@ -21,11 +21,11 @@ impl<'a> Input<'a> {
bail!("Highlighting is disabled"); bail!("Highlighting is disabled");
} }
let (theme, syntaxes) = Highlighter::init(); let (theme, syntaxes) = futures::executor::block_on(Highlighter::init());
if let Some(syntax) = syntaxes.find_syntax_by_name("Bourne Again Shell (bash)") { if let Some(syntax) = syntaxes.find_syntax_by_name("Bourne Again Shell (bash)") {
let mut h = HighlightLines::new(syntax, theme); let mut h = HighlightLines::new(syntax, theme);
let regions = h.highlight_line(self.cx.input.value(), syntaxes)?; let regions = h.highlight_line(self.cx.input.value(), syntaxes)?;
return Ok(Highlighter::to_line_widget(regions)); return Ok(Highlighter::to_line_widget(regions, &" ".repeat(PREVIEW.tab_size as usize)));
} }
bail!("Failed to find syntax") bail!("Failed to find syntax")
} }

View File

@ -264,19 +264,18 @@ impl Files {
macro_rules! go { macro_rules! go {
($dist:expr, $src:expr, $inc:literal) => { ($dist:expr, $src:expr, $inc:literal) => {
let mut b = false; let mut b = true;
for i in 0..$dist.len() { for i in 0..$dist.len() {
if let Some(f) = $src.remove(&$dist[i].url) { if let Some(f) = $src.remove(&$dist[i].url) {
if $dist[i] != f { b &= $dist[i].cha.hits(f.cha);
b = true; $dist[i] = f;
$dist[i] = f;
}
if $src.is_empty() { if $src.is_empty() {
break; break;
} }
} }
} }
self.revision += if b { $inc } else { 0 }; self.revision += if b { 0 } else { $inc };
}; };
} }

View File

@ -28,6 +28,7 @@ function M:preload()
string.format("%dx%d^", PREVIEW.max_width, PREVIEW.max_height), string.format("%dx%d^", PREVIEW.max_width, PREVIEW.max_height),
"-quality", "-quality",
tostring(PREVIEW.image_quality), tostring(PREVIEW.image_quality),
"-auto-orient",
"JPG:" .. tostring(cache), "JPG:" .. tostring(cache),
}):spawn() }):spawn()

View File

@ -1,15 +1,14 @@
use std::{io::Cursor, mem, path::{Path, PathBuf}, sync::{atomic::{AtomicUsize, Ordering}, OnceLock}}; use std::{io::Cursor, mem, path::{Path, PathBuf}, sync::atomic::{AtomicUsize, Ordering}};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use ratatui::text::{Line, Span, Text}; use ratatui::text::{Line, Span, Text};
use syntect::{dumps, easy::HighlightLines, highlighting::{self, Theme, ThemeSet}, parsing::{SyntaxReference, SyntaxSet}}; use syntect::{dumps, easy::HighlightLines, highlighting::{self, Theme, ThemeSet}, parsing::{SyntaxReference, SyntaxSet}, LoadingError};
use tokio::{fs::File, io::{AsyncBufReadExt, BufReader}}; use tokio::{fs::File, io::{AsyncBufReadExt, BufReader}, sync::OnceCell};
use yazi_config::{PREVIEW, THEME}; use yazi_config::{PREVIEW, THEME};
use yazi_shared::PeekError; use yazi_shared::PeekError;
static INCR: AtomicUsize = AtomicUsize::new(0); static INCR: AtomicUsize = AtomicUsize::new(0);
static SYNTECT_SYNTAX: OnceLock<SyntaxSet> = OnceLock::new(); static SYNTECT: OnceCell<(Theme, SyntaxSet)> = OnceCell::const_new();
static SYNTECT_THEME: OnceLock<Theme> = OnceLock::new();
pub struct Highlighter { pub struct Highlighter {
path: PathBuf, path: PathBuf,
@ -19,27 +18,28 @@ impl Highlighter {
#[inline] #[inline]
pub fn new(path: &Path) -> Self { Self { path: path.to_owned() } } pub fn new(path: &Path) -> Self { Self { path: path.to_owned() } }
pub fn init() -> (&'static Theme, &'static SyntaxSet) { pub async fn init() -> (&'static Theme, &'static SyntaxSet) {
#[inline] let fut = async {
fn from_file() -> Result<Theme> { tokio::task::spawn_blocking(|| {
let file = std::fs::File::open(&THEME.manager.syntect_theme)?; let theme = std::fs::File::open(&THEME.manager.syntect_theme)
Ok(ThemeSet::load_from_reader(&mut std::io::BufReader::new(file))?) .map_err(LoadingError::Io)
} .and_then(|f| ThemeSet::load_from_reader(&mut std::io::BufReader::new(f)))
.or_else(|_| ThemeSet::load_from_reader(&mut Cursor::new(yazi_prebuild::ansi_theme())));
let theme = SYNTECT_THEME.get_or_init(|| { let syntaxes = dumps::from_uncompressed_data(yazi_prebuild::syntaxes());
from_file().unwrap_or_else(|_| {
ThemeSet::load_from_reader(&mut Cursor::new(yazi_prebuild::ansi_theme())).unwrap() (theme.unwrap(), syntaxes.unwrap())
}) })
}); .await
.unwrap()
};
let syntaxes = SYNTECT_SYNTAX let r = SYNTECT.get_or_init(|| fut).await;
.get_or_init(|| dumps::from_uncompressed_data(yazi_prebuild::syntaxes()).unwrap()); (&r.0, &r.1)
(theme, syntaxes)
} }
async fn find_syntax(path: &Path) -> Result<&'static SyntaxReference> { async fn find_syntax(path: &Path) -> Result<&'static SyntaxReference> {
let (_, syntaxes) = Self::init(); let (_, syntaxes) = Self::init().await;
let name = path.file_name().map(|n| n.to_string_lossy()).unwrap_or_default(); let name = path.file_name().map(|n| n.to_string_lossy()).unwrap_or_default();
if let Some(s) = syntaxes.find_syntax_by_extension(&name) { if let Some(s) = syntaxes.find_syntax_by_extension(&name) {
return Ok(s); return Ok(s);
@ -73,7 +73,7 @@ impl Highlighter {
break; break;
} }
if !plain && buf.len() > 6000 { if !plain && (buf.len() > 5000 || buf.contains(&0x1b)) {
plain = true; plain = true;
drop(mem::take(&mut before)); drop(mem::take(&mut before));
} }
@ -98,7 +98,7 @@ impl Highlighter {
if plain { if plain {
let indent = " ".repeat(PREVIEW.tab_size as usize); let indent = " ".repeat(PREVIEW.tab_size as usize);
Ok(Text::from(after.join("").replace('\t', &indent))) Ok(Text::from(after.join("").replace('\x1b', "^[").replace('\t', &indent)))
} else { } else {
Self::highlight_with(before, after, syntax.unwrap()).await Self::highlight_with(before, after, syntax.unwrap()).await
} }
@ -110,11 +110,10 @@ impl Highlighter {
syntax: &'static SyntaxReference, syntax: &'static SyntaxReference,
) -> Result<Text<'static>, PeekError> { ) -> Result<Text<'static>, PeekError> {
let ticket = INCR.load(Ordering::Relaxed); let ticket = INCR.load(Ordering::Relaxed);
let (theme, syntaxes) = Self::init().await;
tokio::task::spawn_blocking(move || { tokio::task::spawn_blocking(move || {
let (theme, syntaxes) = Self::init();
let mut h = HighlightLines::new(syntax, theme); let mut h = HighlightLines::new(syntax, theme);
for line in before { for line in before {
if ticket != INCR.load(Ordering::Relaxed) { if ticket != INCR.load(Ordering::Relaxed) {
return Err("Highlighting cancelled".into()); return Err("Highlighting cancelled".into());
@ -122,6 +121,7 @@ impl Highlighter {
h.highlight_line(&line, syntaxes).map_err(|e| anyhow!(e))?; h.highlight_line(&line, syntaxes).map_err(|e| anyhow!(e))?;
} }
let indent = " ".repeat(PREVIEW.tab_size as usize);
let mut lines = Vec::with_capacity(after.len()); let mut lines = Vec::with_capacity(after.len());
for line in after { for line in after {
if ticket != INCR.load(Ordering::Relaxed) { if ticket != INCR.load(Ordering::Relaxed) {
@ -129,7 +129,7 @@ impl Highlighter {
} }
let regions = h.highlight_line(&line, syntaxes).map_err(|e| anyhow!(e))?; let regions = h.highlight_line(&line, syntaxes).map_err(|e| anyhow!(e))?;
lines.push(Self::to_line_widget(regions)); lines.push(Self::to_line_widget(regions, &indent));
} }
Ok(Text::from(lines)) Ok(Text::from(lines))
@ -182,8 +182,7 @@ impl Highlighter {
} }
} }
pub fn to_line_widget(regions: Vec<(highlighting::Style, &str)>) -> Line<'static> { pub fn to_line_widget(regions: Vec<(highlighting::Style, &str)>, indent: &str) -> Line<'static> {
let indent = " ".repeat(PREVIEW.tab_size as usize);
let spans: Vec<_> = regions let spans: Vec<_> = regions
.into_iter() .into_iter()
.map(|(style, s)| { .map(|(style, s)| {
@ -199,7 +198,7 @@ impl Highlighter {
} }
Span { Span {
content: s.replace('\t', &indent).into(), content: s.replace('\t', indent).into(),
style: ratatui::style::Style { style: ratatui::style::Style {
fg: Self::to_ansi_color(style.foreground), fg: Self::to_ansi_color(style.foreground),
// bg: Self::to_ansi_color(style.background), // bg: Self::to_ansi_color(style.background),

View File

@ -120,6 +120,20 @@ impl Cha {
self.kind |= kind; self.kind |= kind;
self self
} }
#[inline]
pub fn hits(self, c: Self) -> bool {
self.len == c.len && self.mtime == c.mtime && self.ctime == c.ctime && self.kind == c.kind && {
#[cfg(unix)]
{
self.perm == c.perm
}
#[cfg(windows)]
{
true
}
}
}
} }
impl Cha { impl Cha {

View File

@ -25,13 +25,6 @@ impl AsRef<File> for File {
fn as_ref(&self) -> &File { self } fn as_ref(&self) -> &File { self }
} }
impl PartialEq for File {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.cha == other.cha && self.url == other.url && self.link_to == other.link_to
}
}
impl File { impl File {
#[inline] #[inline]
pub async fn from(url: Url) -> Result<Self> { pub async fn from(url: Url) -> Result<Self> {