Skip to content

Commit 2b98d16

Browse files
Split adding marks / virtual text into individual handlers by language. Add cache for latex expressions.
# Details This change primarily functions as an internal refactor. Changes the UI module to mostly be a top level orchestrator and moves main rendering logic into language specific handlers. This makes changing indivual parts easier without intermingling any logic only needed for a specific part. This made it easier to cache the encodings from pylatexenc to avoid encoding the same expression multiple times without adding a cache to the entire UI module which is only needed for the latex component.
1 parent a296d0b commit 2b98d16

File tree

4 files changed

+211
-177
lines changed

4 files changed

+211
-177
lines changed

lua/render-markdown/handler/latex.lua

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
local state = require('render-markdown.state')
2+
3+
---@class Cache
4+
---@field expressions table<string,string[]>
5+
6+
---@type Cache
7+
local cache = {
8+
expressions = {},
9+
}
10+
11+
local M = {}
12+
13+
---@param namespace number
14+
---@param root TSNode
15+
M.render = function(namespace, root)
16+
if vim.fn.executable('latex2text') ~= 1 then
17+
return
18+
end
19+
20+
local latex = vim.treesitter.get_node_text(root, 0)
21+
local cached_expressions = cache.expressions[latex]
22+
if cached_expressions == nil then
23+
local raw_expression = vim.fn.system('latex2text', latex)
24+
local expressions = vim.split(vim.trim(raw_expression), '\n', { plain = true })
25+
cached_expressions = vim.tbl_map(vim.trim, expressions)
26+
cache.expressions[latex] = cached_expressions
27+
end
28+
29+
local start_row, start_col, end_row, end_col = root:range()
30+
local virt_lines = vim.tbl_map(function(expression)
31+
return { { expression, state.config.highlights.latex } }
32+
end, cached_expressions)
33+
vim.api.nvim_buf_set_extmark(0, namespace, start_row, start_col, {
34+
end_row = end_row,
35+
end_col = end_col,
36+
virt_lines = virt_lines,
37+
virt_lines_above = true,
38+
})
39+
end
40+
41+
return M
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
local list = require('render-markdown.list')
2+
local state = require('render-markdown.state')
3+
4+
local M = {}
5+
6+
---@param namespace number
7+
---@param root TSNode
8+
M.render = function(namespace, root)
9+
local highlights = state.config.highlights
10+
---@diagnostic disable-next-line: missing-parameter
11+
for id, node in state.markdown_query:iter_captures(root, 0) do
12+
local capture = state.markdown_query.captures[id]
13+
local value = vim.treesitter.get_node_text(node, 0)
14+
local start_row, start_col, end_row, end_col = node:range()
15+
16+
if capture == 'heading' then
17+
local level = #value
18+
local heading = list.cycle(state.config.headings, level)
19+
local background = list.clamp_last(highlights.heading.backgrounds, level)
20+
local foreground = list.clamp_last(highlights.heading.foregrounds, level)
21+
22+
local virt_text = { string.rep(' ', level - 1) .. heading, { foreground, background } }
23+
vim.api.nvim_buf_set_extmark(0, namespace, start_row, 0, {
24+
end_row = end_row + 1,
25+
end_col = 0,
26+
hl_group = background,
27+
virt_text = { virt_text },
28+
virt_text_pos = 'overlay',
29+
hl_eol = true,
30+
})
31+
elseif capture == 'code' then
32+
vim.api.nvim_buf_set_extmark(0, namespace, start_row, 0, {
33+
end_row = end_row,
34+
end_col = 0,
35+
hl_group = highlights.code,
36+
hl_eol = true,
37+
})
38+
elseif capture == 'list_marker' then
39+
-- List markers from tree-sitter should have leading spaces removed, however there are known
40+
-- edge cases in the parser: https://github.com/tree-sitter-grammars/tree-sitter-markdown/issues/127
41+
-- As a result we handle leading spaces here, can remove if this gets fixed upstream
42+
local _, leading_spaces = value:find('^%s*')
43+
local level = M.calculate_list_level(node)
44+
local bullet = list.cycle(state.config.bullets, level)
45+
46+
local virt_text = { string.rep(' ', leading_spaces or 0) .. bullet, highlights.bullet }
47+
vim.api.nvim_buf_set_extmark(0, namespace, start_row, start_col, {
48+
end_row = end_row,
49+
end_col = end_col,
50+
virt_text = { virt_text },
51+
virt_text_pos = 'overlay',
52+
})
53+
elseif capture == 'quote_marker' then
54+
local virt_text = { value:gsub('>', state.config.quote), highlights.quote }
55+
vim.api.nvim_buf_set_extmark(0, namespace, start_row, start_col, {
56+
end_row = end_row,
57+
end_col = end_col,
58+
virt_text = { virt_text },
59+
virt_text_pos = 'overlay',
60+
})
61+
elseif capture == 'table' then
62+
if state.config.fat_tables then
63+
local lines = vim.api.nvim_buf_get_lines(0, start_row, end_row, false)
64+
local table_head = list.first(lines)
65+
local table_tail = list.last(lines)
66+
if #table_head == #table_tail then
67+
local headings = vim.split(table_head, '|', { plain = true, trimempty = true })
68+
local sections = vim.tbl_map(function(part)
69+
return string.rep('', #part)
70+
end, headings)
71+
72+
local line_above = { { '' .. table.concat(sections, '') .. '', highlights.table.head } }
73+
vim.api.nvim_buf_set_extmark(0, namespace, start_row, start_col, {
74+
virt_lines_above = true,
75+
virt_lines = { line_above },
76+
})
77+
78+
local line_below = { { '' .. table.concat(sections, '') .. '', highlights.table.row } }
79+
vim.api.nvim_buf_set_extmark(0, namespace, end_row, start_col, {
80+
virt_lines_above = true,
81+
virt_lines = { line_below },
82+
})
83+
end
84+
end
85+
elseif vim.tbl_contains({ 'table_head', 'table_delim', 'table_row' }, capture) then
86+
local row = value:gsub('|', '')
87+
if capture == 'table_delim' then
88+
-- Order matters here, in particular handling inner intersections before left & right
89+
row = row:gsub('-', '')
90+
:gsub(' ', '')
91+
:gsub('─│─', '─┼─')
92+
:gsub('│─', '├─')
93+
:gsub('─│', '─┤')
94+
end
95+
96+
local highlight = highlights.table.head
97+
if capture == 'table_row' then
98+
highlight = highlights.table.row
99+
end
100+
101+
local virt_text = { row, highlight }
102+
vim.api.nvim_buf_set_extmark(0, namespace, start_row, start_col, {
103+
end_row = end_row,
104+
end_col = end_col,
105+
virt_text = { virt_text },
106+
virt_text_pos = 'overlay',
107+
})
108+
else
109+
-- Should only get here if user provides custom capture, currently unhandled
110+
vim.print('Unhandled markdown capture: ' .. capture)
111+
end
112+
end
113+
end
114+
115+
--- Walk through all parent nodes and count the number of nodes with type list
116+
--- to calculate the level of the given node
117+
---@param node TSNode
118+
---@return integer
119+
M.calculate_list_level = function(node)
120+
local level = 0
121+
local parent = node:parent()
122+
while parent ~= nil do
123+
local parent_type = parent:type()
124+
if vim.tbl_contains({ 'section', 'document' }, parent_type) then
125+
-- when reaching a section or the document we are clearly at the
126+
-- top of the list
127+
break
128+
elseif parent_type == 'list' then
129+
-- found a list increase the level and continue
130+
level = level + 1
131+
end
132+
parent = parent:parent()
133+
end
134+
return level
135+
end
136+
137+
return M
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
local state = require('render-markdown.state')
2+
3+
local M = {}
4+
5+
---@param namespace number
6+
---@param root TSNode
7+
M.render = function(namespace, root)
8+
local highlights = state.config.highlights
9+
---@diagnostic disable-next-line: missing-parameter
10+
for id, node in state.inline_query:iter_captures(root, 0) do
11+
local capture = state.inline_query.captures[id]
12+
local start_row, start_col, end_row, end_col = node:range()
13+
14+
if capture == 'code' then
15+
vim.api.nvim_buf_set_extmark(0, namespace, start_row, start_col, {
16+
end_row = end_row,
17+
end_col = end_col,
18+
hl_group = highlights.code,
19+
})
20+
else
21+
-- Should only get here if user provides custom capture, currently unhandled
22+
vim.print('Unhandled inline capture: ' .. capture)
23+
end
24+
end
25+
end
26+
27+
return M

0 commit comments

Comments
 (0)