Skip to content

Commit

Permalink
Add support for treesitter completion for non-LSP supported files
Browse files Browse the repository at this point in the history
  • Loading branch information
deathbeam committed Feb 22, 2024
1 parent 6bcd764 commit 6bc1ed1
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 84 deletions.
11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# autocomplete.nvim
Very simple and minimal autocompletion for cmdline and LSP with signature help.
Very simple and minimal autocompletion for cmdline and buffer using LSP and treesitter with signature help.

Originally I made this just for my dotfiles as I did not needed most of stuff existing plugins provided I had
some issues with the ones that were close to what I wanted so as a learning exercise I decided to try and
Expand All @@ -23,7 +23,7 @@ Read the documentation of whatever you want to use.

## Usage

Just require either lsp or cmd module or both and call setup on them.
Just require either buffer or cmd module or both and call setup on them.
**NOTE**: You dont need to provide the configuration, below is just default config, you can just
call setup with no arguments for default.

Expand All @@ -36,9 +36,8 @@ require("autocomplete.signature").setup {
debounce_delay = 100
}

-- LSP autocompletion
require("autocomplete.lsp").setup {
server_side_filtering = true, -- Use LSP filtering instead of vim's
-- buffer autocompletion with LSP and treesitter
require("autocomplete.buffer").setup {
entry_mapper = nil, -- Custom completion entry mapper
debounce_delay = 100
}
Expand Down Expand Up @@ -82,7 +81,7 @@ when resolving completion items:
-- Here we grab default Neovim capabilities and extend them with ones we want on top
local capabilities = vim.tbl_deep_extend('force',
vim.lsp.protocol.make_client_capabilities(),
require('completion.lsp').capabilities)
require('autocomplete.buffer').capabilities)

-- Now set capabilities on your LSP servers
require('lspconfig')['<YOUR_LSP_SERVER>'].setup {
Expand Down
202 changes: 140 additions & 62 deletions lua/autocomplete/lsp.lua → lua/autocomplete/buffer.lua
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,113 @@ local state = {
},
}

local function complete_done(client, bufnr)
local function complete_treesitter(bufnr, cmp_start)
local items = {}
local ok, locals = pcall(require, 'nvim-treesitter.locals')
if not ok then
return items
end

local defs = locals.get_definitions(bufnr)

for _, def in ipairs(defs) do
local node
local kind
for k, cap in pairs(def) do
if k ~= 'associated' then
node = cap.node
kind = k
break
end
end

local lsp_kind
for _, k in ipairs(vim.lsp.protocol.CompletionItemKind) do
vim.print(k)
if k:lower() == kind:lower() then
lsp_kind = k
break
end
end

if not lsp_kind then
for _, k in ipairs(vim.lsp.protocol.CompletionItemKind) do
if string.find(k:lower(), kind:lower()) then
lsp_kind = k
break
end
end
end

if not lsp_kind then
lsp_kind = 'Unknown'
end

if node then
items[#items + 1] = {
word = vim.treesitter.get_node_text(node, 0),
kind = lsp_kind,
icase = 1,
dup = 1,
empty = 1,
}
end
end

if M.config.entry_mapper then
items = vim.tbl_map(M.config.entry_mapper, items)
end

if vim.fn.mode() == 'i' then
vim.fn.complete(cmp_start + 1, items)
end
end

local function complete_lsp(bufnr, cmp_start, client, char)
local context = {
triggerKind = vim.lsp.protocol.CompletionTriggerKind.Invoked,
triggerCharacter = '',
}

-- Check if we are triggering completion automatically or on trigger character
if
vim.tbl_contains(
client.server_capabilities.completionProvider.triggerCharacters or {},
char
)
then
context = {
triggerKind = vim.lsp.protocol.CompletionTriggerKind.TriggerCharacter,
triggerCharacter = char,
}
else
-- We do not want to trigger completion again if we just accepted a completion
-- We check it here because trigger characters call complete done
if state.skip_next then
state.skip_next = false
return
end
end

local params =
vim.lsp.util.make_position_params(vim.api.nvim_get_current_win(), client.offset_encoding)
params.context = context
return util.request(client, methods.textDocument_completion, params, function(result)
local items = vim.lsp._completion._lsp_to_complete_items(result, '')
items = vim.tbl_filter(function(item)
return item.kind ~= 'Snippet'
end, items)
if M.config.entry_mapper then
items = vim.tbl_map(M.config.entry_mapper, items)
end

if vim.fn.mode() == 'i' then
vim.fn.complete(cmp_start + 1, items)
end
end, bufnr)
end

local function complete_done(args)
if
not vim.v
or not vim.v.completed_item
Expand All @@ -24,6 +130,11 @@ local function complete_done(client, bufnr)
return
end

local client = util.get_client(args.buf, methods.completionItem_resolve)
if not client then
return
end

local item = vim.v.completed_item.user_data.nvim.lsp.completion_item

if vim.tbl_isempty(item.additionalTextEdits or {}) then
Expand All @@ -35,19 +146,19 @@ local function complete_done(client, bufnr)

vim.lsp.util.apply_text_edits(
result.additionalTextEdits,
bufnr,
args.buf,
client.offset_encoding
)
end, bufnr)
end, args.buf)
end)
else
vim.lsp.util.apply_text_edits(item.additionalTextEdits, bufnr, client.offset_encoding)
vim.lsp.util.apply_text_edits(item.additionalTextEdits, args.buf, client.offset_encoding)
end

state.skip_next = true
end

local function complete_changed(client, bufnr)
local function complete_changed(args)
if
not vim.v.event
or not vim.v.event.completed_item
Expand All @@ -59,6 +170,11 @@ local function complete_changed(client, bufnr)
return
end

