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
|
-- todo: nvim-cmp only updates the lines that got changed which is better
-- but this is *speeeeeed* and simple. should add the better way
-- but ensure it doesn't add too much complexity
local uv = vim.uv
--- @param bufnr integer
--- @return string
local function get_buf_text(bufnr)
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
if bufnr ~= vim.api.nvim_get_current_buf() then return table.concat(lines, '\n') end
-- exclude word under the cursor for the current buffer
local line_number = vim.api.nvim_win_get_cursor(0)[1]
local column = vim.api.nvim_win_get_cursor(0)[2]
local line = lines[line_number]
local start_col = column
while start_col > 1 do
local char = line:sub(start_col, start_col)
if char:match('[%w_\\-]') == nil then break end
start_col = start_col - 1
end
local end_col = column
while end_col < #line do
local char = line:sub(end_col + 1, end_col + 1)
if char:match('[%w_\\-]') == nil then break end
end_col = end_col + 1
end
lines[line_number] = line:sub(1, start_col) .. ' ' .. line:sub(end_col + 1)
return table.concat(lines, '\n')
end
local function words_to_items(words)
local items = {}
for _, word in ipairs(words) do
table.insert(items, {
label = word,
kind = require('blink.cmp.types').CompletionItemKind.Text,
insertTextFormat = vim.lsp.protocol.InsertTextFormat.PlainText,
insertText = word,
})
end
return items
end
--- @param buf_text string
--- @param callback fun(items: blink.cmp.CompletionItem[])
local function run_sync(buf_text, callback) callback(words_to_items(require('blink.cmp.fuzzy').get_words(buf_text))) end
local function run_async(buf_text, callback)
local worker = uv.new_work(
-- must use ffi directly since the normal one requires the config which isnt present
function(items, cpath)
package.cpath = cpath
return table.concat(require('blink.cmp.fuzzy.rust').get_words(items), '\n')
end,
function(words)
local items = words_to_items(vim.split(words, '\n'))
vim.schedule(function() callback(items) end)
end
)
worker:queue(buf_text, package.cpath)
end
--- @class blink.cmp.BufferOpts
--- @field get_bufnrs fun(): integer[]
--- Public API
local buffer = {}
function buffer.new(opts)
--- @cast opts blink.cmp.BufferOpts
local self = setmetatable({}, { __index = buffer })
self.get_bufnrs = opts.get_bufnrs
or function()
return vim
.iter(vim.api.nvim_list_wins())
:map(function(win) return vim.api.nvim_win_get_buf(win) end)
:filter(function(buf) return vim.bo[buf].buftype ~= 'nofile' end)
:totable()
end
return self
end
function buffer:get_completions(_, callback)
local transformed_callback = function(items)
callback({ is_incomplete_forward = false, is_incomplete_backward = false, items = items })
end
vim.schedule(function()
local bufnrs = require('blink.cmp.lib.utils').deduplicate(self.get_bufnrs())
local buf_texts = {}
for _, buf in ipairs(bufnrs) do
table.insert(buf_texts, get_buf_text(buf))
end
local buf_text = table.concat(buf_texts, '\n')
-- should take less than 2ms
if #buf_text < 20000 then
run_sync(buf_text, transformed_callback)
-- should take less than 10ms
elseif #buf_text < 500000 then
run_async(buf_text, transformed_callback)
-- too big so ignore
else
transformed_callback({})
end
end)
-- TODO: cancel run_async
return function() end
end
return buffer
|