@@ -2,11 +2,13 @@ local component = require('render-markdown.component')
22local icons = require (' render-markdown.icons' )
33local list = require (' render-markdown.list' )
44local logger = require (' render-markdown.logger' )
5+ local shared = require (' render-markdown.handler.shared' )
56local state = require (' render-markdown.state' )
67local str = require (' render-markdown.str' )
78local ts = require (' render-markdown.ts' )
89local util = require (' render-markdown.util' )
910
11+ --- @class render.md.handler.Markdown
1012local M = {}
1113
1214--- @param namespace integer
@@ -32,15 +34,15 @@ M.render_node = function(namespace, buf, capture, node)
3234 if not heading .enabled then
3335 return
3436 end
35- local level = vim . fn . strdisplaywidth (info .text )
37+ local level = str . width (info .text )
3638
3739 local icon = list .cycle (heading .icons , level )
3840 local background = list .clamp (heading .backgrounds , level )
3941 local foreground = list .clamp (heading .foregrounds , level )
4042
4143 -- Available width is level + 1, where level = number of `#` characters and one is
4244 -- added to account for the space after the last `#` but before the heading title
43- local padding = level + 1 - vim . fn . strdisplaywidth (icon )
45+ local padding = level + 1 - str . width (icon )
4446
4547 vim .api .nvim_buf_set_extmark (buf , namespace , info .start_row , 0 , {
4648 end_row = info .end_row + 1 ,
@@ -212,85 +214,7 @@ M.render_node = function(namespace, buf, capture, node)
212214 virt_text_pos = ' overlay' ,
213215 })
214216 elseif capture == ' table' then
215- local pipe_table = state .config .pipe_table
216- if not pipe_table .enabled then
217- return
218- end
219- if pipe_table .style == ' none' then
220- return
221- end
222- local border = pipe_table .border
223-
224- local function render_table_full ()
225- local delim_node = ts .child (info .node , ' pipe_table_delimiter_row' )
226- if delim_node == nil then
227- return
228- end
229-
230- local delim = ts .info (delim_node , buf )
231- local lines = vim .api .nvim_buf_get_lines (buf , info .start_row , info .end_row , true )
232-
233- local delim_width = vim .fn .strdisplaywidth (delim .text )
234- local start_width = vim .fn .strdisplaywidth (list .first (lines ))
235- local end_width = vim .fn .strdisplaywidth (list .last (lines ))
236- if delim_width ~= start_width or start_width ~= end_width then
237- return
238- end
239-
240- local headings = vim .split (delim .text , ' |' , { plain = true , trimempty = true })
241- local lengths = vim .tbl_map (function (cell )
242- return border [11 ]:rep (vim .fn .strdisplaywidth (cell ))
243- end , headings )
244-
245- local line_above = border [1 ] .. table.concat (lengths , border [2 ]) .. border [3 ]
246- vim .api .nvim_buf_set_extmark (buf , namespace , info .start_row , info .start_col , {
247- virt_lines_above = true ,
248- virt_lines = { { { line_above , pipe_table .head } } },
249- })
250-
251- local line_below = border [7 ] .. table.concat (lengths , border [8 ]) .. border [9 ]
252- vim .api .nvim_buf_set_extmark (buf , namespace , info .end_row , info .start_col , {
253- virt_lines_above = true ,
254- virt_lines = { { { line_below , pipe_table .row } } },
255- })
256- end
257-
258- --- @param row_info render.md.NodeInfo
259- local function render_table_delimiter (row_info )
260- -- Order matters here, in particular handling inner intersections before left & right
261- local row = row_info .text
262- :gsub (' ' , ' -' )
263- :gsub (' %-|%-' , border [11 ] .. border [5 ] .. border [11 ])
264- :gsub (' |%-' , border [4 ] .. border [11 ])
265- :gsub (' %-|' , border [11 ] .. border [6 ])
266- :gsub (' %-' , border [11 ])
267-
268- vim .api .nvim_buf_set_extmark (buf , namespace , row_info .start_row , row_info .start_col , {
269- end_row = row_info .end_row ,
270- end_col = row_info .end_col ,
271- virt_text = { { row , pipe_table .head } },
272- virt_text_pos = ' overlay' ,
273- })
274- end
275-
276- if pipe_table .style == ' full' then
277- render_table_full ()
278- end
279-
280- for row in info .node :iter_children () do
281- local row_info = ts .info (row , buf )
282- local row_type = row_info .node :type ()
283- if row_type == ' pipe_table_delimiter_row' then
284- render_table_delimiter (row_info )
285- elseif row_type == ' pipe_table_header' then
286- M .render_table_row (namespace , buf , row_info , pipe_table .head )
287- elseif row_type == ' pipe_table_row' then
288- M .render_table_row (namespace , buf , row_info , pipe_table .row )
289- else
290- -- Should only get here if markdown introduces more row types, currently unhandled
291- logger .error (' Unhandled markdown row type: ' .. row_type )
292- end
293- end
217+ M .render_table (namespace , buf , info )
294218 else
295219 -- Should only get here if user provides custom capture, currently unhandled
296220 logger .error (' Unhandled markdown capture: ' .. capture )
@@ -300,62 +224,166 @@ end
300224--- @param namespace integer
301225--- @param buf integer
302226--- @param info render.md.NodeInfo
303- --- @param highlight string
304- M .render_table_row = function (namespace , buf , info , highlight )
305- --- @param text string
306- --- @return integer
307- local function inline_width (text )
308- local query = state .inline_link_query
309- local tree = vim .treesitter .get_string_parser (text , ' markdown_inline' )
310- local result = 0
311- for id in query :iter_captures (tree :parse ()[1 ]:root (), text ) do
312- if query .captures [id ] == ' link' then
313- result = result + vim .fn .strdisplaywidth (state .config .link .hyperlink )
227+ M .render_table = function (namespace , buf , info )
228+ local pipe_table = state .config .pipe_table
229+ if not pipe_table .enabled then
230+ return
231+ end
232+ if pipe_table .style == ' none' then
233+ return
234+ end
235+ local delim = nil
236+ local first = nil
237+ local last = nil
238+ for row_node in info .node :iter_children () do
239+ local row = ts .info (row_node , buf )
240+ if row .type == ' pipe_table_delimiter_row' then
241+ delim = row
242+ M .render_table_delimiter (namespace , buf , row )
243+ elseif row .type == ' pipe_table_header' then
244+ first = row
245+ M .render_table_row (namespace , buf , row , pipe_table .head )
246+ elseif row .type == ' pipe_table_row' then
247+ if last == nil or row .start_row > last .start_row then
248+ last = row
314249 end
250+ M .render_table_row (namespace , buf , row , pipe_table .row )
251+ else
252+ -- Should only get here if markdown introduces more row types, currently unhandled
253+ logger .error (' Unhandled markdown row type: ' .. row .type )
315254 end
316- return result
317255 end
256+ if pipe_table .style == ' full' then
257+ M .render_table_full (namespace , buf , delim , first , last )
258+ end
259+ end
318260
261+ --- @param namespace integer
262+ --- @param buf integer
263+ --- @param row render.md.NodeInfo
264+ M .render_table_delimiter = function (namespace , buf , row )
319265 local pipe_table = state .config .pipe_table
320266 local border = pipe_table .border
267+ -- Order matters here, in particular handling inner intersections before left & right
268+ local delimiter = row .text
269+ :gsub (' ' , ' -' )
270+ :gsub (' %-|%-' , border [11 ] .. border [5 ] .. border [11 ])
271+ :gsub (' |%-' , border [4 ] .. border [11 ])
272+ :gsub (' %-|' , border [11 ] .. border [6 ])
273+ :gsub (' %-' , border [11 ])
321274
275+ vim .api .nvim_buf_set_extmark (buf , namespace , row .start_row , row .start_col , {
276+ end_row = row .end_row ,
277+ end_col = row .end_col ,
278+ virt_text = { { delimiter , pipe_table .head } },
279+ virt_text_pos = ' overlay' ,
280+ })
281+ end
282+
283+ --- @param namespace integer
284+ --- @param buf integer
285+ --- @param row render.md.NodeInfo
286+ --- @param highlight string
287+ M .render_table_row = function (namespace , buf , row , highlight )
288+ local pipe_table = state .config .pipe_table
322289 if vim .tbl_contains ({ ' raw' , ' padded' }, pipe_table .cell ) then
323- for cell in info .node :iter_children () do
324- local cell_info = ts .info (cell , buf )
325- local cell_type = cell_info .node :type ()
326- if cell_type == ' |' then
327- vim .api .nvim_buf_set_extmark (buf , namespace , cell_info .start_row , cell_info .start_col , {
328- end_row = cell_info .end_row ,
329- end_col = cell_info .end_col ,
330- virt_text = { { border [10 ], highlight } },
290+ for cell_node in row .node :iter_children () do
291+ local cell = ts .info (cell_node , buf )
292+ if cell .type == ' |' then
293+ vim .api .nvim_buf_set_extmark (buf , namespace , cell .start_row , cell .start_col , {
294+ end_row = cell .end_row ,
295+ end_col = cell .end_col ,
296+ virt_text = { { pipe_table .border [10 ], highlight } },
331297 virt_text_pos = ' overlay' ,
332298 })
333- elseif cell_type == ' pipe_table_cell' then
334- if pipe_table .cell == ' padded' then
335- -- Requires inline extmarks
336- if util .has_10 then
337- local concealed = ts .concealed (buf , cell_info ) - inline_width (cell_info .text )
338- if concealed > 0 then
339- vim .api .nvim_buf_set_extmark (buf , namespace , cell_info .start_row , cell_info .end_col , {
340- virt_text = { { str .pad (' ' , concealed ), pipe_table .filler } },
341- virt_text_pos = ' inline' ,
342- })
343- end
299+ elseif cell .type == ' pipe_table_cell' then
300+ -- Requires inline extmarks
301+ if pipe_table .cell == ' padded' and util .has_10 then
302+ local offset = M .table_visual_offset (buf , cell )
303+ if offset > 0 then
304+ vim .api .nvim_buf_set_extmark (buf , namespace , cell .start_row , cell .end_col , {
305+ virt_text = { { str .pad (' ' , offset ), pipe_table .filler } },
306+ virt_text_pos = ' inline' ,
307+ })
344308 end
345309 end
346310 else
347311 -- Should only get here if markdown introduces more cell types, currently unhandled
348- logger .error (' Unhandled markdown cell type: ' .. cell_type )
312+ logger .error (' Unhandled markdown cell type: ' .. cell . type )
349313 end
350314 end
351315 elseif pipe_table .cell == ' overlay' then
352- vim .api .nvim_buf_set_extmark (buf , namespace , info .start_row , info .start_col , {
353- end_row = info .end_row ,
354- end_col = info .end_col ,
355- virt_text = { { info .text :gsub (' |' , border [10 ]), highlight } },
316+ vim .api .nvim_buf_set_extmark (buf , namespace , row .start_row , row .start_col , {
317+ end_row = row .end_row ,
318+ end_col = row .end_col ,
319+ virt_text = { { row .text :gsub (' |' , pipe_table . border [10 ]), highlight } },
356320 virt_text_pos = ' overlay' ,
357321 })
358322 end
359323end
360324
325+ --- @param namespace integer
326+ --- @param buf integer
327+ --- @param delim ? render.md.NodeInfo
328+ --- @param first ? render.md.NodeInfo
329+ --- @param last ? render.md.NodeInfo
330+ M .render_table_full = function (namespace , buf , delim , first , last )
331+ local pipe_table = state .config .pipe_table
332+ local border = pipe_table .border
333+ if delim == nil or first == nil or last == nil then
334+ return
335+ end
336+
337+ --- @param info render.md.NodeInfo
338+ --- @return integer
339+ local function width (info )
340+ local result = str .width (info .text )
341+ if pipe_table .cell == ' raw' then
342+ -- For the raw cell style we want the lengths to match after
343+ -- concealing & inlined elements
344+ result = result - M .table_visual_offset (buf , info )
345+ end
346+ return result
347+ end
348+
349+ -- Do not need to account for concealed / inlined text on delimiter row
350+ local delim_width = str .width (delim .text )
351+ if delim_width ~= width (first ) or delim_width ~= width (last ) then
352+ return
353+ end
354+
355+ local headings = vim .split (delim .text , ' |' , { plain = true , trimempty = true })
356+ local lengths = vim .tbl_map (function (cell )
357+ return border [11 ]:rep (str .width (cell ))
358+ end , headings )
359+
360+ local line_above = border [1 ] .. table.concat (lengths , border [2 ]) .. border [3 ]
361+ vim .api .nvim_buf_set_extmark (buf , namespace , first .start_row , first .start_col , {
362+ virt_lines_above = true ,
363+ virt_lines = { { { line_above , pipe_table .head } } },
364+ })
365+
366+ local line_below = border [7 ] .. table.concat (lengths , border [8 ]) .. border [9 ]
367+ vim .api .nvim_buf_set_extmark (buf , namespace , last .start_row , last .start_col , {
368+ virt_lines_above = false ,
369+ virt_lines = { { { line_below , pipe_table .row } } },
370+ })
371+ end
372+
373+ --- @param buf integer
374+ --- @param info render.md.NodeInfo
375+ --- @return integer
376+ M .table_visual_offset = function (buf , info )
377+ local result = ts .concealed (buf , info )
378+ local query = state .inline_link_query
379+ local tree = vim .treesitter .get_string_parser (info .text , ' markdown_inline' )
380+ for id , node in query :iter_captures (tree :parse ()[1 ]:root (), info .text ) do
381+ if query .captures [id ] == ' link' then
382+ local link_info = ts .info (node , info .text )
383+ result = result - str .width (shared .link_icon (link_info ))
384+ end
385+ end
386+ return result
387+ end
388+
361389return M
0 commit comments