Skip to content

feat(table): add cell wrapping using virt lines#617

Open
MaxDillon wants to merge 8 commits into
MeanderingProgrammer:mainfrom
MaxDillon:feat/table-cell-wrapping
Open

feat(table): add cell wrapping using virt lines#617
MaxDillon wants to merge 8 commits into
MeanderingProgrammer:mainfrom
MaxDillon:feat/table-cell-wrapping

Conversation

@MaxDillon
Copy link
Copy Markdown

@MaxDillon MaxDillon commented Mar 3, 2026

Closes: #616

What it does

  • Adds max_table_width for controlling wrapping behavior for tables. Wrap table when exceeds width setting
  • Updates offset to carry virt_text metadata (necesary for reconstructing hl groups inside tables)
  • compute_layout in Renderer:setup() calculates layout (column widths, row heights)
  • Covers original buffer line with overlay to prevent text overflow sticking out to right of table
  • Adds virtual lines below to display wrapped text
  • Re-render on resize or wrap toggle

Copy link
Copy Markdown
Owner

@MeanderingProgrammer MeanderingProgrammer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a really interesting PR, it's going to take me a bit of time to parse through it.

I appreciate the effort! This could be a truly awesome feature.

Comment thread lua/render-markdown/render/markdown/table.lua Outdated
-- | 0.1–1.0 | fraction of window width, e.g. 0.8 = 80% |
-- | 2+ | absolute character width, e.g. 80 = 80 columns |
-- | < 0 | window width minus N, e.g. -10 = width minus 10 |
max_table_width = 0,
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One minor thing is if this does get merged in we'd definitely enable it by default, probably set the default to 1.0. Unless we feel it's unstable and want to keep stress testing it before switching it on for everyone.

@MaxDillon MaxDillon force-pushed the feat/table-cell-wrapping branch from fa67526 to fb51061 Compare March 4, 2026 00:48
Comment thread lua/render-markdown/core/manager.lua Outdated
Comment thread lua/render-markdown/render/markdown/table.lua Outdated
Comment thread lua/render-markdown/render/markdown/table.lua Outdated
-- uses the capped widths (padding is included in delim col width).
if self.layout.wrap then
for i, w in ipairs(self.layout.col_widths) do
self.data.delim.cols[i].width = w + 2 * self.config.padding
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implementation (along with the way the line is built) will always add additional padding to cells that do not need it. The point of padding is to make sure that each cell has space around it, i.e. |A| -> | A |.

However if the cell already has padding like | A | it can be kept as is. With this logic it gets changed to | A | (with 2 extra spaces).

end

local filler = self.config.filler
local function build_line(visual_line)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This currently doesn't use the alignment signal, not strictly necessary from the start, but we probably wouldn't enable it by default without that. We would probably only try to align cells that are not wrapped.

Comment thread lua/render-markdown/render/markdown/table.lua Outdated
Comment thread lua/render-markdown/render/markdown/table.lua Outdated
Comment thread lua/render-markdown/render/markdown/table.lua Outdated

---@private
---@return render.md.table.Layout
function Render:compute_layout()
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently does not handle tables with leading spaces, i.e.

  | Col | Col | Col |
  | --- | --- | --- |
  | Row | Row | Row |

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I can tell it handles tables with spaces gracefully... Is there a specific circumstance you are running into where you are having trouble here?

For me, the original table formatter and the wrapping table formatter both handle spacing up to 3 spaces deep, then they both stop working

Screenshot 2026-03-10 at 5 47 16 PM Screenshot 2026-03-10 at 5 48 22 PM

Note... When I was testing this and I turned on list chats, I noticed that spaces dont show unless we are actively hovering over the line, as these are constructed lines. not sure if this is a feature or a bug.

Screenshot 2026-03-10 at 5 50 05 PM

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I should have been more clear here. Does not handle leading spaces with max_table_width = 1. The spaces are not accounted for in the width budget, so the rendering ends up going passed the end of the window.

@MaxDillon MaxDillon force-pushed the feat/table-cell-wrapping branch 3 times, most recently from 9ab4ea7 to 145387c Compare March 11, 2026 00:36
@MaxDillon MaxDillon force-pushed the feat/table-cell-wrapping branch from 145387c to 1da7686 Compare March 11, 2026 00:38
@b0o
Copy link
Copy Markdown

b0o commented Mar 31, 2026

Testing this out, it seems to be working well and is definitely a big improvement!

@b0o
Copy link
Copy Markdown

b0o commented Mar 31, 2026

I do also notice some slight breakage with leading spaces when the window is too narrow, though:
image

   | Approach | Allocations | Performance |
   |----------|-------------|-------------|
   | `format!()` in loop | N | Slow |
   | `write!()` to reused buffer | 1 | Fast |
   | `push_str()` + `push()` | 1 | Fastest |
   | Pre-sized `String::with_capacity()` | 1 (no realloc) | Fast |

But it's still an improvement over the base behavior.

@okuuva
Copy link
Copy Markdown

okuuva commented Jun 5, 2026

It's also having trouble with some very long header separators, and barfs a bit if the table is at the bottom of the file without an extra empty line.

| ID    | Title                                                                                                                                                                                                                                                                                                                                                                           | Severity | Status |
|-------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|--------|
| [T-1] | Here's a long text that definitely causes wrapping. My hands are typing words. If you're reading this then my hands continued typing words to make long enough a line for testing. Also you possess the highly coveted skill of reading. Good for you. Sorry, didn't mean to sound so sarcastic there. Honestly I'm thrilled for you. I have just trouble showing it. | High     | Open   |
ID Title Severity Status
[T-1] Here's a long text that definitely causes wrapping. My hands are typing words. If you're reading this then my hands continued typing words to make long enough a line for testing. Also you possess the highly coveted skill of reading. Good for you. Sorry, didn't mean to sound so sarcastic there. Honestly I'm thrilled for you. I have just trouble showing it. High Open
image

@okuuva
Copy link
Copy Markdown

okuuva commented Jun 5, 2026

There's also currently no way to make anti_conceal ignore the wrapped lines.

| ID    | Title                                                                                           | Severity | Status |
|-------|-------------------------------------------------------------------------------------------------|----------|--------|
| [T-2] | Here's a short text that should not wrap. As hard I mean. Depends on your monitor width really. | High     | Open   |
ID Title Severity Status
[T-2] Here's a short text that should not wrap. As hard I mean. Depends on your monitor width really. High Open
image

Here are the anti_conceal settings I used while taking the screenshot:

    anti_conceal = {
      ignore = {
        bullet = true,
        callout = true,
        check_icon = true,
        check_scope = true,
        code_background = true,
        code_border = true,
        code_language = true,
        dash = true,
        head_background = true,
        head_border = true,
        head_icon = true,
        indent = true,
        latex = true,
        link = true,
        quote = true,
        sign = true,
        table_border = true,
        virtual_lines = true,
      },
    },

I think the virtual_lines and table_border alone should handle this already. A minor thing that can be fixed later, just thought to mention it as I ran into it.

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.

feature: Cell-wrapping for wide tables

4 participants