cmp.lua 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  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. ---when inside a snippet, seeks to the nearest luasnip field if possible, and checks if it is jumpable
  20. ---@param dir number 1 for forward, -1 for backward; defaults to 1
  21. ---@return boolean true if a jumpable luasnip field is found while inside a snippet
  22. local function jumpable(dir)
  23. local luasnip_ok, luasnip = pcall(require, "luasnip")
  24. if not luasnip_ok then
  25. return false
  26. end
  27. local win_get_cursor = vim.api.nvim_win_get_cursor
  28. local get_current_buf = vim.api.nvim_get_current_buf
  29. ---sets the current buffer's luasnip to the one nearest the cursor
  30. ---@return boolean true if a node is found, false otherwise
  31. local function seek_luasnip_cursor_node()
  32. -- TODO(kylo252): upstream this
  33. -- for outdated versions of luasnip
  34. if not luasnip.session.current_nodes then
  35. return false
  36. end
  37. local node = luasnip.session.current_nodes[get_current_buf()]
  38. if not node then
  39. return false
  40. end
  41. local snippet = node.parent.snippet
  42. local exit_node = snippet.insert_nodes[0]
  43. local pos = win_get_cursor(0)
  44. pos[1] = pos[1] - 1
  45. -- exit early if we're past the exit node
  46. if exit_node then
  47. local exit_pos_end = exit_node.mark:pos_end()
  48. if (pos[1] > exit_pos_end[1]) or (pos[1] == exit_pos_end[1] and pos[2] > exit_pos_end[2]) then
  49. snippet:remove_from_jumplist()
  50. luasnip.session.current_nodes[get_current_buf()] = nil
  51. return false
  52. end
  53. end
  54. node = snippet.inner_first:jump_into(1, true)
  55. while node ~= nil and node.next ~= nil and node ~= snippet do
  56. local n_next = node.next
  57. local next_pos = n_next and n_next.mark:pos_begin()
  58. local candidate = n_next ~= snippet and next_pos and (pos[1] < next_pos[1])
  59. or (pos[1] == next_pos[1] and pos[2] < next_pos[2])
  60. -- Past unmarked exit node, exit early
  61. if n_next == nil or n_next == snippet.next then
  62. snippet:remove_from_jumplist()
  63. luasnip.session.current_nodes[get_current_buf()] = nil
  64. return false
  65. end
  66. if candidate then
  67. luasnip.session.current_nodes[get_current_buf()] = node
  68. return true
  69. end
  70. local ok
  71. ok, node = pcall(node.jump_from, node, 1, true) -- no_move until last stop
  72. if not ok then
  73. snippet:remove_from_jumplist()
  74. luasnip.session.current_nodes[get_current_buf()] = nil
  75. return false
  76. end
  77. end
  78. -- No candidate, but have an exit node
  79. if exit_node then
  80. -- to jump to the exit node, seek to snippet
  81. luasnip.session.current_nodes[get_current_buf()] = snippet
  82. return true
  83. end
  84. -- No exit node, exit from snippet
  85. snippet:remove_from_jumplist()
  86. luasnip.session.current_nodes[get_current_buf()] = nil
  87. return false
  88. end
  89. if dir == -1 then
  90. return luasnip.in_snippet() and luasnip.jumpable(-1)
  91. else
  92. return luasnip.in_snippet() and seek_luasnip_cursor_node() and luasnip.jumpable(1)
  93. end
  94. end
  95. M.methods.jumpable = jumpable
  96. M.config = function()
  97. local status_cmp_ok, cmp = pcall(require, "cmp")
  98. if not status_cmp_ok then
  99. return
  100. end
  101. local status_luasnip_ok, luasnip = pcall(require, "luasnip")
  102. if not status_luasnip_ok then
  103. return
  104. end
  105. lvim.builtin.cmp = {
  106. confirm_opts = {
  107. behavior = cmp.ConfirmBehavior.Replace,
  108. select = false,
  109. },
  110. completion = {
  111. ---@usage The minimum length of a word to complete on.
  112. keyword_length = 1,
  113. },
  114. experimental = {
  115. ghost_text = true,
  116. native_menu = false,
  117. },
  118. formatting = {
  119. fields = { "kind", "abbr", "menu" },
  120. max_width = 0,
  121. kind_icons = {
  122. Class = " ",
  123. Color = " ",
  124. Constant = "ﲀ ",
  125. Constructor = " ",
  126. Enum = "練",
  127. EnumMember = " ",
  128. Event = " ",
  129. Field = " ",
  130. File = "",
  131. Folder = " ",
  132. Function = " ",
  133. Interface = "ﰮ ",
  134. Keyword = " ",
  135. Method = " ",
  136. Module = " ",
  137. Operator = "",
  138. Property = " ",
  139. Reference = " ",
  140. Snippet = " ",
  141. Struct = " ",
  142. Text = " ",
  143. TypeParameter = " ",
  144. Unit = "塞",
  145. Value = " ",
  146. Variable = " ",
  147. },
  148. source_names = {
  149. nvim_lsp = "(LSP)",
  150. emoji = "(Emoji)",
  151. path = "(Path)",
  152. calc = "(Calc)",
  153. cmp_tabnine = "(Tabnine)",
  154. vsnip = "(Snippet)",
  155. luasnip = "(Snippet)",
  156. buffer = "(Buffer)",
  157. tmux = "(TMUX)",
  158. },
  159. duplicates = {
  160. buffer = 1,
  161. path = 1,
  162. nvim_lsp = 0,
  163. luasnip = 1,
  164. },
  165. duplicates_default = 0,
  166. format = function(entry, vim_item)
  167. local max_width = lvim.builtin.cmp.formatting.max_width
  168. if max_width ~= 0 and #vim_item.abbr > max_width then
  169. vim_item.abbr = string.sub(vim_item.abbr, 1, max_width - 1) .. "…"
  170. end
  171. if lvim.use_icons then
  172. vim_item.kind = lvim.builtin.cmp.formatting.kind_icons[vim_item.kind]
  173. end
  174. vim_item.menu = lvim.builtin.cmp.formatting.source_names[entry.source.name]
  175. vim_item.dup = lvim.builtin.cmp.formatting.duplicates[entry.source.name]
  176. or lvim.builtin.cmp.formatting.duplicates_default
  177. return vim_item
  178. end,
  179. },
  180. snippet = {
  181. expand = function(args)
  182. require("luasnip").lsp_expand(args.body)
  183. end,
  184. },
  185. window = {
  186. completion = cmp.config.window.bordered(),
  187. documentation = cmp.config.window.bordered(),
  188. },
  189. sources = {
  190. { name = "nvim_lsp" },
  191. { name = "path" },
  192. { name = "luasnip" },
  193. { name = "cmp_tabnine" },
  194. { name = "nvim_lua" },
  195. { name = "buffer" },
  196. { name = "calc" },
  197. { name = "emoji" },
  198. { name = "treesitter" },
  199. { name = "crates" },
  200. { name = "tmux" },
  201. },
  202. mapping = cmp.mapping.preset.insert {
  203. ["<C-k>"] = cmp.mapping.select_prev_item(),
  204. ["<C-j>"] = cmp.mapping.select_next_item(),
  205. ["<Down>"] = cmp.mapping(cmp.mapping.select_next_item { behavior = cmp.SelectBehavior.Select }, { "i" }),
  206. ["<Up>"] = cmp.mapping(cmp.mapping.select_prev_item { behavior = cmp.SelectBehavior.Select }, { "i" }),
  207. ["<C-d>"] = cmp.mapping.scroll_docs(-4),
  208. ["<C-f>"] = cmp.mapping.scroll_docs(4),
  209. ["<C-y>"] = cmp.mapping {
  210. i = cmp.mapping.confirm { behavior = cmp.ConfirmBehavior.Replace, select = false },
  211. c = function(fallback)
  212. if cmp.visible() then
  213. cmp.confirm { behavior = cmp.ConfirmBehavior.Replace, select = false }
  214. else
  215. fallback()
  216. end
  217. end,
  218. },
  219. ["<Tab>"] = cmp.mapping(function(fallback)
  220. if cmp.visible() then
  221. cmp.select_next_item()
  222. elseif luasnip.expand_or_locally_jumpable() then
  223. luasnip.expand_or_jump()
  224. elseif jumpable(1) then
  225. luasnip.jump(1)
  226. elseif has_words_before() then
  227. cmp.complete()
  228. else
  229. fallback()
  230. end
  231. end, { "i", "s" }),
  232. ["<S-Tab>"] = cmp.mapping(function(fallback)
  233. if cmp.visible() then
  234. cmp.select_prev_item()
  235. elseif luasnip.jumpable(-1) then
  236. luasnip.jump(-1)
  237. else
  238. fallback()
  239. end
  240. end, { "i", "s" }),
  241. ["<C-Space>"] = cmp.mapping.complete(),
  242. ["<C-e>"] = cmp.mapping.abort(),
  243. ["<CR>"] = cmp.mapping(function(fallback)
  244. if cmp.visible() then
  245. local confirm_opts = vim.deepcopy(lvim.builtin.cmp.confirm_opts) -- avoid mutating the original opts below
  246. local is_insert_mode = function()
  247. return vim.api.nvim_get_mode().mode:sub(1, 1) == "i"
  248. end
  249. if is_insert_mode() then -- prevent overwriting brackets
  250. confirm_opts.behavior = cmp.ConfirmBehavior.Insert
  251. end
  252. cmp.confirm(confirm_opts)
  253. if jumpable(1) then
  254. luasnip.jump(1)
  255. end
  256. return
  257. end
  258. if jumpable(1) then
  259. if not luasnip.jump(1) then
  260. fallback()
  261. end
  262. else
  263. fallback()
  264. end
  265. end),
  266. },
  267. }
  268. end
  269. function M.setup()
  270. require("cmp").setup(lvim.builtin.cmp)
  271. end
  272. return M