Skip to content

Commit 90d6394

Browse files
committed
feat: custom documentation highlighting
closes #113
1 parent af68874 commit 90d6394

File tree

3 files changed

+121
-26
lines changed

3 files changed

+121
-26
lines changed

lua/blink/cmp/utils.lua

+94
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,98 @@ function utils.is_special_buffer()
3636
return buftype ~= ''
3737
end
3838

39+
function utils.split_lines(text)
40+
local lines = {}
41+
for s in text:gmatch('[^\r\n]+') do
42+
table.insert(lines, s)
43+
end
44+
return lines
45+
end
46+
47+
--- Combines adjacent paragraph lines together
48+
--- @param lines string[]
49+
--- @return string[]
50+
--- TODO: Likely buggy
51+
function utils.combine_markdown_lines(lines)
52+
local combined_lines = {}
53+
54+
local special_starting_chars = { '#', '>', '-', '|' }
55+
local in_code_block = false
56+
local prev_is_special = false
57+
for _, line in ipairs(lines) do
58+
if line:match('^%s*```') then in_code_block = not in_code_block end
59+
60+
local is_special = line:match('^%s*[' .. table.concat(special_starting_chars) .. ']') or line:match('^%s*%d\\.$')
61+
local is_empty = line:match('^%s*$')
62+
local has_linebreak = line:match('%s%s$')
63+
64+
if #combined_lines == 0 or in_code_block or is_special or prev_is_special or is_empty or has_linebreak then
65+
table.insert(combined_lines, line)
66+
elseif line:match('^%s*$') then
67+
table.insert(combined_lines, '')
68+
else
69+
combined_lines[#combined_lines] = combined_lines[#combined_lines] .. '' .. line
70+
end
71+
72+
prev_is_special = is_special
73+
end
74+
75+
return combined_lines
76+
end
77+
78+
function utils.highlight_with_treesitter(bufnr, root_lang, start_line, end_line)
79+
local Range = require('vim.treesitter._range')
80+
81+
local trees = vim.treesitter.get_parser(bufnr, root_lang)
82+
trees:parse({ start_line, end_line })
83+
if not trees then return end
84+
85+
trees:for_each_tree(function(tree, tstree)
86+
local lang = tstree:lang()
87+
local highlighter_query = vim.treesitter.query.get(lang, 'highlights')
88+
if not highlighter_query then return end
89+
90+
local root_node = tree:root()
91+
local _, _, root_end_row, _ = root_node:range()
92+
93+
local iter = highlighter_query:iter_captures(tree:root(), bufnr, start_line, end_line)
94+
local line = start_line
95+
while line < end_line do
96+
local capture, node, metadata, _ = iter(line)
97+
if capture == nil then break end
98+
99+
local range = { root_end_row + 1, 0, root_end_row + 1, 0 }
100+
if node then range = vim.treesitter.get_range(node, bufnr, metadata and metadata[capture]) end
101+
local start_row, start_col, end_row, end_col = Range.unpack4(range)
102+
103+
if capture then
104+
local name = highlighter_query.captures[capture]
105+
local hl = 0
106+
if not vim.startswith(name, '_') then hl = vim.api.nvim_get_hl_id_by_name('@' .. name .. '.' .. lang) end
107+
108+
-- The "priority" attribute can be set at the pattern level or on a particular capture
109+
local priority = (
110+
tonumber(metadata.priority or metadata[capture] and metadata[capture].priority)
111+
or vim.highlight.priorities.treesitter
112+
)
113+
114+
-- The "conceal" attribute can be set at the pattern level or on a particular capture
115+
local conceal = metadata.conceal or metadata[capture] and metadata[capture].conceal
116+
117+
if hl and end_row >= line then
118+
vim.api.nvim_buf_set_extmark(bufnr, require('blink.cmp.config').highlight.ns, start_row, start_col, {
119+
end_line = end_row,
120+
end_col = end_col,
121+
hl_group = hl,
122+
priority = priority,
123+
conceal = conceal,
124+
})
125+
end
126+
end
127+
128+
if start_row > line then line = start_row end
129+
end
130+
end)
131+
end
132+
39133
return utils

lua/blink/cmp/windows/documentation.lua

+26-25
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
local config = require('blink.cmp.config').windows.documentation
2+
local utils = require('blink.cmp.utils')
23
local sources = require('blink.cmp.sources.lib')
34
local autocomplete = require('blink.cmp.windows.autocomplete')
45
local docs = {}
@@ -11,7 +12,6 @@ function docs.setup()
1112
border = config.border,
1213
winhighlight = config.winhighlight,
1314
wrap = true,
14-
filetype = 'markdown',
1515
})
1616

