@@ -2488,7 +2488,6 @@ markdown.table = function (buffer, item)
24882488 local right_border , right_hl = get_border (" row" , 3 );
24892489 item .__right_border_vt = { { right_border , right_hl } };
24902490 item .__table_width = utils .virt_len (continuation_vt );
2491- item .__col_widths = col_widths ;
24922491
24932492 --- Register for post_render so __table runs after inline extmarks.
24942493 table.insert (markdown .cache , item );
@@ -2627,8 +2626,6 @@ markdown.__table = function (buffer, item)
26272626
26282627 local range = item .range ;
26292628
2630- local col_widths = item .__col_widths ;
2631-
26322629 --- Compute the window's text-area width (excluding sign/number columns).
26332630 local textoff = vim .fn .getwininfo (win )[1 ].textoff ;
26342631 local text_width = vim .api .nvim_win_get_width (win ) - textoff ;
@@ -2652,67 +2649,50 @@ markdown.__table = function (buffer, item)
26522649 });
26532650 end
26542651
2655- --- Find wrap-break byte positions analytically by walking
2656- --- the parsed table structure. The rendered width of each
2657- --- element is known: separators = 1 col (│), columns =
2658- --- col_widths[c]. We accumulate display width and record
2659- --- the byte at the start of each element that crosses a
2660- --- wrap boundary (multiples of text_width).
2652+ --- Find the first byte on each continuation (wrapped)
2653+ --- screen row via binary search over virtual columns.
26612654 ---
2662- --- Unlike a binary search over virtual columns, this
2663- --- approach is immune to the coordinate-space mismatch
2664- --- between virtcol (which ignores extmark conceal) and
2665- --- nvim_win_text_height (which accounts for it). See
2666- --- CommonMark §6.7 links inside table cells for a case
2667- --- where concealed URLs broke the old binary search.
2668- local parts ;
2669- if row == range .row_start then
2670- parts = item .header ;
2671- elseif row == range .row_start + 1 then
2672- parts = item .separator ;
2673- else
2674- local ri = row - (range .row_start + 2 ) + 1 ;
2675- parts = item .rows [ri ];
2676- end
2677-
2678- if parts and col_widths then
2679- local disp = 0 ; --- cumulative display columns
2680- local wrap_line = 1 ; --- next wrap line to place
2681- local cc = 1 ; --- column counter
2682-
2683- for _ , part in ipairs (parts ) do
2684- local elem_width ;
2685- if part .class == " separator" then
2686- elem_width = 1 ;
2687- elseif part .class == " column" then
2688- elem_width = col_widths [cc ] or 0 ;
2689- cc = cc + 1 ;
2655+ --- nvim_win_text_height with start_vcol/end_vcol operates
2656+ --- in the same coordinate space Neovim uses for wrapping
2657+ --- (raw text width + inline virt_text, ignoring conceal).
2658+ --- This makes it the correct predicate for locating wrap
2659+ --- boundaries — unlike an analytical walk over rendered
2660+ --- widths, which underestimates when cells contain
2661+ --- concealed URLs that still count towards wrap width.
2662+ ---
2663+ --- Upper bound: height.all * text_width is guaranteed to
2664+ --- exceed the effective wrap width (including any inline
2665+ --- virt_text additions that push past strdisplaywidth).
2666+ local lnum = row + 1 ;
2667+ local hi_bound = height .all * text_width ;
2668+
2669+ for w = 1 , height .all - 1 do
2670+ local lo , hi = 1 , hi_bound ;
2671+
2672+ while lo < hi do
2673+ local mid = math.floor ((lo + hi ) / 2 );
2674+
2675+ if vim .api .nvim_win_text_height (win , {
2676+ start_row = row , end_row = row ,
2677+ start_vcol = 0 , end_vcol = mid ,
2678+ }).all <= w then
2679+ lo = mid + 1 ;
26902680 else
2691- goto continue ;
2692- end
2693-
2694- --- Check if this element spans a wrap boundary.
2695- while wrap_line <= height .all - 1
2696- and disp + elem_width >= text_width * (wrap_line )
2697- do
2698- --- The byte at the start of this element is
2699- --- guaranteed to be visible (not inside an
2700- --- extmark-concealed URL). Using it as the
2701- --- anchor ensures the overlay lands on the
2702- --- correct screen row.
2703- local anchor = range .col_start + part .col_start ;
2704- vim .api .nvim_buf_set_extmark (buffer , markdown .ns , row , anchor , {
2705- undo_restore = false , invalidate = true ,
2706- virt_text = continuation_vt ,
2707- virt_text_win_col = 0 ,
2708- hl_mode = " combine" ,
2709- });
2710- wrap_line = wrap_line + 1 ;
2681+ hi = mid ;
27112682 end
2683+ end
27122684
2713- disp = disp + elem_width ;
2685+ --- lo is the first vcol on wrap line w+1.
2686+ --- Convert to a byte column for the extmark anchor.
2687+ local byte_col = vim .fn .virtcol2col (win , lnum , lo );
27142688
2715- :: continue::
2689+ if byte_col >= 1 then
2690+ vim .api .nvim_buf_set_extmark (buffer , markdown .ns , row , byte_col - 1 , {
2691+ undo_restore = false , invalidate = true ,
2692+ virt_text = continuation_vt ,
2693+ virt_text_win_col = 0 ,
2694+ hl_mode = " combine" ,
2695+ });
27162696 end
27172697 end
27182698 end
0 commit comments