Skip to content

Commit

Permalink
feat: support LSPs with only full semantic tokens and cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
Saghen committed Oct 24, 2024
1 parent c218faf commit 0626cb5
Show file tree
Hide file tree
Showing 6 changed files with 249 additions and 194 deletions.
194 changes: 0 additions & 194 deletions lua/blink/cmp/accept/brackets.lua

This file was deleted.

32 changes: 32 additions & 0 deletions lua/blink/cmp/accept/brackets/config.lua
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 = { '[', ']' },
},
}
6 changes: 6 additions & 0 deletions lua/blink/cmp/accept/brackets/init.lua
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
50 changes: 50 additions & 0 deletions lua/blink/cmp/accept/brackets/kind.lua
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
108 changes: 108 additions & 0 deletions lua/blink/cmp/accept/brackets/semantic.lua
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
Loading

0 comments on commit 0626cb5

Please sign in to comment.