summaryrefslogtreecommitdiff
path: root/lua/blink/cmp/completion/accept/init.lua
blob: c4041d39f6f3cce0ffa161c200ae1dba9eaf0ba7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
local text_edits_lib = require('blink.cmp.lib.text_edits')
local brackets_lib = require('blink.cmp.completion.brackets')

--- Applies a completion item to the current buffer
--- @param ctx blink.cmp.Context
--- @param item blink.cmp.CompletionItem
--- @param callback fun()
local function accept(ctx, item, callback)
  local sources = require('blink.cmp.sources.lib')
  require('blink.cmp.completion.trigger').hide()

  -- Start the resolve immediately since text changes can invalidate the item
  -- with some LSPs (i.e. rust-analyzer) causing them to return the item as-is
  -- without i.e. auto-imports
  sources
    .resolve(ctx, item)
    :map(function(item)
      item = vim.deepcopy(item)

      -- Get additional text edits, converted to utf-8
      local all_text_edits = vim.deepcopy(item.additionalTextEdits or {})
      all_text_edits = vim.tbl_map(
        function(text_edit) return text_edits_lib.to_utf_8(text_edit, text_edits_lib.offset_encoding_from_item(item)) end,
        all_text_edits
      )

      -- TODO: it's not obvious that this is converting to utf-8
      item.textEdit = text_edits_lib.get_from_item(item)

      -- Create an undo point, if it's not a snippet, since the snippet engine should handle undo
      if
        ctx.mode == 'default'
        and require('blink.cmp.config').completion.accept.create_undo_point
        and item.insertTextFormat ~= vim.lsp.protocol.InsertTextFormat.Snippet
        -- HACK: We check the kind here because the Luasnip source returns PlainText and handles
        -- expansion itself. Otherwise, Luasnip will fail to enter select mode
        -- https://github.com/Saghen/blink.cmp/commit/284dd37f9bbc632f8281d6361e877db5b45e6ff0#r150498482
        and item.kind ~= require('blink.cmp.types').CompletionItemKind.Snippet
      then
        -- setting the undolevels forces neovim to create an undo point
        vim.o.undolevels = vim.o.undolevels
      end

      -- Ignore snippets that only contain text
      -- FIXME: doesn't handle escaped snippet placeholders "\\$1" should output "$1", not "\$1"
      if
        item.insertTextFormat == vim.lsp.protocol.InsertTextFormat.Snippet
        and item.kind ~= require('blink.cmp.types').CompletionItemKind.Snippet
      then
        local parsed_snippet = require('blink.cmp.sources.snippets.utils').safe_parse(item.textEdit.newText)
        if
          parsed_snippet ~= nil
          and #parsed_snippet.data.children == 1
          and parsed_snippet.data.children[1].type == vim.lsp._snippet_grammar.NodeType.Text
        then
          item.insertTextFormat = vim.lsp.protocol.InsertTextFormat.PlainText
        end
      end

      -- Add brackets to the text edit if needed
      local brackets_status, text_edit_with_brackets, offset = brackets_lib.add_brackets(ctx, vim.bo.filetype, item)
      item.textEdit = text_edit_with_brackets

      -- Snippet
      if item.insertTextFormat == vim.lsp.protocol.InsertTextFormat.Snippet then
        assert(ctx.mode == 'default', 'Snippets are only supported in default mode')

        -- We want to handle offset_encoding and the text edit api can do this for us
        -- so we empty the newText and apply
        local temp_text_edit = vim.deepcopy(item.textEdit)
        temp_text_edit.newText = ''
        table.insert(all_text_edits, temp_text_edit)
        text_edits_lib.apply(all_text_edits)

        -- Expand the snippet
        require('blink.cmp.config').snippets.expand(item.textEdit.newText)

      -- OR Normal: Apply the text edit and move the cursor
      else
        table.insert(all_text_edits, item.textEdit)
        text_edits_lib.apply(all_text_edits)
        -- TODO: should move the cursor only by the offset since text edit handles everything else?
        ctx.set_cursor({ ctx.get_cursor()[1], item.textEdit.range.start.character + #item.textEdit.newText + offset })
      end

      -- Let the source execute the item itself
      sources.execute(ctx, item):map(function()
        -- Check semantic tokens for brackets, if needed, and apply additional text edits
        if brackets_status == 'check_semantic_token' then
          -- TODO: since we apply the additional text edits after, auto imported functions will not
          -- get auto brackets. If we apply them before, we have to modify the textEdit to compensate
          brackets_lib.add_brackets_via_semantic_token(vim.bo.filetype, item, function()
            require('blink.cmp.completion.trigger').show_if_on_trigger_character({ is_accept = true })
            require('blink.cmp.signature.trigger').show_if_on_trigger_character()
            callback()
          end)
        else
          require('blink.cmp.completion.trigger').show_if_on_trigger_character({ is_accept = true })
          require('blink.cmp.signature.trigger').show_if_on_trigger_character()
          callback()
        end

        -- Notify the rust module that the item was accessed
        require('blink.cmp.fuzzy').access(item)
      end)
    end)
    :catch(function(err) vim.notify(err, vim.log.levels.ERROR, { title = 'blink.cmp' }) end)
end

return accept