fix(table): correct rendering of wrapped tables with concealed content#481
fix(table): correct rendering of wrapped tables with concealed content#481ImmanuelHaffner wants to merge 14 commits intoOXY2DEV:mainfrom
Conversation
|
There is still an issue with the |
|
@ImmanuelHaffner you have to test this for every single screen width otherwise it won't work universally. That's the reason this hasn't been implemented. It's really hard to get it right for all screen sizes. |
|
I am looking at 4a7b3de and do we have to use |
@OXY2DEV Not sure i fully understand this requirement. I tried resizing the window and calling This behavior seems fine to me. What is odd is that i have to call |
Basically, adding stuff to the window(e.g. a wider statuscolumn) can mess up the calculations. So, adding You should test lines that have 2 or more wrapped lines and with different terminal sizes. |
That's expected since in most cases re-drawing wouldn't have any visual changes. |
|
Yeah, in the 2nd image you can see that |
|
I would still advise against using
|
|
Thanks for your feedback. I will look into |
675be20 to
ceecae4
Compare
|
@ImmanuelHaffner I think you missed an issue with It doesn't work for text outside of the visible part of the window. So, when a buffer is fully rendered, things that are outside of the visible part of the window have missing table borders. I would encourage you to fix these issues in |
|
I just rebased the PR after you merged my other fix and other PRs. Still working on it |
ceecae4 to
62c7191
Compare
The table wrap check used vim_width (raw text width including concealed content like URLs) to decide whether to skip rendering. This caused tables with long hyperlinks to not render at all when wrap is enabled, even though the visual width after conceal was well within the window. Use col_widths (visual width after conceal resolution) instead, which correctly reflects the rendered table width.
Add strikethrough handler to strip ~~ markers when computing visual text width. Without this, ~~text~~ in table cells inflated column widths by 4 extra characters (2 per marker pair). - Add md_str.strikethrough() to strip ~~ delimiters - Add LPEG strike pattern for ~~content~~ syntax - Register strike in the token alternatives
When a table row contains long concealed text (e.g. URLs), the raw buffer line wraps even though the visual content fits. This places table border characters (│...│) on wrap continuation screen rows to preserve the visual table structure. The placement is deferred to post_render (markdown.__table) which runs after all renderers including markdown_inline. This is critical because inline extmarks (padding, conceal) affect the visual line height — if checked too early, nvim_win_text_height reports no wrapping for lines that will wrap once inline extmarks are added. Key design decisions: - Use nvim_win_text_height for accurate wrap detection (accounts for inline extmarks and linebreak, unlike strdisplaywidth which is context-dependent with linebreak=true) - Use binary search with screenpos for precise wrap boundary positions (respects linebreak word-boundary wrapping) - Store continuation_vt on the item during markdown.table, register in markdown.cache for post_render dispatch
…mode Remove is_wrapped guards that skipped pipe conceal/replacement and overrode highlights with @punctuation.special.markdown. Table borders now consistently use markview's own highlight groups (MarkviewTableBorder, MarkviewTableHeader) and fancy border characters (│) in both wrapped and non-wrapped modes. Concealing | and replacing with │ (same display width) does not affect Neovim's line wrapping behavior, so the guards were unnecessary.
When a table row wraps, the concealed right pipe (│) ends up on the continuation screen line, leaving the first screen row without a right border. Fix by placing an additional │ via virt_text_win_col at the table's right edge (table_width - 1) on the first screen row. Stores right border virt_text and table visual width on the item during markdown.table, then places the overlay in markdown.__table post_render.
When a table appears inside an indented block (e.g. list items), the org_indent post-render adds visual indentation to lines. The top/bottom border extmarks included their own col_start-based indent, which was cumulative with org_indent — causing the top border to be indented too far and the bottom border not enough. Fix by computing the target visual indent from the first data row's org_indent marks in __table post_render, then adjusting each border's leading spaces: border_leading = target - border_line_org_indent. Also saves top/bottom border extmark IDs on the item so __table can find and update them. Both the separator and missing_separator code paths now store the bottom border ID.
… renderer Neovim's mark tree traversal order is not stable for inline virt_text when range marks (conceal+border) and point marks (padding/decoration) coexist at the same (row, col). The traversal order depends on the internal tree structure, which varies with the total number of marks in the buffer. In hybrid mode, filtering out preceding content changes how many marks are created, shifting the mark tree structure and causing padding/decoration marks to swap visual order with junction/border marks at shared positions. This made table borders appear misplaced when the cursor was on a heading or inside a nearby table. Fix: set right_gravity=false on all inline padding/decoration marks at col_end positions (header row and separator row). Neovim sorts right_gravity=false before right_gravity=true at the same position, guaranteeing stable traversal order regardless of tree structure.
get_node_text() only applies col_start offset to the first line of a tree-sitter node. For tables inside blockquotes, lines 2+ retain the '> ' prefix, causing the lpeg row parser to fail silently and produce empty results. This left separator and data rows unrendered. Strip the prefix via line:sub(col_start + 1) for lines after the first, and skip empty/blank lines (e.g. trailing '>' markers).
Covers feature matrix tables, alignment torture (left/center/right), nested structures (lists, blockquotes, code blocks), inline chaos, fenced blocks, horizontal rules, single-column tables, tables inside blockquotes, and math blocks.
…rap detection Use nvim_win_text_height and virtcol2col instead of screenpos to locate wrap line boundaries in table continuation border rendering. This avoids reliance on screen state and works correctly when the window is not visible or during deferred rendering.
Replace the binary-search approach for placing continuation borders on wrapped table lines with an analytical walk over the parsed table structure. The old method relied on virtcol, which ignores extmark conceal and caused misaligned borders when cells contained concealed elements (e.g. CommonMark links with long URLs). Additionally: - Remove unused vim_width tracking across three column-width loops - Fix wrap detection to account for textoff (sign/number columns) and compare rendered table width against the usable text area - Store col_widths on the item for use in the post_render phase - Accept minor wrap artefacts from unconcealed soft-wrap rather than bailing out of rendering entirely
…ders Replace the analytical walk (which used rendered/concealed column widths) with a binary search using nvim_win_text_height and vcol parameters. The analytical walk underestimated wrap boundaries because Neovim wraps based on raw text width + inline virt_text, ignoring extmark conceal — so concealed URLs still count towards the wrap width. nvim_win_text_height with start_vcol/end_vcol operates in the same coordinate space Neovim uses for wrapping, making it the correct predicate for locating wrap boundaries. - Use height.all * text_width as upper bound (safe, exceeds the effective wrap width including inline virt_text additions) - Convert vcol to byte position via virtcol2col for extmark anchor - Remove unused __col_widths storage from table items
…st underlines Remove hl_mode="combine" from the concealing extmarks in link_hyperlink and link_image. When a link URL is concealed, the virtual text highlight (e.g. underline) bled across every concealed byte, producing ghost underlines on phantom screen rows created by soft-wrap of the hidden text. Add inline conceal torture section to stress test.
62c7191 to
7c6421a
Compare













