Skip to content

Commit 211311e

Browse files
committed
feat(debounce)!: stream debounce usage slightly adjusted and changed
1 parent 69f697d commit 211311e

5 files changed

Lines changed: 88 additions & 86 deletions

File tree

README.md

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -365,8 +365,10 @@ picker:open()
365365

366366
### Stream buffering
367367

368-
Stream buffering controls how quickly entries are flushed to the UI. Smaller `stream_step` values show results sooner but may increase
369-
overhead; larger values reduce overhead but can delay updates. Tune these when you care about perceived responsiveness.
368+
Stream buffering controls how quickly entries are accumulated and handed off to the picker. Smaller `stream_step` values show results sooner
369+
but may increase overhead; larger values reduce overhead but can delay updates. `stream_debounce` controls how aggressively those stream
370+
updates are coalesced before the picker refreshes and, when needed, re-matches the accumulated result set. Tune both when you care about
371+
perceived responsiveness under large or very fast streams.
370372

371373
```lua
372374
-- here is an example of using `stdbuf` to make grep stdout line buffered, this is useful when the executable does not support line buffering,
@@ -776,20 +778,20 @@ These advanced options are not required, but can be used to fine tune the behavi
776778
functionality, performance, and appearance of the picker. Some of the `*_step` and `*_debounce` options are particularly useful for
777779
optimizing the runtime performance when dealing with large datasets or slow content stream providers.
778780

779-
| Field | Type | Description |
780-
| ----------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
781-
| `headers` | `table?` | Help or information headers for the prompt interface. Can be a user-provided table or auto-generated based on `actions`. Header values can be represented as strings or function callback. |
782-
| `match_limit` | `number?` | Maximum number of matches. `nil` means no limit. If a valid non nil number value is provided the fuzzy matching will stop the moment this number is reached. |
783-
| `match_timer` | `number` | Time in milliseconds between processing matching result batches (useful for large result sets). |
784-
| `match_step` | `number` | Number of entries to process in each matching step or batch (useful for large result sets). |
785-
| `stream_map` | `function?` | Optional per-entry transform applied before entries are added to the stream. Return nil/false to skip an entry. |
786-
| `stream_type` | `lines\|bytes` | Type of the stream content (`lines` splits on newlines, `bytes` splits on bytes, both are controller by the stream step size). |
787-
| `stream_step` | `number` | Number of lines, or byte count to read per streaming step determining when to flush the stream items batch, based on the stream content type - lines or bytes. |
788-
| `stream_debounce` | `number` | The time in milliseconds to debounce the flush calls of the stream, this is useful to avoid stream batch flushes in quick succession, when the results accumulate fast enough that we can combine into a single flush call instead, caused by the executable being too fast, or the `stream_step` being too small. |
789-
| `window_size` | `number` | Ratio (0-1) specifying the picker window size relative to the screen. |
790-
| `prompt_debounce` | `number` | Debounce time in ms to delay handling input (helps avoid overwhelming the matching process). |
791-
| `prompt_query` | `string` | Initial user query for starting the picker prompt with. |
792-
| `prompt_decor` | `string,table` | Prefix/suffix for the prompt. Can take the form of a string (just one) or a table (both) providing a table with `{ suffix = "", prefix = "" }` keys. |
781+
| Field | Type | Description |
782+
| ----------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
783+
| `headers` | `table?` | Help or information headers for the prompt interface. Can be a user-provided table or auto-generated based on `actions`. Header values can be represented as strings or function callback. |
784+
| `match_limit` | `number?` | Maximum number of matches. `nil` means no limit. If a valid non nil number value is provided the fuzzy matching will stop the moment this number is reached. |
785+
| `match_timer` | `number` | Time in milliseconds between processing matching result batches (useful for large result sets). |
786+
| `match_step` | `number` | Number of entries to process in each matching step or batch (useful for large result sets). |
787+
| `stream_map` | `function?` | Optional per-entry transform applied before entries are added to the stream. Return nil/false to skip an entry. |
788+
| `stream_type` | `lines\|bytes` | Type of the stream content (`lines` splits on newlines, `bytes` splits on bytes, both are controller by the stream step size). |
789+
| `stream_step` | `number` | Number of lines, or byte count to read per streaming step determining when to flush the stream items batch, based on the stream content type - lines or bytes. |
790+
| `stream_debounce` | `number` | The time in milliseconds to debounce the matching work while a non-interactive picker stream is still producing entries and the user types in a query. As the stream accumulates, unchanged queries continue matching against new stream chunks instead of the complete stream, query changes trigger a debounced full re-match of everything seen thus far. When the query is empty, results are rendered directly without matching. |
791+
| `window_size` | `number` | Ratio (0-1) specifying the picker window size relative to the screen. |
792+
| `prompt_debounce` | `number` | Debounce time in ms to delay handling input (helps avoid overwhelming the matching process). |
793+
| `prompt_query` | `string` | Initial user query for starting the picker prompt with. |
794+
| `prompt_decor` | `string,table` | Prefix/suffix for the prompt. Can take the form of a string (just one) or a table (both) providing a table with `{ suffix = "", prefix = "" }` keys. |
793795

794796
### Option details
795797

@@ -879,9 +881,16 @@ hl? }, { start, length, hl? } }`. The highlight start is automatically offset by
879881
- **stream_step**: The number of lines/bytes to read per streaming step. This determines when to flush the stream batch. This is useful for
880882
large data streams to avoid blocking the UI for too long.
881883

