-
Notifications
You must be signed in to change notification settings - Fork 201
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: support LSPs with only full semantic tokens and cleanup
- Loading branch information
Showing
6 changed files
with
249 additions
and
194 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
return { | ||
-- stylua: ignore | ||
blocked_filetypes = { | ||
'rust', 'sql', 'ruby', 'perl', 'lisp', 'scheme', 'clojure', | ||
'prolog', 'vb', 'elixir', 'smalltalk', 'applescript' | ||
}, | ||
per_filetype = { | ||
-- languages with a space | ||
haskell = { ' ', '' }, | ||
fsharp = { ' ', '' }, | ||
ocaml = { ' ', '' }, | ||
erlang = { ' ', '' }, | ||
tcl = { ' ', '' }, | ||
nix = { ' ', '' }, | ||
helm = { ' ', '' }, | ||
|
||
shell = { ' ', '' }, | ||
sh = { ' ', '' }, | ||
bash = { ' ', '' }, | ||
fish = { ' ', '' }, | ||
zsh = { ' ', '' }, | ||
powershell = { ' ', '' }, | ||
|
||
make = { ' ', '' }, | ||
|
||
-- languages with square brackets | ||
wl = { '[', ']' }, | ||
wolfram = { '[', ']' }, | ||
mma = { '[', ']' }, | ||
mathematica = { '[', ']' }, | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
local brackets = {} | ||
|
||
brackets.add_brackets = require('blink.cmp.accept.brackets.kind') | ||
brackets.add_brackets_via_semantic_token = require('blink.cmp.accept.brackets.semantic') | ||
|
||
return brackets |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
local config = require('blink.cmp.config').accept.auto_brackets | ||
local utils = require('blink.cmp.accept.brackets.utils') | ||
|
||
--- @param filetype string | ||
--- @param item blink.cmp.CompletionItem | ||
--- @return 'added' | 'check_semantic_token' | 'skipped', lsp.TextEdit | lsp.InsertReplaceEdit, number | ||
local function add_brackets(filetype, item) | ||
local text_edit = item.textEdit | ||
assert(text_edit ~= nil, 'Got nil text edit while adding brackets via kind') | ||
local brackets_for_filetype = utils.get_for_filetype(filetype, item) | ||
|
||
-- if there's already the correct brackets in front, skip but indicate the cursor should move in front of the bracket | ||
-- TODO: what if the brackets_for_filetype[1] == '' or ' ' (haskell/ocaml)? | ||
if utils.has_brackets_in_front(text_edit, brackets_for_filetype[1]) then | ||
return 'skipped', text_edit, #brackets_for_filetype[1] | ||
end | ||
|
||
-- if the item already contains the brackets, conservatively skip adding brackets | ||
-- todo: won't work for snippets when the brackets_for_filetype is { '{', '}' } | ||
-- I've never seen a language like that though | ||
if brackets_for_filetype[1] ~= ' ' and text_edit.newText:match('[\\' .. brackets_for_filetype[1] .. ']') ~= nil then | ||
return 'skipped', text_edit, 0 | ||
end | ||
|
||
-- check if configuration incidates we should skip | ||
if not utils.should_run_resolution(filetype, 'kind') then return 'check_semantic_token', text_edit, 0 end | ||
-- not a function, skip | ||
local CompletionItemKind = require('blink.cmp.types').CompletionItemKind | ||
if item.kind ~= CompletionItemKind.Function and item.kind ~= CompletionItemKind.Method then | ||
return 'check_semantic_token', text_edit, 0 | ||
end | ||
|
||
text_edit = vim.deepcopy(text_edit) | ||
-- For snippets, we add the cursor position between the brackets as the last placeholder | ||
if item.insertTextFormat == vim.lsp.protocol.InsertTextFormat.Snippet then | ||
local placeholders = utils.snippets_extract_placeholders(text_edit.newText) | ||
local last_placeholder_index = math.max(0, unpack(placeholders)) | ||
text_edit.newText = text_edit.newText | ||
.. brackets_for_filetype[1] | ||
.. '$' | ||
.. tostring(last_placeholder_index + 1) | ||
.. brackets_for_filetype[2] | ||
-- Otherwise, we add as usual | ||
else | ||
text_edit.newText = text_edit.newText .. brackets_for_filetype[1] .. brackets_for_filetype[2] | ||
end | ||
return 'added', text_edit, -#brackets_for_filetype[2] | ||
end | ||
|
||
return add_brackets |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
local config = require('blink.cmp.config').accept.auto_brackets | ||
local utils = require('blink.cmp.accept.brackets.utils') | ||
|
||
local semantic = {} | ||
|
||
--- Asynchronously use semantic tokens to determine if brackets should be added | ||
--- @param filetype string | ||
--- @param item blink.cmp.CompletionItem | ||
--- @param callback fun() | ||
function semantic.add_brackets_via_semantic_token(filetype, item, callback) | ||
if not utils.should_run_resolution(filetype, 'semantic_token') then return callback() end | ||
|
||
local text_edit = item.textEdit | ||
assert(text_edit ~= nil, 'Got nil text edit while adding brackets via semantic tokens') | ||
local client = vim.lsp.get_client_by_id(item.client_id) | ||
if client == nil then return callback() end | ||
|
||
local capabilities = client.server_capabilities.semanticTokensProvider | ||
if not capabilities or not capabilities.legend or (not capabilities.range and not capabilities.full) then | ||
return callback() | ||
end | ||
|
||
local token_types = client.server_capabilities.semanticTokensProvider.legend.tokenTypes | ||
local params = { | ||
textDocument = vim.lsp.util.make_text_document_params(), | ||
range = capabilities.range and { | ||
start = { line = text_edit.range.start.line, character = text_edit.range.start.character }, | ||
['end'] = { line = text_edit.range.start.line + 1, character = 0 }, | ||
} or nil, | ||
} | ||
|
||
local cursor_before_call = vim.api.nvim_win_get_cursor(0) | ||
|
||
local start_time = vim.uv.hrtime() | ||
client.request( | ||
capabilities.range and 'textDocument/semanticTokens/range' or 'textDocument/semanticTokens/full', | ||
params, | ||
function(err, result) | ||
if err ~= nil or result == nil or #result.data == 0 then return callback() end | ||
|
||
-- cancel if it's been too long, or if the cursor moved | ||
local ms_since_call = (vim.uv.hrtime() - start_time) / 1000000 | ||
local cursor_after_call = vim.api.nvim_win_get_cursor(0) | ||
if | ||
ms_since_call > config.semantic_token_resolution.timeout_ms | ||
or cursor_before_call[1] ~= cursor_after_call[1] | ||
or cursor_before_call[2] ~= cursor_after_call[2] | ||
then | ||
return callback() | ||
end | ||
|
||
for _, token in ipairs(semantic.process_semantic_token_data(result.data, token_types)) do | ||
if | ||
cursor_after_call[1] == token.line | ||
and cursor_after_call[2] >= token.start_col | ||
and cursor_after_call[2] <= token.end_col | ||
then | ||
-- add the brackets | ||
local brackets_for_filetype = utils.get_for_filetype(filetype, item) | ||
local line = vim.api.nvim_get_current_line() | ||
local start_col = text_edit.range.start.character + #text_edit.newText | ||
local new_line = line:sub(1, start_col) | ||
.. brackets_for_filetype[1] | ||
.. brackets_for_filetype[2] | ||
.. line:sub(start_col + 1) | ||
vim.api.nvim_set_current_line(new_line) | ||
vim.api.nvim_win_set_cursor(0, { cursor_after_call[1], start_col + #brackets_for_filetype[1] }) | ||
callback() | ||
return | ||
end | ||
end | ||
|
||
callback() | ||
end | ||
) | ||
end | ||
|
||
function semantic.process_semantic_token_data(data, token_types) | ||
local tokens = {} | ||
local idx = 0 | ||
local token_line = 0 | ||
local token_start_col = 0 | ||
|
||
while (idx + 1) * 5 <= #data do | ||
local delta_token_line = data[idx * 5 + 1] | ||
local delta_token_start_col = data[idx * 5 + 2] | ||
local delta_token_length = data[idx * 5 + 3] | ||
local type = token_types[data[idx * 5 + 4] + 1] | ||
|
||
if delta_token_line > 0 then token_start_col = 0 end | ||
token_line = token_line + delta_token_line | ||
token_start_col = token_start_col + delta_token_start_col | ||
|
||
table.insert(tokens, { | ||
line = token_line + 1, | ||
start_col = token_start_col, | ||
end_col = token_start_col + delta_token_length, | ||
type = type, | ||
}) | ||
|
||
token_start_col = token_start_col + delta_token_length | ||
idx = idx + 1 | ||
end | ||
|
||
return tokens | ||
end | ||
|
||
return semantic.add_brackets_via_semantic_token |
Oops, something went wrong.