@@ -2,11 +2,13 @@ local component = require('render-markdown.component')
2
2
local icons = require (' render-markdown.icons' )
3
3
local list = require (' render-markdown.list' )
4
4
local logger = require (' render-markdown.logger' )
5
+ local shared = require (' render-markdown.handler.shared' )
5
6
local state = require (' render-markdown.state' )
6
7
local str = require (' render-markdown.str' )
7
8
local ts = require (' render-markdown.ts' )
8
9
local util = require (' render-markdown.util' )
9
10
11
+ --- @class render.md.handler.Markdown
10
12
local M = {}
11
13
12
14
--- @param namespace integer
@@ -32,15 +34,15 @@ M.render_node = function(namespace, buf, capture, node)
32
34
if not heading .enabled then
33
35
return
34
36
end
35
- local level = vim . fn . strdisplaywidth (info .text )
37
+ local level = str . width (info .text )
36
38
37
39
local icon = list .cycle (heading .icons , level )
38
40
local background = list .clamp (heading .backgrounds , level )
39
41
local foreground = list .clamp (heading .foregrounds , level )
40
42
41
43
-- Available width is level + 1, where level = number of `#` characters and one is
42
44
-- 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 )
44
46
45
47
vim .api .nvim_buf_set_extmark (buf , namespace , info .start_row , 0 , {
46
48
end_row = info .end_row + 1 ,
@@ -212,85 +214,7 @@ M.render_node = function(namespace, buf, capture, node)
212
214
virt_text_pos = ' overlay' ,
213
215
})
214
216
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 )
294
218
else
295
219
-- Should only get here if user provides custom capture, currently unhandled
296
220
logger .error (' Unhandled markdown capture: ' .. capture )
@@ -300,62 +224,166 @@ end
300
224
--- @param namespace integer
301
225
--- @param buf integer
302
226
--- @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
314
249
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 )
315
254
end
316
- return result
317
255
end
256
+ if pipe_table .style == ' full' then
257
+ M .render_table_full (namespace , buf , delim , first , last )
258
+ end
259
+ end
318
260
261
+ --- @param namespace integer
262
+ --- @param buf integer
263
+ --- @param row render.md.NodeInfo
264
+ M .render_table_delimiter = function (namespace , buf , row )
319
265
local pipe_table = state .config .pipe_table
320
266
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 ])
321
274
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
322
289
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 } },
331
297
virt_text_pos = ' overlay' ,
332
298
})
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
+ })
344
308
end
345
309
end
346
310
else
347
311
-- 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 )
349
313
end
350
314
end
351
315
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 } },
356
320
virt_text_pos = ' overlay' ,
357
321
})
358
322
end
359
323
end
360
324
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
+
361
389
return M
0 commit comments