Skip to content

Commit 24a7b97

Browse files
committed
fix(table): align wrapped continuations
Narrow windows with linebreak, showbreak, or breakindent move continuation screen-line starts. Wrapped table overlays must use the same slots or rows render under the continuation prefix.
1 parent 01f4718 commit 24a7b97

2 files changed

Lines changed: 102 additions & 9 deletions

File tree

lua/render-markdown/render/markdown/table.lua

Lines changed: 71 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,74 @@ function Render:row_wrapped_lines(row, row_index)
723723
return result
724724
end
725725

726+
---@private
727+
---@param text string
728+
---@param win_width integer
729+
---@return integer[]
730+
function Render:wrapped_slots(text, win_width)
731+
if #text == 0 then
732+
return { 0 }
733+
end
734+
735+
local linebreak = env.win.get(self.context.win, 'linebreak') == true
736+
local breakat = vim.api.nvim_get_option_value('breakat', {})
737+
local showbreak = env.win.get(self.context.win, 'showbreak')
738+
local breakindent = env.win.get(self.context.win, 'breakindent') == true
739+
local indent = breakindent and str.spaces('start', text) or 0
740+
local continuation_width =
741+
math.max(win_width - str.width(tostring(showbreak)) - indent, 1)
742+
743+
local chars = {} ---@type { col: integer, text: string, width: integer }[]
744+
local bytes = vim.str_utf_pos(text)
745+
for index, start_byte in ipairs(bytes) do
746+
local end_byte = index < #bytes and bytes[index + 1] - 1 or #text
747+
local char = text:sub(start_byte, end_byte)
748+
chars[#chars + 1] = {
749+
col = start_byte - 1,
750+
text = char,
751+
width = str.width(char),
752+
}
753+
end
754+
755+
local slots = {} ---@type integer[]
756+
local index = 1
757+
local first = true
758+
while index <= #chars do
759+
slots[#slots + 1] = chars[index].col
760+
local width = first and win_width or continuation_width
761+
local used = 0
762+
local next_index = index
763+
local break_index = nil ---@type integer?
764+
while next_index <= #chars do
765+
local char = chars[next_index]
766+
if used > 0 and used + char.width > width then
767+
break
768+
end
769+
used = used + char.width
770+
if linebreak and breakat:find(char.text, 1, true) then
771+
break_index = next_index
772+
end
773+
next_index = next_index + 1
774+
if used >= width then
775+
break
776+
end
777+
end
778+
if next_index > #chars then
779+
break
780+
elseif linebreak and break_index and break_index >= index then
781+
index = break_index + 1
782+
while index <= #chars and chars[index].text:match('^%s$') do
783+
index = index + 1
784+
end
785+
else
786+
index = next_index
787+
end
788+
first = false
789+
end
790+
791+
return slots
792+
end
793+
726794
---@private
727795
function Render:wrapped()
728796
local visual = {} ---@type render.md.Line[]
@@ -774,15 +842,8 @@ function Render:wrapped()
774842
conceal = '',
775843
})
776844
end
777-
local screen_lines =
778-
math.max(math.ceil(str.width(buf_line) / win_width), 1)
779-
for line = 0, screen_lines - 1 do
780-
local byte_col = line == 0 and 0
781-
or vim.fn.byteidx(buf_line, line * win_width)
782-
if byte_col < 0 then
783-
byte_col = #buf_line
784-
end
785-
slots[#slots + 1] = { row = node.start_row, col = byte_col }
845+
for _, col in ipairs(self:wrapped_slots(buf_line, win_width)) do
846+
slots[#slots + 1] = { row = node.start_row, col = col }
786847
end
787848
end
788849

@@ -793,6 +854,7 @@ function Render:wrapped()
793854
self.marks:add(self.config, 'table_border', slot.row, slot.col, {
794855
virt_text = line:get(),
795856
virt_text_pos = 'overlay',
857+
virt_text_win_col = 0,
796858
hl_mode = 'combine',
797859
})
798860
else

tests/table_spec.lua

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,37 @@ describe('table', function()
346346
end)
347347
end)
348348

349+
it('wrapped showbreak continuation', function()
350+
with_columns(36, function()
351+
util.setup.text({
352+
'',
353+
'| ID | Title |',
354+
'| -- | ----- |',
355+
'| 1 | alpha beta gamma delta epsilon zeta eta theta iota kappa lambda mu nu xi omicron pi rho sigma tau |',
356+
}, {
357+
pipe_table = { max_table_width = 30 },
358+
win_options = {
359+
wrap = { default = false, rendered = true },
360+
linebreak = { default = false, rendered = true },
361+
showbreak = { default = '', rendered = '>>>>' },
362+
},
363+
})
364+
365+
util.assert_screen({
366+
'┌──────┬─────────────────────┐',
367+
'│ ID │ Title │',
368+
'├──────┼─────────────────────┤',
369+
'│ 1 │ alpha beta gamma │',
370+
'│ │ delta epsilon zeta │',
371+
'│ │ eta theta iota │',
372+
'│ │ kappa lambda mu nu │',
373+
'│ │ xi omicron pi rho │',
374+
'│ │ sigma tau │',
375+
'└──────┴─────────────────────┘',
376+
})
377+
end)
378+
end)
379+
349380
it('wrapped long delimiter', function()
350381
util.setup.text({
351382
'',

0 commit comments

Comments
 (0)