fix(tui): wrap CJK runs in diff and pager
Hard-wrap overlong CJK/no-whitespace runs in diff and pager text wrappers so they do not overflow the right edge. Fixes #1571. Co-authored-by: Aitensa <1900013029@pku.edu.cn>
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
use ratatui::style::{Modifier, Style};
|
||||
use ratatui::text::{Line, Span};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
|
||||
|
||||
use crate::palette;
|
||||
|
||||
@@ -357,6 +357,14 @@ fn wrap_text(text: &str, width: usize) -> Vec<String> {
|
||||
|
||||
for word in text.split_whitespace() {
|
||||
let word_width = word.width();
|
||||
if word_width > width {
|
||||
if !current.is_empty() {
|
||||
lines.push(std::mem::take(&mut current));
|
||||
current_width = 0;
|
||||
}
|
||||
push_word_breaking_chars(word, width, &mut current, &mut current_width, &mut lines);
|
||||
continue;
|
||||
}
|
||||
let additional = if current.is_empty() {
|
||||
word_width
|
||||
} else {
|
||||
@@ -385,6 +393,24 @@ fn wrap_text(text: &str, width: usize) -> Vec<String> {
|
||||
lines
|
||||
}
|
||||
|
||||
fn push_word_breaking_chars(
|
||||
word: &str,
|
||||
width: usize,
|
||||
current: &mut String,
|
||||
current_width: &mut usize,
|
||||
lines: &mut Vec<String>,
|
||||
) {
|
||||
for ch in word.chars() {
|
||||
let char_width = ch.width().unwrap_or(1);
|
||||
if *current_width + char_width > width && *current_width > 0 {
|
||||
lines.push(std::mem::take(current));
|
||||
*current_width = 0;
|
||||
}
|
||||
current.push(ch);
|
||||
*current_width += char_width;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -450,4 +476,16 @@ diff --git a/src/a.rs b/src/a.rs
|
||||
"deleted line should carry - gutter: {text:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrap_text_breaks_overlong_cjk_runs() {
|
||||
let text = "这是一个非常长的中文字符串".repeat(10);
|
||||
let lines = wrap_text(&text, 16);
|
||||
|
||||
for line in &lines {
|
||||
assert!(line.width() <= 16, "line {line:?} exceeds width 16");
|
||||
}
|
||||
|
||||
assert_eq!(lines.join(""), text);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ use ratatui::{
|
||||
text::{Line, Span},
|
||||
widgets::{Block, Borders, Clear, Padding, Paragraph, Widget, Wrap},
|
||||
};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
|
||||
|
||||
use crate::palette;
|
||||
use crate::tui::views::{ModalKind, ModalView, ViewAction, ViewEvent};
|
||||
@@ -482,6 +482,14 @@ fn wrap_text(text: &str, width: usize) -> Vec<String> {
|
||||
|
||||
for word in text.split_whitespace() {
|
||||
let word_width = word.width();
|
||||
if word_width > width {
|
||||
if !current.is_empty() {
|
||||
lines.push(std::mem::take(&mut current));
|
||||
current_width = 0;
|
||||
}
|
||||
push_word_breaking_chars(word, width, &mut current, &mut current_width, &mut lines);
|
||||
continue;
|
||||
}
|
||||
let additional = if current.is_empty() {
|
||||
word_width
|
||||
} else {
|
||||
@@ -510,6 +518,24 @@ fn wrap_text(text: &str, width: usize) -> Vec<String> {
|
||||
lines
|
||||
}
|
||||
|
||||
fn push_word_breaking_chars(
|
||||
word: &str,
|
||||
width: usize,
|
||||
current: &mut String,
|
||||
current_width: &mut usize,
|
||||
lines: &mut Vec<String>,
|
||||
) {
|
||||
for ch in word.chars() {
|
||||
let char_width = ch.width().unwrap_or(1);
|
||||
if *current_width + char_width > width && *current_width > 0 {
|
||||
lines.push(std::mem::take(current));
|
||||
*current_width = 0;
|
||||
}
|
||||
current.push(ch);
|
||||
*current_width += char_width;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -886,4 +912,16 @@ mod tests {
|
||||
"expected a Yellow/DarkGray highlight cell on the matched-line text columns"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrap_text_breaks_overlong_cjk_runs() {
|
||||
let text = "这是一个非常长的中文字符串".repeat(10);
|
||||
let lines = wrap_text(&text, 16);
|
||||
|
||||
for line in &lines {
|
||||
assert!(line.width() <= 16, "line {line:?} exceeds width 16");
|
||||
}
|
||||
|
||||
assert_eq!(lines.join(""), text);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user