completion.lua 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811
  1. --- @brief
  2. --- The `vim.lsp.completion` module enables insert-mode completion driven by an LSP server. Call
  3. --- `enable()` to make it available through Nvim builtin completion (via the |CompleteDone| event).
  4. --- Specify `autotrigger=true` to activate "auto-completion" when you type any of the server-defined
  5. --- `triggerCharacters`.
  6. ---
  7. --- Example: activate LSP-driven auto-completion:
  8. --- ```lua
  9. --- vim.lsp.start({
  10. --- name = 'ts_ls',
  11. --- cmd = …,
  12. --- on_attach = function(client, bufnr)
  13. --- vim.lsp.completion.enable(true, client.id, bufnr, {
  14. --- autotrigger = true,
  15. --- convert = function(item)
  16. --- return { abbr = item.label:gsub('%b()', '') }
  17. --- end,
  18. --- })
  19. --- end,
  20. --- })
  21. --- ```
  22. local M = {}
  23. local api = vim.api
  24. local lsp = vim.lsp
  25. local protocol = lsp.protocol
  26. local ms = protocol.Methods
  27. local rtt_ms = 50
  28. local ns_to_ms = 0.000001
  29. --- @alias vim.lsp.CompletionResult lsp.CompletionList | lsp.CompletionItem[]
  30. -- TODO(mariasolos): Remove this declaration once we figure out a better way to handle
  31. -- literal/anonymous types (see https://github.com/neovim/neovim/pull/27542/files#r1495259331).
  32. --- @nodoc
  33. --- @class lsp.ItemDefaults
  34. --- @field editRange lsp.Range | { insert: lsp.Range, replace: lsp.Range } | nil
  35. --- @field insertTextFormat lsp.InsertTextFormat?
  36. --- @field insertTextMode lsp.InsertTextMode?
  37. --- @field data any
  38. --- @nodoc
  39. --- @class vim.lsp.completion.BufHandle
  40. --- @field clients table<integer, vim.lsp.Client>
  41. --- @field triggers table<string, vim.lsp.Client[]>
  42. --- @field convert? fun(item: lsp.CompletionItem): table
  43. --- @type table<integer, vim.lsp.completion.BufHandle>
  44. local buf_handles = {}
  45. --- @nodoc
  46. --- @class vim.lsp.completion.Context
  47. local Context = {
  48. cursor = nil, --- @type [integer, integer]?
  49. last_request_time = nil, --- @type integer?
  50. pending_requests = {}, --- @type function[]
  51. isIncomplete = false,
  52. }
  53. --- @nodoc
  54. function Context:cancel_pending()
  55. for _, cancel in ipairs(self.pending_requests) do
  56. cancel()
  57. end
  58. self.pending_requests = {}
  59. end
  60. --- @nodoc
  61. function Context:reset()
  62. -- Note that the cursor isn't reset here, it needs to survive a `CompleteDone` event.
  63. self.isIncomplete = false
  64. self.last_request_time = nil
  65. self:cancel_pending()
  66. end
  67. --- @type uv.uv_timer_t?
  68. local completion_timer = nil
  69. --- @return uv.uv_timer_t
  70. local function new_timer()
  71. return assert(vim.uv.new_timer())
  72. end
  73. local function reset_timer()
  74. if completion_timer then
  75. completion_timer:stop()
  76. completion_timer:close()
  77. end
  78. completion_timer = nil
  79. end
  80. --- @param window integer
  81. --- @param warmup integer
  82. --- @return fun(sample: number): number
  83. local function exp_avg(window, warmup)
  84. local count = 0
  85. local sum = 0
  86. local value = 0
  87. return function(sample)
  88. if count < warmup then
  89. count = count + 1
  90. sum = sum + sample
  91. value = sum / count
  92. else
  93. local factor = 2.0 / (window + 1)
  94. value = value * (1 - factor) + sample * factor
  95. end
  96. return value
  97. end
  98. end
  99. local compute_new_average = exp_avg(10, 10)
  100. --- @return number
  101. local function next_debounce()
  102. if not Context.last_request_time then
  103. return rtt_ms
  104. end
  105. local ms_since_request = (vim.uv.hrtime() - Context.last_request_time) * ns_to_ms
  106. return math.max((ms_since_request - rtt_ms) * -1, 0)
  107. end
  108. --- @param input string Unparsed snippet
  109. --- @return string # Parsed snippet if successful, else returns its input
  110. local function parse_snippet(input)
  111. local ok, parsed = pcall(function()
  112. return lsp._snippet_grammar.parse(input)
  113. end)
  114. return ok and tostring(parsed) or input
  115. end
  116. --- @param item lsp.CompletionItem
  117. local function apply_snippet(item)
  118. if item.textEdit then
  119. vim.snippet.expand(item.textEdit.newText)
  120. elseif item.insertText then
  121. vim.snippet.expand(item.insertText)
  122. end
  123. end
  124. --- Returns text that should be inserted when a selecting completion item. The
  125. --- precedence is as follows: textEdit.newText > insertText > label
  126. ---
  127. --- See https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
  128. ---
  129. --- @param item lsp.CompletionItem
  130. --- @param prefix string
  131. --- @param match fun(text: string, prefix: string):boolean
  132. --- @return string
  133. local function get_completion_word(item, prefix, match)
  134. if item.insertTextFormat == protocol.InsertTextFormat.Snippet then
  135. if item.textEdit or (item.insertText and item.insertText ~= '') then
  136. -- Use label instead of text if text has different starting characters.
  137. -- label is used as abbr (=displayed), but word is used for filtering
  138. -- This is required for things like postfix completion.
  139. -- E.g. in lua:
  140. --
  141. -- local f = {}
  142. -- f@|
  143. -- ▲
  144. -- └─ cursor
  145. --
  146. -- item.textEdit.newText: table.insert(f, $0)
  147. -- label: insert
  148. --
  149. -- Typing `i` would remove the candidate because newText starts with `t`.
  150. local text = parse_snippet(item.insertText or item.textEdit.newText)
  151. local word = #text < #item.label and vim.fn.matchstr(text, '\\k*') or item.label
  152. if item.filterText and not match(word, prefix) then
  153. return item.filterText
  154. else
  155. return word
  156. end
  157. else
  158. return item.label
  159. end
  160. elseif item.textEdit then
  161. local word = item.textEdit.newText
  162. return word:match('^(%S*)') or word
  163. elseif item.insertText and item.insertText ~= '' then
  164. return item.insertText
  165. end
  166. return item.label
  167. end
  168. --- Applies the given defaults to the completion item, modifying it in place.
  169. ---
  170. --- @param item lsp.CompletionItem
  171. --- @param defaults lsp.ItemDefaults?
  172. local function apply_defaults(item, defaults)
  173. if not defaults then
  174. return
  175. end
  176. item.insertTextFormat = item.insertTextFormat or defaults.insertTextFormat
  177. item.insertTextMode = item.insertTextMode or defaults.insertTextMode
  178. item.data = item.data or defaults.data
  179. if defaults.editRange then
  180. local textEdit = item.textEdit or {}
  181. item.textEdit = textEdit
  182. textEdit.newText = textEdit.newText or item.textEditText or item.insertText or item.label
  183. if defaults.editRange.start then
  184. textEdit.range = textEdit.range or defaults.editRange
  185. elseif defaults.editRange.insert then
  186. textEdit.insert = defaults.editRange.insert
  187. textEdit.replace = defaults.editRange.replace
  188. end
  189. end
  190. end
  191. --- @param result vim.lsp.CompletionResult
  192. --- @return lsp.CompletionItem[]
  193. local function get_items(result)
  194. if result.items then
  195. -- When we have a list, apply the defaults and return an array of items.
  196. for _, item in ipairs(result.items) do
  197. ---@diagnostic disable-next-line: param-type-mismatch
  198. apply_defaults(item, result.itemDefaults)
  199. end
  200. return result.items
  201. else
  202. -- Else just return the items as they are.
  203. return result
  204. end
  205. end
  206. ---@param item lsp.CompletionItem
  207. ---@return string
  208. local function get_doc(item)
  209. local doc = item.documentation
  210. if not doc then
  211. return ''
  212. end
  213. if type(doc) == 'string' then
  214. return doc
  215. end
  216. if type(doc) == 'table' and type(doc.value) == 'string' then
  217. return doc.value
  218. end
  219. vim.notify('invalid documentation value: ' .. vim.inspect(doc), vim.log.levels.WARN)
  220. return ''
  221. end
  222. ---@param value string
  223. ---@param prefix string
  224. ---@return boolean
  225. local function match_item_by_value(value, prefix)
  226. if prefix == '' then
  227. return true
  228. end
  229. if vim.o.completeopt:find('fuzzy') ~= nil then
  230. return next(vim.fn.matchfuzzy({ value }, prefix)) ~= nil
  231. end
  232. if vim.o.ignorecase and (not vim.o.smartcase or not prefix:find('%u')) then
  233. return vim.startswith(value:lower(), prefix:lower())
  234. end
  235. return vim.startswith(value, prefix)
  236. end
  237. --- Turns the result of a `textDocument/completion` request into vim-compatible
  238. --- |complete-items|.
  239. ---
  240. --- @private
  241. --- @param result vim.lsp.CompletionResult Result of `textDocument/completion`
  242. --- @param prefix string prefix to filter the completion items
  243. --- @param client_id integer? Client ID
  244. --- @return table[]
  245. --- @see complete-items
  246. function M._lsp_to_complete_items(result, prefix, client_id)
  247. local items = get_items(result)
  248. if vim.tbl_isempty(items) then
  249. return {}
  250. end
  251. ---@type fun(item: lsp.CompletionItem):boolean
  252. local matches
  253. if not prefix:find('%w') then
  254. matches = function(_)
  255. return true
  256. end
  257. else
  258. ---@param item lsp.CompletionItem
  259. matches = function(item)
  260. if item.filterText then
  261. return match_item_by_value(item.filterText, prefix)
  262. end
  263. if item.textEdit then
  264. -- server took care of filtering
  265. return true
  266. end
  267. return match_item_by_value(item.label, prefix)
  268. end
  269. end
  270. local candidates = {}
  271. local bufnr = api.nvim_get_current_buf()
  272. local user_convert = vim.tbl_get(buf_handles, bufnr, 'convert')
  273. for _, item in ipairs(items) do
  274. if matches(item) then
  275. local word = get_completion_word(item, prefix, match_item_by_value)
  276. local hl_group = ''
  277. if
  278. item.deprecated
  279. or vim.list_contains((item.tags or {}), protocol.CompletionTag.Deprecated)
  280. then
  281. hl_group = 'DiagnosticDeprecated'
  282. end
  283. local completion_item = {
  284. word = word,
  285. abbr = item.label,
  286. kind = protocol.CompletionItemKind[item.kind] or 'Unknown',
  287. menu = item.detail or '',
  288. info = get_doc(item),
  289. icase = 1,
  290. dup = 1,
  291. empty = 1,
  292. abbr_hlgroup = hl_group,
  293. user_data = {
  294. nvim = {
  295. lsp = {
  296. completion_item = item,
  297. client_id = client_id,
  298. },
  299. },
  300. },
  301. }
  302. if user_convert then
  303. completion_item = vim.tbl_extend('keep', user_convert(item), completion_item)
  304. end
  305. table.insert(candidates, completion_item)
  306. end
  307. end
  308. ---@diagnostic disable-next-line: no-unknown
  309. table.sort(candidates, function(a, b)
  310. ---@type lsp.CompletionItem
  311. local itema = a.user_data.nvim.lsp.completion_item
  312. ---@type lsp.CompletionItem
  313. local itemb = b.user_data.nvim.lsp.completion_item
  314. return (itema.sortText or itema.label) < (itemb.sortText or itemb.label)
  315. end)
  316. return candidates
  317. end
  318. --- @param lnum integer 0-indexed
  319. --- @param line string
  320. --- @param items lsp.CompletionItem[]
  321. --- @param encoding string
  322. --- @return integer?
  323. local function adjust_start_col(lnum, line, items, encoding)
  324. local min_start_char = nil
  325. for _, item in pairs(items) do
  326. if item.textEdit and item.textEdit.range.start.line == lnum then
  327. if min_start_char and min_start_char ~= item.textEdit.range.start.character then
  328. return nil
  329. end
  330. min_start_char = item.textEdit.range.start.character
  331. end
  332. end
  333. if min_start_char then
  334. return vim.str_byteindex(line, encoding, min_start_char, false)
  335. else
  336. return nil
  337. end
  338. end
  339. --- @private
  340. --- @param line string line content
  341. --- @param lnum integer 0-indexed line number
  342. --- @param cursor_col integer
  343. --- @param client_id integer client ID
  344. --- @param client_start_boundary integer 0-indexed word boundary
  345. --- @param server_start_boundary? integer 0-indexed word boundary, based on textEdit.range.start.character
  346. --- @param result vim.lsp.CompletionResult
  347. --- @param encoding string
  348. --- @return table[] matches
  349. --- @return integer? server_start_boundary
  350. function M._convert_results(
  351. line,
  352. lnum,
  353. cursor_col,
  354. client_id,
  355. client_start_boundary,
  356. server_start_boundary,
  357. result,
  358. encoding
  359. )
  360. -- Completion response items may be relative to a position different than `client_start_boundary`.
  361. -- Concrete example, with lua-language-server:
  362. --
  363. -- require('plenary.asy|
  364. -- ▲ ▲ ▲
  365. -- │ │ └── cursor_pos: 20
  366. -- │ └────── client_start_boundary: 17
  367. -- └────────────── textEdit.range.start.character: 9
  368. -- .newText = 'plenary.async'
  369. -- ^^^
  370. -- prefix (We'd remove everything not starting with `asy`,
  371. -- so we'd eliminate the `plenary.async` result
  372. --
  373. -- `adjust_start_col` is used to prefer the language server boundary.
  374. --
  375. local candidates = get_items(result)
  376. local curstartbyte = adjust_start_col(lnum, line, candidates, encoding)
  377. if server_start_boundary == nil then
  378. server_start_boundary = curstartbyte
  379. elseif curstartbyte ~= nil and curstartbyte ~= server_start_boundary then
  380. server_start_boundary = client_start_boundary
  381. end
  382. local prefix = line:sub((server_start_boundary or client_start_boundary) + 1, cursor_col)
  383. local matches = M._lsp_to_complete_items(result, prefix, client_id)
  384. return matches, server_start_boundary
  385. end
  386. --- @param clients table<integer, vim.lsp.Client> # keys != client_id
  387. --- @param bufnr integer
  388. --- @param win integer
  389. --- @param callback fun(responses: table<integer, { err: lsp.ResponseError, result: vim.lsp.CompletionResult }>)
  390. --- @return function # Cancellation function
  391. local function request(clients, bufnr, win, callback)
  392. local responses = {} --- @type table<integer, { err: lsp.ResponseError, result: any }>
  393. local request_ids = {} --- @type table<integer, integer>
  394. local remaining_requests = vim.tbl_count(clients)
  395. for _, client in pairs(clients) do
  396. local client_id = client.id
  397. local params = lsp.util.make_position_params(win, client.offset_encoding)
  398. local ok, request_id = client:request(ms.textDocument_completion, params, function(err, result)
  399. responses[client_id] = { err = err, result = result }
  400. remaining_requests = remaining_requests - 1
  401. if remaining_requests == 0 then
  402. callback(responses)
  403. end
  404. end, bufnr)
  405. if ok then
  406. request_ids[client_id] = request_id
  407. end
  408. end
  409. return function()
  410. for client_id, request_id in pairs(request_ids) do
  411. local client = lsp.get_client_by_id(client_id)
  412. if client then
  413. client:cancel_request(request_id)
  414. end
  415. end
  416. end
  417. end
  418. local function trigger(bufnr, clients)
  419. reset_timer()
  420. Context:cancel_pending()
  421. if tonumber(vim.fn.pumvisible()) == 1 and not Context.isIncomplete then
  422. return
  423. end
  424. local win = api.nvim_get_current_win()
  425. local cursor_row, cursor_col = unpack(api.nvim_win_get_cursor(win)) --- @type integer, integer
  426. local line = api.nvim_get_current_line()
  427. local line_to_cursor = line:sub(1, cursor_col)
  428. local word_boundary = vim.fn.match(line_to_cursor, '\\k*$')
  429. local start_time = vim.uv.hrtime()
  430. Context.last_request_time = start_time
  431. local cancel_request = request(clients, bufnr, win, function(responses)
  432. local end_time = vim.uv.hrtime()
  433. rtt_ms = compute_new_average((end_time - start_time) * ns_to_ms)
  434. Context.pending_requests = {}
  435. Context.isIncomplete = false
  436. local row_changed = api.nvim_win_get_cursor(win)[1] ~= cursor_row
  437. local mode = api.nvim_get_mode().mode
  438. if row_changed or not (mode == 'i' or mode == 'ic') then
  439. return
  440. end
  441. local matches = {}
  442. local server_start_boundary --- @type integer?
  443. for client_id, response in pairs(responses) do
  444. if response.err then
  445. vim.notify_once(response.err.message, vim.log.levels.WARN)
  446. end
  447. local result = response.result
  448. if result then
  449. Context.isIncomplete = Context.isIncomplete or result.isIncomplete
  450. local client = lsp.get_client_by_id(client_id)
  451. local encoding = client and client.offset_encoding or 'utf-16'
  452. local client_matches
  453. client_matches, server_start_boundary = M._convert_results(
  454. line,
  455. cursor_row - 1,
  456. cursor_col,
  457. client_id,
  458. word_boundary,
  459. nil,
  460. result,
  461. encoding
  462. )
  463. vim.list_extend(matches, client_matches)
  464. end
  465. end
  466. local start_col = (server_start_boundary or word_boundary) + 1
  467. Context.cursor = { cursor_row, start_col }
  468. vim.fn.complete(start_col, matches)
  469. end)
  470. table.insert(Context.pending_requests, cancel_request)
  471. end
  472. --- @param handle vim.lsp.completion.BufHandle
  473. local function on_insert_char_pre(handle)
  474. if tonumber(vim.fn.pumvisible()) == 1 then
  475. if Context.isIncomplete then
  476. reset_timer()
  477. local debounce_ms = next_debounce()
  478. if debounce_ms == 0 then
  479. vim.schedule(M.trigger)
  480. else
  481. completion_timer = new_timer()
  482. completion_timer:start(debounce_ms, 0, vim.schedule_wrap(M.trigger))
  483. end
  484. end
  485. return
  486. end
  487. local char = api.nvim_get_vvar('char')
  488. local matched_clients = handle.triggers[char]
  489. if not completion_timer and matched_clients then
  490. completion_timer = assert(vim.uv.new_timer())
  491. completion_timer:start(25, 0, function()
  492. reset_timer()
  493. vim.schedule(function()
  494. trigger(api.nvim_get_current_buf(), matched_clients)
  495. end)
  496. end)
  497. end
  498. end
  499. local function on_insert_leave()
  500. reset_timer()
  501. Context.cursor = nil
  502. Context:reset()
  503. end
  504. local function on_complete_done()
  505. local completed_item = api.nvim_get_vvar('completed_item')
  506. if not completed_item or not completed_item.user_data or not completed_item.user_data.nvim then
  507. Context:reset()
  508. return
  509. end
  510. local cursor_row, cursor_col = unpack(api.nvim_win_get_cursor(0)) --- @type integer, integer
  511. cursor_row = cursor_row - 1
  512. local completion_item = completed_item.user_data.nvim.lsp.completion_item --- @type lsp.CompletionItem
  513. local client_id = completed_item.user_data.nvim.lsp.client_id --- @type integer
  514. if not completion_item or not client_id then
  515. Context:reset()
  516. return
  517. end
  518. local bufnr = api.nvim_get_current_buf()
  519. local expand_snippet = completion_item.insertTextFormat == protocol.InsertTextFormat.Snippet
  520. and (completion_item.textEdit ~= nil or completion_item.insertText ~= nil)
  521. Context:reset()
  522. local client = lsp.get_client_by_id(client_id)
  523. if not client then
  524. return
  525. end
  526. local position_encoding = client.offset_encoding or 'utf-16'
  527. local resolve_provider = (client.server_capabilities.completionProvider or {}).resolveProvider
  528. local function clear_word()
  529. if not expand_snippet then
  530. return nil
  531. end
  532. -- Remove the already inserted word.
  533. api.nvim_buf_set_text(
  534. bufnr,
  535. Context.cursor[1] - 1,
  536. Context.cursor[2] - 1,
  537. cursor_row,
  538. cursor_col,
  539. { '' }
  540. )
  541. end
  542. local function apply_snippet_and_command()
  543. if expand_snippet then
  544. apply_snippet(completion_item)
  545. end
  546. local command = completion_item.command
  547. if command then
  548. client:exec_cmd(command, { bufnr = bufnr })
  549. end
  550. end
  551. if completion_item.additionalTextEdits and next(completion_item.additionalTextEdits) then
  552. clear_word()
  553. lsp.util.apply_text_edits(completion_item.additionalTextEdits, bufnr, position_encoding)
  554. apply_snippet_and_command()
  555. elseif resolve_provider and type(completion_item) == 'table' then
  556. local changedtick = vim.b[bufnr].changedtick
  557. --- @param result lsp.CompletionItem
  558. client:request(ms.completionItem_resolve, completion_item, function(err, result)
  559. if changedtick ~= vim.b[bufnr].changedtick then
  560. return
  561. end
  562. clear_word()
  563. if err then
  564. vim.notify_once(err.message, vim.log.levels.WARN)
  565. elseif result then
  566. if result.additionalTextEdits then
  567. lsp.util.apply_text_edits(result.additionalTextEdits, bufnr, position_encoding)
  568. end
  569. if result.command then
  570. completion_item.command = result.command
  571. end
  572. end
  573. apply_snippet_and_command()
  574. end, bufnr)
  575. else
  576. clear_word()
  577. apply_snippet_and_command()
  578. end
  579. end
  580. ---@param bufnr integer
  581. ---@return string
  582. local function get_augroup(bufnr)
  583. return string.format('nvim.lsp.completion_%d', bufnr)
  584. end
  585. --- @class vim.lsp.completion.BufferOpts
  586. --- @field autotrigger? boolean Default: false When true, completion triggers automatically based on the server's `triggerCharacters`.
  587. --- @field convert? fun(item: lsp.CompletionItem): table Transforms an LSP CompletionItem to |complete-items|.
  588. ---@param client_id integer
  589. ---@param bufnr integer
  590. ---@param opts vim.lsp.completion.BufferOpts
  591. local function enable_completions(client_id, bufnr, opts)
  592. local buf_handle = buf_handles[bufnr]
  593. if not buf_handle then
  594. buf_handle = { clients = {}, triggers = {}, convert = opts.convert }
  595. buf_handles[bufnr] = buf_handle
  596. -- Attach to buffer events.
  597. api.nvim_buf_attach(bufnr, false, {
  598. on_detach = function(_, buf)
  599. buf_handles[buf] = nil
  600. end,
  601. on_reload = function(_, buf)
  602. M.enable(true, client_id, buf, opts)
  603. end,
  604. })
  605. -- Set up autocommands.
  606. local group = api.nvim_create_augroup(get_augroup(bufnr), { clear = true })
  607. api.nvim_create_autocmd('CompleteDone', {
  608. group = group,
  609. buffer = bufnr,
  610. callback = function()
  611. local reason = api.nvim_get_vvar('event').reason --- @type string
  612. if reason == 'accept' then
  613. on_complete_done()
  614. end
  615. end,
  616. })
  617. if opts.autotrigger then
  618. api.nvim_create_autocmd('InsertCharPre', {
  619. group = group,
  620. buffer = bufnr,
  621. callback = function()
  622. on_insert_char_pre(buf_handles[bufnr])
  623. end,
  624. })
  625. api.nvim_create_autocmd('InsertLeave', {
  626. group = group,
  627. buffer = bufnr,
  628. callback = on_insert_leave,
  629. })
  630. end
  631. end
  632. if not buf_handle.clients[client_id] then
  633. local client = lsp.get_client_by_id(client_id)
  634. assert(client, 'invalid client ID')
  635. -- Add the new client to the buffer's clients.
  636. buf_handle.clients[client_id] = client
  637. -- Add the new client to the clients that should be triggered by its trigger characters.
  638. --- @type string[]
  639. local triggers = vim.tbl_get(
  640. client.server_capabilities,
  641. 'completionProvider',
  642. 'triggerCharacters'
  643. ) or {}
  644. for _, char in ipairs(triggers) do
  645. local clients_for_trigger = buf_handle.triggers[char]
  646. if not clients_for_trigger then
  647. clients_for_trigger = {}
  648. buf_handle.triggers[char] = clients_for_trigger
  649. end
  650. local client_exists = vim.iter(clients_for_trigger):any(function(c)
  651. return c.id == client_id
  652. end)
  653. if not client_exists then
  654. table.insert(clients_for_trigger, client)
  655. end
  656. end
  657. end
  658. end
  659. --- @param client_id integer
  660. --- @param bufnr integer
  661. local function disable_completions(client_id, bufnr)
  662. local handle = buf_handles[bufnr]
  663. if not handle then
  664. return
  665. end
  666. handle.clients[client_id] = nil
  667. if not next(handle.clients) then
  668. buf_handles[bufnr] = nil
  669. api.nvim_del_augroup_by_name(get_augroup(bufnr))
  670. else
  671. for char, clients in pairs(handle.triggers) do
  672. --- @param c vim.lsp.Client
  673. handle.triggers[char] = vim.tbl_filter(function(c)
  674. return c.id ~= client_id
  675. end, clients)
  676. end
  677. end
  678. end
  679. --- Enables or disables completions from the given language client in the given buffer.
  680. ---
  681. --- @param enable boolean True to enable, false to disable
  682. --- @param client_id integer Client ID
  683. --- @param bufnr integer Buffer handle, or 0 for the current buffer
  684. --- @param opts? vim.lsp.completion.BufferOpts
  685. function M.enable(enable, client_id, bufnr, opts)
  686. bufnr = vim._resolve_bufnr(bufnr)
  687. if enable then
  688. enable_completions(client_id, bufnr, opts or {})
  689. else
  690. disable_completions(client_id, bufnr)
  691. end
  692. end
  693. --- Triggers LSP completion once in the current buffer.
  694. function M.trigger()
  695. local bufnr = api.nvim_get_current_buf()
  696. local clients = (buf_handles[bufnr] or {}).clients or {}
  697. trigger(bufnr, clients)
  698. end
  699. --- Implements 'omnifunc' compatible LSP completion.
  700. ---
  701. --- @see |complete-functions|
  702. --- @see |complete-items|
  703. --- @see |CompleteDone|
  704. ---
  705. --- @param findstart integer 0 or 1, decides behavior
  706. --- @param base integer findstart=0, text to match against
  707. ---
  708. --- @return integer|table Decided by {findstart}:
  709. --- - findstart=0: column where the completion starts, or -2 or -3
  710. --- - findstart=1: list of matches (actually just calls |complete()|)
  711. function M._omnifunc(findstart, base)
  712. vim.lsp.log.debug('omnifunc.findstart', { findstart = findstart, base = base })
  713. assert(base) -- silence luals
  714. local bufnr = api.nvim_get_current_buf()
  715. local clients = lsp.get_clients({ bufnr = bufnr, method = ms.textDocument_completion })
  716. local remaining = #clients
  717. if remaining == 0 then
  718. return findstart == 1 and -1 or {}
  719. end
  720. trigger(bufnr, clients)
  721. -- Return -2 to signal that we should continue completion so that we can
  722. -- async complete.
  723. return -2
  724. end
  725. return M