Skip to content

Commit 0626cb5

Browse files
committed
feat: support LSPs with only full semantic tokens and cleanup
1 parent c218faf commit 0626cb5

File tree

6 files changed

+249
-194
lines changed

6 files changed

+249
-194
lines changed

lua/blink/cmp/accept/brackets.lua

-194
This file was deleted.
+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
return {
2+
-- stylua: ignore
3+
blocked_filetypes = {
4+
'rust', 'sql', 'ruby', 'perl', 'lisp', 'scheme', 'clojure',
5+
'prolog', 'vb', 'elixir', 'smalltalk', 'applescript'
6+
},
7+
per_filetype = {
8+
-- languages with a space
9+
haskell = { ' ', '' },
10+
fsharp = { ' ', '' },
11+
ocaml = { ' ', '' },
12+
erlang = { ' ', '' },
13+
tcl = { ' ', '' },
14+
nix = { ' ', '' },
15+
helm = { ' ', '' },
16+
17+
shell = { ' ', '' },
18+
sh = { ' ', '' },
19+
bash = { ' ', '' },
20+
fish = { ' ', '' },
21+
zsh = { ' ', '' },
22+
powershell = { ' ', '' },
23+
24+
make = { ' ', '' },
25+
26+
-- languages with square brackets
27+
wl = { '[', ']' },
28+
wolfram = { '[', ']' },
29+
mma = { '[', ']' },
30+
mathematica = { '[', ']' },
31+
},
32+
}
+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
local brackets = {}
2+
3+
brackets.add_brackets = require('blink.cmp.accept.brackets.kind')
4+
brackets.add_brackets_via_semantic_token = require('blink.cmp.accept.brackets.semantic')
5+
6+
return brackets
+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
local config = require('blink.cmp.config').accept.auto_brackets
2+
local utils = require('blink.cmp.accept.brackets.utils')
3+
4+
--- @param filetype string
5+
--- @param item blink.cmp.CompletionItem
6+
--- @return 'added' | 'check_semantic_token' | 'skipped', lsp.TextEdit | lsp.InsertReplaceEdit, number
7+
local function add_brackets(filetype, item)
8+
local text_edit = item.textEdit
9+
assert(text_edit ~= nil, 'Got nil text edit while adding brackets via kind')
10+
local brackets_for_filetype = utils.get_for_filetype(filetype, item)
11+
12+
-- if there's already the correct brackets in front, skip but indicate the cursor should move in front of the bracket
13+
-- TODO: what if the brackets_for_filetype[1] == '' or ' ' (haskell/ocaml)?
14+
if utils.has_brackets_in_front(text_edit, brackets_for_filetype[1]) then
15+
return 'skipped', text_edit, #brackets_for_filetype[1]
16+
end
17+
18+
-- if the item already contains the brackets, conservatively skip adding brackets
19+
-- todo: won't work for snippets when the brackets_for_filetype is { '{', '}' }
20+
-- I've never seen a language like that though
21+
if brackets_for_filetype[1] ~= ' ' and text_edit.newText:match('[\\' .. brackets_for_filetype[1] .. ']') ~= nil then
22+
return 'skipped', text_edit, 0
23+
end
24+
25+
-- check if configuration incidates we should skip
26+
if not utils.should_run_resolution(filetype, 'kind') then return 'check_semantic_token', text_edit, 0 end
27+
-- not a function, skip
28+
local CompletionItemKind = require('blink.cmp.types').CompletionItemKind
29+
if item.kind ~= CompletionItemKind.Function and item.kind ~= CompletionItemKind.Method then
30+
return 'check_semantic_token', text_edit, 0
31+
end
32+
33+
text_edit = vim.deepcopy(text_edit)
34+
-- For snippets, we add the cursor position between the brackets as the last placeholder
35+
if item.insertTextFormat == vim.lsp.protocol.InsertTextFormat.Snippet then
36+
local placeholders = utils.snippets_extract_placeholders(text_edit.newText)
37+
local last_placeholder_index = math.max(0, unpack(placeholders))
38+
text_edit.newText = text_edit.newText
39+
.. brackets_for_filetype[1]
40+
.. '$'
41+
.. tostring(last_placeholder_index + 1)
42+
.. brackets_for_filetype[2]
43+
-- Otherwise, we add as usual
44+
else
45+
text_edit.newText = text_edit.newText .. brackets_for_filetype[1] .. brackets_for_filetype[2]
46+
end
47+
return 'added', text_edit, -#brackets_for_filetype[2]
48+
end
49+
50+
return add_brackets
+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
local config = require('blink.cmp.config').accept.auto_brackets
2+
local utils = require('blink.cmp.accept.brackets.utils')
3+
4+
local semantic = {}
5+
6+
--- Asynchronously use semantic tokens to determine if brackets should be added
7+
--- @param filetype string
8+
--- @param item blink.cmp.CompletionItem
9+
--- @param callback fun()
10+
function semantic.add_brackets_via_semantic_token(filetype, item, callback)
11+
if not utils.should_run_resolution(filetype, 'semantic_token') then return callback() end
12+
13+
local text_edit = item.textEdit
14+
assert(text_edit ~= nil, 'Got nil text edit while adding brackets via semantic tokens')
15+
local client = vim.lsp.get_client_by_id(item.client_id)
16+
if client == nil then return callback() end
17+
18+
local capabilities = client.server_capabilities.semanticTokensProvider
19+
if not capabilities or not capabilities.legend or (not capabilities.range and not capabilities.full) then
20+
return callback()
21+
end
22+
23+
local token_types = client.server_capabilities.semanticTokensProvider.legend.tokenTypes
24+
local params = {
25+
textDocument = vim.lsp.util.make_text_document_params(),
26+
range = capabilities.range and {
27+
start = { line = text_edit.range.start.line, character = text_edit.range.start.character },
28+
['end'] = { line = text_edit.range.start.line + 1, character = 0 },
29+
} or nil,
30+
}
31+
32+
local cursor_before_call = vim.api.nvim_win_get_cursor(0)
33+
34+
local start_time = vim.uv.hrtime()
35+
client.request(
36+
capabilities.range and 'textDocument/semanticTokens/range' or 'textDocument/semanticTokens/full',
37+
params,
38+
function(err, result)
39+
if err ~= nil or result == nil or #result.data == 0 then return callback() end
40+
41+
-- cancel if it's been too long, or if the cursor moved
42+
local ms_since_call = (vim.uv.hrtime() - start_time) / 1000000
43+
local cursor_after_call = vim.api.nvim_win_get_cursor(0)
44+
if
45+
ms_since_call > config.semantic_token_resolution.timeout_ms
46+
or cursor_before_call[1] ~= cursor_after_call[1]
47+
or cursor_before_call[2] ~= cursor_after_call[2]
48+
then
49+
return callback()
50+
end
51+
52+
for _, token in ipairs(semantic.process_semantic_token_data(result.data, token_types)) do
53+
if
54+
cursor_after_call[1] == token.line
55+
and cursor_after_call[2] >= token.start_col
56+
and cursor_after_call[2] <= token.end_col
57+
then
58+
-- add the brackets
59+
local brackets_for_filetype = utils.get_for_filetype(filetype, item)
60+
local line = vim.api.nvim_get_current_line()
61+
local start_col = text_edit.range.start.character + #text_edit.newText
62+
local new_line = line:sub(1, start_col)
63+
.. brackets_for_filetype[1]
64+
.. brackets_for_filetype[2]
65+
.. line:sub(start_col + 1)
66+
vim.api.nvim_set_current_line(new_line)
67+
vim.api.nvim_win_set_cursor(0, { cursor_after_call[1], start_col + #brackets_for_filetype[1] })
68+
callback()
69+
return
70+
end
71+
end
72+
73+
callback()
74+
end
75+
)
76+
end
77+
78+
function semantic.process_semantic_token_data(data, token_types)
79+
local tokens = {}
80+
local idx = 0
81+
local token_line = 0
82+
local token_start_col = 0
83+
84+
while (idx + 1) * 5 <= #data do
85+
local delta_token_line = data[idx * 5 + 1]
86+
local delta_token_start_col = data[idx * 5 + 2]
87+
local delta_token_length = data[idx * 5 + 3]
88+
local type = token_types[data[idx * 5 + 4] + 1]
89+
90+
if delta_token_line > 0 then token_start_col = 0 end
91+
token_line = token_line + delta_token_line
92+
token_start_col = token_start_col + delta_token_start_col
93+
94+
table.insert(tokens, {
95+
line = token_line + 1,
96+
start_col = token_start_col,
97+
end_col = token_start_col + delta_token_length,
98+
type = type,
99+
})
100+
101+
token_start_col = token_start_col + delta_token_length
102+
idx = idx + 1
103+
end
104+
105+
return tokens
106+
end
107+
108+
return semantic.add_brackets_via_semantic_token

0 commit comments

Comments
 (0)