Hello Alvaro,
Point navigation inside rendered markdown tables doesn't work well. C-n/C-p can't cross block boundaries, and C-f/C-b can't enter table content at all because the displayed table is overlay decoration, not navigable buffer text.
This sometimes causes the point to completely stuck when scrolling up. I'm only able to get to where I want while moving through scroll commands/mouse click.
I tried a few things with claude to no avail:
- Used
inhibit-line-move-field-capture along with field. This improved the situation slightly but didn't solve the overall problem
I then tried changing the https://github.com/xenodium/shell-maker implementation:
display overlay property: replaces text visually while keeping buffer positions navigable. Problem: cursor appears stuck at the start of each row because Emacs renders the cursor at the beginning of a display replacement regardless of point position within the overlay.
- Direct buffer text replacement: delete original markdown, insert formatted text, store original in a text property for restoration. Problem: the interaction with
markdown-overlays-put/markdown-overlays-remove lifecycle is fragile. markdown-overlays-put calls remove (which restores tables) then scans (which needs to find markdown syntax) then formats — the ordering is tricky and faces applied to inserted text can be lost depending on when other code touches the region.
Both approaches required re-implementing column width computation, padding, zebra striping, and glyph normalization, all of which markdown-overlays-tables.el already does in ~400 lines of code.
I going through the markdown-overlays-tables code, I realised that it is trying to do a lot of vtable has inherently solved and we might be able to use it instead.
Emacs 29.1 introduced vtable, a built-in package for rendering variable-pitch tables in buffers. It could replace the alignment/padding/coloring/overlay logic while keeping the existing markdown parsing and inline formatting code.
The approach: hide the original markdown with an invisible overlay, then insert a vtable after it using the parsed/processed cell content.
What vtable handles:
- Column width computation and alignment (variable-pitch aware)
- Padding
- Zebra striping (
:row-colors)
- Cursor navigation through cells (character by character)
- Column sorting (
S), resizing ({/}), navigation (M-left/M-right)
- Truncation with ellipsis
What stays from the current code:
- Markdown table parsing (
find-tables, parse-table-row)
- Inline markdown processing (
process-cell-content — bold, italic, code, links, strikethrough)
- Glyph height normalization
Pros:
- Proper cursor navigation — the main motivation
- Eliminates ~400 lines of manual width computation, padding, wrapping, overlay management, and caching
- Free column sorting, resizing, and navigation
- Maintained by Emacs core
Cons:
- Requires Emacs 29.1+ (shell-maker currently requires 27.1). I'm not sure if it's a deal breaker.
- No horizontal separator line between header and data (
├───┼───┤) — vtable doesn't render one. Header is distinguished by bold face instead.
- Cell wrapping is lost, vtable truncates with ellipsis instead of wrapping long content to multiple lines (We might be able to get away with this by doing it ourselves)
- vtable uses
variable-pitch face by default (overridable with :face)
Let me know what do you think. I can give this some time on the weekends and share a PR to shell maker if you're interested.
Anyway, a small independent fix is also needed in agent-shell: adding inhibit-line-move-field-capture t alongside (field output) text properties so C-n/C-p cross block boundaries. We can do that right away
Regards
Checklist
Hello Alvaro,
Point navigation inside rendered markdown tables doesn't work well.
C-n/C-pcan't cross block boundaries, andC-f/C-bcan't enter table content at all because the displayed table is overlay decoration, not navigable buffer text.This sometimes causes the point to completely stuck when scrolling up. I'm only able to get to where I want while moving through scroll commands/mouse click.
I tried a few things with claude to no avail:
inhibit-line-move-field-capturealong withfield. This improved the situation slightly but didn't solve the overall problemI then tried changing the https://github.com/xenodium/shell-maker implementation:
displayoverlay property: replaces text visually while keeping buffer positions navigable. Problem: cursor appears stuck at the start of each row because Emacs renders the cursor at the beginning of adisplayreplacement regardless of point position within the overlay.markdown-overlays-put/markdown-overlays-removelifecycle is fragile.markdown-overlays-putcallsremove(which restores tables) then scans (which needs to find markdown syntax) then formats — the ordering is tricky and faces applied to inserted text can be lost depending on when other code touches the region.Both approaches required re-implementing column width computation, padding, zebra striping, and glyph normalization, all of which
markdown-overlays-tables.elalready does in ~400 lines of code.I going through the markdown-overlays-tables code, I realised that it is trying to do a lot of
vtablehas inherently solved and we might be able to use it instead.Emacs 29.1 introduced vtable, a built-in package for rendering variable-pitch tables in buffers. It could replace the alignment/padding/coloring/overlay logic while keeping the existing markdown parsing and inline formatting code.
The approach: hide the original markdown with an invisible overlay, then insert a vtable after it using the parsed/processed cell content.
What vtable handles:
:row-colors)S), resizing ({/}), navigation (M-left/M-right)What stays from the current code:
find-tables,parse-table-row)process-cell-content— bold, italic, code, links, strikethrough)Pros:
Cons:
├───┼───┤) — vtable doesn't render one. Header is distinguished by bold face instead.variable-pitchface by default (overridable with:face)Let me know what do you think. I can give this some time on the weekends and share a PR to shell maker if you're interested.
Anyway, a small independent fix is also needed in agent-shell: adding
inhibit-line-move-field-capture talongside(field output)text properties soC-n/C-pcross block boundaries. We can do that right awayRegards
Checklist
@agentclientprotocol/claude-agent-acp@0.24.2