cmp.lua 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. local M = {}
  2. M.methods = {}
  3. local has_words_before = function()
  4. local line, col = unpack(vim.api.nvim_win_get_cursor(0))
  5. return col ~= 0 and vim.api.nvim_buf_get_lines(0, line - 1, line, true)[1]:sub(col, col):match "%s" == nil
  6. end
  7. M.methods.has_words_before = has_words_before
  8. ---@deprecated use M.methods.has_words_before instead
  9. M.methods.check_backspace = function()
  10. return not has_words_before()
  11. end
  12. local T = function(str)
  13. return vim.api.nvim_replace_termcodes(str, true, true, true)
  14. end
  15. local function feedkeys(key, mode)
  16. vim.api.nvim_feedkeys(T(key), mode, true)
  17. end
  18. M.methods.feedkeys = feedkeys
  19. M.methods.jumpable = require("lvim.core.luasnip").methods.jumpable
  20. M.config = function()
  21. local status_cmp_ok, cmp_types = pcall(require, "cmp.types.cmp")
  22. if not status_cmp_ok then
  23. return
  24. end
  25. local ConfirmBehavior = cmp_types.ConfirmBehavior
  26. local SelectBehavior = cmp_types.SelectBehavior
  27. local cmp = require("lvim.utils.modules").require_on_index "cmp"
  28. local luasnip = require("lvim.utils.modules").require_on_index "luasnip"
  29. local cmp_window = require "cmp.config.window"
  30. local cmp_mapping = require "cmp.config.mapping"
  31. lvim.builtin.cmp = {
  32. active = true,
  33. on_config_done = nil,
  34. enabled = function()
  35. local buftype = vim.api.nvim_buf_get_option(0, "buftype")
  36. if buftype == "prompt" then
  37. return false
  38. end
  39. return lvim.builtin.cmp.active
  40. end,
  41. confirm_opts = {
  42. behavior = ConfirmBehavior.Replace,
  43. select = false,
  44. },
  45. completion = {
  46. ---@usage The minimum length of a word to complete on.
  47. keyword_length = 1,
  48. },
  49. experimental = {
  50. ghost_text = false,
  51. native_menu = false,
  52. },
  53. formatting = {
  54. fields = { "kind", "abbr", "menu" },
  55. max_width = 0,
  56. kind_icons = lvim.icons.kind,
  57. source_names = {
  58. nvim_lsp = "(LSP)",
  59. emoji = "(Emoji)",
  60. path = "(Path)",
  61. calc = "(Calc)",
  62. cmp_tabnine = "(Tabnine)",
  63. vsnip = "(Snippet)",
  64. luasnip = "(Snippet)",
  65. buffer = "(Buffer)",
  66. tmux = "(TMUX)",
  67. copilot = "(Copilot)",
  68. treesitter = "(TreeSitter)",
  69. },
  70. duplicates = {
  71. buffer = 1,
  72. path = 1,
  73. nvim_lsp = 0,
  74. luasnip = 1,
  75. },
  76. duplicates_default = 0,
  77. format = function(entry, vim_item)
  78. local max_width = lvim.builtin.cmp.formatting.max_width
  79. if max_width ~= 0 and #vim_item.abbr > max_width then
  80. vim_item.abbr = string.sub(vim_item.abbr, 1, max_width - 1) .. lvim.icons.ui.Ellipsis
  81. end
  82. if lvim.use_icons then
  83. vim_item.kind = lvim.builtin.cmp.formatting.kind_icons[vim_item.kind]
  84. if entry.source.name == "copilot" then
  85. vim_item.kind = lvim.icons.git.Octoface
  86. vim_item.kind_hl_group = "CmpItemKindCopilot"
  87. end
  88. if entry.source.name == "cmp_tabnine" then
  89. vim_item.kind = lvim.icons.misc.Robot
  90. vim_item.kind_hl_group = "CmpItemKindTabnine"
  91. end
  92. if entry.source.name == "crates" then
  93. vim_item.kind = lvim.icons.misc.Package
  94. vim_item.kind_hl_group = "CmpItemKindCrate"
  95. end
  96. if entry.source.name == "lab.quick_data" then
  97. vim_item.kind = lvim.icons.misc.CircuitBoard
  98. vim_item.kind_hl_group = "CmpItemKindConstant"
  99. end
  100. if entry.source.name == "emoji" then
  101. vim_item.kind = lvim.icons.misc.Smiley
  102. vim_item.kind_hl_group = "CmpItemKindEmoji"
  103. end
  104. end
  105. vim_item.menu = lvim.builtin.cmp.formatting.source_names[entry.source.name]
  106. vim_item.dup = lvim.builtin.cmp.formatting.duplicates[entry.source.name]
  107. or lvim.builtin.cmp.formatting.duplicates_default
  108. return vim_item
  109. end,
  110. },
  111. snippet = {
  112. expand = function(args)
  113. luasnip.lsp_expand(args.body)
  114. end,
  115. },
  116. window = {
  117. completion = cmp_window.bordered(),
  118. documentation = cmp_window.bordered(),
  119. },
  120. sources = {
  121. {
  122. name = "copilot",
  123. -- keyword_length = 0,
  124. max_item_count = 3,
  125. trigger_characters = {
  126. {
  127. ".",
  128. ":",
  129. "(",
  130. "'",
  131. '"',
  132. "[",
  133. ",",
  134. "#",
  135. "*",
  136. "@",
  137. "|",
  138. "=",
  139. "-",
  140. "{",
  141. "/",
  142. "\\",
  143. "+",
  144. "?",
  145. " ",
  146. -- "\t",
  147. -- "\n",
  148. },
  149. },
  150. },
  151. {
  152. name = "nvim_lsp",
  153. entry_filter = function(entry, ctx)
  154. local kind = require("cmp.types.lsp").CompletionItemKind[entry:get_kind()]
  155. if kind == "Snippet" and ctx.prev_context.filetype == "java" then
  156. return false
  157. end
  158. if kind == "Text" then
  159. return false
  160. end
  161. return true
  162. end,
  163. },
  164. { name = "path" },
  165. { name = "luasnip" },
  166. { name = "cmp_tabnine" },
  167. { name = "nvim_lua" },
  168. { name = "buffer" },
  169. { name = "calc" },
  170. { name = "emoji" },
  171. { name = "treesitter" },
  172. { name = "crates" },
  173. { name = "tmux" },
  174. },
  175. mapping = cmp_mapping.preset.insert {
  176. ["<C-k>"] = cmp_mapping(cmp_mapping.select_prev_item(), { "i", "c" }),
  177. ["<C-j>"] = cmp_mapping(cmp_mapping.select_next_item(), { "i", "c" }),
  178. ["<Down>"] = cmp_mapping(cmp_mapping.select_next_item { behavior = SelectBehavior.Select }, { "i" }),
  179. ["<Up>"] = cmp_mapping(cmp_mapping.select_prev_item { behavior = SelectBehavior.Select }, { "i" }),
  180. ["<C-d>"] = cmp_mapping.scroll_docs(-4),
  181. ["<C-f>"] = cmp_mapping.scroll_docs(4),
  182. ["<C-y>"] = cmp_mapping {
  183. i = cmp_mapping.confirm { behavior = ConfirmBehavior.Replace, select = false },
  184. c = function(fallback)
  185. if cmp.visible() then
  186. cmp.confirm { behavior = ConfirmBehavior.Replace, select = false }
  187. else
  188. fallback()
  189. end
  190. end,
  191. },
  192. ["<Tab>"] = cmp_mapping(function(fallback)
  193. if cmp.visible() then
  194. cmp.select_next_item()
  195. elseif luasnip.expand_or_locally_jumpable() then
  196. luasnip.expand_or_jump()
  197. elseif M.methods.jumpable(1) then
  198. luasnip.jump(1)
  199. elseif M.methods.has_words_before() then
  200. -- cmp.complete()
  201. fallback()
  202. else
  203. fallback()
  204. end
  205. end, { "i", "s" }),
  206. ["<S-Tab>"] = cmp_mapping(function(fallback)
  207. if cmp.visible() then
  208. cmp.select_prev_item()
  209. elseif luasnip.jumpable(-1) then
  210. luasnip.jump(-1)
  211. else
  212. fallback()
  213. end
  214. end, { "i", "s" }),
  215. ["<C-Space>"] = cmp_mapping.complete(),
  216. ["<C-e>"] = cmp_mapping.abort(),
  217. ["<CR>"] = cmp_mapping(function(fallback)
  218. if cmp.visible() then
  219. local confirm_opts = vim.deepcopy(lvim.builtin.cmp.confirm_opts) -- avoid mutating the original opts below
  220. local is_insert_mode = function()
  221. return vim.api.nvim_get_mode().mode:sub(1, 1) == "i"
  222. end
  223. if is_insert_mode() then -- prevent overwriting brackets
  224. confirm_opts.behavior = ConfirmBehavior.Insert
  225. end
  226. if cmp.confirm(confirm_opts) then
  227. return -- success, exit early
  228. end
  229. end
  230. fallback() -- if not exited early, always fallback
  231. end),
  232. },
  233. cmdline = {
  234. enable = false,
  235. options = {
  236. {
  237. type = ":",
  238. sources = {
  239. { name = "path" },
  240. { name = "cmdline" },
  241. },
  242. },
  243. {
  244. type = { "/", "?" },
  245. sources = {
  246. { name = "buffer" },
  247. },
  248. },
  249. },
  250. },
  251. }
  252. end
  253. function M.setup()
  254. local cmp = require "cmp"
  255. cmp.setup(lvim.builtin.cmp)
  256. if lvim.builtin.cmp.cmdline.enable then
  257. for _, option in ipairs(lvim.builtin.cmp.cmdline.options) do
  258. cmp.setup.cmdline(option.type, {
  259. mapping = cmp.mapping.preset.cmdline(),
  260. sources = option.sources,
  261. })
  262. end
  263. end
  264. if lvim.builtin.cmp.on_config_done then
  265. lvim.builtin.cmp.on_config_done(cmp)
  266. end
  267. end
  268. return M