intelli_shell/utils/
widget.rs

1use ratatui::{style::Style, text::Span};
2use unicode_width::UnicodeWidthChar;
3
4/// Truncates a slice of spans to fit within a maximum width
5pub fn truncate_spans<'a>(spans: &[Span<'a>], max_width: u16) -> (Vec<Span<'a>>, u16) {
6    let mut current_width: u16 = 0;
7    let mut truncated_spans: Vec<Span<'a>> = Vec::new();
8
9    for span in spans {
10        let span_width = span.width() as u16;
11        if current_width.saturating_add(span_width) <= max_width {
12            current_width = current_width.saturating_add(span_width);
13            truncated_spans.push(span.clone());
14        } else {
15            let remaining_width = max_width.saturating_sub(current_width);
16            if remaining_width > 0 {
17                let mut content = String::new();
18                let mut content_width: u16 = 0;
19                for c in span.content.as_ref().chars() {
20                    let char_width = UnicodeWidthChar::width(c).unwrap_or(0) as u16;
21                    if content_width.saturating_add(char_width) <= remaining_width {
22                        content.push(c);
23                        content_width = content_width.saturating_add(char_width);
24                    } else {
25                        break;
26                    }
27                }
28                if !content.is_empty() {
29                    truncated_spans.push(Span::styled(content, span.style));
30                    current_width = current_width.saturating_add(content_width);
31                }
32            }
33            break;
34        }
35    }
36    (truncated_spans, current_width)
37}
38
39/// Truncates a slice of spans and adds an ellipsis if truncation occurred
40pub fn truncate_spans_with_ellipsis<'a>(spans: &[Span<'a>], max_width: u16) -> (Vec<Span<'a>>, u16) {
41    let original_width = spans.iter().map(|s| s.width()).sum::<usize>() as u16;
42    if original_width <= max_width {
43        return (spans.to_vec(), original_width);
44    }
45    if max_width == 0 {
46        return (Vec::new(), 0);
47    }
48
49    // Reserve space for ellipsis
50    let target_width = max_width.saturating_sub(1);
51    let (mut truncated_spans, mut current_width) = truncate_spans(spans, target_width);
52
53    // Get style from the last span for the ellipsis, or default
54    let ellipsis_style = truncated_spans
55        .last()
56        .map_or_else(|| spans.first().map_or(Style::default(), |s| s.style), |s| s.style);
57
58    truncated_spans.push(Span::styled("…", ellipsis_style));
59    current_width += 1;
60
61    (truncated_spans, current_width)
62}