summaryrefslogtreecommitdiff
path: root/lua/blink/cmp/fuzzy/init.lua
blob: ad4db037271f604901aa11cc5d7fdeaef14fb0aa (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
111
112
113
114
115
116
117
118
local config = require('blink.cmp.config')

--- @class blink.cmp.Fuzzy
local fuzzy = {
  rust = require('blink.cmp.fuzzy.rust'),
  haystacks_by_provider_cache = {},
  has_init_db = false,
}

function fuzzy.init_db()
  if fuzzy.has_init_db then return end

  fuzzy.rust.init_db(vim.fn.stdpath('data') .. '/blink/cmp/fuzzy.db', config.use_unsafe_no_lock)

  vim.api.nvim_create_autocmd('VimLeavePre', {
    callback = fuzzy.rust.destroy_db,
  })

  fuzzy.has_init_db = true
end

---@param item blink.cmp.CompletionItem
function fuzzy.access(item)
  fuzzy.init_db()

  -- writing to the db takes ~10ms, so schedule writes in another thread
  vim.uv
    .new_work(function(itm, cpath)
      package.cpath = cpath
      require('blink.cmp.fuzzy.rust').access(vim.mpack.decode(itm))
    end, function() end)
    :queue(vim.mpack.encode(item), package.cpath)
end

---@param lines string
function fuzzy.get_words(lines) return fuzzy.rust.get_words(lines) end

--- @param line string
--- @param cursor_col number
--- @param haystack string[]
--- @param range blink.cmp.CompletionKeywordRange
function fuzzy.fuzzy_matched_indices(line, cursor_col, haystack, range)
  return fuzzy.rust.fuzzy_matched_indices(line, cursor_col, haystack, range == 'full')
end

--- @param line string
--- @param cursor_col number
--- @param haystacks_by_provider table<string, blink.cmp.CompletionItem[]>
--- @param range blink.cmp.CompletionKeywordRange
--- @return blink.cmp.CompletionItem[]
function fuzzy.fuzzy(line, cursor_col, haystacks_by_provider, range)
  fuzzy.init_db()

  for provider_id, haystack in pairs(haystacks_by_provider) do
    -- set the provider items once since Lua <-> Rust takes the majority of the time
    if fuzzy.haystacks_by_provider_cache[provider_id] ~= haystack then
      fuzzy.haystacks_by_provider_cache[provider_id] = haystack
      fuzzy.rust.set_provider_items(provider_id, haystack)
    end
  end

  -- get the nearby words
  local cursor_row = vim.api.nvim_win_get_cursor(0)[1]
  local start_row = math.max(0, cursor_row - 30)
  local end_row = math.min(cursor_row + 30, vim.api.nvim_buf_line_count(0))
  local nearby_text = table.concat(vim.api.nvim_buf_get_lines(0, start_row, end_row, false), '\n')
  local nearby_words = #nearby_text < 10000 and fuzzy.rust.get_words(nearby_text) or {}

  local keyword_start_col, keyword_end_col =
    require('blink.cmp.fuzzy').get_keyword_range(line, cursor_col, config.completion.keyword.range)
  local keyword_length = keyword_end_col - keyword_start_col

  local filtered_items = {}
  for provider_id, haystack in pairs(haystacks_by_provider) do
    -- perform fuzzy search
    local scores, matched_indices = fuzzy.rust.fuzzy(line, cursor_col, provider_id, {
      -- each matching char is worth 7 points (+ 1 for matching capitalization)
      -- and it receives a bonus for capitalization, delimiter and prefix
      -- so this should generally be good
      -- TODO: make this configurable
      -- TODO: instead of a min score, set X number of allowed typos
      min_score = config.fuzzy.use_typo_resistance and (6 * keyword_length) or 0,
      use_typo_resistance = config.fuzzy.use_typo_resistance,
      use_frecency = config.fuzzy.use_frecency and keyword_length > 0,
      use_proximity = config.fuzzy.use_proximity and keyword_length > 0,
      sorts = config.fuzzy.sorts,
      nearby_words = nearby_words,
      match_suffix = range == 'full',
    })

    for idx, item_index in ipairs(matched_indices) do
      local item = haystack[item_index + 1]
      item.score = scores[idx]
      table.insert(filtered_items, item)
    end
  end

  return require('blink.cmp.fuzzy.sort').sort(filtered_items, config.fuzzy.sorts)
end

--- @param line string
--- @param col number
--- @param range? blink.cmp.CompletionKeywordRange
--- @return number, number
function fuzzy.get_keyword_range(line, col, range)
  return require('blink.cmp.fuzzy.rust').get_keyword_range(line, col, range == 'full')
end

--- @param item blink.cmp.CompletionItem
--- @param line string
--- @param col number
--- @param range blink.cmp.CompletionKeywordRange
--- @return number, number
function fuzzy.guess_edit_range(item, line, col, range)
  return require('blink.cmp.fuzzy.rust').guess_edit_range(item, line, col, range == 'full')
end

return fuzzy