1717
autocomplete.listen_on_position_update(function()
@@ -54,39 +54,40 @@ function docs.show_item(item)
5454
return
5555
end
5656

57-
local doc_lines = {}
58-
if item.detail and item.detail ~= '' then
59-
table.insert(doc_lines, '```' .. vim.bo.filetype)
60-
for s in item.detail:gmatch('[^\r\n]+') do
61-
table.insert(doc_lines, s)
62-
end
63-
table.insert(doc_lines, '```')
64-
table.insert(doc_lines, '---')
65-
end
57+
local detail_lines = {}
58+
if item.detail and item.detail ~= '' then detail_lines = utils.split_lines(item.detail) end
6659

6760
local doc = type(item.documentation) == 'string' and item.documentation or item.documentation.value
68-
for s in doc:gmatch('[^\r\n]+') do
69-
table.insert(doc_lines, s)
61+
local doc_lines = utils.split_lines(doc)
62+
if type(item.documentation) ~= 'string' and item.documentation.kind == 'markdown' then
63+
-- if the rendering seems bugged, it's likely due to this function
64+
doc_lines = utils.combine_markdown_lines(doc_lines)
7065
end
71-
vim.api.nvim_buf_set_lines(docs.win:get_buf(), 0, -1, true, doc_lines)
66+
67+
local combined_lines = vim.list_extend({}, detail_lines)
68+
-- add a blank line for the --- separator
69+
if #detail_lines > 0 then table.insert(combined_lines, '') end
70+
vim.list_extend(combined_lines, doc_lines)
71+
72+
vim.api.nvim_buf_set_lines(docs.win:get_buf(), 0, -1, true, combined_lines)
7273
vim.api.nvim_set_option_value('modified', false, { buf = docs.win:get_buf() })
7374

75+
vim.api.nvim_buf_clear_namespace(docs.win:get_buf(), require('blink.cmp.config').highlight.ns, 0, -1)
76+
if #detail_lines > 0 then
77+
utils.highlight_with_treesitter(docs.win:get_buf(), vim.bo.filetype, 0, #detail_lines)
78+
vim.api.nvim_buf_set_extmark(docs.win:get_buf(), require('blink.cmp.config').highlight.ns, #detail_lines, 0, {
79+
virt_text = { { string.rep('', docs.win.config.max_width) } },
80+
virt_text_pos = 'overlay',
81+
hl_eol = true,
82+
hl_group = 'BlinkCmpDocDetail',
83+
})
84+
end
85+
utils.highlight_with_treesitter(docs.win:get_buf(), 'markdown', #detail_lines + 1, #detail_lines + 1 + #doc_lines)
86+
7487
if autocomplete.win:get_win() then
7588
docs.win:open()
7689
docs.update_position()
7790
end
78-
79-
-- use the built-in markdown styling
80-
-- NOTE: according to https://github.com/Saghen/blink.cmp/pull/33#issuecomment-2400195950
81-
-- it's possible for this to fail so we call it safely
82-
pcall(
83-
function()
84-
vim.lsp.util.stylize_markdown(docs.win:get_buf(), doc_lines, {
85-
height = vim.api.nvim_win_get_height(docs.win:get_win()),
86-
width = vim.api.nvim_win_get_width(docs.win:get_win()),
87-
})
88-
end
89-
)
9091
end)
9192
end
9293

lua/blink/cmp/windows/lib/init.lua

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ function win:open()
6969
col = 1,
7070
focusable = false,
7171
zindex = 1001,
72-
border = self.config.border == 'padded' and { ' ', '', '', '', '', '', ' ', ' ' } or self.config.border,
72+
border = self.config.border == 'padded' and { ' ', '', '', ' ', '', '', ' ', ' ' } or self.config.border,
7373
})
7474
vim.api.nvim_set_option_value('winhighlight', self.config.winhighlight, { win = self.id })
7575
vim.api.nvim_set_option_value('wrap', self.config.wrap, { win = self.id })

0 commit comments

Comments
 (0)