1use std::fmt;
43
44use crate::style::Style;
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
52pub enum ControlType {
53 Bell,
54 CarriageReturn,
55 Home,
56 Clear,
57 ShowCursor,
58 HideCursor,
59 EnableAltScreen,
60 DisableAltScreen,
61 CursorUp,
62 CursorDown,
63 CursorForward,
64 CursorBackward,
65 CursorMoveToColumn,
66 CursorMoveTo,
67 EraseInLine,
68 SetWindowTitle,
69}
70
71impl ControlType {
72 pub fn to_ansi(&self, params: &[i32]) -> String {
74 match self {
75 Self::Bell => "\x07".into(),
76 Self::CarriageReturn => "\r".into(),
77 Self::Home => "\x1b[H".into(),
78 Self::Clear => "\x1b[2J".into(),
79 Self::ShowCursor => "\x1b[?25h".into(),
80 Self::HideCursor => "\x1b[?25l".into(),
81 Self::EnableAltScreen => "\x1b[?1049h".into(),
82 Self::DisableAltScreen => "\x1b[?1049l".into(),
83 Self::CursorUp => {
84 let n = params.first().copied().unwrap_or(1);
85 format!("\x1b[{n}A")
86 }
87 Self::CursorDown => {
88 let n = params.first().copied().unwrap_or(1);
89 format!("\x1b[{n}B")
90 }
91 Self::CursorForward => {
92 let n = params.first().copied().unwrap_or(1);
93 format!("\x1b[{n}C")
94 }
95 Self::CursorBackward => {
96 let n = params.first().copied().unwrap_or(1);
97 format!("\x1b[{n}D")
98 }
99 Self::CursorMoveToColumn => {
100 let col = params.first().copied().unwrap_or(0);
101 format!("\x1b[{col}G")
102 }
103 Self::CursorMoveTo => {
104 let row = params.first().copied().unwrap_or(0);
105 let col = params.get(1).copied().unwrap_or(0);
106 format!("\x1b[{row};{col}H")
107 }
108 Self::EraseInLine => {
109 let mode = params.first().copied().unwrap_or(0);
110 format!("\x1b[{mode}K")
111 }
112 Self::SetWindowTitle => {
113 let title: String = params
114 .iter()
115 .map(|n| char::from(*n as u8))
116 .collect();
117 format!("\x1b]0;{title}\x07")
118 }
119 }
120 }
121}
122
123#[derive(Debug, Clone, PartialEq, Eq, Hash)]
128pub enum ControlCode {
129 Simple(ControlType),
130 WithInt(ControlType, i32),
131 WithTwoInts(ControlType, i32, i32),
132 WithString(ControlType, String),
133}
134
135#[derive(Debug, Clone, PartialEq)]
144pub struct Segment {
145 pub text: String,
146 pub style: Option<Style>,
147 pub control: Option<ControlCode>,
148}
149
150impl Segment {
151 pub fn new(text: impl Into<String>) -> Self {
153 Self {
154 text: text.into(),
155 style: None,
156 control: None,
157 }
158 }
159
160 pub fn styled(text: impl Into<String>, style: Style) -> Self {
162 Self {
163 text: text.into(),
164 style: Some(style),
165 control: None,
166 }
167 }
168
169 pub fn control(code: ControlCode) -> Self {
171 Self {
172 text: String::new(),
173 style: None,
174 control: Some(code),
175 }
176 }
177
178 pub fn line() -> Self {
180 Self::new("\n")
181 }
182
183 pub fn cell_length(&self) -> usize {
185 if self.control.is_some() {
186 return 0;
187 }
188 unicode_width::UnicodeWidthStr::width(self.text.as_str())
189 }
190
191 pub fn is_empty(&self) -> bool {
193 self.text.is_empty() && self.control.is_none()
194 }
195
196 pub fn split(&self, offset: usize) -> (Segment, Option<Segment>) {
200 if offset == 0 {
201 return (Segment::new(""), Some(self.clone()));
202 }
203 let cell_len = self.cell_length();
204 if offset >= cell_len {
205 return (self.clone(), None);
206 }
207
208 let mut cell_count = 0usize;
210 let mut byte_pos = 0usize;
211 for (i, ch) in self.text.char_indices() {
212 let w = unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0);
213 if cell_count + w > offset {
214 break;
215 }
216 cell_count += w;
217 byte_pos = i + ch.len_utf8();
218 }
219
220 let left = Segment {
221 text: self.text[..byte_pos].to_string(),
222 style: self.style.clone(),
223 control: self.control.clone(),
224 };
225 let right = Segment {
226 text: self.text[byte_pos..].to_string(),
227 style: self.style.clone(),
228 control: self.control.clone(),
229 };
230 (left, Some(right))
231 }
232
233 pub fn to_ansi(&self) -> String {
235 if let Some(ref code) = self.control {
236 return match code {
237 ControlCode::Simple(ct) => ct.to_ansi(&[]),
238 ControlCode::WithInt(ct, a) => ct.to_ansi(&[*a]),
239 ControlCode::WithTwoInts(ct, a, b) => ct.to_ansi(&[*a, *b]),
240 ControlCode::WithString(ct, s) => {
241 let params: Vec<i32> = s.bytes().map(|b| b as i32).collect();
242 ct.to_ansi(¶ms)
243 }
244 };
245 }
246
247 let style_ansi = self.style.as_ref().map(|s| s.to_ansi()).unwrap_or_default();
248 let reset = self.style.as_ref().map(|s| s.reset_ansi()).unwrap_or("");
249
250 if style_ansi.is_empty() {
251 self.text.clone()
252 } else {
253 format!("{style_ansi}{}{reset}", self.text)
254 }
255 }
256}
257
258impl fmt::Display for Segment {
259 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
260 write!(f, "{}", self.to_ansi())
261 }
262}
263
264#[derive(Debug, Clone, Default)]
270pub struct Segments {
271 pub segments: Vec<Segment>,
272}
273
274impl Segments {
275 pub fn new() -> Self {
276 Self {
277 segments: Vec::new(),
278 }
279 }
280
281 pub fn push(&mut self, seg: Segment) {
282 self.segments.push(seg);
283 }
284
285 pub fn extend(&mut self, other: impl IntoIterator<Item = Segment>) {
286 self.segments.extend(other);
287 }
288
289 pub fn to_ansi(&self) -> String {
291 let mut out = String::new();
292 for seg in &self.segments {
293 out.push_str(&seg.to_ansi());
294 }
295 out
296 }
297
298 pub fn cell_len(&self) -> usize {
300 self.segments.iter().map(Segment::cell_length).sum()
301 }
302}
303
304impl fmt::Display for Segments {
305 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
306 write!(f, "{}", self.to_ansi())
307 }
308}
309
310impl From<Vec<Segment>> for Segments {
311 fn from(segments: Vec<Segment>) -> Self {
312 Self { segments }
313 }
314}
315
316impl IntoIterator for Segments {
317 type Item = Segment;
318 type IntoIter = std::vec::IntoIter<Segment>;
319
320 fn into_iter(self) -> Self::IntoIter {
321 self.segments.into_iter()
322 }
323}
324
325pub fn line() -> Segment {
331 Segment::line()
332}
333
334pub fn space(count: usize) -> Segment {
336 Segment::new(" ".repeat(count))
337}
338
339impl Segments {
344 pub fn simplify(&self) -> Segments {
346 let mut result: Vec<Segment> = Vec::new();
347 for seg in &self.segments {
348 if let Some(last) = result.last_mut() {
349 if last.style == seg.style && last.control.is_none() && seg.control.is_none() {
350 last.text.push_str(&seg.text);
351 continue;
352 }
353 }
354 result.push(seg.clone());
355 }
356 Segments { segments: result }
357 }
358}
359
360pub fn split_lines(segments: &[Segment]) -> Vec<Vec<Segment>> {
362 let mut lines: Vec<Vec<Segment>> = Vec::new();
363 let mut current: Vec<Segment> = Vec::new();
364 for seg in segments {
365 if seg.text == "\n" && seg.style.is_none() && seg.control.is_none() {
366 lines.push(std::mem::take(&mut current));
367 } else if seg.text.contains('\n') && seg.style.is_none() && seg.control.is_none() {
368 let parts: Vec<&str> = seg.text.split('\n').collect();
369 for (i, part) in parts.iter().enumerate() {
370 if i > 0 {
371 lines.push(std::mem::take(&mut current));
372 }
373 if !part.is_empty() {
374 current.push(Segment::new(*part));
375 }
376 }
377 } else {
378 current.push(seg.clone());
379 }
380 }
381 if !current.is_empty() {
382 lines.push(current);
383 }
384 lines
385}
386
387pub fn strip_styles(segments: &[Segment]) -> String {
389 let mut out = String::new();
390 for seg in segments {
391 if seg.control.is_none() {
392 out.push_str(&seg.text);
393 }
394 }
395 out
396}
397
398pub fn strip_links(segments: &[Segment]) -> Vec<Segment> {
400 segments
401 .iter()
402 .map(|seg| {
403 let mut s = seg.clone();
404 if let Some(ref style) = seg.style {
405 let mut new_style = style.clone();
406 new_style.link_id = 0;
407 new_style.link = None;
408 s.style = Some(new_style);
409 }
410 s
411 })
412 .collect()
413}
414
415pub fn align_top(
417 lines: &[Vec<Segment>],
418 _width: usize,
419 height: usize,
420 _style: Option<&Style>,
421) -> Vec<Vec<Segment>> {
422 let blank_line = vec![Segment::new(" ".repeat(_width))];
423 let mut result: Vec<Vec<Segment>> = lines.to_vec();
424 while result.len() < height {
425 result.push(blank_line.clone());
426 }
427 result.truncate(height);
428 result
429}
430
431pub fn align_middle(
433 lines: &[Vec<Segment>],
434 _width: usize,
435 height: usize,
436 _style: Option<&Style>,
437) -> Vec<Vec<Segment>> {
438 let blank_line = vec![Segment::new(" ".repeat(_width))];
439 let top_pad = (height.saturating_sub(lines.len())) / 2;
440 let mut result: Vec<Vec<Segment>> = Vec::new();
441 for _ in 0..top_pad {
442 result.push(blank_line.clone());
443 }
444 result.extend(lines.iter().cloned());
445 while result.len() < height {
446 result.push(blank_line.clone());
447 }
448 result.truncate(height);
449 result
450}
451
452pub fn align_bottom(
454 lines: &[Vec<Segment>],
455 _width: usize,
456 height: usize,
457 _style: Option<&Style>,
458) -> Vec<Vec<Segment>> {
459 let blank_line = vec![Segment::new(" ".repeat(_width))];
460 let bottom_pad = height.saturating_sub(lines.len());
461 let mut result: Vec<Vec<Segment>> = Vec::new();
462 for _ in 0..bottom_pad {
463 result.push(blank_line.clone());
464 }
465 result.extend(lines.iter().cloned());
466 result.truncate(height);
467 result
468}
469
470pub fn divide(segments: &[Segment], cuts: &[usize]) -> Vec<Vec<Segment>> {
472 let mut result: Vec<Vec<Segment>> = Vec::new();
473 let mut remaining = segments.to_vec();
474 let mut offset = 0usize;
475
476 for &cut in cuts {
477 let mut chunk: Vec<Segment> = Vec::new();
478 let target = cut.saturating_sub(offset);
479
480 let mut chunk_cells = 0usize;
481 while chunk_cells < target && !remaining.is_empty() {
482 let seg = remaining.remove(0);
483 let seg_len = seg.cell_length();
484 if chunk_cells + seg_len <= target {
485 chunk_cells += seg_len;
486 chunk.push(seg);
487 } else {
488 let split_at = target - chunk_cells;
489 let (left, right) = seg.split(split_at);
490 chunk.push(left);
491 if let Some(r) = right {
492 remaining.insert(0, r);
493 }
494 chunk_cells = target;
495 }
496 }
497 result.push(chunk);
498 offset = cut;
499 }
500
501 if !remaining.is_empty() {
502 result.push(remaining);
503 }
504
505 result
506}
507
508pub fn set_shape(
510 lines: &[Vec<Segment>],
511 width: usize,
512 height: usize,
513 _style: Option<&Style>,
514) -> Vec<Vec<Segment>> {
515 let blank_line = vec![Segment::new(" ".repeat(width))];
516 let mut result: Vec<Vec<Segment>> = Vec::new();
517
518 for line in lines.iter().take(height) {
519 let cell_len: usize = line.iter().map(|s| s.cell_length()).sum();
520 let mut new_line = line.clone();
521 if cell_len < width {
522 new_line.push(Segment::new(" ".repeat(width - cell_len)));
523 } else if cell_len > width {
524 let mut truncated = Vec::new();
525 let mut count = 0usize;
526 for seg in line {
527 let seg_len = seg.cell_length();
528 if count + seg_len <= width {
529 truncated.push(seg.clone());
530 count += seg_len;
531 } else if count < width {
532 let (left, _) = seg.split(width - count);
533 truncated.push(left);
534 break;
535 }
536 }
537 new_line = truncated;
538 }
539 result.push(new_line);
540 }
541
542 while result.len() < height {
543 result.push(blank_line.clone());
544 }
545
546 result
547}
548
549pub fn filter_control(segments: &[Segment], is_control: bool) -> Vec<Segment> {
552 segments
553 .iter()
554 .filter(|seg| seg.control.is_some() == is_control)
555 .cloned()
556 .collect()
557}
558
559pub fn get_line_length(line: &[Segment]) -> usize {
561 line.iter().map(|s| s.cell_length()).sum()
562}
563
564#[cfg(test)]
565mod tests {
566 use super::*;
567 use crate::style::Style;
568
569 #[test]
570 fn test_segment_cell_length() {
571 let seg = Segment::new("Hello");
572 assert_eq!(seg.cell_length(), 5);
573 }
574
575 #[test]
576 fn test_segment_split() {
577 let seg = Segment::new("Hello World");
578 let (left, right) = seg.split(5);
579 assert_eq!(left.text, "Hello");
580 assert_eq!(right.unwrap().text, " World");
581 }
582
583 #[test]
584 fn test_segment_to_ansi() {
585 let style = Style::new().bold(true);
586 let seg = Segment::styled("Bold", style);
587 let ansi = seg.to_ansi();
588 assert!(ansi.contains("[1m"));
589 assert!(ansi.contains("Bold"));
590 }
591}