sauce-nvim-config/lua/themenacesauce/plugins/nvim-cmp.lua

388 lines
15 KiB
Lua

return {
"hrsh7th/nvim-cmp",
dependencies = {
"hrsh7th/cmp-buffer",
"hrsh7th/cmp-path",
{
"L3MON4D3/LuaSnip",
-- follow latest release.
version = "v2.*", -- Replace <CurrentMajor> by the latest released major (first number of latest release)
-- install jsregexp (optional!),
build = "make install_jsregexp",
},
"saadparwaiz1/cmp_luasnip", -- autocompletion
"rafamadriz/friendly-snippets", -- snippets
"nvim-treesitter/nvim-treesitter",
"onsails/lspkind.nvim", -- vs code pictographs
"roobert/tailwindcss-colorizer-cmp.nvim",
},
config = function()
local cmp = require("cmp")
local has_luasnip, luasnip = pcall(require, 'luasnip')
local lspkind = require("lspkind")
local colorizer = require("tailwindcss-colorizer-cmp").formatter
local rhs = function(keys)
return vim.api.nvim_replace_termcodes(keys, true, true, true)
end
local lsp_kinds = {
Class = '',
Color = '',
Constant = '',
Constructor = '',
Enum = '',
EnumMember = '',
Event = '',
Field = '',
File = '',
Folder = '',
Function = '',
Interface = '',
Keyword = '',
Method = '',
Module = '',
Operator = '',
Property = '',
Reference = '',
Snippet = '',
Struct = '',
Text = '',
TypeParameter = '',
Unit = '',
Value = '',
Variable = '',
}
local column = function()
local _line, col = unpack(vim.api.nvim_win_get_cursor(0))
return col
end
-- luasnip custom function
local in_snippet = function()
local session = require('luasnip.session')
local node = session.current_nodes[vim.api.nvim_get_current_buf()]
if not node then
return false
end
local snippet = node.parent.snippet
local snip_begin_pos, snip_end_pos = snippet.mark:pos_begin_end()
local pos = vim.api.nvim_win_get_cursor(0)
if pos[1] - 1 >= snip_begin_pos[1] and pos[1] - 1 <= snip_end_pos[1] then
return true
end
end
-- return true if the cursor is in leftmost column or at a whitespace char
local in_whitespace = function()
local col = column()
return col == 0 or vim.api.nvim_get_current_line():sub(col, col):match('%s')
end
local in_leading_indent = function()
local col = column()
local line = vim.api.nvim_get_current_line()
local prefix = line:sub(1, col)
return prefix:find('^%s*$')
end
-- custom shift width
local shift_width = function()
if vim.o.softtabstop <= 0 then
return vim.fn.shiftwidth()
else
return vim.o.softtabstop
end
end
-- custom smart backspace
local smart_bs = function(dedent)
local keys = nil
if vim.o.expandtab then
if dedent then
keys = rhs('<C-D>')
else
keys = rhs('<BS>')
end
else
local col = column()
local line = vim.api.nvim_get_current_line()
local prefix = line:sub(1, col)
if in_leading_indent() then
keys = rhs('<BS>')
else
local previous_char = prefix:sub(#prefix, #prefix)
if previous_char ~= ' ' then
keys = rhs('<BS>')
else
keys = rhs('<C-\\><C-o>:set expandtab<CR><BS><C-\\><C-o>:set noexpandtab<CR>')
end
end
end
vim.api.nvim_feedkeys(keys, 'nt', true)
end
-- custom smart tabs function
local smart_tab = function(opts)
local keys = nil
if vim.o.expandtab then
keys = '<Tab>' -- Neovim will insert spaces.
else
local col = column()
local line = vim.api.nvim_get_current_line()
local prefix = line:sub(1, col)
local in_leading_indent = prefix:find('^%s*$')
if in_leading_indent then
-- inserts a hard tab.
keys = '<Tab>'
else
local sw = shift_width()
local previous_char = prefix:sub(#prefix, #prefix)
local current_column = #prefix - #previous_char + 1
local remainder = (current_column - 1) % sw
local move = remainder == 0 and sw or sw - remainder
keys = (' '):rep(move)
end
end
vim.api.nvim_feedkeys(rhs(keys), 'nt', true)
end
local select_next_item = function(fallback)
if cmp.visible() then
cmp.select_next_item()
else
fallback()
end
end
local select_prev_item = function(fallback)
if cmp.visible() then
cmp.select_prev_item()
else
fallback()
end
end
local confirm = function(entry)
local behavior = cmp.ConfirmBehavior.Replace
if entry then
local completion_item = entry.completion_item
local newText = ''
if completion_item.textEdit then
newText = completion_item.textEdit.newText
elseif type(completion_item.insertText) == 'string' and completion_item.insertText ~= '' then
newText = completion_item.insertText
else
newText = completion_item.word or completion_item.label or ''
end
-- checks how many characters will be different after the cursor position if we replace,
local diff_after = math.max(0, entry.replace_range['end'].character + 1) - entry.context.cursor.col
-- does the text that will be replaced after the cursor match the suffix
-- of the newText to be inserted ? if not, then Insert instead,
if entry.context.cursor_after_line:sub(1, diff_after) ~= newText:sub(-diff_after) then
behavior = cmp.ConfirmBehavior.Insert
end
end
cmp.confirm({ select = true, behavior = behavior })
end
-- loads vscode style snippets from installed plugins (e.g. friendly-snippets)
require("luasnip.loaders.from_vscode").lazy_load()
cmp.setup({
experimental = {
-- HACK: experimenting with ghost text
-- look at `toggle_ghost_text()` function below.
ghost_text = false,
},
completion = {
completeopt = "menu,menuone,noinsert",
},
window = {
documentation = {
border = {'', '', '', '', '', '', '', ''},
},
completion = {
border = {'', '', '', '', '', '', '', ''},
}
},
-- config nvim cmp to work with snippet engine
snippet = {
expand = function(args)
luasnip.lsp_expand(args.body)
end,
},
-- autocompletion sources
sources = cmp.config.sources({
{ name = "luasnip" }, -- snippets
{ name = "lazydev" },
{ name = "nvim_lsp"},
{ name = "buffer" }, -- text within current buffer
{ name = "path" }, -- file system paths
{ name = "tailwindcss-colorizer-cmp" },
{ name = "spell", -- for markdown spellchecks completions
option = {
enable_in_context = function()
local ft = vim.bo.filetype
return ft == "markdown" or ft == "text"
end,
},
},
}),
-- mapping = cmp.mapping.preset.insert({
-- ["<C-k>"] = cmp.mapping.select_prev_item(), -- previous suggestion
-- ["<C-j>"] = cmp.mapping.select_next_item(), -- next suggestion
-- ["<C-b>"] = cmp.mapping.scroll_docs(-4),
-- ["<C-f>"] = cmp.mapping.scroll_docs(4),
-- ["<C-Space>"] = cmp.mapping.complete(), -- show completion suggestions
-- ["<C-e>"] = cmp.mapping.abort(), -- close completion window
-- ["<CR>"] = cmp.mapping.confirm({ select = false }),
-- }),
-- NOTE: ! Experimenting with Customized Mappings ! --
mapping = cmp.mapping.preset.insert({
-- ['<BS>'] = cmp.mapping(function(_fallback)
-- smart_bs()
-- end, { 'i', 's' }),
["<C-e>"] = cmp.mapping.abort(), -- close completion window
['<C-d>'] = cmp.mapping(function()
cmp.close_docs()
end, { 'i', 's' }),
['<C-f>'] = cmp.mapping.scroll_docs(4),
['<C-b>'] = cmp.mapping.scroll_docs(-4),
['<C-j>'] = cmp.mapping(select_next_item),
['<C-k>'] = cmp.mapping(select_prev_item),
['<C-n>'] = cmp.mapping(select_next_item),
['<C-p>'] = cmp.mapping(select_prev_item),
['<Down>'] = cmp.mapping(select_next_item),
['<Up>'] = cmp.mapping(select_prev_item),
['<C-y>'] = cmp.mapping(function(fallback)
if cmp.visible() then
local entry = cmp.get_selected_entry()
confirm(entry)
else
fallback()
end
end, { 'i', 's' }),
['<CR>'] = cmp.mapping(function(fallback)
if cmp.visible() then
local entry = cmp.get_selected_entry()
confirm(entry)
else
fallback()
end
end, { 'i', 's' }),
['<S-Tab>'] = cmp.mapping(function(fallback)
if cmp.visible() then
cmp.select_prev_item()
elseif has_luasnip and in_snippet() and luasnip.jumpable(-1) then
luasnip.jump(-1)
elseif in_leading_indent() then
smart_bs(true) -- true means to dedent
elseif in_whitespace() then
smart_bs()
else
fallback()
end
end, { 'i', 's' }),
['<Tab>'] = cmp.mapping(function(_fallback)
if cmp.visible() then
-- if there is only one completion candidate then use it.
local entries = cmp.get_entries()
if #entries == 1 then
confirm(entries[1])
else
cmp.select_next_item()
end
elseif has_luasnip and luasnip.expand_or_locally_jumpable() then
luasnip.expand_or_jump()
elseif in_whitespace() then
smart_tab()
else
cmp.complete()
end
end, { 'i', 's' }),
}),
-- setup lspkind for vscode pictograms in autocompletion dropdown menu
formatting = {
format = function(entry, vim_item)
-- Add custom lsp_kinds icons
vim_item.kind = string.format('%s %s', lsp_kinds[vim_item.kind] or '', vim_item.kind)
-- add menu tags (e.g., [Buffer], [LSP])
vim_item.menu = ({
buffer = "[Buffer]",
nvim_lsp = "[LSP]",
luasnip = "[LuaSnip]",
nvim_lua = "[Lua]",
latex_symbols = "[LaTeX]",
})[entry.source.name]
-- use lspkind and tailwindcss-colorizer-cmp for additional formatting
vim_item = lspkind.cmp_format({
maxwidth = 25,
ellipsis_char = "...",
})(entry, vim_item)
if entry.source.name == "nvim_lsp" then
vim_item = colorizer(entry, vim_item)
end
return vim_item
end,
-- format = lspkind.cmp_format({
-- maxwidth = 30,
-- ellipsis_char = "...",
-- before = require("tailwindcss-colorizer-cmp").formatter
-- }),
-- format = require("tailwindcss-colorizer-cmp").formatter
},
})
-- NOTE: Added Ghost text stuff
-- Only show ghost text at word boundaries, not inside keywords. Based on idea
-- from: https://github.com/hrsh7th/nvim-cmp/issues/2035#issuecomment-2347186210
-- local config = require('cmp.config')
-- local toggle_ghost_text = function()
-- if vim.api.nvim_get_mode().mode ~= 'i' then
-- return
-- end
--
-- local cursor_column = vim.fn.col('.')
-- local current_line_contents = vim.fn.getline('.')
-- local character_after_cursor = current_line_contents:sub(cursor_column, cursor_column)
--
-- local should_enable_ghost_text = character_after_cursor == '' or vim.fn.match(character_after_cursor, [[\k]]) == -1
--
-- local current = config.get().experimental.ghost_text
-- if current ~= should_enable_ghost_text then
-- config.set_global({
-- experimental = {
-- ghost_text = should_enable_ghost_text,
-- },
-- })
-- end
-- end
--
-- vim.api.nvim_create_autocmd({ 'InsertEnter', 'CursorMovedI' }, {
-- callback = toggle_ghost_text,
-- })
-- ! Ghost text stuff ! --
end
}