1414--- @field public lines_count number
1515--- @field public timer_current_line number
1616--- @field public lines_words table<number , 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
174187function 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
0 commit comments