Skip to content

Display-only wrapping for markdown pipe tables#167

Merged
dnouri merged 1 commit intomasterfrom
markdown-table-wrap
Mar 16, 2026
Merged

Display-only wrapping for markdown pipe tables#167
dnouri merged 1 commit intomasterfrom
markdown-table-wrap

Conversation

@dnouri
Copy link
Copy Markdown
Owner

@dnouri dnouri commented Mar 16, 2026

LLM responses often contain pipe tables that overflow the chat window.
Until now these rendered as raw markdown — unaligned, truncated by the
window edge, painful to read. This teaches the chat buffer to detect
pipe tables via tree-sitter, wrap them to the visible window width
using markdown-table-wrap, and display the result as per-line overlays.

The key design choice is that the buffer text stays canonical. The raw
markdown is never rewritten; overlays carry the wrapped view and evaporate
on kill. Tree-sitter, isearch, and copy all operate on the source text.
This sidesteps the class of bugs where a pretty-printer desynchronizes
the buffer from the parse tree.

Wrapping happens at three points in the lifecycle: when stable content
arrives (history replay, user messages, compaction summaries), during
streaming as each newline-containing delta completes a table row, and on
window resize. Resize only touches a recent suffix of the chat — the
"hot tail" — so widening a window with a long conversation history
does not trigger a whole-buffer redecoration pass.

Inline markdown faces (bold, code, links) are preserved in the wrapped
display strings. A persistent md-ts-mode scratch buffer extracts the
visible rendering of each cell, with a session-level cache so the same
cell content is never fontified twice across tables or resize cycles.

The implementation lives in its own module (pi-coding-agent-table.el)
with a narrow interface: three entry points called from the render and
cleanup paths, plus one hook for the resize refresh. The dependency
chain stays acyclic: render → table → ui.

Performance was the other constraint. The wrapping engine runs on every
streaming delta that contains a newline, so it cannot be slow. The hot
path (stream-last, resize-hot-tail) stays under 1–2 ms in GUI mode.
A benchmark harness under bench/ measures the key scenarios; the numbers
guided ten optimization rounds during development.

New dependency: markdown-table-wrap (available on MELPA).

@dnouri dnouri force-pushed the markdown-table-wrap branch 7 times, most recently from 3553ac7 to 84e792c Compare March 16, 2026 22:58
LLM responses often contain pipe tables that overflow the chat window.
Until now these rendered as raw markdown — unaligned, truncated by the
window edge, painful to read.  This teaches the chat buffer to detect
pipe tables via tree-sitter, wrap them to the visible window width
using markdown-table-wrap, and display the result as per-line overlays.

The key design choice is that the buffer text stays canonical.  The raw
markdown is never rewritten; overlays carry the wrapped view and evaporate
on kill.  Tree-sitter, isearch, and copy all operate on the source text.
This sidesteps the class of bugs where a pretty-printer desynchronizes
the buffer from the parse tree.

Wrapping happens at three points in the lifecycle: when stable content
arrives (history replay, user messages, compaction summaries), during
streaming as each newline-containing delta completes a table row, and on
window resize.  Resize only touches a recent suffix of the chat — the
"hot tail" — so widening a window with a long conversation history
does not trigger a whole-buffer redecoration pass.

Inline markdown faces (bold, code, links) are preserved in the wrapped
display strings.  A persistent md-ts-mode scratch buffer extracts the
visible rendering of each cell, with a session-level cache so the same
cell content is never fontified twice across tables or resize cycles.

The implementation lives in its own module (pi-coding-agent-table.el)
with a narrow interface: three entry points called from the render and
cleanup paths, plus one hook for the resize refresh.  The dependency
chain stays acyclic: render → table → ui.

Performance was the other constraint.  The wrapping engine runs on every
streaming delta that contains a newline, so it cannot be slow.  The hot
path (stream-last, resize-hot-tail) stays under 1–2 ms in GUI mode.
A benchmark harness under bench/ measures the key scenarios; the numbers
guided ten optimization rounds during development.

New dependency: markdown-table-wrap (available on MELPA).
@dnouri dnouri force-pushed the markdown-table-wrap branch from 84e792c to a42234c Compare March 16, 2026 23:01
@dnouri dnouri merged commit e16f71f into master Mar 16, 2026
11 checks passed
@dnouri dnouri deleted the markdown-table-wrap branch March 16, 2026 23:06
dnouri added a commit that referenced this pull request Mar 17, 2026
Inline markdown faces (e.g. md-ts-code) inherit from fixed-pitch,
which uses a different font than the default face.  Since padding
is character-counted but rendering is pixel-based, columns drifted
whenever a cell contained styled text like backtick literals.

Resolve each face in table display strings to an anonymous plist
that retains only visual attributes (foreground, weight, slant)
and drops font-identity ones (family, height).  All table text
now renders in the buffer's default font; colors survive.

Follows up on #167.
dnouri added a commit that referenced this pull request Mar 17, 2026
Inline markdown faces (e.g. md-ts-code) inherit from fixed-pitch,
which uses a different font than the default face.  Since padding
is character-counted but rendering is pixel-based, columns drifted
whenever a cell contained styled text like backtick literals.

Resolve each face in table display strings to an anonymous plist
that retains only visual attributes (foreground, weight, slant)
and drops font-identity ones (family, height).  All table text
now renders in the buffer's default font; colors survive.

Follows up on #167.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant