Display-only wrapping for markdown pipe tables#167
Merged
Conversation
3553ac7 to
84e792c
Compare
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).
84e792c to
a42234c
Compare
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 numbersguided ten optimization rounds during development.
New dependency: markdown-table-wrap (available on MELPA).