// src/textarea/highlight/chunks.rs use ratatui::text::{Line, Span}; use ratatui::style::Style; use unicode_width::UnicodeWidthChar; #[derive(Debug, Clone)] pub struct StyledChunk { pub text: String, pub style: Style, } pub fn display_width_chunks(chunks: &[StyledChunk]) -> u16 { chunks .iter() .map(|c| { c.text .chars() .map(|ch| UnicodeWidthChar::width(ch).unwrap_or(0) as u16) .sum::() }) .sum() } pub fn slice_chunks_by_display_cols( chunks: &[StyledChunk], start_cols: u16, max_cols: u16, ) -> Vec { if max_cols == 0 { return Vec::new(); } let mut skipped: u16 = 0; let mut taken: u16 = 0; let mut out: Vec = Vec::new(); for ch in chunks { if taken >= max_cols { break; } let mut acc = String::new(); for c in ch.text.chars() { let w = UnicodeWidthChar::width(c).unwrap_or(0) as u16; if skipped + w <= start_cols { skipped += w; continue; } if taken + w > max_cols { break; } acc.push(c); taken = taken.saturating_add(w); if taken >= max_cols { break; } } if !acc.is_empty() { out.push(StyledChunk { text: acc, style: ch.style, }); } } out } pub fn clip_chunks_window_with_indicator_padded( chunks: &[StyledChunk], view_width: u16, indicator: char, start_cols: u16, ) -> Line<'static> { if view_width == 0 { return Line::from(""); } let total = display_width_chunks(chunks); let show_left = start_cols > 0; let left_cols: u16 = if show_left { 1 } else { 0 }; let cap_with_right = view_width.saturating_sub(left_cols + 1); let remaining = total.saturating_sub(start_cols); let show_right = remaining > cap_with_right; let max_visible = if show_right { cap_with_right } else { view_width.saturating_sub(left_cols) }; let visible = slice_chunks_by_display_cols(chunks, start_cols, max_visible); let used_cols = left_cols + display_width_chunks(&visible); let mut spans: Vec = Vec::new(); if show_left { spans.push(Span::raw(indicator.to_string())); } for v in visible { spans.push(Span::styled(v.text, v.style)); } if show_right { let right_pos = view_width.saturating_sub(1); let filler = right_pos.saturating_sub(used_cols); if filler > 0 { spans.push(Span::raw(" ".repeat(filler as usize))); } spans.push(Span::raw(indicator.to_string())); } Line::from(spans) } pub fn wrap_chunks_indented( chunks: &[StyledChunk], width: u16, indent: u16, ) -> Vec> { if width == 0 { return vec![Line::from("")]; } let indent = indent.min(width.saturating_sub(1)); let cont_cap = width.saturating_sub(indent); let indent_str = " ".repeat(indent as usize); let mut lines: Vec = Vec::new(); let mut current_spans: Vec = Vec::new(); let mut used: u16 = 0; let mut first_line = true; // Fixed: Restructure to avoid borrow checker issues for chunk in chunks { let mut buf = String::new(); let mut buf_style = chunk.style; for ch in chunk.text.chars() { let w = UnicodeWidthChar::width(ch).unwrap_or(0) as u16; let cap = if first_line { width } else { cont_cap }; if used > 0 && used.saturating_add(w) >= cap { if !buf.is_empty() { current_spans.push(Span::styled(buf.clone(), buf_style)); buf.clear(); } lines.push(Line::from(current_spans)); current_spans = Vec::new(); first_line = false; used = 0; // Add indent directly instead of using closure if !first_line && indent > 0 { current_spans.push(Span::raw(indent_str.clone())); used = indent; } } if !buf.is_empty() && buf_style != chunk.style { current_spans.push(Span::styled(buf.clone(), buf_style)); buf.clear(); } buf_style = chunk.style; // Add indent if needed if used == 0 && !first_line && indent > 0 { current_spans.push(Span::raw(indent_str.clone())); used = indent; } buf.push(ch); used = used.saturating_add(w); } if !buf.is_empty() { current_spans.push(Span::styled(buf, buf_style)); } } lines.push(Line::from(current_spans)); lines }