mirror of
https://github.com/sxyazi/yazi.git
synced 2024-10-27 03:27:42 +03:00
Compare commits
13 Commits
f82aac0113
...
29409c9ee8
Author | SHA1 | Date | |
---|---|---|---|
|
29409c9ee8 | ||
|
9d0ef9a5dc | ||
|
86c53d8ea2 | ||
|
c078fb8513 | ||
|
6fb569746e | ||
|
931a63808a | ||
|
1f562031f4 | ||
|
693ab45625 | ||
|
cd734ee69f | ||
|
0cff3ce15c | ||
|
64d961e49a | ||
|
5a66754604 | ||
|
6a30dfc137 |
25
.github/ISSUE_TEMPLATE/bug.yml
vendored
25
.github/ISSUE_TEMPLATE/bug.yml
vendored
@ -13,6 +13,8 @@ body:
|
||||
- macOS
|
||||
- Windows
|
||||
- Windows WSL
|
||||
- FreeBSD X11
|
||||
- FreeBSD Wayland
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
@ -22,31 +24,32 @@ body:
|
||||
placeholder: "ex: kitty v0.32.2"
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: tried_main
|
||||
attributes:
|
||||
label: Did you try the latest code to see if this problem got fixed?
|
||||
options:
|
||||
- Tried, but the problem still
|
||||
- Not tried, and I'll explain why below
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: debug
|
||||
attributes:
|
||||
label: "`yazi --debug` output"
|
||||
description: Please do a `yazi --debug` and paste the output here.
|
||||
description: Please run `yazi --debug` and paste the debug information here.
|
||||
value: |
|
||||
<details>
|
||||
|
||||
<!-- Paste the output between the backticks below: -->
|
||||
```sh
|
||||
##### ↓↓↓ Paste the output here: ↓↓↓ #####
|
||||
|
||||
|
||||
```
|
||||
|
||||
</details>
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: tried_main
|
||||
attributes:
|
||||
label: Did you try the latest nightly build to see if the problem got fixed?
|
||||
options:
|
||||
- Yes, and I updated the debug information above (`yazi --debug`) to the nightly that I tried
|
||||
- No, and I'll explain why below
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
|
5
.github/workflows/release.yml
vendored
5
.github/workflows/release.yml
vendored
@ -24,7 +24,10 @@ jobs:
|
||||
|
||||
- name: Install gcc-aarch64-linux-gnu
|
||||
if: matrix.target == 'aarch64-unknown-linux-gnu'
|
||||
run: sudo apt-get update && sudo apt-get install -yq gcc-aarch64-linux-gnu
|
||||
run: |
|
||||
sudo apt-get update && sudo apt-get install -yq gcc-aarch64-linux-gnu
|
||||
echo "JEMALLOC_SYS_WITH_LG_PAGE=16" >> $GITHUB_ENV
|
||||
echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=/usr/bin/aarch64-linux-gnu-gcc" >> $GITHUB_ENV
|
||||
|
||||
- name: Build
|
||||
run: ./scripts/build.sh ${{ matrix.target }}
|
||||
|
@ -3,7 +3,6 @@ set -euo pipefail
|
||||
|
||||
export ARTIFACT_NAME="yazi-$1"
|
||||
export YAZI_GEN_COMPLETIONS=1
|
||||
export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=/usr/bin/aarch64-linux-gnu-gcc
|
||||
|
||||
# Setup Rust toolchain
|
||||
if [[ "$1" == *-musl ]]; then
|
||||
|
@ -17,6 +17,7 @@ mouse_events = [ "click", "scroll" ]
|
||||
|
||||
[preview]
|
||||
tab_size = 2
|
||||
word_wrap = false
|
||||
max_width = 600
|
||||
max_height = 900
|
||||
cache_dir = ""
|
||||
|
@ -10,6 +10,7 @@ use crate::Xdg;
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct Preview {
|
||||
pub tab_size: u8,
|
||||
pub word_wrap: bool,
|
||||
pub max_width: u32,
|
||||
pub max_height: u32,
|
||||
|
||||
@ -42,6 +43,7 @@ impl FromStr for Preview {
|
||||
#[derive(Deserialize, Validate)]
|
||||
struct Shadow {
|
||||
tab_size: u8,
|
||||
word_wrap: bool,
|
||||
max_width: u32,
|
||||
max_height: u32,
|
||||
|
||||
@ -66,6 +68,7 @@ impl FromStr for Preview {
|
||||
|
||||
Ok(Preview {
|
||||
tab_size: preview.tab_size,
|
||||
word_wrap: preview.word_wrap,
|
||||
max_width: preview.max_width,
|
||||
max_height: preview.max_height,
|
||||
|
||||
|
209
yazi-plugin/src/external/highlighter.rs
vendored
209
yazi-plugin/src/external/highlighter.rs
vendored
@ -1,9 +1,10 @@
|
||||
use std::{io::Cursor, mem, path::{Path, PathBuf}, sync::{atomic::{AtomicUsize, Ordering}, OnceLock}};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use ratatui::text::{Line, Span, Text};
|
||||
use ratatui::{prelude::Rect, text::{Line, Span, Text}};
|
||||
use syntect::{dumps, easy::HighlightLines, highlighting::{self, Theme, ThemeSet}, parsing::{SyntaxReference, SyntaxSet}};
|
||||
use tokio::{fs::File, io::{AsyncBufReadExt, BufReader}};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
use yazi_config::{PREVIEW, THEME};
|
||||
use yazi_shared::PeekError;
|
||||
|
||||
@ -11,6 +12,8 @@ static INCR: AtomicUsize = AtomicUsize::new(0);
|
||||
static SYNTECT_SYNTAX: OnceLock<SyntaxSet> = OnceLock::new();
|
||||
static SYNTECT_THEME: OnceLock<Theme> = OnceLock::new();
|
||||
|
||||
const MAX_LINE_BYTES_TO_PLAINTEXT_FALLBACK: usize = 6000;
|
||||
|
||||
pub struct Highlighter {
|
||||
path: PathBuf,
|
||||
}
|
||||
@ -56,54 +59,204 @@ impl Highlighter {
|
||||
syntaxes.find_syntax_by_first_line(&line).ok_or_else(|| anyhow!("No syntax found"))
|
||||
}
|
||||
|
||||
pub async fn highlight(&self, skip: usize, limit: usize) -> Result<Text<'static>, PeekError> {
|
||||
pub async fn highlight(
|
||||
&self,
|
||||
skip: usize,
|
||||
area: Rect,
|
||||
wrap: bool,
|
||||
) -> Result<Text<'static>, PeekError> {
|
||||
let mut reader = BufReader::new(File::open(&self.path).await?);
|
||||
|
||||
let syntax = Self::find_syntax(&self.path).await;
|
||||
let mut plain = syntax.is_err();
|
||||
|
||||
let mut before = Vec::with_capacity(if plain { 0 } else { skip });
|
||||
let mut after = Vec::with_capacity(limit);
|
||||
let mut after = Vec::with_capacity(area.height as usize);
|
||||
|
||||
let mut i = 0;
|
||||
let mut buf = vec![];
|
||||
while reader.read_until(b'\n', &mut buf).await.is_ok() {
|
||||
i += 1;
|
||||
if buf.is_empty() || i > skip + limit {
|
||||
let mut lines_handled = 0;
|
||||
let mut long_line = vec![];
|
||||
while reader.read_until(b'\n', &mut long_line).await.is_ok() {
|
||||
if long_line.is_empty() || lines_handled > skip + area.height as usize {
|
||||
break;
|
||||
}
|
||||
|
||||
if !plain && buf.len() > 6000 {
|
||||
if !plain && long_line.len() > MAX_LINE_BYTES_TO_PLAINTEXT_FALLBACK {
|
||||
plain = true;
|
||||
drop(mem::take(&mut before));
|
||||
}
|
||||
|
||||
if buf.ends_with(b"\r\n") {
|
||||
buf.pop();
|
||||
buf.pop();
|
||||
buf.push(b'\n');
|
||||
Self::replace_tabs_with_spaces(&mut long_line, PREVIEW.tab_size as usize);
|
||||
if wrap {
|
||||
Self::handle_line_wrap(
|
||||
&long_line,
|
||||
area,
|
||||
plain,
|
||||
skip,
|
||||
&mut lines_handled,
|
||||
&mut before,
|
||||
&mut after,
|
||||
);
|
||||
} else {
|
||||
lines_handled += 1;
|
||||
Self::handle_single_line(
|
||||
lines_handled,
|
||||
skip,
|
||||
plain,
|
||||
area.height as usize,
|
||||
String::from_utf8_lossy(&long_line).to_string(),
|
||||
&mut before,
|
||||
&mut after,
|
||||
);
|
||||
}
|
||||
long_line.clear();
|
||||
}
|
||||
|
||||
if i > skip {
|
||||
after.push(String::from_utf8_lossy(&buf).into_owned());
|
||||
} else if !plain {
|
||||
before.push(String::from_utf8_lossy(&buf).into_owned());
|
||||
let no_more_scroll = lines_handled < skip + area.height as usize;
|
||||
if skip > 0 && no_more_scroll {
|
||||
return Err(PeekError::Exceed(lines_handled.saturating_sub(area.height as usize)));
|
||||
}
|
||||
buf.clear();
|
||||
}
|
||||
|
||||
if skip > 0 && i < skip + limit {
|
||||
return Err(PeekError::Exceed(i.saturating_sub(limit)));
|
||||
}
|
||||
|
||||
if plain {
|
||||
let indent = " ".repeat(PREVIEW.tab_size as usize);
|
||||
Ok(Text::from(after.join("").replace('\t', &indent)))
|
||||
Ok(Text::from(after.join("")))
|
||||
} else {
|
||||
Self::highlight_with(before, after, syntax.unwrap()).await
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_line_wrap(
|
||||
long_line: &[u8],
|
||||
area: Rect,
|
||||
plain: bool,
|
||||
skip: usize,
|
||||
lines_handled: &mut usize,
|
||||
before: &mut Vec<String>,
|
||||
after: &mut Vec<String>,
|
||||
) {
|
||||
for line in Self::chunk_by_width(long_line, area.width as usize) {
|
||||
*lines_handled += 1;
|
||||
let must_break = Self::handle_single_line(
|
||||
*lines_handled,
|
||||
skip,
|
||||
plain,
|
||||
area.height as usize,
|
||||
line,
|
||||
before,
|
||||
after,
|
||||
);
|
||||
if must_break {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_single_line(
|
||||
lines_handled: usize,
|
||||
skip: usize,
|
||||
plain: bool,
|
||||
limit: usize,
|
||||
mut line: String,
|
||||
before: &mut Vec<String>,
|
||||
after: &mut Vec<String>,
|
||||
) -> bool {
|
||||
if line.is_empty() || lines_handled > skip + limit {
|
||||
return true;
|
||||
}
|
||||
|
||||
if line.ends_with("\r\n") {
|
||||
line.pop();
|
||||
line.pop();
|
||||
line.push('\n');
|
||||
} else if !line.ends_with('\n') {
|
||||
line.push('\n');
|
||||
}
|
||||
|
||||
if lines_handled > skip {
|
||||
after.push(line);
|
||||
} else if !plain {
|
||||
before.push(line);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn chunk_by_width(line: &[u8], width: usize) -> Vec<String> {
|
||||
let line = String::from_utf8_lossy(line);
|
||||
if line.width() <= width {
|
||||
return vec![line.to_string()];
|
||||
}
|
||||
|
||||
let mut resulted_lines = vec![];
|
||||
// Use this buffer to calculate width
|
||||
let mut buf_line = String::with_capacity(width);
|
||||
// Use this buffer to slice line
|
||||
let mut buf_chars = Vec::with_capacity(width);
|
||||
let mut last_break_char_idx = 0;
|
||||
let mut last_break_idx = 0;
|
||||
for (i, char) in line.chars().enumerate() {
|
||||
buf_line.push(char);
|
||||
buf_chars.push(char);
|
||||
|
||||
if ",.; ".contains(char) {
|
||||
last_break_char_idx = i + 1
|
||||
}
|
||||
|
||||
let buf_line_width = buf_line.width();
|
||||
if buf_line_width < width {
|
||||
continue;
|
||||
}
|
||||
|
||||
if last_break_char_idx == 0 {
|
||||
// no spaces in line, break right here
|
||||
match buf_line_width.cmp(&width) {
|
||||
std::cmp::Ordering::Equal => {
|
||||
resulted_lines.push(mem::take(&mut buf_line));
|
||||
last_break_idx = i + 1;
|
||||
|
||||
buf_line = String::with_capacity(width);
|
||||
buf_chars = Vec::with_capacity(width);
|
||||
}
|
||||
std::cmp::Ordering::Greater => {
|
||||
let last_idx = buf_line.len() - char.len_utf8();
|
||||
buf_line = buf_line[..last_idx].to_string();
|
||||
resulted_lines.push(mem::take(&mut buf_line));
|
||||
last_break_idx = i - last_idx + 1;
|
||||
|
||||
buf_line = String::with_capacity(width);
|
||||
buf_line.push(char);
|
||||
buf_chars = Vec::with_capacity(width);
|
||||
buf_chars.push(char);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
let break_idx = last_break_char_idx - last_break_idx;
|
||||
resulted_lines.push(buf_chars[..break_idx].iter().collect());
|
||||
last_break_idx = last_break_char_idx;
|
||||
|
||||
buf_chars = if last_break_char_idx == buf_chars.len() {
|
||||
Vec::with_capacity(width)
|
||||
} else {
|
||||
buf_chars[break_idx..].to_vec()
|
||||
};
|
||||
buf_line = buf_chars.iter().collect();
|
||||
}
|
||||
last_break_char_idx = 0;
|
||||
}
|
||||
if !buf_line.is_empty() && buf_line != "\n" {
|
||||
resulted_lines.push(buf_line);
|
||||
}
|
||||
|
||||
resulted_lines
|
||||
}
|
||||
|
||||
fn replace_tabs_with_spaces(buf: &mut Vec<u8>, tab_size: usize) {
|
||||
let mut i = 0;
|
||||
while i < buf.len() {
|
||||
if buf[i] == b'\t' {
|
||||
buf.splice(i..i + 1, vec![b' '; tab_size]);
|
||||
i += tab_size;
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn highlight_with(
|
||||
before: Vec<String>,
|
||||
after: Vec<String>,
|
||||
|
@ -1,4 +1,5 @@
|
||||
use mlua::{AnyUserData, IntoLuaMulti, Lua, Table, Value};
|
||||
use yazi_config::PREVIEW;
|
||||
use yazi_shared::{emit, event::Cmd, Layer, PeekError};
|
||||
|
||||
use super::Utils;
|
||||
@ -37,7 +38,7 @@ impl Utils {
|
||||
let mut lock = PreviewLock::try_from(t)?;
|
||||
|
||||
let text =
|
||||
match Highlighter::new(&lock.url).highlight(lock.skip, area.height as usize).await {
|
||||
match Highlighter::new(&lock.url).highlight(lock.skip, *area, PREVIEW.word_wrap).await {
|
||||
Ok(text) => text,
|
||||
Err(PeekError::Exceed(max)) => return (false, max).into_lua_multi(lua),
|
||||
Err(_) => return (false, Value::Nil).into_lua_multi(lua),
|
||||
|
Loading…
Reference in New Issue
Block a user