mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
fix: shift of auto-wrap points in both code and JSON previewers (#3739)
This commit is contained in:
parent
9648d8a43b
commit
be91b4111c
15 changed files with 163 additions and 85 deletions
38
Cargo.lock
generated
38
Cargo.lock
generated
|
|
@ -1402,9 +1402,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
|||
|
||||
[[package]]
|
||||
name = "erased-serde"
|
||||
version = "0.4.9"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89e8918065695684b2b0702da20382d5ae6065cf3327bc2d6436bd49a71ce9f3"
|
||||
checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_core",
|
||||
|
|
@ -1735,19 +1735,19 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
|||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"r-efi 5.3.0",
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.4.1"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec"
|
||||
checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"r-efi 6.0.0",
|
||||
"wasip2",
|
||||
"wasip3",
|
||||
]
|
||||
|
|
@ -3366,9 +3366,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.44"
|
||||
version = "1.0.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
|
||||
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
|
@ -3379,6 +3379,12 @@ version = "5.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||
|
||||
[[package]]
|
||||
name = "radium"
|
||||
version = "0.7.0"
|
||||
|
|
@ -4619,9 +4625,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.49.0"
|
||||
version = "1.50.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
|
||||
checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"libc",
|
||||
|
|
@ -4636,9 +4642,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.6.0"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
|
||||
checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -4671,9 +4677,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "1.0.3+spec-1.1.0"
|
||||
version = "1.0.4+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7614eaf19ad818347db24addfa201729cf2a9b6fdfd9eb0ab870fcacc606c0c"
|
||||
checksum = "c94c3321114413476740df133f0d8862c61d87c8d26f04c6841e033c8c80db47"
|
||||
dependencies = [
|
||||
"indexmap 2.13.0",
|
||||
"serde_core",
|
||||
|
|
@ -4902,7 +4908,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb"
|
||||
dependencies = [
|
||||
"atomic",
|
||||
"getrandom 0.4.1",
|
||||
"getrandom 0.4.2",
|
||||
"js-sys",
|
||||
"serde_core",
|
||||
"wasm-bindgen",
|
||||
|
|
@ -6066,6 +6072,7 @@ dependencies = [
|
|||
name = "yazi-plugin"
|
||||
version = "26.2.2"
|
||||
dependencies = [
|
||||
"ansi-to-tui",
|
||||
"anyhow",
|
||||
"crossterm 0.29.0",
|
||||
"futures",
|
||||
|
|
@ -6192,6 +6199,7 @@ name = "yazi-shim"
|
|||
version = "26.2.2"
|
||||
dependencies = [
|
||||
"crossterm 0.29.0",
|
||||
"ratatui",
|
||||
"twox-hash",
|
||||
"yazi-macro",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -68,10 +68,10 @@ serde_json = "1.0.149"
|
|||
serde_with = "3.17.0"
|
||||
syntect = { version = "5.3.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
|
||||
thiserror = "2.0.18"
|
||||
tokio = { version = "1.49.0", features = [ "full" ] }
|
||||
tokio = { version = "1.50.0", features = [ "full" ] }
|
||||
tokio-stream = "0.1.18"
|
||||
tokio-util = "0.7.18"
|
||||
toml = { version = "1.0.3" }
|
||||
toml = { version = "1.0.4" }
|
||||
tracing = { version = "0.1.44", features = [ "max_level_debug", "release_max_level_debug" ] }
|
||||
twox-hash = { version = "2.1.2", default-features = false, features = [ "std", "random", "xxhash3_128" ] }
|
||||
typed-path = "0.12.3"
|
||||
|
|
|
|||
|
|
@ -18,6 +18,10 @@ impl Wrap {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Wrap> for Option<ratatui::widgets::Wrap> {
|
||||
fn from(value: Wrap) -> Self { value.0 }
|
||||
}
|
||||
|
||||
impl From<ratatui::widgets::Wrap> for Wrap {
|
||||
fn from(value: ratatui::widgets::Wrap) -> Self { Self(Some(value)) }
|
||||
}
|
||||
|
|
@ -52,3 +56,13 @@ impl FromLua for Wrap {
|
|||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoLua for Wrap {
|
||||
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
|
||||
match self.0 {
|
||||
None => 0.into_lua(lua),
|
||||
Some(ratatui::widgets::Wrap { trim: false }) => 1.into_lua(lua),
|
||||
Some(ratatui::widgets::Wrap { trim: true }) => 2.into_lua(lua),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,4 +18,4 @@ proc-macro = true
|
|||
[dependencies]
|
||||
# External dependencies
|
||||
syn = { version = "2.0.117", features = [ "full" ] }
|
||||
quote = "1.0.44"
|
||||
quote = "1.0.45"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{borrow::Cow, path::PathBuf};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::{Context, Result, bail};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
@ -9,9 +9,6 @@ use yazi_shared::{SStr, timestamp_us};
|
|||
use super::PreviewWrap;
|
||||
use crate::normalize_path;
|
||||
|
||||
#[rustfmt::skip]
|
||||
const TABS: &[&str] = &["", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "];
|
||||
|
||||
#[derive(Debug, Deserialize, DeserializeOver2, Serialize)]
|
||||
pub struct Preview {
|
||||
pub wrap: PreviewWrap,
|
||||
|
|
@ -34,10 +31,15 @@ impl Preview {
|
|||
self.cache_dir.join(format!("{prefix}-{}", timestamp_us()))
|
||||
}
|
||||
|
||||
pub fn indent(&self) -> SStr { Self::indent_with(self.tab_size as usize) }
|
||||
pub fn indent(&self) -> SStr {
|
||||
#[rustfmt::skip]
|
||||
const TABS: &[&str] = &["", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "];
|
||||
|
||||
pub fn indent_with(n: usize) -> SStr {
|
||||
if let Some(s) = TABS.get(n) { Cow::Borrowed(s) } else { Cow::Owned(" ".repeat(n)) }
|
||||
if let Some(&s) = TABS.get(self.tab_size as usize) {
|
||||
s.into()
|
||||
} else {
|
||||
" ".repeat(self.tab_size as usize).into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,3 +6,12 @@ pub enum PreviewWrap {
|
|||
No,
|
||||
Yes,
|
||||
}
|
||||
|
||||
impl From<PreviewWrap> for Option<ratatui::widgets::Wrap> {
|
||||
fn from(wrap: PreviewWrap) -> Self {
|
||||
match wrap {
|
||||
PreviewWrap::No => None,
|
||||
PreviewWrap::Yes => Some(ratatui::widgets::Wrap { trim: false }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ yazi-vfs = { path = "../yazi-vfs", version = "26.2.2" }
|
|||
yazi-widgets = { path = "../yazi-widgets", version = "26.2.2" }
|
||||
|
||||
# External dependencies
|
||||
ansi-to-tui = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
hashbrown = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ function M:peek(job)
|
|||
return require("code"):peek(job)
|
||||
end
|
||||
|
||||
local wrap = rt.preview.wrap
|
||||
local limit = job.area.h
|
||||
local i, lines = 0, ""
|
||||
repeat
|
||||
|
|
@ -21,7 +22,7 @@ function M:peek(job)
|
|||
break
|
||||
end
|
||||
|
||||
i = i + 1
|
||||
i = i + ui.height(next, { width = job.area.w, ansi = true, wrap = wrap })
|
||||
if i > job.skip then
|
||||
lines = lines .. next
|
||||
end
|
||||
|
|
@ -32,10 +33,7 @@ function M:peek(job)
|
|||
ya.emit("peek", { math.max(0, i - limit), only_if = job.file.url, upper_bound = true })
|
||||
else
|
||||
lines = lines:gsub("\t", string.rep(" ", rt.preview.tab_size))
|
||||
ya.preview_widget(
|
||||
job,
|
||||
ui.Text.parse(lines):area(job.area):wrap(rt.preview.wrap == "yes" and ui.Wrap.YES or ui.Wrap.NO)
|
||||
)
|
||||
ya.preview_widget(job, ui.Text.parse(lines):area(job.area):wrap(wrap))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,21 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use mlua::{AnyUserData, ExternalError, IntoLua, Lua, ObjectLike, Table, Value};
|
||||
use ansi_to_tui::IntoText;
|
||||
use mlua::{AnyUserData, ExternalError, ExternalResult, IntoLua, Lua, ObjectLike, Table, Value};
|
||||
use tracing::error;
|
||||
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
|
||||
use yazi_binding::{Composer, ComposerGet, ComposerSet, Permit, PermitRef, elements::{Line, Rect, Span}, runtime};
|
||||
use yazi_config::LAYOUT;
|
||||
use yazi_binding::{Composer, ComposerGet, ComposerSet, Permit, PermitRef, elements::{Line, Rect, Span, Wrap}, runtime};
|
||||
use yazi_config::{LAYOUT, YAZI};
|
||||
use yazi_proxy::AppProxy;
|
||||
use yazi_shared::replace_to_printable;
|
||||
use yazi_shim::ratatui::line_count;
|
||||
use yazi_term::YIELD_TO_SUBPROCESS;
|
||||
|
||||
pub fn compose() -> Composer<ComposerGet, ComposerSet> {
|
||||
fn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {
|
||||
match key {
|
||||
b"area" => area(lua)?,
|
||||
b"height" => height(lua)?,
|
||||
b"hide" => hide(lua)?,
|
||||
b"printable" => printable(lua)?,
|
||||
b"redraw" => redraw(lua)?,
|
||||
|
|
@ -43,6 +46,21 @@ pub(super) fn area(lua: &Lua) -> mlua::Result<Value> {
|
|||
f.into_lua(lua)
|
||||
}
|
||||
|
||||
pub(super) fn height(lua: &Lua) -> mlua::Result<Value> {
|
||||
let f = lua.create_function(|_, (s, opts): (mlua::String, Table)| {
|
||||
let width = opts.raw_get("width")?;
|
||||
let wrap: Wrap = opts.raw_get("wrap")?;
|
||||
|
||||
Ok(if opts.raw_get("ansi")? {
|
||||
line_count(s.to_string_lossy().to_text().into_lua_err()?, width, YAZI.preview.indent(), wrap)
|
||||
} else {
|
||||
line_count(s.to_string_lossy(), width, YAZI.preview.indent(), wrap)
|
||||
})
|
||||
})?;
|
||||
|
||||
f.into_lua(lua)
|
||||
}
|
||||
|
||||
pub(super) fn hide(lua: &Lua) -> mlua::Result<Value> {
|
||||
let f = lua.create_async_function(|lua, ()| async move {
|
||||
if runtime!(lua)?.blocking {
|
||||
|
|
|
|||
72
yazi-plugin/src/external/highlighter.rs
vendored
72
yazi-plugin/src/external/highlighter.rs
vendored
|
|
@ -1,4 +1,4 @@
|
|||
use std::{borrow::Cow, io::Cursor, mem, path::{Path, PathBuf}, sync::OnceLock};
|
||||
use std::{io::Cursor, path::{Path, PathBuf}, sync::OnceLock};
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
use ratatui::{layout::Size, text::{Line, Span, Text}};
|
||||
|
|
@ -7,6 +7,7 @@ use tokio::io::{AsyncBufReadExt, AsyncSeekExt, BufReader};
|
|||
use yazi_config::{THEME, YAZI, preview::PreviewWrap};
|
||||
use yazi_fs::provider::{Provider, local::Local};
|
||||
use yazi_shared::{Ids, errors::PeekError, push_printable_char};
|
||||
use yazi_shim::ratatui::line_count;
|
||||
|
||||
static INCR: Ids = Ids::new();
|
||||
static SYNTECT: OnceLock<(Theme, SyntaxSet)> = OnceLock::new();
|
||||
|
|
@ -16,7 +17,6 @@ pub struct Highlighter {
|
|||
}
|
||||
|
||||
impl Highlighter {
|
||||
#[inline]
|
||||
pub fn new<P>(path: P) -> Self
|
||||
where
|
||||
P: Into<PathBuf>,
|
||||
|
|
@ -40,10 +40,10 @@ impl Highlighter {
|
|||
(theme, syntaxes)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn abort() { INCR.next(); }
|
||||
|
||||
pub async fn highlight(&self, skip: usize, size: Size) -> Result<Text<'static>, PeekError> {
|
||||
let indent = YAZI.preview.indent();
|
||||
let mut reader = BufReader::new(Local::regular(&self.path).open().await?);
|
||||
|
||||
let syntax = Self::find_syntax(&self.path, &mut reader).await;
|
||||
|
|
@ -60,26 +60,20 @@ impl Highlighter {
|
|||
return Err("Binary file".into());
|
||||
}
|
||||
|
||||
if !plain && (buf.len() > 5000 || Self::contains_control_chars(&buf)) {
|
||||
let remaining = Self::normalize_control_chars(&mut buf);
|
||||
if !plain && (remaining || buf.len() > 5000) {
|
||||
plain = true;
|
||||
drop(mem::take(&mut before));
|
||||
}
|
||||
|
||||
if buf.ends_with(b"\r\n") {
|
||||
buf.pop();
|
||||
buf.pop();
|
||||
buf.push(b'\n');
|
||||
before.clear();
|
||||
}
|
||||
|
||||
i += if i >= skip {
|
||||
buf.iter_mut().for_each(Self::carriage_return_to_line_feed);
|
||||
after.push(String::from_utf8_lossy(&buf).into_owned());
|
||||
Self::line_height(&after[after.len() - 1], size.width)
|
||||
line_count(&**after.last().unwrap(), size.width, &indent, YAZI.preview.wrap)
|
||||
} else if !plain {
|
||||
before.push(String::from_utf8_lossy(&buf).into_owned());
|
||||
Self::line_height(&before[before.len() - 1], size.width)
|
||||
} else if YAZI.preview.wrap == PreviewWrap::Yes {
|
||||
Self::line_height(&String::from_utf8_lossy(&buf), size.width)
|
||||
line_count(&**before.last().unwrap(), size.width, &indent, YAZI.preview.wrap)
|
||||
} else if YAZI.preview.wrap != PreviewWrap::No {
|
||||
line_count(String::from_utf8_lossy(&buf), size.width, &indent, YAZI.preview.wrap)
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
|
@ -166,42 +160,22 @@ impl Highlighter {
|
|||
}
|
||||
}
|
||||
|
||||
fn line_height(s: &str, width: u16) -> usize {
|
||||
if YAZI.preview.wrap != PreviewWrap::Yes {
|
||||
return 1;
|
||||
fn normalize_control_chars(buf: &mut Vec<u8>) -> bool {
|
||||
if buf.ends_with(b"\r\n") {
|
||||
buf.pop();
|
||||
buf.pop();
|
||||
buf.push(b'\n');
|
||||
}
|
||||
|
||||
let pad = YAZI
|
||||
.preview
|
||||
.tab_size
|
||||
.checked_sub(1)
|
||||
.map(|n| s.bytes().filter(|&b| b == b'\t').count() * n as usize)
|
||||
.map(|n| yazi_config::preview::Preview::indent_with(n))
|
||||
.unwrap_or_default();
|
||||
|
||||
let line = Line {
|
||||
spans: vec![Span { content: pad, style: Default::default() }, Span {
|
||||
content: Cow::Borrowed(s),
|
||||
..Default::default()
|
||||
}],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
ratatui::widgets::Paragraph::new(line)
|
||||
.wrap(ratatui::widgets::Wrap { trim: false })
|
||||
.line_count(width)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn contains_control_chars(buf: &[u8]) -> bool {
|
||||
buf.iter().any(|&b| b.is_ascii_control() && !matches!(b, b'\t' | b'\n' | b'\r'))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn carriage_return_to_line_feed(b: &mut u8) {
|
||||
if *b == b'\r' {
|
||||
*b = b'\n';
|
||||
let mut remaining = false;
|
||||
for b in buf.iter_mut() {
|
||||
if *b == b'\r' {
|
||||
*b = b'\n';
|
||||
} else {
|
||||
remaining |= matches!(b, 0..=0x08 | 0x0B..=0x1F | 0x7F);
|
||||
}
|
||||
}
|
||||
remaining
|
||||
}
|
||||
|
||||
fn merge_highlight_lines(s: &[String], tab_size: u8) -> String {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use mlua::{IntoLua, Lua, LuaSerdeExt, Value};
|
||||
use yazi_binding::{Composer, ComposerGet, ComposerSet, SER_OPT, Url};
|
||||
use yazi_binding::{Composer, ComposerGet, ComposerSet, SER_OPT, Url, elements::Wrap};
|
||||
use yazi_boot::ARGS;
|
||||
use yazi_config::YAZI;
|
||||
|
||||
|
|
@ -78,7 +78,7 @@ fn preview() -> Composer<ComposerGet, ComposerSet> {
|
|||
fn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {
|
||||
let p = &YAZI.preview;
|
||||
match key {
|
||||
b"wrap" => lua.to_value_with(&p.wrap, SER_OPT)?,
|
||||
b"wrap" => Wrap::from(p.wrap).into_lua(lua)?,
|
||||
b"tab_size" => p.tab_size.into_lua(lua)?,
|
||||
b"max_width" => p.max_width.into_lua(lua)?,
|
||||
b"max_height" => p.max_height.into_lua(lua)?,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ yazi-macro = { path = "../yazi-macro", version = "26.2.2" }
|
|||
|
||||
# External dependencies
|
||||
crossterm = { workspace = true }
|
||||
ratatui = { workspace = true }
|
||||
twox-hash = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
yazi_macro::mod_pub!(crossterm);
|
||||
yazi_macro::mod_pub!(crossterm ratatui);
|
||||
|
||||
yazi_macro::mod_flat!(twox);
|
||||
|
|
|
|||
1
yazi-shim/src/ratatui/mod.rs
Normal file
1
yazi-shim/src/ratatui/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
yazi_macro::mod_flat!(paragraph);
|
||||
52
yazi-shim/src/ratatui/paragraph.rs
Normal file
52
yazi-shim/src/ratatui/paragraph.rs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
use ratatui::{text::Text, widgets::{Paragraph, Wrap}};
|
||||
|
||||
pub fn line_count<'a, T, W, I>(text: T, width: u16, indent: I, wrap: W) -> usize
|
||||
where
|
||||
T: Into<Text<'a>>,
|
||||
I: AsRef<str>,
|
||||
W: Into<Option<Wrap>>,
|
||||
{
|
||||
line_count_impl(text.into(), width, indent.as_ref(), wrap.into())
|
||||
}
|
||||
|
||||
fn line_count_impl(mut text: Text<'_>, mut width: u16, indent: &str, wrap: Option<Wrap>) -> usize {
|
||||
width = width.max(1);
|
||||
|
||||
let Some(wrap) = wrap else {
|
||||
return Paragraph::new(text).line_count(width);
|
||||
};
|
||||
|
||||
if indent.len() == 1 {
|
||||
return Paragraph::new(text).wrap(wrap).line_count(width);
|
||||
}
|
||||
|
||||
let extra = indent.len().saturating_sub(1);
|
||||
for line in &mut text.lines {
|
||||
for span in &mut line.spans {
|
||||
let mut out = None::<String>;
|
||||
let mut start = 0;
|
||||
for (idx, b) in span.content.bytes().enumerate() {
|
||||
if b != b'\t' {
|
||||
continue;
|
||||
}
|
||||
|
||||
let out = out.get_or_insert_with(|| String::with_capacity(span.content.len() + extra));
|
||||
if start < idx {
|
||||
out.push_str(unsafe { span.content.get_unchecked(start..idx) });
|
||||
}
|
||||
|
||||
out.push_str(indent);
|
||||
start = idx + 1;
|
||||
}
|
||||
|
||||
if let Some(mut out) = out {
|
||||
if start < span.content.len() {
|
||||
out.push_str(unsafe { span.content.get_unchecked(start..) });
|
||||
}
|
||||
span.content = out.into();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Paragraph::new(text).wrap(wrap).line_count(width)
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue