Skip to content

Commit 41b955c

Browse files
Operate at event buffer level. Be more clever with autocommands.
# Details Previously all operations assumed only the current buffer was being interacted with. This can lead to poor behavior when interacting with multiple markdown buffers. For instance toggling would only change the current buffer but a buffer open in a separate window would still be rendered and stop being updated since enable / disable is a global operation. nvim_cmp popup windows do not change the current buffer so would trigger a re-render event though no content actually changes. To get around this use the buffer from events when making changes. By applying the validation logic on these buffers we avoid doing work where previously we were. To make toggling propagate through all buffers use nvim_list_bufs method. Additionally there 2 events the provide information that can be used to improve rendering. - ModeChanged tells us the start and end mode which we can use to avoid re-rendering when the change has no impact. I.e. going from normal mode to command mode should not do anything. - WinResized tells us all impacted windows which we can use to re-render all impacted buffers.
1 parent e38795f commit 41b955c

File tree

7 files changed

+132
-68
lines changed

7 files changed

+132
-68
lines changed

lua/render-markdown/handler/latex.lua

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,15 @@ local M = {}
1313

1414
---@param namespace number
1515
---@param root TSNode
16-
M.render = function(namespace, root)
16+
---@param buf integer
17+
M.render = function(namespace, root, buf)
1718
if vim.fn.executable('latex2text') ~= 1 then
1819
return
1920
end
2021

21-
local value = vim.treesitter.get_node_text(root, 0)
22+
local value = vim.treesitter.get_node_text(root, buf)
2223
local start_row, start_col, end_row, end_col = root:range()
23-
logger.debug_node('latex', root)
24+
logger.debug_node('latex', root, buf)
2425

