Skip to content

Commit ef0c921

Browse files
feat(perf): Improve hiding marks under cursor
## Details Currently on every cursor movement we clear all buffer marks then add all the marks back one at a time avoiding the ones at the current cursor position. Instead what we can do is avoid touching marks that are unaffected, i.e. all of the ones that remain rendered, render any previously removed marks, and remove the relevant marks for the current line. To accomplish this have added an Extmark class that wraps the raw mark information from our parsers. These marks store the id of the extmark from the call to nvim_buf_set_extmark. Now when we attempt to render a mark we check if we should, i.e. is it on the current row or not. - If we should render the mark only do it if it does not have an id set and then we set the id - If we should not render the mark only do it if it does have an id set and then we unset the id By doing this we still loop through all the marks but we only touch marks that change from shown -> hidden or from hidden -> shown. Other minor changes: - Rename `request` module to `context` - Add benchmark for medium table
1 parent 4d046cd commit ef0c921

File tree

10 files changed

+208
-121
lines changed

10 files changed

+208
-121
lines changed

benches/medium_spec.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ describe('medium.md', function()
77
util.between(25, 75, util.setup('temp/medium.md'))
88
util.num_marks(2998)
99

10-
util.between(1, 5, util.move_down(3))
10+
util.between(0, 1, util.move_down(3))
1111
util.num_marks(3000)
1212

1313
util.between(25, 50, util.insert_mode())

benches/medium_table_spec.lua

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---@module 'luassert'
2+
3+
local util = require('benches.util')
4+
5+
describe('medium-table.md', function()
6+
it('default', function()
7+
util.between(450, 600, util.setup('temp/medium-table.md'))
8+
util.num_marks(30012)
9+
10+
util.between(1, 5, util.move_down(1))
11+
util.num_marks(30014)
12+
13+
util.between(400, 550, util.insert_mode())
14+
util.num_marks(30014)
15+
end)
16+
end)

doc/render-markdown.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
*render-markdown.txt* For 0.10.0 Last change: 2024 July 30
1+
*render-markdown.txt* For 0.10.0 Last change: 2024 July 31
22

33
==============================================================================
44
Table of Contents *render-markdown-table-of-contents*

lua/render-markdown/context.lua

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
local util = require('render-markdown.util')
2+
3+
---@class render.md.ContextCache
4+
local cache = {
5+
---@type table<integer, table<integer, [integer, integer][]>>
6+
conceal = {},
7+
---@type table<integer, table<integer, [integer, integer, string][]>>
8+
inline_links = {},
9+
}
10+
11+
---@class render.md.Context
12+
local M = {}
13+
14+
---@param buf integer
15+
function M.reset_buf(buf)
16+
cache.conceal[buf] = {}
17+
cache.inline_links[buf] = {}
18+
end
19+
20+
---@param buf integer
21+
---@param parser vim.treesitter.LanguageTree
22+
function M.compute_conceal(buf, parser)
23+
local ranges = {}
24+
if util.get_win(util.buf_to_win(buf), 'conceallevel') > 0 then
25+
parser:for_each_tree(function(tree, language_tree)
26+
local nodes = M.get_conceal_nodes(buf, language_tree:lang(), tree:root())
27+
for _, node in ipairs(nodes) do
28+
local row, start_col, _, end_col = node:range()
29+
if ranges[row] == nil then
30+
ranges[row] = {}
31+
end
32+
table.insert(ranges[row], { start_col, end_col })
33+
end
34+
end)
35+
end
36+
cache.conceal[buf] = ranges
37+
end
38+
39+
---@private
40+
---@param buf integer
41+
---@param language string
42+
---@param root TSNode
43+
---@return TSNode[]
44+
function M.get_conceal_nodes(buf, language, root)
45+
if not vim.tbl_contains({ 'markdown', 'markdown_inline' }, language) then
46+
return {}
47+
end
48+
local query = vim.treesitter.query.get(language, 'highlights')
49+
if query == nil then
50+
return {}
51+
end
52+
local nodes = {}
53+
for _, node, metadata in query:iter_captures(root, buf) do
54+
if metadata.conceal ~= nil then
55+
table.insert(nodes, node)
56+
end
57+
end
58+
return nodes
59+
end
60+
61+
---@param buf integer
62+
---@param row integer
63+
---@return [integer, integer][]
64+
function M.concealed(buf, row)
65+
return cache.conceal[buf][row] or {}
66+
end
67+
68+
---@param buf integer
69+
---@param info render.md.NodeInfo
70+
---@param icon string
71+
function M.add_inline_link(buf, info, icon)
72+
local inline_links = cache.inline_links[buf]
73+
local row = info.start_row
74+
if inline_links[row] == nil then
75+
inline_links[row] = {}
76+
end
77+
table.insert(inline_links[row], { info.start_col, info.end_col, icon })
78+
end
79+
80+
---@param buf integer
81+
---@param row integer
82+
---@return [integer, integer, string][]
83+
function M.inline_links(buf, row)
84+
return cache.inline_links[buf][row] or {}
85+
end
86+
87+
return M

lua/render-markdown/extmark.lua

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
local state = require('render-markdown.state')
2+
3+
---@class render.md.Extmark
4+
---@field private namespace integer
5+
---@field private buf integer
6+
---@field private mark render.md.Mark
7+
---@field private id? integer
8+
local Extmark = {}
9+
Extmark.__index = Extmark
10+
11+
---@param namespace integer
12+
---@param buf integer
13+
---@param mark render.md.Mark
14+
function Extmark.new(namespace, buf, mark)
15+
local self = setmetatable({}, Extmark)
16+
self.namespace = namespace
17+
self.buf = buf
18+
self.mark = mark
19+
self.id = nil
20+
return self
21+
end
22+
23+
---@param row? integer
24+
function Extmark:render(row)
25+
if self:should_show(row) then
26+
self:show()
27+
else
28+
self:hide()
29+
end
30+
end
31+
32+
---@private
33+
function Extmark:show()
34+
if self.id == nil then
35+
self.id = vim.api.nvim_buf_set_extmark(
36+
self.buf,
37+
self.namespace,
38+
self.mark.start_row,
39+
self.mark.start_col,
40+
self.mark.opts
41+
)
42+
end
43+
end
44+
45+
function Extmark:hide()
46+
if self.id ~= nil then
47+
vim.api.nvim_buf_del_extmark(self.buf, self.namespace, self.id)
48+
self.id = nil
49+
end
50+
end
51+
52+
---Render marks based on anti-conceal behavior and current row
53+
---@private
54+
---@param row? integer
55+
---@return boolean
56+
function Extmark:should_show(row)
57+
-- Anti-conceal is not enabled -> all marks should be shown
58+
local config = state.get_config(self.buf)
59+
if not config.anti_conceal.enabled then
60+
return true
61+
end
62+
-- Row is not known means buffer is not active -> all marks should be shown
63+
if row == nil then
64+
return true
65+
end
66+
-- Mark is not concealable -> mark should always be shown
67+
if not self.mark.conceal then
68+
return true
69+
end
70+
-- Show mark if it is not on the current row
71+
return self.mark.start_row ~= row
72+
end
73+
74+
return Extmark

lua/render-markdown/handler/markdown.lua

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
local colors = require('render-markdown.colors')
22
local component = require('render-markdown.component')
3+
local context = require('render-markdown.context')
34
local icons = require('render-markdown.icons')
45
local list = require('render-markdown.list')
56
local logger = require('render-markdown.logger')
67
local pipe_table_parser = require('render-markdown.parser.pipe_table')
7-
local request = require('render-markdown.request')
88
local state = require('render-markdown.state')
99
local str = require('render-markdown.str')
1010
local ts = require('render-markdown.ts')
@@ -726,7 +726,7 @@ end
726726
---@param info render.md.NodeInfo
727727
---@return integer
728728
function M.concealed(buf, info)
729-
local ranges = request.concealed(buf, info.start_row)
729+
local ranges = context.concealed(buf, info.start_row)
730730
if #ranges == 0 then
731731
return 0
732732
end
@@ -751,7 +751,7 @@ end
751751
---@return integer
752752
function M.table_visual_offset(buf, info)
753753
local result = M.concealed(buf, info)
754-
local icon_ranges = request.inline_links(buf, info.start_row)
754+
local icon_ranges = context.inline_links(buf, info.start_row)
755755
for _, icon_range in ipairs(icon_ranges) do
756756
if info.start_col < icon_range[2] and info.end_col > icon_range[1] then
757757
result = result - str.width(icon_range[3])

lua/render-markdown/handler/markdown_inline.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
local component = require('render-markdown.component')
2+
local context = require('render-markdown.context')
23
local list = require('render-markdown.list')
34
local logger = require('render-markdown.logger')
4-
local request = require('render-markdown.request')
55
local state = require('render-markdown.state')
66
local str = require('render-markdown.str')
77
local ts = require('render-markdown.ts')
@@ -132,7 +132,7 @@ function M.link(config, buf, info)
132132
if icon == nil then
133133
return nil
134134
end
135-
request.add_inline_link(buf, info, icon)
135+
context.add_inline_link(buf, info, icon)
136136
---@type render.md.Mark
137137
return {
138138
conceal = true,

lua/render-markdown/request.lua

Lines changed: 0 additions & 75 deletions
This file was deleted.

0 commit comments

Comments
 (0)