Files
LazyVim/lua/lazyvim/util/cmp.lua
Folke Lemaitre ad52bf91bc feat(extras): blink (#4680)
## Description

Extra to use [blink.cmp](https://github.com/Saghen/blink.cmp) instead of
**nvim-cmp**.

## Todo

- [x] tokyonight suport
- [x] basic integration
- [ ] check / update all cmp sources
- [ ] copilot and others integration 
- [x] native lazydev source

## Limitations

There's no copilot source, so instead when enabling both blink and
copilot:
- blink ghost text is disabled
- copilot suggestions are enabled
- use `<tab>` to navigate snippets and accept copilot completions 

## Related Issue(s)

- https://github.com/LazyVim/LazyVim/discussions/4679
2024-11-02 09:54:55 +01:00

168 lines
5.1 KiB
Lua

---@class lazyvim.util.cmp
local M = {}
---@alias Placeholder {n:number, text:string}
---@param snippet string
---@param fn fun(placeholder:Placeholder):string
---@return string
function M.snippet_replace(snippet, fn)
return snippet:gsub("%$%b{}", function(m)
local n, name = m:match("^%${(%d+):(.+)}$")
return n and fn({ n = n, text = name }) or m
end) or snippet
end
-- This function resolves nested placeholders in a snippet.
---@param snippet string
---@return string
function M.snippet_preview(snippet)
local ok, parsed = pcall(function()
return vim.lsp._snippet_grammar.parse(snippet)
end)
return ok and tostring(parsed)
or M.snippet_replace(snippet, function(placeholder)
return M.snippet_preview(placeholder.text)
end):gsub("%$0", "")
end
-- This function replaces nested placeholders in a snippet with LSP placeholders.
function M.snippet_fix(snippet)
local texts = {} ---@type table<number, string>
return M.snippet_replace(snippet, function(placeholder)
texts[placeholder.n] = texts[placeholder.n] or M.snippet_preview(placeholder.text)
return "${" .. placeholder.n .. ":" .. texts[placeholder.n] .. "}"
end)
end
---@param entry cmp.Entry
function M.auto_brackets(entry)
local cmp = require("cmp")
local Kind = cmp.lsp.CompletionItemKind
local item = entry:get_completion_item()
if vim.tbl_contains({ Kind.Function, Kind.Method }, item.kind) then
local cursor = vim.api.nvim_win_get_cursor(0)
local prev_char = vim.api.nvim_buf_get_text(0, cursor[1] - 1, cursor[2], cursor[1] - 1, cursor[2] + 1, {})[1]
if prev_char ~= "(" and prev_char ~= ")" then
local keys = vim.api.nvim_replace_termcodes("()<left>", false, false, true)
vim.api.nvim_feedkeys(keys, "i", true)
end
end
end
-- This function adds missing documentation to snippets.
-- The documentation is a preview of the snippet.
---@param window cmp.CustomEntriesView|cmp.NativeEntriesView
function M.add_missing_snippet_docs(window)
local cmp = require("cmp")
local Kind = cmp.lsp.CompletionItemKind
local entries = window:get_entries()
for _, entry in ipairs(entries) do
if entry:get_kind() == Kind.Snippet then
local item = entry:get_completion_item()
if not item.documentation and item.insertText then
item.documentation = {
kind = cmp.lsp.MarkupKind.Markdown,
value = string.format("```%s\n%s\n```", vim.bo.filetype, M.snippet_preview(item.insertText)),
}
end
end
end
end
function M.visible()
---@module 'blink.cmp'
local blink = package.loaded["blink.cmp"]
if blink then
return blink.windows and blink.windows.autocomplete.win:is_open()
end
---@module 'cmp'
local cmp = package.loaded["cmp"]
if cmp then
return cmp.core.view:visible()
end
return false
end
-- This is a better implementation of `cmp.confirm`:
-- * check if the completion menu is visible without waiting for running sources
-- * create an undo point before confirming
-- This function is both faster and more reliable.
---@param opts? {select: boolean, behavior: cmp.ConfirmBehavior}
function M.confirm(opts)
local cmp = require("cmp")
opts = vim.tbl_extend("force", {
select = true,
behavior = cmp.ConfirmBehavior.Insert,
}, opts or {})
return function(fallback)
if cmp.core.view:visible() or vim.fn.pumvisible() == 1 then
LazyVim.create_undo()
if cmp.confirm(opts) then
return
end
end
return fallback()
end
end
function M.expand(snippet)
-- Native sessions don't support nested snippet sessions.
-- Always use the top-level session.
-- Otherwise, when on the first placeholder and selecting a new completion,
-- the nested session will be used instead of the top-level session.
-- See: https://github.com/LazyVim/LazyVim/issues/3199
local session = vim.snippet.active() and vim.snippet._session or nil
local ok, err = pcall(vim.snippet.expand, snippet)
if not ok then
local fixed = M.snippet_fix(snippet)
ok = pcall(vim.snippet.expand, fixed)
local msg = ok and "Failed to parse snippet,\nbut was able to fix it automatically."
or ("Failed to parse snippet.\n" .. err)
LazyVim[ok and "warn" or "error"](
([[%s
```%s
%s
```]]):format(msg, vim.bo.filetype, snippet),
{ title = "vim.snippet" }
)
end
-- Restore top-level session when needed
if session then
vim.snippet._session = session
end
end
---@param opts cmp.ConfigSchema | {auto_brackets?: string[]}
function M.setup(opts)
for _, source in ipairs(opts.sources) do
source.group_index = source.group_index or 1
end
local parse = require("cmp.utils.snippet").parse
require("cmp.utils.snippet").parse = function(input)
local ok, ret = pcall(parse, input)
if ok then
return ret
end
return LazyVim.cmp.snippet_preview(input)
end
local cmp = require("cmp")
cmp.setup(opts)
cmp.event:on("confirm_done", function(event)
if vim.tbl_contains(opts.auto_brackets or {}, vim.bo.filetype) then
LazyVim.cmp.auto_brackets(event.entry)
end
end)
cmp.event:on("menu_opened", function(event)
LazyVim.cmp.add_missing_snippet_docs(event.window)
end)
end
return M