table markdown rendering improvement

This commit is contained in:
Jonas H
2026-06-29 07:24:20 +02:00
parent 9c5356c6ba
commit 27b0f791aa
2 changed files with 39 additions and 7 deletions

View File

@@ -168,9 +168,12 @@ UI thread redraws on its own tick (no channel; just the mutex).
## Gotchas
- `tui-markdown` is pinned `=0.3.5`: later versions use `ratatui-core` (0.30
alpha types), incompatible with ratatui 0.29. Its gaps (no tables, literal
heading markers) are compensated in `src/markdown.rs`, not by upgrading.
- `tui-markdown` is pinned `=0.3.5`: 0.3.7+ moved to `ratatui-core` (0.30 alpha
types), incompatible with ratatui 0.29 — 0.3.6 is the last 0.29-compatible
release, but *no* version (through 0.3.8) enables pulldown-cmark's table
extension, so upgrading still wouldn't render tables. Its gaps (no tables,
literal heading markers) are compensated in `src/markdown.rs`, not by
upgrading.
- `wezterm-term`/`wezterm-surface` are not on crates.io: pinned to a git rev
of the wezterm monorepo (keep both revs identical).
- The compact pane *dynamically frames* Claude Code's input box rather than

View File

@@ -3,9 +3,14 @@
//! tui-markdown 0.3.5 never enables pulldown-cmark's table extension, so GFM
//! pipe tables fall through as raw paragraph text, and it keeps the literal
//! `### ` markers on headings. This module splits table blocks out of the
//! text, renders them itself (box-drawing borders, width-fitted columns,
//! word-wrapped cells — matching Claude Code's own table style), routes the
//! rest through tui-markdown, and strips heading markers from its output.
//! text, renders them itself (box-drawing borders with a rule between every
//! row, width-fitted columns, word-wrapped cells), routes the rest through
//! tui-markdown, and strips heading markers from its output.
//!
//! Why not hand tables to a crate: the only tui-markdown still compatible with
//! ratatui 0.29 is 0.3.6 (0.3.7+ moved to ratatui-core / the 0.30 alpha), and
//! none of those versions enable pulldown-cmark's table extension — so an
//! upgrade still wouldn't render tables. This stays the owner of table layout.
//!
//! Re-rendered every frame on partial content: a table only renders as a
//! table once its header + separator row have streamed in, so half-received
@@ -278,7 +283,13 @@ fn render_table<'a>(rows: &[&str], width: u16, out: &mut Vec<Line<'a>>) {
out.push(border(&widths, '┌', '┬', '┐'));
push_row(out, &header, &widths, &[Align::Center].repeat(cols));
out.push(border(&widths, '├', '┼', '┤'));
for row in &body {
// A rule between every body row, not just under the header: cells wrap to
// several lines each, so without internal separators consecutive rows run
// together into one unreadable block.
for (idx, row) in body.iter().enumerate() {
if idx > 0 {
out.push(border(&widths, '├', '┼', '┤'));
}
push_row(out, row, &widths, &aligns);
}
out.push(border(&widths, '└', '┴', '┘'));
@@ -396,6 +407,24 @@ mod tests {
);
}
#[test]
fn body_rows_are_separated_by_a_rule() {
let md = "| A | B |\n|---|---|\n| 1 | 2 |\n| 3 | 4 |\n";
let got = flat(&render(md, 40));
assert_eq!(
got,
vec![
"┌───┬───┐",
"│ A │ B │",
"├───┼───┤",
"│ 1 │ 2 │",
"├───┼───┤",
"│ 3 │ 4 │",
"└───┴───┘",
]
);
}
#[test]
fn narrow_table_wraps_cells_within_width() {
let md = "| Option | Notes |\n|---|---|\n| Delayed data | Far fewer licensing restrictions apply here |\n";