use super::writer::Writer;
use super::{InlineSpan, TextFragment};
use crate::pager::text_utils::{WrappedLine, wrap_spans_with_ranges};
use pulldown_cmark::Event;
use ratatui::text::{Line, Span};
use unicode_width::UnicodeWidthStr;
struct TableLine {
text_index: usize,
wrapped: WrappedLine,
}
impl<'a, I: Iterator<Item = Event<'a>>> Writer<'a, I> {
pub(super) fn render_table(&mut self) {
if self.table_rows.is_empty() {
return;
}
let n_cols = self.table_rows.iter().map(|r| r.len()).max().unwrap_or(0);
if n_cols == 0 {
return;
}
let mut split_rows: Vec<Vec<Vec<TableLine>>> = Vec::new();
for row in std::mem::take(&mut self.table_rows) {
let mut cells = Vec::new();
for cell in row {
let mut cell_lines = Vec::new();
for spans in split_cell_lines(&cell) {
let text_index = self.push_search_text(&spans);
let spans = spans.into_iter().map(|span| span.span).collect();
cell_lines.push(TableLine {
text_index,
wrapped: WrappedLine::unwrapped(spans),
});
}
cells.push(cell_lines);
}
split_rows.push(cells);
}
let mut col_widths = vec![0usize; n_cols];
for row in &split_rows {
for (col_idx, cell_lines) in row.iter().enumerate() {
for sub_line in cell_lines {
let w = spans_width(&sub_line.wrapped.spans);
col_widths[col_idx] = col_widths[col_idx].max(w);
}
}
}
if let Some(wrap_width) = self.wrap_width {
let decoration = self.blockquote_depth * 2 + 2 + n_cols * 3;
let available = wrap_width.saturating_sub(decoration);
let total_natural: usize = col_widths.iter().sum();
if total_natural > available && available > 0 {
let target_widths = distribute_column_widths(&col_widths, available);
for row in &mut split_rows {
for (col_idx, cell_lines) in row.iter_mut().enumerate() {
let target = target_widths[col_idx];
if target == 0 {
continue;
}
let mut new_lines = Vec::new();
for sub_line in cell_lines.drain(..) {
for wrapped in
wrap_spans_with_ranges(&sub_line.wrapped.spans, target, 0)
{
new_lines.push(TableLine {
text_index: sub_line.text_index,
wrapped,
});
}
}
*cell_lines = new_lines;
}
}
col_widths = target_widths;
}
}
for (row_idx, row) in split_rows.iter().enumerate() {
let max_sub_lines = row.iter().map(|cell| cell.len()).max().unwrap_or(1);
for sub_line_idx in 0..max_sub_lines {
let mut spans: Vec<Span<'static>> = Vec::new();
for _ in 0..self.blockquote_depth {
spans.push(Span::styled("> ", self.styles.blockquote_prefix));
}
spans.push(Span::raw("| "));
for (col_idx, cell_lines) in row.iter().enumerate() {
let target = col_widths.get(col_idx).copied().unwrap_or(0);
if let Some(sub_line) = cell_lines.get(sub_line_idx) {
let w = spans_width(&sub_line.wrapped.spans);
let visual_start =
spans.iter().map(|span| span.content.len()).sum::<usize>();
self.text[sub_line.text_index].fragments.push(TextFragment {
line: self.lines.len(),
source_range: sub_line.wrapped.source_range.clone(),
visual_start: visual_start + sub_line.wrapped.indent,
});
for s in &sub_line.wrapped.spans {
spans.push(s.clone());
}
let pad = target.saturating_sub(w);
if pad > 0 {
spans.push(Span::raw(" ".repeat(pad)));
}
} else {
spans.push(Span::raw(" ".repeat(target)));
}
spans.push(Span::raw(" | "));
}
self.lines.push(Line::from(spans));
self.has_output = true;
}
if row_idx == 0 {
let mut sep_spans: Vec<Span<'static>> = Vec::new();
for _ in 0..self.blockquote_depth {
sep_spans.push(Span::styled("> ", self.styles.blockquote_prefix));
}
sep_spans.push(Span::raw("| "));
for (col_idx, w) in col_widths.iter().enumerate() {
sep_spans.push(Span::raw("-".repeat(*w)));
if col_idx + 1 < n_cols {
sep_spans.push(Span::raw(" | "));
}
}
sep_spans.push(Span::raw(" |"));
self.lines.push(Line::from(sep_spans));
}
}
}
}
fn split_cell_lines(cell: &[InlineSpan]) -> Vec<Vec<InlineSpan>> {
let mut lines: Vec<Vec<InlineSpan>> = vec![Vec::new()];
for span in cell {
if span.span.content.as_ref() == "\n" {
lines.push(Vec::new());
} else {
lines.last_mut().unwrap().push(span.clone());
}
}
lines
}
fn spans_width(spans: &[Span<'static>]) -> usize {
spans
.iter()
.map(|s| UnicodeWidthStr::width(s.content.as_ref()))
.sum()
}
pub(super) fn distribute_column_widths(natural: &[usize], available: usize) -> Vec<usize> {
let n = natural.len();
if n == 0 {
return vec![];
}
if available <= n {
let mut widths = vec![0usize; n];
for w in widths.iter_mut().take(available) {
*w = 1;
}
return widths;
}
let mut result = natural.to_vec();
let mut locked = vec![false; n];
let mut locked_total = 0usize;
let mut unlocked_count = n;
loop {
let remaining = available.saturating_sub(locked_total);
let share = remaining.checked_div(unlocked_count).unwrap_or(0);
let mut changed = false;
for i in 0..n {
if !locked[i] && result[i] <= share {
locked[i] = true;
locked_total += result[i];
unlocked_count -= 1;
changed = true;
}
}
if !changed {
break;
}
}
if unlocked_count > 0 {
let remaining = available.saturating_sub(locked_total);
let share = remaining.checked_div(unlocked_count).unwrap_or(0);
let extra = remaining % unlocked_count;
let mut idx = 0;
for i in 0..n {
if !locked[i] {
result[i] = share + if idx < extra { 1 } else { 0 };
idx += 1;
}
}
}
result
}