use ratatui::style::{Color, Style};
use ratatui::text::{Line, Span};
use unicode_width::UnicodeWidthStr;
pub fn wrap_code_block(code: &str, width: usize, style: Style, bg: Color) -> Vec<Line<'static>> {
let w = width.max(1);
let mut out = Vec::new();
for src_line in code.lines() {
if src_line.is_empty() {
out.push(make_code_line("", w, style, bg));
continue;
}
let cols = UnicodeWidthStr::width(src_line);
if cols <= w {
out.push(make_code_line(src_line, w, style, bg));
} else {
let mut remain = src_line;
while !remain.is_empty() {
let wrap_at = find_wrap_col(remain, w);
let (part, rest) = remain.split_at(wrap_at);
out.push(make_code_line(part, w, style, bg));
remain = rest;
}
}
}
out
}
fn make_code_line(text: &str, width: usize, style: Style, bg: Color) -> Line<'static> {
let padding = width.saturating_sub(UnicodeWidthStr::width(text));
let mut s = text.to_string();
if padding > 0 {
s.push_str(&" ".repeat(padding));
}
Line::from(Span::styled(s, style.bg(bg)))
}
fn find_wrap_col(line: &str, max_width: usize) -> usize {
let mut col = 0;
for (i, c) in line.char_indices() {
let cw = UnicodeWidthStr::width(c.to_string().as_str());
if col + cw > max_width {
return i;
}
col += cw;
}
line.len()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn short_line_padded() {
let lines = wrap_code_block("hi", 10, Style::default(), Color::Black);
assert_eq!(lines.len(), 1);
let rendered = lines[0].to_string();
assert_eq!(rendered.chars().count(), 10);
}
#[test]
fn hard_wrap_long_line() {
let lines = wrap_code_block("abcdefghij", 5, Style::default(), Color::Black);
assert_eq!(lines.len(), 2);
}
#[test]
fn empty_lines_preserved() {
let lines = wrap_code_block("a\n\nb", 5, Style::default(), Color::Black);
assert_eq!(lines.len(), 3);
}
}