|
| 1 | +local function has_words_before() |
| 2 | + local line, col = (unpack or table.unpack)(vim.api.nvim_win_get_cursor(0)) |
| 3 | + return col ~= 0 and vim.api.nvim_buf_get_lines(0, line - 1, line, true)[1]:sub(col, col):match "%s" == nil |
| 4 | +end |
| 5 | +local function is_visible(cmp) return cmp.core.view:visible() or vim.fn.pumvisible() == 1 end |
| 6 | + |
| 7 | +return { |
| 8 | + "hrsh7th/nvim-cmp", |
| 9 | + dependencies = { |
| 10 | + { "hrsh7th/cmp-buffer", lazy = true }, |
| 11 | + { "hrsh7th/cmp-path", lazy = true }, |
| 12 | + { "hrsh7th/cmp-nvim-lsp", lazy = true }, |
| 13 | + }, |
| 14 | + event = "InsertEnter", |
| 15 | + opts_extend = { "sources" }, |
| 16 | + opts = function(_, opts) |
| 17 | + local cmp, astro = require "cmp", require "astrocore" |
| 18 | + |
| 19 | + local sources = {} |
| 20 | + for source_plugin, source in pairs { |
| 21 | + ["cmp-buffer"] = { name = "buffer", priority = 500, group_index = 2 }, |
| 22 | + ["cmp-nvim-lsp"] = { name = "nvim_lsp", priority = 1000 }, |
| 23 | + ["cmp-path"] = { name = "path", priority = 250 }, |
| 24 | + } do |
| 25 | + if astro.is_available(source_plugin) then table.insert(sources, source) end |
| 26 | + end |
| 27 | + |
| 28 | + local function get_icon_provider() |
| 29 | + local _, mini_icons = pcall(require, "mini.icons") |
| 30 | + if _G.MiniIcons then |
| 31 | + return function(kind) return mini_icons.get("lsp", kind or "") end |
| 32 | + end |
| 33 | + local lspkind_avail, lspkind = pcall(require, "lspkind") |
| 34 | + if lspkind_avail then |
| 35 | + return function(kind) return lspkind.symbolic(kind, { mode = "symbol" }) end |
| 36 | + end |
| 37 | + end |
| 38 | + local icon_provider = get_icon_provider() |
| 39 | + |
| 40 | + local function format(entry, item) |
| 41 | + local highlight_colors_avail, highlight_colors = pcall(require, "nvim-highlight-colors") |
| 42 | + local color_item = highlight_colors_avail and highlight_colors.format(entry, { kind = item.kind }) |
| 43 | + if icon_provider then |
| 44 | + local icon = icon_provider(item.kind) |
| 45 | + if icon then item.kind = icon end |
| 46 | + end |
| 47 | + if color_item and color_item.abbr and color_item.abbr_hl_group then |
| 48 | + item.kind, item.kind_hl_group = color_item.abbr, color_item.abbr_hl_group |
| 49 | + end |
| 50 | + return item |
| 51 | + end |
| 52 | + |
| 53 | + return astro.extend_tbl(opts, { |
| 54 | + enabled = function() |
| 55 | + -- Disable completion when recording macros |
| 56 | + if vim.fn.reg_recording() ~= "" or vim.fn.reg_executing() ~= "" then return false end |
| 57 | + -- Disable completion for prompt windows that are not `nvim-dap` prompts |
| 58 | + local dap_prompt = astro.is_available "cmp-dap" -- add interoperability with cmp-dap |
| 59 | + and vim.tbl_contains({ "dap-repl", "dapui_watches", "dapui_hover" }, vim.bo.filetype) |
| 60 | + if vim.bo.buftype == "prompt" and not dap_prompt then return false end |
| 61 | + -- Disable completion when disabled in AstroNvim |
| 62 | + return vim.F.if_nil(vim.b.completion, astro.config.features.cmp) |
| 63 | + end, |
| 64 | + preselect = cmp.PreselectMode.None, |
| 65 | + formatting = { fields = { "kind", "abbr", "menu" }, format = format }, |
| 66 | + confirm_opts = { |
| 67 | + behavior = cmp.ConfirmBehavior.Replace, |
| 68 | + select = false, |
| 69 | + }, |
| 70 | + window = { |
| 71 | + completion = cmp.config.window.bordered { |
| 72 | + col_offset = -2, |
| 73 | + side_padding = 0, |
| 74 | + border = "rounded", |
| 75 | + winhighlight = "Normal:NormalFloat,FloatBorder:FloatBorder,CursorLine:PmenuSel,Search:None", |
| 76 | + }, |
| 77 | + documentation = cmp.config.window.bordered { |
| 78 | + border = "rounded", |
| 79 | + winhighlight = "Normal:NormalFloat,FloatBorder:FloatBorder,CursorLine:PmenuSel,Search:None", |
| 80 | + }, |
| 81 | + }, |
| 82 | + mapping = { |
| 83 | + ["<Up>"] = cmp.mapping.select_prev_item { behavior = cmp.SelectBehavior.Select }, |
| 84 | + ["<Down>"] = cmp.mapping.select_next_item { behavior = cmp.SelectBehavior.Select }, |
| 85 | + ["<C-P>"] = cmp.mapping(function() |
| 86 | + if is_visible(cmp) then |
| 87 | + cmp.select_prev_item() |
| 88 | + else |
| 89 | + cmp.complete() |
| 90 | + end |
| 91 | + end), |
| 92 | + ["<C-N>"] = cmp.mapping(function() |
| 93 | + if is_visible(cmp) then |
| 94 | + cmp.select_next_item() |
| 95 | + else |
| 96 | + cmp.complete() |
| 97 | + end |
| 98 | + end), |
| 99 | + ["<C-K>"] = cmp.mapping(cmp.mapping.select_prev_item(), { "i", "c" }), |
| 100 | + ["<C-J>"] = cmp.mapping(cmp.mapping.select_next_item(), { "i", "c" }), |
| 101 | + ["<C-U>"] = cmp.mapping(cmp.mapping.scroll_docs(-4), { "i", "c" }), |
| 102 | + ["<C-D>"] = cmp.mapping(cmp.mapping.scroll_docs(4), { "i", "c" }), |
| 103 | + ["<C-Space>"] = cmp.mapping(cmp.mapping.complete(), { "i", "c" }), |
| 104 | + ["<C-Y>"] = cmp.config.disable, |
| 105 | + ["<C-E>"] = cmp.mapping(cmp.mapping.abort(), { "i", "c" }), |
| 106 | + ["<CR>"] = cmp.mapping(cmp.mapping.confirm { select = false }, { "i", "c" }), |
| 107 | + ["<Tab>"] = cmp.mapping(function(fallback) |
| 108 | + if is_visible(cmp) then |
| 109 | + cmp.select_next_item() |
| 110 | + elseif vim.api.nvim_get_mode().mode ~= "c" and vim.snippet and vim.snippet.active { direction = 1 } then |
| 111 | + vim.schedule(function() vim.snippet.jump(1) end) |
| 112 | + elseif has_words_before() then |
| 113 | + cmp.complete() |
| 114 | + else |
| 115 | + fallback() |
| 116 | + end |
| 117 | + end, { "i", "s" }), |
| 118 | + ["<S-Tab>"] = cmp.mapping(function(fallback) |
| 119 | + if is_visible(cmp) then |
| 120 | + cmp.select_prev_item() |
| 121 | + elseif vim.api.nvim_get_mode().mode ~= "c" and vim.snippet and vim.snippet.active { direction = -1 } then |
| 122 | + vim.schedule(function() vim.snippet.jump(-1) end) |
| 123 | + else |
| 124 | + fallback() |
| 125 | + end |
| 126 | + end, { "i", "s" }), |
| 127 | + }, |
| 128 | + sources = sources, |
| 129 | + }) |
| 130 | + end, |
| 131 | + config = function(_, opts) |
| 132 | + for _, source in ipairs(opts.sources or {}) do |
| 133 | + if not source.group_index then source.group_index = 1 end |
| 134 | + end |
| 135 | + local cmp = require "cmp" |
| 136 | + cmp.setup(opts) |
| 137 | + |
| 138 | + local autopairs_avail, autopairs = pcall(require, "nvim-autopairs.completion.cmp") |
| 139 | + if autopairs_avail then cmp.event:on("confirm_done", autopairs.on_confirm_done { tex = false }) end |
| 140 | + end, |
| 141 | + specs = { |
| 142 | + { |
| 143 | + "AstroNvim/astrolsp", |
| 144 | + optional = true, |
| 145 | + opts = function(_, opts) |
| 146 | + local astrocore = require "astrocore" |
| 147 | + if astrocore.is_available "cmp-nvim-lsp" then |
| 148 | + opts.capabilities = astrocore.extend_tbl(opts.capabilities, { |
| 149 | + textDocument = { |
| 150 | + completion = { |
| 151 | + completionItem = { |
| 152 | + documentationFormat = { "markdown", "plaintext" }, |
| 153 | + snippetSupport = true, |
| 154 | + preselectSupport = true, |
| 155 | + insertReplaceSupport = true, |
| 156 | + labelDetailsSupport = true, |
| 157 | + deprecatedSupport = true, |
| 158 | + commitCharactersSupport = true, |
| 159 | + tagSupport = { valueSet = { 1 } }, |
| 160 | + resolveSupport = { properties = { "documentation", "detail", "additionalTextEdits" } }, |
| 161 | + }, |
| 162 | + }, |
| 163 | + }, |
| 164 | + }) |
| 165 | + end |
| 166 | + end, |
| 167 | + }, |
| 168 | + { |
| 169 | + "AstroNvim/astrocore", |
| 170 | + opts = function(_, opts) |
| 171 | + local maps = opts.mappings |
| 172 | + maps.n["<Leader>uc"] = |
| 173 | + { function() require("astrocore.toggles").buffer_cmp() end, desc = "Toggle autocompletion (buffer)" } |
| 174 | + maps.n["<Leader>uC"] = |
| 175 | + { function() require("astrocore.toggles").cmp() end, desc = "Toggle autocompletion (global)" } |
| 176 | + end, |
| 177 | + }, |
| 178 | + { |
| 179 | + "L3MON4D3/LuaSnip", |
| 180 | + optional = true, |
| 181 | + specs = { |
| 182 | + { |
| 183 | + "hrsh7th/nvim-cmp", |
| 184 | + dependencies = { { "saadparwaiz1/cmp_luasnip", lazy = true } }, |
| 185 | + opts = function(_, opts) |
| 186 | + local luasnip, cmp = require "luasnip", require "cmp" |
| 187 | + |
| 188 | + if not opts.snippet then opts.snippet = {} end |
| 189 | + opts.snippet.expand = function(args) luasnip.lsp_expand(args.body) end |
| 190 | + |
| 191 | + if not opts.sources then opts.sources = {} end |
| 192 | + table.insert(opts.sources, { name = "luasnip", priority = 750 }) |
| 193 | + |
| 194 | + if not opts.mappings then opts.mappings = {} end |
| 195 | + opts.mapping["<Tab>"] = cmp.mapping(function(fallback) |
| 196 | + if is_visible(cmp) then |
| 197 | + cmp.select_next_item() |
| 198 | + elseif vim.api.nvim_get_mode().mode ~= "c" and luasnip.expand_or_locally_jumpable() then |
| 199 | + luasnip.expand_or_jump() |
| 200 | + elseif has_words_before() then |
| 201 | + cmp.complete() |
| 202 | + else |
| 203 | + fallback() |
| 204 | + end |
| 205 | + end, { "i", "s" }) |
| 206 | + opts.mapping["<S-Tab>"] = cmp.mapping(function(fallback) |
| 207 | + if is_visible(cmp) then |
| 208 | + cmp.select_prev_item() |
| 209 | + elseif vim.api.nvim_get_mode().mode ~= "c" and luasnip.jumpable(-1) then |
| 210 | + luasnip.jump(-1) |
| 211 | + else |
| 212 | + fallback() |
| 213 | + end |
| 214 | + end, { "i", "s" }) |
| 215 | + end, |
| 216 | + }, |
| 217 | + }, |
| 218 | + }, |
| 219 | + { |
| 220 | + "folke/lazydev.nvim", |
| 221 | + optional = true, |
| 222 | + specs = { |
| 223 | + { |
| 224 | + "hrsh7th/nvim-cmp", |
| 225 | + opts = function(_, opts) table.insert(opts.sources, { name = "lazydev", group_index = 0 }) end, |
| 226 | + }, |
| 227 | + }, |
| 228 | + }, |
| 229 | + { |
| 230 | + "rcarriga/cmp-dap", |
| 231 | + optional = true, |
| 232 | + dependencies = { "hrsh7th/nvim-cmp" }, |
| 233 | + config = function(...) |
| 234 | + require "astronvim.plugins.configs.cmp-dap"(...) |
| 235 | + require("cmp").setup.filetype({ "dap-repl", "dapui_watches", "dapui_hover" }, { |
| 236 | + sources = { |
| 237 | + { name = "dap" }, |
| 238 | + }, |
| 239 | + }) |
| 240 | + end, |
| 241 | + }, |
| 242 | + }, |
| 243 | +} |
0 commit comments