-
Notifications
You must be signed in to change notification settings - Fork 106
Description
Is your feature request related to a problem? Please describe.
When a markdown table is wider than the available window width, render-markdown has no way to gracefully reflow it. The behaviour depends on the wrap window setting:
- With
nowrap: the table scrolls off the right edge — readable but requiring horizontal navigation on every row - With
wrap(the more common default): the underlying buffer text wraps mid-row, breaking the pipe-delimited structure entirely and producing unreadable output that looks nothing like a table
This makes wide tables essentially unusable in narrow windows or split-pane layouts. There is currently no option to tell the renderer "fit this table into N columns and wrap cell content across multiple lines if necessary."
Describe the solution you'd like
A new max_width option on pipe_table (a fraction of the window width, e.g. 0.5 for 50%) that activates a cell-wrapping mode when a table's natural width exceeds the budget.
Column Width Redistribution
Short columns should never be made narrower than their content. Only the widest columns should be wrapped. An iterative lock-and-redistribute algorithm achieves this:
- Compute an equal initial share:
share = floor(budget / num_cols) - For each column whose natural content width fits within
share: lock it at its natural width, remove it from the pool, and return its unused budget to the remaining columns - Recompute
shareacross the remaining unlocked columns - Repeat until stable — all remaining unlocked columns receive the final
shareas their capped width
Row Height
Once column widths are known, the required height of each row (in screen lines) is:
height[r] = max over all columns i of ceil(content_width[r][i] / col_width[i])
A row whose content fits entirely within the redistributed widths has height = 1 and is rendered identically to today.
Rendering
Neovim's extmark API provides the two primitives needed:
virt_textwithvirt_text_pos = 'overlay'— draws a complete rendered row over the underlying buffer line (screen line 0 of the row). The source line is hidden first so raw buffer text does not bleed through.virt_lines— inserts fully synthetic screen lines attached to a buffer line, used for screen lines 1..height-1 of each wrapped row.
When the window is narrow enough that the buffer line itself wraps onto multiple screen lines, those continuation lines already exist in the display. The renderer should place additional rendered rows as overlays on those continuation lines rather than appending new virt_lines, to avoid double-counting screen lines and producing blank gaps.
Inline Content (Links, Icons)
Cell content may include inline-rendered elements — link icons injected as inline virtual text, and markdown punctuation hidden by treesitter conceal. The raw buffer text cannot be sliced directly into per-line chunks; the rendered display string must be reconstructed first by:
- Collecting inline virtual-text injections within the cell's column range (the
markdown_inlinehandler runs beforemarkdown, so this data is available at table render time) - Collecting concealed character ranges within the cell
- Walking the raw text character by character, skipping concealed characters and inserting injected text at the correct positions
The resulting display string is then sliced by display column to fill each wrapped line.
Consistency: Autocmd Hooks
For the rendered table to stay correct as the environment changes, re-renders must be triggered by:
| Event | Reason |
|---|---|
WinResized |
Window split or resize changes the available width |
VimResized |
Terminal/GUI resize changes all window widths |
OptionSet (pattern: wrap) |
:set wrap / :set nowrap should toggle the feature on/off immediately |
The OptionSet hook is the most important addition — without it, toggling wrap leaves stale overlay marks until the next unrelated event fires.
Option API
A single new field on the existing pipe_table config:
| Option | Type | Default | Description |
|---|---|---|---|
max_width |
number |
0 |
Fraction of window width (0–1) to use as the table budget. 0 disables cell-wrapping entirely. |
When max_width is 0, or when the window has nowrap active, or when the table already fits within the budget, the existing renderer is used unchanged — full backwards compatibility.
Describe alternatives you've considered
Truncation — cap each column at a fixed width and truncate overflowing content with an ellipsis. This avoids the multi-line complexity but silently loses data, which seems worse than the current behaviour.
Horizontal scrolling hint — leave the table as-is but add a visual indicator that it extends beyond the window. This addresses discoverability but not readability.
Doing nothing / relying on nowrap — directing users to use nowrap for wide-table buffers is a reasonable workaround today, but it affects the entire buffer and is the opposite of what most users want as a default.
Additional information
- The feature is only intended for
cell = 'padded'andcell = 'trimmed'modes. Therawandoverlaymodes use fundamentally different rendering paths and would not be affected. - At
conceallevel = 0conceal marks have no effect, but the overlay approach still covers the source line correctly — the feature works at any conceallevel. virt_lineshas been available since Neovim 0.6;virt_text_pos = 'inline'(needed for inline content reconstruction) since 0.10. A version gate may be appropriate.- The top/bottom border rows (when
borderis enabled) derive their widths from the delimiter column widths, so they automatically use the redistributed widths with no additional changes.
Open questions for discussion:
- Should
max_widthalso accept an integer (absolute column count) in addition to a float fraction? - Should there be a
min_col_widthguard to prevent any column from being redistributed below a useful minimum? - Should word-boundary wrapping be attempted (break at spaces rather than mid-character)?
- Should column alignment (left/right/center) be applied within the wrapped cell width on each sub-line?