local client = util.get_client(args.buf, methods.completionItem_resolve)
if not client then
return
end

local item = vim.v.event.completed_item.user_data.nvim.lsp.completion_item
local data = vim.fn.complete_info()
local selected = data.selected
Expand All @@ -85,11 +201,11 @@ local function complete_changed(client, bufnr)
vim.bo[wininfo.bufnr].syntax = 'markdown'
end
end
end, bufnr)
end, args.buf)
end)
end

local function text_changed(client, bufnr)
local function text_changed(args)
if vim.fn.pumvisible() == 1 then
state.skip_next = false
return
Expand All @@ -101,59 +217,19 @@ local function text_changed(client, bufnr)
return
end

local char = line:sub(col, col)
local prefix, cmp_start = unpack(vim.fn.matchstrpos(line:sub(1, col), '\\k*$'))
prefix = M.config.server_side_filtering and '' or prefix

local context = {
triggerKind = vim.lsp.protocol.CompletionTriggerKind.Invoked,
triggerCharacter = '',
}

-- Check if we are triggering completion automatically or on trigger character
if
vim.tbl_contains(
client.server_capabilities.completionProvider.triggerCharacters or {},
char
)
then
context = {
triggerKind = vim.lsp.protocol.CompletionTriggerKind.TriggerCharacter,
triggerCharacter = char,
}
else
-- We do not want to trigger completion again if we just accepted a completion
-- We check it here because trigger characters call complete done
if state.skip_next then
state.skip_next = false
return
end
end
local cmp_start = vim.fn.match(line:sub(1, col), '\\k*$')

util.debounce(state.entries.completion, M.config.debounce_delay, function()
local params = vim.lsp.util.make_position_params(
vim.api.nvim_get_current_win(),
client.offset_encoding
)
params.context = context
return util.request(client, methods.textDocument_completion, params, function(result)
local items = vim.lsp._completion._lsp_to_complete_items(result, prefix)
items = vim.tbl_filter(function(item)
return item.kind ~= 'Snippet'
end, items)
if M.config.entry_mapper then
items = vim.tbl_map(M.config.entry_mapper, items)
end

if vim.fn.mode() == 'i' then
vim.fn.complete(cmp_start + 1, items)
end
end, bufnr)
local client = util.get_client(args.buf, methods.textDocument_completion)
if client then
complete_lsp(args.buf, cmp_start, client, line:sub(col, col))
else
complete_treesitter(args.buf, cmp_start)
end
end)
end

M.config = {
server_side_filtering = true, -- Use LSP filtering instead of vim's
entry_mapper = nil, -- Custom completion entry mapper
debounce_delay = 100,
}
Expand Down Expand Up @@ -184,22 +260,24 @@ function M.setup(config)
local group = vim.api.nvim_create_augroup('LspCompletion', {})

vim.api.nvim_create_autocmd('TextChangedI', {
desc = 'Auto show LSP completion',
desc = 'Auto show completion',
group = group,
callback = util.with_client(text_changed, methods.textDocument_completion),
callback = text_changed,
})

vim.api.nvim_create_autocmd('CompleteDone', {
desc = 'Auto apply LSP completion edits after selection',
group = group,
callback = util.with_client(complete_done, methods.textDocument_completion),
callback = complete_done,
})

vim.api.nvim_create_autocmd('CompleteChanged', {
desc = 'Auto update LSP completion info',
group = group,
callback = util.with_client(complete_changed, methods.textDocument_completion),
})
if string.find(vim.o.completeopt, 'popup') then
vim.api.nvim_create_autocmd('CompleteChanged', {
desc = 'Auto show LSP documentation',
group = group,
callback = complete_changed,
})
end
end

return M
13 changes: 9 additions & 4 deletions lua/autocomplete/signature.lua
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,18 @@ local function signature_handler(client, result, bufnr)
end
end

local function cursor_moved(client, bufnr)
local function cursor_moved(args)
local line = vim.api.nvim_get_current_line()
local col = vim.api.nvim_win_get_cursor(0)[2]
if col == 0 or #line == 0 then
return
end

local client = util.get_client(args.buf, methods.textDocument_signatureHelp)
if not client then
return
end

local before_line = line:sub(1, col)

-- Try to find signature help trigger character in current line
Expand All @@ -61,9 +66,9 @@ local function cursor_moved(client, bufnr)
methods.textDocument_signatureHelp,
params,
function(result)
signature_handler(client, result, bufnr)
signature_handler(client, result, args.buf)
end,
bufnr
args.buf
)
end)

Expand All @@ -88,7 +93,7 @@ function M.setup(config)
vim.api.nvim_create_autocmd('CursorMovedI', {
desc = 'Auto show LSP signature help',
group = group,
callback = util.with_client(cursor_moved, methods.textDocument_signatureHelp),
callback = cursor_moved,
})
end

Expand Down
18 changes: 6 additions & 12 deletions lua/autocomplete/util.lua
Original file line number Diff line number Diff line change
Expand Up @@ -60,19 +60,13 @@ function M.request(client, method, params, handler, bufnr)
end
end

function M.with_client(callback, method)
return function(args)
local bufnr = args.buf
local clients = vim.lsp.get_clients({ bufnr = bufnr, method = method })
if vim.tbl_isempty(clients) then
return
end

local client = clients[1]
if client then
callback(client, bufnr)
end
function M.get_client(bufnr, method)
local clients = vim.lsp.get_clients({ bufnr = bufnr, method = method })
if vim.tbl_isempty(clients) then
return
end

return clients[1]
end

return M

0 comments on commit 6bc1ed1

Please sign in to comment.