14
14
--- @field public lines_count number
15
15
--- @field public timer_current_line number
16
16
--- @field public lines_words table<number , false | string[]>
17
+ --- @field public queue table<number , boolean>
18
+ --- @field public debounce_timer cmp_buffer.Timer | nil
17
19
--- @field public unique_words_curr_line table<string , boolean>
18
20
--- @field public unique_words_other_lines table<string , boolean>
19
21
--- @field public unique_words_curr_line_dirty boolean
@@ -50,6 +52,11 @@ function buffer.new(bufnr, opts)
50
52
self .timer_current_line = - 1
51
53
self .lines_words = {}
52
54
55
+ self .queue = {}
56
+ if self .opts .debounce > 0 then
57
+ self .debounce_timer = timer .new ()
58
+ end
59
+
53
60
self .unique_words_curr_line = {}
54
61
self .unique_words_other_lines = {}
55
62
self .unique_words_curr_line_dirty = true
@@ -75,6 +82,12 @@ function buffer.close(self)
75
82
self .timer_current_line = - 1
76
83
self .lines_words = {}
77
84
85
+ self .queue = {}
86
+ if self .debounce_timer then
87
+ self .debounce_timer :close ()
88
+ self .debounce_timer = nil
89
+ end
90
+
78
91
self .unique_words_curr_line = {}
79
92
self .unique_words_other_lines = {}
80
93
self .unique_words_curr_line_dirty = false
@@ -174,14 +187,12 @@ end
174
187
function buffer .watch (self )
175
188
self .lines_count = vim .api .nvim_buf_line_count (self .bufnr )
176
189
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.
185
196
vim .api .nvim_buf_attach (self .bufnr , false , {
186
197
-- NOTE: line indexes are 0-based and the last line is not inclusive.
187
198
on_lines = function (_ , _ , _ , first_line , old_last_line , new_last_line , _ , _ , _ )
@@ -201,14 +212,15 @@ function buffer.watch(self)
201
212
local new_lines_count = old_lines_count + delta
202
213
if new_lines_count == 0 then -- clear
203
214
-- 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.
205
216
-- Which is not true, a buffer always contains at least one empty line,
206
217
-- only unloaded buffers contain zero lines.
207
218
new_lines_count = 1
208
219
for i = old_lines_count , 2 , - 1 do
209
220
self .lines_words [i ] = nil
210
221
end
211
222
self .lines_words [1 ] = {}
223
+ self .queue = {}
212
224
elseif delta > 0 then -- append
213
225
-- Explicitly reserve more slots in the array part of the lines table,
214
226
-- all of them will be filled in the next loop, but in reverse order
@@ -220,6 +232,7 @@ function buffer.watch(self)
220
232
-- Move forwards the unchanged elements in the tail part.
221
233
for i = old_lines_count , old_last_line + 1 , - 1 do
222
234
self .lines_words [i + delta ] = self .lines_words [i ]
235
+ self .queue [i + delta ] = self .queue [i ]
223
236
end
224
237
-- Fill in new tables for the added lines.
225
238
for i = old_last_line + 1 , new_last_line do
@@ -229,11 +242,13 @@ function buffer.watch(self)
229
242
-- Move backwards the unchanged elements in the tail part.
230
243
for i = old_last_line + 1 , old_lines_count do
231
244
self .lines_words [i + delta ] = self .lines_words [i ]
245
+ self .queue [i + delta ] = self .queue [i ]
232
246
end
233
247
-- Remove (already copied) tables from the end, in reverse order, so
234
248
-- that we don't make holes in the lines table.
235
249
for i = old_lines_count , new_lines_count + 1 , - 1 do
236
250
self .lines_words [i ] = nil
251
+ self .queue [i ] = nil
237
252
end
238
253
end
239
254
self .lines_count = new_lines_count
@@ -269,7 +284,21 @@ function buffer.watch(self)
269
284
self .words_distances_dirty = true
270
285
271
286
-- 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
273
302
end ,
274
303
275
304
on_reload = function (_ , _ )
@@ -302,6 +331,7 @@ function buffer.index_line(self, linenr, line)
302
331
else
303
332
clear_table (words )
304
333
end
334
+ self .queue [linenr ] = nil
305
335
local word_i = 1
306
336
307
337
local remaining = line
0 commit comments