pub(crate) fn wrap_text(content: &str, max_width: usize) -> Vec<String> {
let max_width = max_width.max(1);
let mut lines = Vec::new();
for raw in content.split('\n') {
let mut line = String::new();
let mut line_len = 0usize; let mut first = true;
for word in raw.split(' ').filter(|w| !w.is_empty()) {
let word_len = word.chars().count();
if first {
line_len = push_word(&mut lines, &mut line, 0, word, max_width);
first = false;
} else if line_len + 1 + word_len <= max_width {
line.push(' ');
line_len = push_word(&mut lines, &mut line, line_len + 1, word, max_width);
} else {
lines.push(std::mem::take(&mut line));
line_len = push_word(&mut lines, &mut line, 0, word, max_width);
}
}
lines.push(line);
}
lines
}
fn push_word(
lines: &mut Vec<String>,
line: &mut String,
line_len: usize,
word: &str,
max: usize,
) -> usize {
let word_len = word.chars().count();
if line_len + word_len <= max {
line.push_str(word);
return line_len + word_len;
}
let mut current: Vec<char> = line.chars().collect();
current.extend(word.chars());
while current.len() > max {
let rest = current.split_off(max);
lines.push(current.into_iter().collect());
current = rest;
}
let len = current.len();
*line = current.into_iter().collect();
len
}
pub(crate) fn truncate_line(content: &str, max_width: usize) -> String {
if max_width == 0 {
return String::new();
}
let first = content.split('\n').next().unwrap_or("");
let chars: Vec<char> = first.chars().collect();
if chars.len() <= max_width {
return first.to_string();
}
let mut s: String = chars[..max_width.saturating_sub(1)].iter().collect();
s.push('โฆ');
s
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn wraps_on_word_boundaries() {
assert_eq!(
wrap_text("hello brave new world", 11),
vec!["hello brave", "new world"]
);
}
#[test]
fn hard_breaks_overlong_words() {
assert_eq!(wrap_text("abcdefgh", 3), vec!["abc", "def", "gh"]);
}
#[test]
fn respects_embedded_newlines() {
assert_eq!(wrap_text("a\nb", 10), vec!["a", "b"]);
}
#[test]
fn wrap_empty_content_yields_one_empty_line() {
assert_eq!(wrap_text("", 10), vec![""]);
}
#[test]
fn wrap_width_one_hard_breaks_every_char() {
assert_eq!(wrap_text("ab", 1), vec!["a", "b"]);
assert_eq!(wrap_text("ab", 0), vec!["a", "b"]);
}
#[test]
fn wrap_word_exactly_max_width_stays_on_one_line() {
assert_eq!(wrap_text("hello", 5), vec!["hello"]);
}
#[test]
fn wrap_collapses_consecutive_spaces() {
assert_eq!(wrap_text("a b", 10), vec!["a b"]);
}
#[test]
fn truncate_edge_widths_and_multiline() {
assert_eq!(truncate_line("hi", 0), "");
assert_eq!(truncate_line("hello", 1), "โฆ");
assert_eq!(truncate_line("multi\nline", 10), "multi");
}
#[test]
fn wrap_and_truncate_invariants_hold() {
let seeds = [
"",
" ",
"\n",
"\t",
"a\u{0301}", "๐๐๐", "ๆฅๆฌ่ชใฎใใญในใ", " lots of spaces ", "line1\nline2\nline3",
"\u{1b}[2J control esc",
&"x".repeat(300),
];
for s in seeds {
for w in [0usize, 1, 2, 5, 40, 300] {
let effective = w.max(1);
for line in wrap_text(s, w) {
assert!(line.chars().count() <= effective, "wrap {s:?} w={w}");
assert!(!line.contains('\n'));
}
let t = truncate_line(s, w);
assert!(t.chars().count() <= w, "truncate {s:?} w={w}");
assert!(!t.contains('\n'));
}
}
}
#[test]
fn truncate_clips_with_ellipsis() {
assert_eq!(truncate_line("hello world", 7), "hello โฆ");
assert_eq!(truncate_line("hi", 7), "hi");
}
}