/cmp-buffer

nvim-cmp source for buffer words

Primary LanguageLuaMIT LicenseMIT

cmp-buffer

nvim-cmp source for buffer words.

Setup

require('cmp').setup({
  sources = {
    { name = 'buffer' },
  },
})

Configuration

The below source configuration are available. To set any of these options, do:

cmp.setup({
  sources = {
    {
      name = 'buffer',
      option = {
        -- Options go into this table
      },
    },
  },
})

keyword_length (type: number)

Default: 3

The number of characters that need to be typed to trigger auto-completion.

keyword_pattern (type: string)

Default: [[\%(-\?\d\+\%(\.\d\+\)\?\|\h\w*\%([\-.]\w*\)*\)]]

A vim's regular expression for creating a word list from buffer content.

You can set this to [[\k\+]] if you want to use the iskeyword option for recognizing words. Lua's [[ ]] string literals are particularly useful here to avoid escaping all of the backslash (\) characters used for writing regular expressions.

NOTE: Be careful with where you set this option! You must do this:

cmp.setup({
  sources = {
    {
      name = 'buffer',
      -- Correct:
      option = {
        keyword_pattern = [[\k\+]],
      }
    },
  },
})

Instead of this:

cmp.setup({
  sources = {
    {
      name = 'buffer',
      -- Wrong:
      keyword_pattern = [[\k\+]],
    },
  },
})

The second notation is allowed by nvim-cmp (documented here), but it is meant for a different purpose and will not be detected by this plugin as the pattern for searching words.

get_bufnrs (type: fun(): number[])

Default: function() return { vim.api.nvim_get_current_buf() } end

A function that specifies the buffer numbers to complete.

You can use the following pre-defined recipes.

All buffers
cmp.setup {
  sources = {
    {
      name = 'buffer',
      option = {
        get_bufnrs = function()
          return vim.api.nvim_list_bufs()
        end
      }
    }
  }
}
Visible buffers
cmp.setup {
  sources = {
    {
      name = 'buffer',
      option = {
        get_bufnrs = function()
          local bufs = {}
          for _, win in ipairs(vim.api.nvim_list_wins()) do
            bufs[vim.api.nvim_win_get_buf(win)] = true
          end
          return vim.tbl_keys(bufs)
        end
      }
    }
  }
}

indexing_interval (type: number)

Default: 100

Optimization option. See the section Indexing.

indexing_batch_size (type: number)

Default: 1000

Optimization option. See the section Indexing.

max_indexed_line_length (type: number)

Default: 1024 * 40 (40 Kilobytes)

Optimization option. See the section Indexing.

Locality bonus comparator (distance-based sorting)

This source also provides a comparator function which uses information from the word indexer to sort completion results based on the distance of the word from the cursor line. It will also sort completion results coming from other sources, such as Language Servers, which might improve accuracy of their suggestions too. The usage is as follows:

local cmp = require('cmp')
local cmp_buffer = require('cmp_buffer')

cmp.setup({
  sources = {
    { name = 'buffer' },
      -- The rest of your sources...
  },
  sorting = {
    comparators = {
      function(...) return cmp_buffer:compare_locality(...) end,
      -- The rest of your comparators...
    }
  }
})

Indexing and how to optimize it

When a buffer is opened, this source first has to scan all lines in the buffer, match all words and store all of their occurrences. This process is called indexing. When actually editing the text in the buffer, the index of words is kept up-to-date with changes to the buffer's contents, this is called watching. It is done by re-running the indexer on just the changed lines. Indexing happens completely asynchronously in background, unlike watching, which must be performed synchronously to ensure that the index of words is kept perfectly in-sync with the lines in the buffer. However, most of the time this will not be a problem since many typical text edit operations affect only one or two lines, unless you are pasting a 1000-line snippet.

Note that you can freely edit the buffer while it is being indexed, the underlying algorithm is written in such a way that your changes will not break the index or cause errors. If a crash does happen - it is a bug, so please report it.

The speed of indexing is configurable with two options: indexing_interval and indexing_batch_size. Essentially, when indexing, a timer is started, which pulls a batch of indexing_batch_size lines from the buffer, scans them for words, and repeats after indexing_interval milliseconds. Decreasing interval and/or increasing the batch size will make the indexer faster, but at the expense of higher CPU usage and more lag when editing the file while indexing is still in progress. Setting indexing_batch_size to a negative value will switch the indexer to the "synchronous" mode: this will process all lines in one go, take less time in total (since no other code will be running on the Lua thread), but with the obvious downside that the editor UI will be blocked.

The option max_indexed_line_length controls plugin's behavior in files with very long lines. This is known to slow this source down significantly (see issue #13), so by default it will take only the first few kilobytes of the line it is currently on. In other words, very long lines are not ignored, but only a part of them is indexed.

Performance on large text files

This source has been tested on code files of a few megabytes in size (5-10) and contains optimizations for them, however, the indexed words can still take up tens of megabytes of RAM if the file is large. So, if you wish to avoid accidentally running this source on big files, you can tweak get_bufnrs, for example like this:

get_bufnrs = function()
  local buf = vim.api.nvim_get_current_buf()
  local byte_size = vim.api.nvim_buf_get_offset(buf, vim.api.nvim_buf_line_count(buf))
  if byte_size > 1024 * 1024 then -- 1 Megabyte max
    return {}
  end
  return { buf }
end

Of course, this snippet can be combined with any other recipes for get_bufnrs.