diff --git a/lua/blink/cmp/sources/lsp/init.lua b/lua/blink/cmp/sources/lsp/init.lua index bc470e39e..9908dbc23 100644 --- a/lua/blink/cmp/sources/lsp/init.lua +++ b/lua/blink/cmp/sources/lsp/init.lua @@ -26,15 +26,29 @@ end function lsp:get_completions(context, callback) local completion_lib = require('blink.cmp.sources.lsp.completion') + local inlinecompletion_lib = require('blink.cmp.sources.lsp.inlinecompletion') 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' }) ) + local inline_clients = vim.tbl_filter( + function(client) return client.server_capabilities and client.server_capabilities.inlineCompletionProvider end, + vim.lsp.get_clients({ bufnr = 0, method = 'textDocument/inlineCompletion' }) + ) + -- 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)) + .await_all( + vim.list_extend( + vim.tbl_map(function(client) return completion_lib.get_completion_for_client(context, client) end, clients), + vim.tbl_map( + function(client) return inlinecompletion_lib.get_inlinecompletion_for_client(context, client) end, + inline_clients + ) + ) + ) :map(function(responses) local final = { is_incomplete_forward = false, is_incomplete_backward = false, items = {} } for _, response in ipairs(responses) do @@ -58,7 +72,9 @@ end 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 + local resolveProvider = client.server_capabilities.completionProvider + and client.server_capabilities.completionProvider.resolveProvider + if client == nil or not resolveProvider or item.inline then callback(item) return end diff --git a/lua/blink/cmp/sources/lsp/inlinecompletion.lua b/lua/blink/cmp/sources/lsp/inlinecompletion.lua new file mode 100644 index 000000000..8c4b5fca8 --- /dev/null +++ b/lua/blink/cmp/sources/lsp/inlinecompletion.lua @@ -0,0 +1,63 @@ +local async = require('blink.cmp.lib.async') + +-- How a completion was triggered +local InlineCompletionTriggerKind = { + Invoked = 1, + Automatic = 2, +} + +local inline_completion = {} + +--- @param context blink.cmp.Context +--- @param client vim.lsp.Client +--- @return blink.cmp.Task +function inline_completion.get_inlinecompletion_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 == 'Invoked' and InlineCompletionTriggerKind.Invoked + or InlineCompletionTriggerKind.Automatic, + } + + params.formattingOptions = { + tabSize = vim.fn.shiftwidth(), + insertSpaces = vim.o.expandtab, + } + + local _, request_id = client.request('textDocument/inlineCompletion', 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 + for _, item in ipairs(items) do + item.client_id = client.id + item.client_name = client.name + item.documentation = item.insertText + item.label = item.insertText + item.kind = require('blink.cmp.types').CompletionItemKind.Snippet + item.inline = true + + -- convert to traditional TextEdit + item.textEdit = { + newText = item.insertText, + range = item.range, + } + end + + resolve({ + is_incomplete_forward = false, + is_incomplete_backward = false, + items = items, + }) + end) + + -- cancellation function + return function() + if request_id ~= nil then client.cancel_request(request_id) end + end + end) +end + +return inline_completion