use pulldown_cmark::{Alignment, Event};
use ratatui::{
style::Style,
text::{Line, Span},
};
use super::{Cell, cells_width};
use super::{
BlockState,
inline::inline_to_cells,
wrap::{align_cell, cells_to_spans},
};
pub(super) struct TableState {
pub(super) aligns: Vec<Alignment>,
pub(super) rows: Vec<Vec<Vec<Cell>>>,
pub(super) head_rows: usize,
pub(super) in_head: bool,
}
fn table_row_line(
row: &[Vec<Cell>],
widths: &[usize],
aligns: &[Alignment],
border: Style,
) -> Line<'static> {
let mut spans = vec![Span::styled("\u{2502}".to_string(), border)];
for (i, &width) in widths.iter().enumerate() {
let empty = Vec::new();
let cell = row.get(i).unwrap_or(&empty);
let align = aligns.get(i).copied().unwrap_or(Alignment::None);
spans.push(Span::raw(" ".to_string()));
spans.extend(cells_to_spans(&align_cell(cell, width, align)));
spans.push(Span::raw(" ".to_string()));
spans.push(Span::styled("\u{2502}".to_string(), border));
}
Line::from(spans)
}
fn table_separator(widths: &[usize], border: Style) -> Line<'static> {
let mut text = String::from("\u{251c}");
for (i, &width) in widths.iter().enumerate() {
if i > 0 {
text.push('\u{253c}');
}
text.push_str(&"\u{2500}".repeat(width + 2));
}
text.push('\u{2524}');
Line::from(Span::styled(text, border))
}
impl BlockState<'_> {
pub(super) fn table_begin_row(&mut self, head: bool) {
if let Some(table) = self.table.as_mut() {
table.in_head = head;
table.rows.push(Vec::new());
if head {
table.head_rows += 1;
}
}
}
pub(super) fn table_push_cell(&mut self) {
let cell = {
let events: Vec<&Event> = self.inline.iter().collect();
inline_to_cells(&events, self.sheet, self.sheet.base)
};
self.inline.clear();
if let Some(table) = self.table.as_mut()
&& let Some(row) = table.rows.last_mut()
{
row.push(cell);
}
}
pub(super) fn flush_table(&mut self) {
let Some(table) = self.table.take() else {
return;
};
let cols = table
.aligns
.len()
.max(table.rows.iter().map(Vec::len).max().unwrap_or(0));
if cols == 0 {
return;
}
let mut widths = vec![1usize; cols];
for row in &table.rows {
for (i, cell) in row.iter().enumerate() {
widths[i] = widths[i].max(cells_width(cell).max(1));
}
}
let overhead = cols + 1 + 2 * cols;
let avail = self.width.saturating_sub(overhead).max(cols);
if widths.iter().sum::<usize>() > avail {
let cap = (avail / cols).max(1);
for width in &mut widths {
*width = (*width).min(cap);
}
}
let border = match self.sheet.table_border {
Some(color) => Style::default().fg(color),
None => Style::default(),
};
for (index, row) in table.rows.iter().enumerate() {
self.out
.push(table_row_line(row, &widths, &table.aligns, border));
if table.head_rows > 0 && index + 1 == table.head_rows {
self.out.push(table_separator(&widths, border));
}
}
if self.at_top_level() {
self.produced = true;
}
}
}