summaryrefslogtreecommitdiff
path: root/lua/blink/cmp/sources/lsp
diff options
context:
space:
mode:
authorMike Vink <mike@pionative.com>2025-01-19 13:52:52 +0100
committerMike Vink <mike@pionative.com>2025-01-19 13:52:52 +0100
commitb77413ff8f59f380612074f0c9bd49093d8db695 (patch)
tree32c39a811ba96ed4ab0a1c81cce9f8d518ed7e31 /lua/blink/cmp/sources/lsp
Squashed 'mut/neovim/pack/plugins/start/blink.cmp/' content from commit 1cc3b1a
git-subtree-dir: mut/neovim/pack/plugins/start/blink.cmp git-subtree-split: 1cc3b1a908fbcfd15451c4772759549724f38524
Diffstat (limited to 'lua/blink/cmp/sources/lsp')
-rw-r--r--lua/blink/cmp/sources/lsp/completion.lua74
-rw-r--r--lua/blink/cmp/sources/lsp/init.lua131
2 files changed, 205 insertions, 0 deletions
diff --git a/lua/blink/cmp/sources/lsp/completion.lua b/lua/blink/cmp/sources/lsp/completion.lua
new file mode 100644
index 0000000..5d54280
--- /dev/null
+++ b/lua/blink/cmp/sources/lsp/completion.lua
@@ -0,0 +1,74 @@
+local async = require('blink.cmp.lib.async')
+local known_defaults = {
+ 'commitCharacters',
+ 'insertTextFormat',
+ 'insertTextMode',
+ 'data',
+}
+local CompletionTriggerKind = vim.lsp.protocol.CompletionTriggerKind
+
+local completion = {}
+
+--- @param context blink.cmp.Context
+--- @param client vim.lsp.Client
+--- @return blink.cmp.Task
+function completion.get_completion_for_client(context, client)
+ return async.task.new(function(resolve)
+ local params = vim.lsp.util.make_position_params(0, client.offset_encoding)
+ params.context = {
+ triggerKind = context.trigger.kind == 'trigger_character' and CompletionTriggerKind.TriggerCharacter
+ or CompletionTriggerKind.Invoked,
+ }
+ if context.trigger.kind == 'trigger_character' then params.context.triggerCharacter = context.trigger.character end
+
+ local _, request_id = client.request('textDocument/completion', params, function(err, result)
+ if err or result == nil then
+ resolve({ is_incomplete_forward = true, is_incomplete_backward = true, items = {} })
+ return
+ end
+
+ local items = result.items or result
+ local default_edit_range = result.itemDefaults and result.itemDefaults.editRange
+ for _, item in ipairs(items) do
+ item.client_id = client.id
+
+ -- score offset for deprecated items
+ -- todo: make configurable
+ if item.deprecated or (item.tags and vim.tbl_contains(item.tags, 1)) then item.score_offset = -2 end
+
+ -- set defaults
+ for key, value in pairs(result.itemDefaults or {}) do
+ if vim.tbl_contains(known_defaults, key) then item[key] = item[key] or value end
+ end
+ if default_edit_range and item.textEdit == nil then
+ local new_text = item.textEditText or item.insertText or item.label
+ if default_edit_range.replace ~= nil then
+ item.textEdit = {
+ replace = default_edit_range.replace,
+ insert = default_edit_range.insert,
+ newText = new_text,
+ }
+ else
+ item.textEdit = {
+ range = result.itemDefaults.editRange,
+ newText = new_text,
+ }
+ end
+ end
+ end
+
+ resolve({
+ is_incomplete_forward = result.isIncomplete or false,
+ is_incomplete_backward = true,
+ items = items,
+ })
+ end)
+
+ -- cancellation function
+ return function()
+ if request_id ~= nil then client.cancel_request(request_id) end
+ end
+ end)
+end
+
+return completion
diff --git a/lua/blink/cmp/sources/lsp/init.lua b/lua/blink/cmp/sources/lsp/init.lua
new file mode 100644
index 0000000..4dbfd8f
--- /dev/null
+++ b/lua/blink/cmp/sources/lsp/init.lua
@@ -0,0 +1,131 @@
+local async = require('blink.cmp.lib.async')
+
+--- @type blink.cmp.Source
+--- @diagnostic disable-next-line: missing-fields
+local lsp = {}
+
+function lsp.new() return setmetatable({}, { __index = lsp }) end
+
+--- Completion ---
+
+function lsp:get_trigger_characters()
+ local clients = vim.lsp.get_clients({ bufnr = 0 })
+ local trigger_characters = {}
+
+ for _, client in pairs(clients) do
+ local completion_provider = client.server_capabilities.completionProvider
+ if completion_provider and completion_provider.triggerCharacters then
+ for _, trigger_character in pairs(completion_provider.triggerCharacters) do
+ table.insert(trigger_characters, trigger_character)
+ end
+ end
+ end
+
+ return trigger_characters
+end
+
+function lsp:get_completions(context, callback)
+ local completion_lib = require('blink.cmp.sources.lsp.completion')
+ local clients = vim.tbl_filter(
+ function(client) return client.server_capabilities and client.server_capabilities.completionProvider end,
+ vim.lsp.get_clients({ bufnr = 0, method = 'textDocument/completion' })
+ )
+
+ -- TODO: implement a timeout before returning the menu as-is. In the future, it would be neat
+ -- to detect slow LSPs and consistently run them async
+ local task = async.task
+ .await_all(vim.tbl_map(function(client) return completion_lib.get_completion_for_client(context, client) end, clients))
+ :map(function(responses)
+ local final = { is_incomplete_forward = false, is_incomplete_backward = false, items = {} }
+ for _, response in ipairs(responses) do
+ final.is_incomplete_forward = final.is_incomplete_forward or response.is_incomplete_forward
+ final.is_incomplete_backward = final.is_incomplete_backward or response.is_incomplete_backward
+ vim.list_extend(final.items, response.items)
+ end
+ callback(final)
+ end)
+ return function() task:cancel() end
+end
+
+--- Resolve ---
+
+function lsp:resolve(item, callback)
+ local client = vim.lsp.get_client_by_id(item.client_id)
+ if client == nil or not client.server_capabilities.completionProvider.resolveProvider then
+ callback(item)
+ return
+ end
+
+ -- strip blink specific fields to avoid decoding errors on some LSPs
+ item = require('blink.cmp.sources.lib.utils').blink_item_to_lsp_item(item)
+
+ local success, request_id = client.request('completionItem/resolve', item, function(error, resolved_item)
+ if error or resolved_item == nil then callback(item) end
+ callback(resolved_item)
+ end)
+ if not success then callback(item) end
+ if request_id ~= nil then
+ return function() client.cancel_request(request_id) end
+ end
+end
+
+--- Signature help ---
+
+function lsp:get_signature_help_trigger_characters()
+ local clients = vim.lsp.get_clients({ bufnr = 0 })
+ local trigger_characters = {}
+ local retrigger_characters = {}
+
+ for _, client in pairs(clients) do
+ local signature_help_provider = client.server_capabilities.signatureHelpProvider
+ if signature_help_provider and signature_help_provider.triggerCharacters then
+ for _, trigger_character in pairs(signature_help_provider.triggerCharacters) do
+ table.insert(trigger_characters, trigger_character)
+ end
+ end
+ if signature_help_provider and signature_help_provider.retriggerCharacters then
+ for _, retrigger_character in pairs(signature_help_provider.retriggerCharacters) do
+ table.insert(retrigger_characters, retrigger_character)
+ end
+ end
+ end
+
+ return { trigger_characters = trigger_characters, retrigger_characters = retrigger_characters }
+end
+
+function lsp:get_signature_help(context, callback)
+ -- no providers with signature help support
+ if #vim.lsp.get_clients({ bufnr = 0, method = 'textDocument/signatureHelp' }) == 0 then
+ callback(nil)
+ return function() end
+ end
+
+ -- TODO: offset encoding is global but should be per-client
+ local first_client = vim.lsp.get_clients({ bufnr = 0 })[1]
+ local offset_encoding = first_client and first_client.offset_encoding or 'utf-16'
+
+ local params = vim.lsp.util.make_position_params(nil, offset_encoding)
+ params.context = {
+ triggerKind = context.trigger.kind,
+ triggerCharacter = context.trigger.character,
+ isRetrigger = context.is_retrigger,
+ activeSignatureHelp = context.active_signature_help,
+ }
+
+ -- otherwise, we call all clients
+ -- TODO: some LSPs never response (typescript-tools.nvim)
+ return vim.lsp.buf_request_all(0, 'textDocument/signatureHelp', params, function(result)
+ local signature_helps = {}
+ for client_id, res in pairs(result) do
+ local signature_help = res.result
+ if signature_help ~= nil then
+ signature_help.client_id = client_id
+ table.insert(signature_helps, signature_help)
+ end
+ end
+ -- todo: pick intelligently
+ callback(signature_helps[1])
+ end)
+end
+
+return lsp