1use std::fmt;
4
5use anstyle::Style;
6use unicode_segmentation::{GraphemeCursor, GraphemeIncomplete, UnicodeSegmentation};
7
8use crate::cells::{cluster_width, is_space};
9use crate::span::Span;
10
11#[derive(Clone, Copy, Debug)]
13pub(crate) struct Piece<'a> {
14 pub(crate) text: &'a str,
15 pub(crate) style: Style,
16 pub(crate) width: u16,
17 pub(crate) space: bool,
18}
19
20#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
28pub struct Line {
29 spans: Vec<Span>,
30}
31
32impl Line {
33 #[must_use]
35 pub fn new() -> Self {
36 Self::default()
37 }
38
39 pub fn push(&mut self, span: Span) {
54 if span.text.is_empty() {
55 return;
56 }
57 match self.spans.last_mut() {
58 Some(last) if last.style == span.style => last.text.push_str(&span.text),
59 Some(_) | None => self.push_new(span),
60 }
61 }
62
63 #[must_use]
65 pub fn spans(&self) -> &[Span] {
66 &self.spans
67 }
68
69 #[must_use]
72 pub fn width(&self) -> u16 {
73 self.spans
74 .iter()
75 .map(Span::width)
76 .fold(0, u16::saturating_add)
77 }
78
79 #[must_use]
81 pub fn is_empty(&self) -> bool {
82 self.spans.iter().all(|span| span.text.is_empty())
83 }
84
85 pub fn trim_end(&mut self) {
90 while let Some(last) = self.spans.last_mut() {
91 let trailing = trailing_space_len(&last.text);
92 if trailing == last.text.len() {
93 self.spans.pop();
94 } else {
95 let kept = last.text.len() - trailing;
96 last.text.truncate(kept);
97 break;
98 }
99 }
100 }
101
102 pub(crate) fn append(&mut self, text: &str, style: Style) {
105 if text.is_empty() {
106 return;
107 }
108 match self.spans.last_mut() {
109 Some(last) if last.style == style => last.text.push_str(text),
110 Some(_) | None => self.push_new(Span {
111 text: text.to_owned(),
112 style,
113 }),
114 }
115 }
116
117 fn push_new(&mut self, span: Span) {
119 self.spans.push(span);
120 self.keep_clusters_whole();
121 }
122
123 fn keep_clusters_whole(&mut self) {
127 let [.., before, last] = self.spans.as_mut_slice() else {
128 return;
129 };
130 let moved = continuation_len(&before.text, &last.text);
131 if moved == 0 {
132 return;
133 }
134 before.text.extend(last.text.drain(..moved));
135 if last.text.is_empty() {
136 self.spans.pop();
137 }
138 }
139
140 pub(crate) fn pieces(&self) -> Vec<Piece<'_>> {
141 self.spans
142 .iter()
143 .flat_map(|span| {
144 span.text.graphemes(true).map(|cluster| Piece {
145 text: cluster,
146 style: span.style,
147 width: cluster_width(cluster),
148 space: is_space(cluster),
149 })
150 })
151 .collect()
152 }
153
154 pub(crate) fn from_pieces(pieces: &[Piece<'_>]) -> Self {
155 let mut line = Self::new();
156 for piece in pieces {
157 line.append(piece.text, piece.style);
158 }
159 line
160 }
161}
162
163fn continuation_len(before: &str, after: &str) -> usize {
169 let junction = before.len();
170 let mut cursor = GraphemeCursor::new(junction, junction + after.len(), true);
171 loop {
172 match cursor.is_boundary(after, junction) {
173 Ok(true)
174 | Err(
175 GraphemeIncomplete::PrevChunk
176 | GraphemeIncomplete::NextChunk
177 | GraphemeIncomplete::InvalidOffset,
178 ) => return 0,
179 Ok(false) => break,
180 Err(GraphemeIncomplete::PreContext(end)) => cursor.provide_context(&before[..end], 0),
181 }
182 }
183 loop {
184 match cursor.next_boundary(after, junction) {
185 Ok(Some(end)) => return end.saturating_sub(junction),
186 Ok(None) => return after.len(),
187 Err(GraphemeIncomplete::PreContext(end)) => cursor.provide_context(&before[..end], 0),
188 Err(
189 GraphemeIncomplete::PrevChunk
190 | GraphemeIncomplete::NextChunk
191 | GraphemeIncomplete::InvalidOffset,
192 ) => return 0,
193 }
194 }
195}
196
197fn trailing_space_len(text: &str) -> usize {
199 text.graphemes(true)
200 .rev()
201 .take_while(|cluster| is_space(cluster))
202 .map(str::len)
203 .sum()
204}
205
206impl From<Span> for Line {
207 fn from(span: Span) -> Self {
208 let mut line = Self::new();
209 line.push(span);
210 line
211 }
212}
213
214impl Extend<Span> for Line {
215 fn extend<I: IntoIterator<Item = Span>>(&mut self, spans: I) {
216 for span in spans {
217 self.push(span);
218 }
219 }
220}
221
222impl FromIterator<Span> for Line {
223 fn from_iter<I: IntoIterator<Item = Span>>(spans: I) -> Self {
224 let mut line = Self::new();
225 line.extend(spans);
226 line
227 }
228}
229
230impl fmt::Display for Line {
231 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
232 self.spans
233 .iter()
234 .try_for_each(|span| formatter.write_str(&span.text))
235 }
236}