Skip to content

Commit ba6592c

Browse files
committed
Add debouncing to per-update indexing
1 parent 62fc67a commit ba6592c

File tree

3 files changed

+48
-10
lines changed

3 files changed

+48
-10
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,13 @@ _Default:_ `1024 * 40` (40 Kilobytes)
129129

130130
Optimization option. See the section [Indexing](#indexing-and-how-to-optimize-it).
131131

132+
### debounce (type: number)
133+
134+
_Default_: `400`
135+
136+
Optimization option. The time (ms) for debouncing the per-update indexing.
137+
Use `0` for no debouncing.
138+
132139

133140
## Locality bonus comparator (distance-based sorting)
134141

lua/cmp_buffer/buffer.lua

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ end
1414
---@field public lines_count number
1515
---@field public timer_current_line number
1616
---@field public lines_words table<number, false|string[]>
17+
---@field public queue table<number, boolean>
18+
---@field public debounce_timer cmp_buffer.Timer|nil
1719
---@field public unique_words_curr_line table<string, boolean>
1820
---@field public unique_words_other_lines table<string, boolean>
1921
---@field public unique_words_curr_line_dirty boolean
@@ -50,6 +52,11 @@ function buffer.new(bufnr, opts)
5052
self.timer_current_line = -1
5153
self.lines_words = {}
5254

55+
self.queue = {}
56+
if self.opts.debounce > 0 then
57+
self.debounce_timer = timer.new()
58+
end
59+
5360
self.unique_words_curr_line = {}
5461
self.unique_words_other_lines = {}
5562
self.unique_words_curr_line_dirty = true
@@ -75,6 +82,12 @@ function buffer.close(self)
7582
self.timer_current_line = -1
7683
self.lines_words = {}
7784

85+
self.queue = {}
86+
if self.debounce_timer then
87+
self.debounce_timer:close()
88+
self.debounce_timer = nil
89+
end
90+
7891
self.unique_words_curr_line = {}
7992
self.unique_words_other_lines = {}
8093
self.unique_words_curr_line_dirty = false
@@ -174,14 +187,12 @@ end
174187
function buffer.watch(self)
175188
self.lines_count = vim.api.nvim_buf_line_count(self.bufnr)
176189

177-
-- NOTE: As far as I know, indexing in watching can't be done asynchronously
178-
-- because even built-in commands generate multiple consequent `on_lines`
179-
-- events, and I'm not even mentioning plugins here. To get accurate results
180-
-- we would have to either re-index the entire file on throttled events (slow
181-
-- and looses the benefit of on_lines watching), or put the events in a
182-
-- queue, which would complicate the plugin a lot. Plus, most changes which
183-
-- trigger this event will be from regular editing, and so 99% of the time
184-
-- they will affect only 1-2 lines.
190+
-- NOTE: Indexing in watching can't be done asynchronously because many
191+
-- editing commands generate multiple `on_lines` events for a single edit. To
192+
-- get accurate results, the indexer should re-index the changed lines on
193+
-- each event. This can be optimized by debouncing the indexer. On each
194+
-- event, we mark the lines to be re-indexed, and run the indexer when we
195+
-- don't receive events for a while.
185196
vim.api.nvim_buf_attach(self.bufnr, false, {
186197
-- NOTE: line indexes are 0-based and the last line is not inclusive.
187198
on_lines = function(_, _, _, first_line, old_last_line, new_last_line, _, _, _)
@@ -201,14 +212,15 @@ function buffer.watch(self)
201212
local new_lines_count = old_lines_count + delta
202213
if new_lines_count == 0 then -- clear
203214
-- This branch protects against bugs after full-file deletion. If you
204-
-- do, for example, gdGG, the new_last_line of the event will be zero.
215+
-- do, for example, ggdG, the new_last_line of the event will be zero.
205216
-- Which is not true, a buffer always contains at least one empty line,
206217
-- only unloaded buffers contain zero lines.
207218
new_lines_count = 1
208219
for i = old_lines_count, 2, -1 do
209220
self.lines_words[i] = nil
210221
end
211222
self.lines_words[1] = {}
223+
self.queue = {}
212224
elseif delta > 0 then -- append
213225
-- Explicitly reserve more slots in the array part of the lines table,
214226
-- all of them will be filled in the next loop, but in reverse order
@@ -220,6 +232,7 @@ function buffer.watch(self)
220232
-- Move forwards the unchanged elements in the tail part.
221233
for i = old_lines_count, old_last_line + 1, -1 do
222234
self.lines_words[i + delta] = self.lines_words[i]
235+
self.queue[i + delta] = self.queue[i]
223236
end
224237
-- Fill in new tables for the added lines.
225238
for i = old_last_line + 1, new_last_line do
@@ -229,11 +242,13 @@ function buffer.watch(self)
229242
-- Move backwards the unchanged elements in the tail part.
230243
for i = old_last_line + 1, old_lines_count do
231244
self.lines_words[i + delta] = self.lines_words[i]
245+
self.queue[i + delta] = self.queue[i]
232246
end
233247
-- Remove (already copied) tables from the end, in reverse order, so
234248
-- that we don't make holes in the lines table.
235249
for i = old_lines_count, new_lines_count + 1, -1 do
236250
self.lines_words[i] = nil
251+
self.queue[i] = nil
237252
end
238253
end
239254
self.lines_count = new_lines_count
@@ -269,7 +284,21 @@ function buffer.watch(self)
269284
self.words_distances_dirty = true
270285

271286
-- replace lines
272-
self:index_range(first_line, new_last_line)
287+
if self.debounce_timer then
288+
for i = first_line + 1, new_last_line do
289+
self.queue[i] = true
290+
end
291+
self.debounce_timer:stop()
292+
self.debounce_timer:start(self.opts.debounce, 0, vim.schedule_wrap(function()
293+
self:safe_buf_call(function()
294+
for linenr, _ in pairs(self.queue) do
295+
self:index_line(linenr, vim.api.nvim_buf_get_lines(self.bufnr, linenr - 1, linenr, true)[1])
296+
end
297+
end)
298+
end))
299+
else
300+
self:index_range(first_line, new_last_line)
301+
end
273302
end,
274303

275304
on_reload = function(_, _)
@@ -302,6 +331,7 @@ function buffer.index_line(self, linenr, line)
302331
else
303332
clear_table(words)
304333
end
334+
self.queue[linenr] = nil
305335
local word_i = 1
306336

307337
local remaining = line

lua/cmp_buffer/source.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ local defaults = {
1818
indexing_batch_size = 1000,
1919
indexing_interval = 100,
2020
max_indexed_line_length = 1024 * 40,
21+
debounce = 400,
2122
}
2223

2324
local source = {}

0 commit comments

Comments
 (0)