1use ratatui::style::Style;
4use ratatui::text::{Line, Span};
5
6pub fn wrap(line: &Line<'_>, width: usize) -> Vec<Line<'static>> {
8 if width == 0 {
9 return vec![Line::default()];
10 }
11 let indent = line
12 .spans
13 .first()
14 .map(|s| s.content.len() - s.content.trim_start().len())
15 .unwrap_or(0)
16 .min(width / 2);
17
18 let mut w = Wrapper {
19 width,
20 indent,
21 lines: Vec::new(),
22 cur: Vec::new(),
23 col: 0,
24 };
25 for span in &line.spans {
26 let mut rest: &str = &span.content;
27 while !rest.is_empty() {
28 let end = rest.find(' ').unwrap_or(rest.len());
29 if end == 0 {
30 w.space(span.style);
31 rest = &rest[1..];
32 } else {
33 w.word(&rest[..end], span.style);
34 rest = &rest[end..];
35 }
36 }
37 }
38 w.finish(line.style)
39}
40
41pub fn wrap_all(lines: &[Line<'static>], width: usize) -> Vec<Line<'static>> {
43 lines.iter().flat_map(|l| wrap(l, width)).collect()
44}
45
46struct Wrapper {
47 width: usize,
48 indent: usize,
49 lines: Vec<Line<'static>>,
50 cur: Vec<Span<'static>>,
51 col: usize,
52}
53
54impl Wrapper {
55 fn word(&mut self, word: &str, style: Style) {
56 let len = word.chars().count();
57 if self.col + len > self.width && self.col > self.indent {
58 self.newline();
59 }
60 if len > self.width {
61 let mut chars = word.chars().peekable();
63 while chars.peek().is_some() {
64 let room = self.width.saturating_sub(self.col).max(1);
65 let chunk: String = chars.by_ref().take(room).collect();
66 self.col += chunk.chars().count();
67 self.cur.push(Span::styled(chunk, style));
68 if chars.peek().is_some() {
69 self.newline();
70 }
71 }
72 return;
73 }
74 self.cur.push(Span::styled(word.to_owned(), style));
75 self.col += len;
76 }
77
78 fn space(&mut self, style: Style) {
79 if self.col == 0 || self.col >= self.width {
80 if self.col == 0 && self.cur.is_empty() && self.lines.is_empty() {
82 self.cur.push(Span::styled(" ".to_owned(), style));
83 self.col += 1;
84 }
85 return;
86 }
87 self.cur.push(Span::styled(" ".to_owned(), style));
88 self.col += 1;
89 }
90
91 fn newline(&mut self) {
92 let spans = std::mem::take(&mut self.cur);
93 self.lines.push(Line::from(spans));
94 self.col = 0;
95 if self.indent > 0 {
96 self.cur.push(Span::raw(" ".repeat(self.indent)));
97 self.col = self.indent;
98 }
99 }
100
101 fn finish(mut self, style: Style) -> Vec<Line<'static>> {
102 let spans = std::mem::take(&mut self.cur);
103 if !spans.is_empty() || self.lines.is_empty() {
104 self.lines.push(Line::from(spans));
105 }
106 self.lines
107 .into_iter()
108 .map(|l| l.patch_style(style))
109 .collect()
110 }
111}