882-
- **stream_debounce**: The number of milliseconds between successive stream flush calls, can help if the `stream_step` is too small or the
883-
executable is too fast and can accumulate `stream_step` entries into the stream fast enough that can cause the `matcher` to be overwhelmed,
884-
by default it is set to 0, as for most use cases it is never really required.
884+
- **stream_debounce**: This option only matters for non-interactive pickers while their stream is still alive and new entries are still
885+
being accumulated. In that state there are three distinct behaviors. If the prompt query is empty, the picker does not fuzzy match at all
886+
and simply renders the accumulated stream results directly. If the query is non-empty and has not changed since the last matching pass, the
887+
picker continues incrementally matching only the newly arrived stream chunk and merges those matches into the running accumulated match
888+
state. If the query changes while the stream is still producing entries, the previous incremental match state is no longer valid, so the
889+
picker must re-match everything that has been streamed so far. `stream_debounce` controls that last case. Instead of immediately starting a
890+
new full match every time the user changes the query while the stream is still growing, the picker waits for this debounce window and then
891+
re-matches the complete accumulated result set once. This keeps rapid prompt edits from repeatedly restarting expensive full-list matching
892+
work while the command is still emitting results. In other words, unchanged queries stay incremental, empty queries bypass matching
893+
entirely, and changed queries are the only case that `stream_debounce` actually delays.
885894

886895
- **window_size**: The ratio (0-1) specifying the picker window size relative to the screen. A value of 0.5 means the picker will take up
887896
half the screen. If the preview is enabled the picker will take up half that height, the rest will be used for the preview window.

lua/fuzzy/match.lua

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,12 @@ function Match:_populate_chunks()
4444
local size = 0
4545
if assert(self.list) and #self.list > 0 then
4646
-- each time we are called we fill the chunks with the next step of items from the source list, the chunks are always the same size except for the last one which can be smaller.
47-
local iteration_limit = self._state.offset + self._options.step
47+
local match_step = self._options.step
48+
local iteration_limit = self._state.offset + match_step
49+
4850
local start_index = self._state.offset + 1
4951
local destination = self._state.chunks
52+
5053
if #self.list < iteration_limit then
5154
-- in case the list has less items than the current step we create a smaller tail chunk to avoid re-sizing the
5255
-- chunks, instead we use a smaller tail table which accepts the very last items, if the new size is invalid
@@ -58,7 +61,7 @@ function Match:_populate_chunks()
5861
if not self._state.tail then
5962
-- only use pooled tail when it is close to the normal step size to avoid
6063
-- shrinking and chruning pool tables.
61-
local use_pool = new_size >= math.floor(self._options.step * 0.50)
64+
local use_pool = new_size >= math.floor(match_step * 0.50)
6265
self._state.tail = use_pool and Pool.obtain(new_size) or {}
6366
end
6467
utils.resize_table(self._state.tail, new_size, false)
@@ -223,7 +226,10 @@ function Match:_match_worker()
223226
self._state.accum[2] = positions
224227
self._state.accum[3] = scores
225228
else
226-
-- merge into scratch buffer to avoid shrinking pooled buffers, then copy into accum
229+
-- merge into intermediate scratch buffer to avoid shrinking pooled buffers, first merge accum and current
230+
-- matching results into a temporary constant buffer that only expands. Then move back the results into the
231+
-- accum that is user facing results table expressing the total amount of matches accumulated so far ordered and
232+
-- sorted according to the score
227233
local accum = self._state.accum
228234
local size = #accum[1] + #matches
229235
local scratch = utils.timed_call(Match.merge,
@@ -379,7 +385,9 @@ function Match:match(list, pattern, callback, transform)
379385
if not self._state.buffer then
380386
-- prepare buffer for storing intermediate results, start from step-sized buffers, the finaly buffer will likely contain much less
381387
-- items than the initial list.
382-
local size = math.min(#list, self._options.step or 1)
388+
local size = math.min(
389+
#list, self._options.step
390+
)
383391
self._state.buffer = {
384392
Pool.obtain(size),
385393
Pool.obtain(size),

0 commit comments

Comments
 (0)