Compare commits

...

13 Commits

Author SHA1 Message Date
Artyom Artamonov
29409c9ee8
Merge 86c53d8ea2 into 9d0ef9a5dc 2024-07-07 10:25:06 +05:30
三咲雅 · Misaki Masa
9d0ef9a5dc
fix: build jemalloc with 64KB pagesize for linux/arm64 (#1270) 2024-07-07 10:30:03 +08:00
Artyom Artamonov
86c53d8ea2 Merge branch 'main' into preview-word-wrap 2024-06-22 01:55:21 +05:00
Artyom Artamonov
c078fb8513 Merge branch 'main' into preview-word-wrap 2024-06-22 01:44:43 +05:00
Artyom Artamonov
6fb569746e remove second loop in word wrapping, refactoring 2024-06-22 01:42:31 +05:00
Artyom Artamonov
931a63808a chunk_by_width optimization 2024-06-19 22:50:19 +05:00
三咲雅 · Misaki Masa
1f562031f4
feat: keep file creation time on macOS (#1169) 2024-06-19 23:10:30 +08:00
Artyom Artamonov
693ab45625 soft wrap on some characters 2024-06-19 02:33:47 +05:00
Artyom Artamonov
cd734ee69f word wrapping feature refactor 2024-06-16 17:08:55 +05:00
Artyom Artamonov
0cff3ce15c word wrap feature 2024-06-16 16:12:28 +05:00
Artyom Artamonov
64d961e49a Merge branch 'preview-word-wrap' of github.com:mskvsk/yazi into preview-word-wrap 2024-06-16 02:57:14 +05:00
Lex Moskovski
5a66754604 indentation fix 2024-06-08 15:17:05 -07:00
Lex Moskovski
6a30dfc137 word wrap in preview 2024-06-08 15:12:22 -07:00
7 changed files with 204 additions and 41 deletions

View File

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

View File

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

View File

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

View File

@ -17,6 +17,7 @@ mouse_events = [ "click", "scroll" ]
[preview]
tab_size = 2
word_wrap = false
max_width = 600
max_height = 900
cache_dir = ""

View File

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

View File

@ -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,
);
}
if i > skip {
after.push(String::from_utf8_lossy(&buf).into_owned());
} else if !plain {
before.push(String::from_utf8_lossy(&buf).into_owned());
}
buf.clear();
long_line.clear();
}
if skip > 0 && i < skip + limit {
return Err(PeekError::Exceed(i.saturating_sub(limit)));
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)));
}
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>,

View File

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