Skip to content

Commit 08a0777

Browse files
authored
feat: treesitter highlighter (#404)
* feat: treesitter highlighter * perf(treesitter): cache treesitter highlights * feat(menu): treesitter draw config option
1 parent 4c65dbd commit 08a0777

File tree

3 files changed

+80
-0
lines changed

3 files changed

+80
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
local treesitter = {}
2+
3+
---@type table<string, blink.cmp.DrawHighlight[]>
4+
local cache = {}
5+
local cache_size = 0
6+
local MAX_CACHE_SIZE = 1000
7+
8+
--- @param ctx blink.cmp.DrawItemContext
9+
--- @param opts? {offset?: number}
10+
function treesitter.highlight(ctx, opts)
11+
-- early return if treesitter highlight is disabled
12+
if not vim.b.ts_highlight then return {} end
13+
14+
local ret = cache[ctx.label]
15+
if not ret then
16+
-- cleanup cache if it's too big
17+
cache_size = cache_size + 1
18+
if cache_size > MAX_CACHE_SIZE then
19+
cache = {}
20+
cache_size = 0
21+
end
22+
ret = treesitter._highlight(ctx)
23+
cache[ctx.label] = ret
24+
end
25+
26+
-- offset highlights if needed
27+
if opts and opts.offset then
28+
ret = vim.deepcopy(ret)
29+
for _, hl in ipairs(ret) do
30+
hl[1] = hl[1] + opts.offset
31+
hl[2] = hl[2] + opts.offset
32+
end
33+
end
34+
return ret
35+
end
36+
37+
--- @param ctx blink.cmp.DrawItemContext
38+
function treesitter._highlight(ctx)
39+
local ret = {} ---@type blink.cmp.DrawHighlight[]
40+
41+
local source = ctx.label
42+
local lang = vim.treesitter.language.get_lang(vim.bo.filetype)
43+
if not lang then return ret end
44+
45+
local ok, parser = pcall(vim.treesitter.get_string_parser, source, lang)
46+
if not ok then return ret end
47+
48+
parser:parse(true)
49+
50+
parser:for_each_tree(function(tstree, tree)
51+
if not tstree then return end
52+
local query = vim.treesitter.query.get(tree:lang(), 'highlights')
53+
-- Some injected languages may not have highlight queries.
54+
if not query then return end
55+
56+
for capture, node in query:iter_captures(tstree:root(), source) do
57+
local _, start_col, _, end_col = node:range()
58+
59+
---@type string
60+
local name = query.captures[capture]
61+
if name ~= 'spell' then
62+
ret[#ret + 1] = {
63+
start_col,
64+
end_col,
65+
group = '@' .. name .. '.' .. lang,
66+
}
67+
end
68+
end
69+
end)
70+
return ret
71+
end
72+
73+
return treesitter

lua/blink/cmp/completion/windows/render/types.lua

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
--- @field gap? number Gap between columns
55
--- @field columns? { [number]: string, gap?: number }[] Components to render, grouped by column
66
--- @field components? table<string, blink.cmp.DrawComponent> Component definitions
7+
--- @field treesitter? boolean Use treesitter to highlight the label text
78
---
89
--- @class blink.cmp.DrawHighlight
910
--- @field [number] number Start and end index of the highlight

lua/blink/cmp/config/completion/menu.lua

+6
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ local window = {
4444
padding = 1,
4545
-- Gap between columns
4646
gap = 1,
47+
treesitter = false, -- Use treesitter to highlight the label text
4748
-- Components to render, grouped by column
4849
columns = { { 'kind_icon' }, { 'label', 'label_description', gap = 1 } },
4950
-- Definitions for possible components to render. Each component defines:
@@ -82,6 +83,11 @@ local window = {
8283
table.insert(highlights, { #label, #label + #ctx.label_detail, group = 'BlinkCmpLabelDetail' })
8384
end
8485

86+
if ctx.self.treesitter then
87+
-- add treesitter highlights
88+
vim.list_extend(highlights, require('blink.cmp.completion.windows.render.treesitter').highlight(ctx))
89+
end
90+
8591
-- characters matched on the label by the fuzzy matcher
8692
for _, idx in ipairs(ctx.label_matched_indices) do
8793
table.insert(highlights, { idx, idx + 1, group = 'BlinkCmpLabelMatch' })

0 commit comments

Comments
 (0)