2526
local expressions = cache.expressions[value]
2627
if expressions == nil then
@@ -33,7 +34,7 @@ M.render = function(namespace, root)
3334
local virt_lines = vim.tbl_map(function(expression)
3435
return { { expression, state.config.highlights.latex } }
3536
end, expressions)
36-
vim.api.nvim_buf_set_extmark(0, namespace, start_row, start_col, {
37+
vim.api.nvim_buf_set_extmark(buf, namespace, start_row, start_col, {
3738
end_row = end_row,
3839
end_col = end_col,
3940
virt_lines = virt_lines,

lua/render-markdown/handler/markdown.lua

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
local list = require('render-markdown.list')
22
local logger = require('render-markdown.logger')
33
local state = require('render-markdown.state')
4+
local util = require('render-markdown.util')
45

56
local M = {}
67

78
---@param namespace number
89
---@param root TSNode
9-
M.render = function(namespace, root)
10+
---@param buf integer
11+
M.render = function(namespace, root, buf)
1012
local highlights = state.config.highlights
1113
---@diagnostic disable-next-line: missing-parameter
12-
for id, node in state.markdown_query:iter_captures(root, 0) do
14+
for id, node in state.markdown_query:iter_captures(root, buf) do
1315
local capture = state.markdown_query.captures[id]
14-
local value = vim.treesitter.get_node_text(node, 0)
16+
local value = vim.treesitter.get_node_text(node, buf)
1517
local start_row, start_col, end_row, end_col = node:range()
16-
logger.debug_node(capture, node)
18+
logger.debug_node(capture, node, buf)
1719

1820
if capture == 'heading' then
1921
local level = #value
@@ -27,7 +29,7 @@ M.render = function(namespace, root)
2729
local foreground = list.clamp_last(highlights.heading.foregrounds, level)
2830

2931
local virt_text = { string.rep(' ', padding) .. heading, { foreground, background } }
30-
vim.api.nvim_buf_set_extmark(0, namespace, start_row, 0, {
32+
vim.api.nvim_buf_set_extmark(buf, namespace, start_row, 0, {
3133
end_row = end_row + 1,
3234
end_col = 0,
3335
hl_group = background,
@@ -36,14 +38,14 @@ M.render = function(namespace, root)
3638
hl_eol = true,
3739
})
3840
elseif capture == 'dash' then
39-
local width = vim.api.nvim_win_get_width(0)
41+
local width = vim.api.nvim_win_get_width(util.buf_to_win(buf))
4042
local virt_text = { state.config.dash:rep(width), highlights.dash }
41-
vim.api.nvim_buf_set_extmark(0, namespace, start_row, 0, {
43+
vim.api.nvim_buf_set_extmark(buf, namespace, start_row, 0, {
4244
virt_text = { virt_text },
4345
virt_text_pos = 'overlay',
4446
})
4547
elseif capture == 'code' then
46-
vim.api.nvim_buf_set_extmark(0, namespace, start_row, 0, {
48+
vim.api.nvim_buf_set_extmark(buf, namespace, start_row, 0, {
4749
end_row = end_row,
4850
end_col = 0,
4951
hl_group = highlights.code,
@@ -65,15 +67,15 @@ M.render = function(namespace, root)
6567
end
6668

6769
local virt_text = { list_marker_overlay, highlights.bullet }
68-
vim.api.nvim_buf_set_extmark(0, namespace, start_row, start_col, {
70+
vim.api.nvim_buf_set_extmark(buf, namespace, start_row, start_col, {
6971
end_row = end_row,
7072
end_col = end_col,
7173
virt_text = { virt_text },
7274
virt_text_pos = 'overlay',
7375
})
7476
elseif capture == 'quote_marker' then
7577
local virt_text = { value:gsub('>', state.config.quote), highlights.quote }
76-
vim.api.nvim_buf_set_extmark(0, namespace, start_row, start_col, {
78+
vim.api.nvim_buf_set_extmark(buf, namespace, start_row, start_col, {
7779
end_row = end_row,
7880
end_col = end_col,
7981
virt_text = { virt_text },
@@ -90,7 +92,7 @@ M.render = function(namespace, root)
9092

9193
if padding >= 0 then
9294
local virt_text = { string.rep(' ', padding) .. checkbox, highlight }
93-
vim.api.nvim_buf_set_extmark(0, namespace, start_row, start_col, {
95+
vim.api.nvim_buf_set_extmark(buf, namespace, start_row, start_col, {
9496
end_row = end_row,
9597
end_col = end_col,
9698
virt_text = { virt_text },
@@ -99,7 +101,7 @@ M.render = function(namespace, root)
99101
end
100102
elseif capture == 'table' then
101103
if state.config.fat_tables then
102-
local lines = vim.api.nvim_buf_get_lines(0, start_row, end_row, false)
104+
local lines = vim.api.nvim_buf_get_lines(buf, start_row, end_row, false)
103105
local table_head = list.first(lines)
104106
local table_tail = list.last(lines)
105107
if #table_head == #table_tail then
@@ -109,13 +111,13 @@ M.render = function(namespace, root)
109111
end, headings)
110112

111113
local line_above = { { '' .. table.concat(sections, '') .. '', highlights.table.head } }
112-
vim.api.nvim_buf_set_extmark(0, namespace, start_row, start_col, {
114+
vim.api.nvim_buf_set_extmark(buf, namespace, start_row, start_col, {
113115
virt_lines_above = true,
114116
virt_lines = { line_above },
115117
})
116118

117119
local line_below = { { '' .. table.concat(sections, '') .. '', highlights.table.row } }
118-
vim.api.nvim_buf_set_extmark(0, namespace, end_row, start_col, {
120+
vim.api.nvim_buf_set_extmark(buf, namespace, end_row, start_col, {
119121
virt_lines_above = true,
120122
virt_lines = { line_below },
121123
})
@@ -138,7 +140,7 @@ M.render = function(namespace, root)
138140
end
139141

140142
local virt_text = { row, highlight }
141-
vim.api.nvim_buf_set_extmark(0, namespace, start_row, start_col, {
143+
vim.api.nvim_buf_set_extmark(buf, namespace, start_row, start_col, {
142144
end_row = end_row,
143145
end_col = end_col,
144146
virt_text = { virt_text },

lua/render-markdown/handler/markdown_inline.lua

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,17 @@ local M = {}
55

66
---@param namespace number
77
---@param root TSNode
8-
M.render = function(namespace, root)
8+
---@param buf integer
9+
M.render = function(namespace, root, buf)
910
local highlights = state.config.highlights
1011
---@diagnostic disable-next-line: missing-parameter
11-
for id, node in state.inline_query:iter_captures(root, 0) do
12+
for id, node in state.inline_query:iter_captures(root, buf) do
1213
local capture = state.inline_query.captures[id]
1314
local start_row, start_col, end_row, end_col = node:range()
14-
logger.debug_node(capture, node)
15+
logger.debug_node(capture, node, buf)
1516

1617
if capture == 'code' then
17-
vim.api.nvim_buf_set_extmark(0, namespace, start_row, start_col, {
18+
vim.api.nvim_buf_set_extmark(buf, namespace, start_row, start_col, {
1819
end_row = end_row,
1920
end_col = end_col,
2021
hl_group = highlights.code,

lua/render-markdown/init.lua

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
local state = require('render-markdown.state')
22
local ui = require('render-markdown.ui')
3+
local util = require('render-markdown.util')
34

45
local M = {}
56

@@ -139,18 +140,42 @@ function M.setup(opts)
139140
state.inline_query = vim.treesitter.query.parse('markdown_inline', state.config.inline_query)
140141

141142
-- Call immediately to re-render on LazyReload
142-
vim.schedule(ui.refresh)
143-
144-
vim.api.nvim_create_autocmd({
145-
'FileChangedShellPost',
146-
'ModeChanged',
147-
'Syntax',
148-
'TextChanged',
149-
'WinResized',
150-
}, {
151-
group = vim.api.nvim_create_augroup('RenderMarkdown', { clear = true }),
143+
vim.schedule(function()
144+
ui.refresh(vim.api.nvim_get_current_buf())
145+
end)
146+
147+
local group = vim.api.nvim_create_augroup('RenderMarkdown', { clear = true })
148+
vim.api.nvim_create_autocmd({ 'ModeChanged' }, {
149+
group = group,
150+
callback = function(event)
151+
local was_rendered = vim.tbl_contains(state.config.render_modes, vim.v.event.old_mode)
152+
local should_render = vim.tbl_contains(state.config.render_modes, vim.v.event.new_mode)
153+
-- Only need to re-render if render state is changing. I.e. going from normal mode to
154+
-- command mode with the default config, both are rendered, so no point re-rendering
155+
if was_rendered ~= should_render then
156+
vim.schedule(function()
157+
ui.refresh(event.buf)
158+
end)
159+
end
160+
end,
161+
})
162+
vim.api.nvim_create_autocmd({ 'WinResized' }, {
163+
group = group,
152164
callback = function()
153-
vim.schedule(ui.refresh)
165+
for _, win in ipairs(vim.v.event.windows) do
166+
local buf = util.win_to_buf(win)
167+
vim.schedule(function()
168+
ui.refresh(buf)
169+
end)
170+
end
171+
end,
172+
})
173+
vim.api.nvim_create_autocmd({ 'FileChangedShellPost', 'Syntax', 'TextChanged' }, {
174+
group = group,
175+
callback = function(event)
176+
vim.schedule(function()
177+
ui.refresh(event.buf)
178+
end)
154179
end,
155180
})
156181

@@ -162,13 +187,15 @@ function M.setup(opts)
162187
end
163188

164189
M.toggle = function()
165-
if state.enabled then
166-
state.enabled = false
167-
vim.schedule(ui.clear)
168-
else
169-
state.enabled = true
170-
-- Call to refresh must happen after state change
171-
vim.schedule(ui.refresh)
190+
state.enabled = not state.enabled
191+
for _, buf in ipairs(vim.api.nvim_list_bufs()) do
192+
vim.schedule(function()
193+
if state.enabled then
194+
ui.refresh(buf)
195+
else
196+
ui.clear_valid(buf)
197+
end
198+
end)
172199
end
173200
end
174201

lua/render-markdown/logger.lua

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,10 @@ end
5959

6060
---@param capture string
6161
---@param node TSNode
62-
M.debug_node = function(capture, node)
62+
---@param buf integer
63+
M.debug_node = function(capture, node, buf)
6364
if vim.tbl_contains({ 'debug' }, state.config.log_level) then
64-
local value = vim.treesitter.get_node_text(node, 0)
65+
local value = vim.treesitter.get_node_text(node, buf)
6566
local start_row, start_col, end_row, end_col = node:range()
6667
log.add('debug', {
6768
capture = capture,

lua/render-markdown/ui.lua

Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,64 +3,62 @@ local logger = require('render-markdown.logger')
33
local markdown = require('render-markdown.handler.markdown')
44
local markdown_inline = require('render-markdown.handler.markdown_inline')
55
local state = require('render-markdown.state')
6+
local util = require('render-markdown.util')
67

78
local M = {}
89

910
M.namespace = vim.api.nvim_create_namespace('render-markdown.nvim')
1011

11-
M.clear = function()
12-
-- Remove existing highlights / virtual text
13-
vim.api.nvim_buf_clear_namespace(0, M.namespace, 0, -1)
14-
vim.opt_local.conceallevel = state.config.conceal.default
15-
end
16-
17-
M.refresh = function()
12+
---@param buf integer
13+
M.refresh = function(buf)
1814
if not state.enabled then
1915
return
2016
end
21-
if not vim.tbl_contains(state.config.file_types, vim.bo.filetype) then
17+
if not M.clear_valid(buf) then
2218
return
2319
end
24-
-- Needs to happen after file_type check and before mode check
25-
M.clear()
2620
if not vim.tbl_contains(state.config.render_modes, vim.fn.mode()) then
2721
return
2822
end
29-
if M.file_size_mb() > state.config.max_file_size then
23+
if util.file_size_mb(buf) > state.config.max_file_size then
3024
return
3125
end
3226

3327
logger.start()
34-
vim.opt_local.conceallevel = state.config.conceal.rendered
28+
util.set_conceal(buf, state.config.conceal.rendered)
3529

3630
-- Make sure injections are processed
37-
vim.treesitter.get_parser():parse(true)
31+
vim.treesitter.get_parser(buf):parse(true)
3832

39-
vim.treesitter.get_parser():for_each_tree(function(tree, language_tree)
33+
vim.treesitter.get_parser(buf):for_each_tree(function(tree, language_tree)
4034
local language = language_tree:lang()
4135
logger.debug({ language = language })
4236
if language == 'markdown' then
43-
markdown.render(M.namespace, tree:root())
37+
markdown.render(M.namespace, tree:root(), buf)
4438
elseif language == 'markdown_inline' then
45-
markdown_inline.render(M.namespace, tree:root())
39+
markdown_inline.render(M.namespace, tree:root(), buf)
4640
elseif language == 'latex' then
47-
latex.render(M.namespace, tree:root())
41+
latex.render(M.namespace, tree:root(), buf)
4842
else
4943
logger.debug('No handler found')
5044
end
5145
end)
5246
logger.flush()
5347
end
5448

55-
---@return number
56-
M.file_size_mb = function()
57-
local ok, stats = pcall(function()
58-
return vim.uv.fs_stat(vim.api.nvim_buf_get_name(0))
59-
end)
60-
if not (ok and stats) then
61-
return 0
49+
--- Remove existing highlights / virtual text for valid buffers
50+
---@param buf integer
51+
---@return boolean
52+
M.clear_valid = function(buf)
53+
if not vim.api.nvim_buf_is_valid(buf) then
54+
return false
55+
end
56+
if not vim.tbl_contains(state.config.file_types, vim.bo[buf].filetype) then
57+
return false
6258
end
63-
return stats.size / (1024 * 1024)
59+
vim.api.nvim_buf_clear_namespace(buf, M.namespace, 0, -1)
60+
util.set_conceal(buf, state.config.conceal.default)
61+
return true
6462
end
6563

6664
return M

lua/render-markdown/util.lua

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
local M = {}
2+
3+
---@param win integer
4+
---@return integer
5+
M.win_to_buf = function(win)
6+
return vim.fn.winbufnr(win)
7+
end
8+
9+
---@param buf integer
10+
---@return integer
11+
M.buf_to_win = function(buf)
12+
return vim.fn.bufwinid(buf)
13+
end
14+
15+
---@param buf integer
16+
---@param value integer
17+
M.set_conceal = function(buf, value)
18+
local win = M.buf_to_win(buf)
19+
vim.api.nvim_set_option_value('conceallevel', value, { scope = 'local', win = win })
20+
end
21+
22+
---@param buf integer
23+
---@return number
24+
M.file_size_mb = function(buf)
25+
local ok, stats = pcall(function()
26+
return vim.uv.fs_stat(vim.api.nvim_buf_get_name(buf))
27+
end)
28+
if not (ok and stats) then
29+
return 0
30+
end
31+
return stats.size / (1024 * 1024)
32+
end
33+
34+
return M

0 commit comments

Comments
 (0)