Summary
Fixes table rendering when
set wrapis enabled and table cells contain concealed content (e.g. hyperlinks with long URLs,~~strikethrough~~). Previously, tables with long raw-text lines were either skipped entirely or rendered with broken borders, misaligned widths, and inconsistent highlights.This branch addresses 8 related issues across the table renderer, parser, and visual-width calculation.
Changes
col_widths(post-conceal) instead ofvim_width.│…│border characters on continuation screen rows via deferredpost_render. Usesnvim_win_text_height+ binary search withscreenposfor accurate wrap detection (inline extmarks frommarkdown_inlineaffect visual height, so placement must happen after all renderers finish).is_wrappedguards that fell back to@punctuation.special.markdown; all table borders now useMarkviewTableBorder/MarkviewTableHeaderin both wrapped and non-wrapped modes.|ends up on the continuation line, so an additional│is placed viavirt_text_win_colat the table's visual right edge.org_indentpost-render was cumulative with the table's owncol_startindent, causing misaligned borders for tables inside list items.virt_textis not stable when range marks and point marks share a position. Setright_gravity=falseon padding/decoration marks to guarantee consistent ordering in hybrid mode.~~text~~markers were included in column width computation, inflating widths by 4 characters. Addedstrikethroughhandler totostring.lua.get_node_text()only appliescol_startoffset to the first line; subsequent lines inside>blockquotes retained the prefix, causing the row parser to fail silently.Regression Matrix
Tested with
set wrapenabled againsttest/stress.mdandtest/regression-examples.md.Fixes
~~strikethrough~~doesn't inflate column widthsMarkviewTable*highlights (not TS punctuation hl)│appears on first screen row of wrapping linesNo Regressions
set nowrap)