use ratatui::style::{Color, Style};
use ratatui::text::{Line, Span};
use crate::util::text::{char_width, display_width};
pub fn wrap_md_line_in_bubble(
md_line: Line<'static>,
bubble_bg: Color,
pad_left_w: usize,
pad_right_w: usize,
bubble_total_w: usize,
) -> Line<'static> {
for span in &md_line.spans {
if span.content.starts_with("\x00IMG:") {
let marker = span.content.clone();
let spans: Vec<Span> = vec![
Span::styled(" ".repeat(bubble_total_w), Style::default().bg(bubble_bg)),
Span::styled(marker, Style::default()),
];
return Line::from(spans);
}
}
let pad_left = " ".repeat(pad_left_w);
let pad_right = " ".repeat(pad_right_w);
let mut styled_spans: Vec<Span> = Vec::new();
styled_spans.push(Span::styled(pad_left, Style::default().bg(bubble_bg)));
let target_content_w = bubble_total_w.saturating_sub(pad_left_w + pad_right_w);
let mut content_w: usize = 0;
for span in md_line.spans {
let sw = display_width(&span.content);
if content_w + sw > target_content_w {
let remaining = target_content_w.saturating_sub(content_w);
if remaining > 0 {
let mut truncated = String::new();
let mut tw = 0;
for ch in span.content.chars() {
let cw = char_width(ch);
if tw + cw > remaining {
break;
}
truncated.push(ch);
tw += cw;
}
if !truncated.is_empty() {
content_w += tw;
let merged_style = span.style.bg(bubble_bg);
styled_spans.push(Span::styled(truncated, merged_style));
}
}
break;
}
content_w += sw;
let merged_style = span.style.bg(bubble_bg);
styled_spans.push(Span::styled(span.content.to_string(), merged_style));
}
let fill = target_content_w.saturating_sub(content_w);
if fill > 0 {
styled_spans.push(Span::styled(
" ".repeat(fill),
Style::default().bg(bubble_bg),
));
}
styled_spans.push(Span::styled(pad_right, Style::default().bg(bubble_bg)));
Line::from(styled_spans)
}
pub(crate) fn wrap_md_line_in_bubble_with_margin(
md_line: Line<'static>,
bubble_bg: Color,
pad_left_w: usize,
pad_right_w: usize,
bubble_total_w: usize,
margin: &str,
) -> Line<'static> {
let mut line =
wrap_md_line_in_bubble(md_line, bubble_bg, pad_left_w, pad_right_w, bubble_total_w);
line.spans
.insert(0, Span::styled(margin.to_string(), Style::default()));
line
}
pub(crate) fn bordered_line(
content_spans: Vec<Span<'static>>,
bubble_max_width: usize,
border_color: Color,
bg: Color,
) -> Line<'static> {
let border_overhead = 4 + 2;
let target_content_w = bubble_max_width.saturating_sub(border_overhead);
let mut clamped_spans: Vec<Span<'static>> = Vec::with_capacity(content_spans.len());
let mut used: usize = 0;
for span in content_spans {
let sw = display_width(&span.content);
if used + sw <= target_content_w {
used += sw;
clamped_spans.push(span);
} else {
let remaining = target_content_w.saturating_sub(used);
if remaining > 0 {
let mut truncated = String::new();
let mut tw = 0;
for ch in span.content.chars() {
let cw = char_width(ch);
if tw + cw > remaining {
break;
}
truncated.push(ch);
tw += cw;
}
if !truncated.is_empty() {
used += tw;
clamped_spans.push(Span::styled(truncated, span.style));
}
}
break;
}
}
let fill = target_content_w.saturating_sub(used);
let mut spans = Vec::with_capacity(clamped_spans.len() + 3);
spans.push(Span::styled(
" │ ",
Style::default().fg(border_color).bg(bg),
));
spans.extend(clamped_spans);
spans.push(Span::styled(" ".repeat(fill), Style::default().bg(bg)));
spans.push(Span::styled(" │", Style::default().fg(border_color).bg(bg)));
Line::from(spans)
}