Skip to content

Debounce search buffer updates during feed fetches#558

Open
benthamite wants to merge 1 commit intoskeeto:masterfrom
benthamite:debounce-search-update
Open

Debounce search buffer updates during feed fetches#558
benthamite wants to merge 1 commit intoskeeto:masterfrom
benthamite:debounce-search-update

Conversation

@benthamite
Copy link
Contributor

@benthamite benthamite commented Mar 8, 2026

Summary

  • Debounce elfeed-search-update so that rapid feed completions coalesce into a single redraw instead of one per feed
  • Fix an argument mismatch where elfeed-update-hooks passed the feed URL as the force parameter, bypassing the staleness guard and forcing a full redraw on every callback
  • Add user-customizable elfeed-search-update-delay (default 1.5s)

Closes #293. Also addresses #317 and #474.

Problem

When elfeed-update fetches feeds, each completion runs:

(run-hook-with-args 'elfeed-update-hooks url)

elfeed-search-update is on this hook and receives the URL string as its optional force argument. Since any string is truthy, every feed completion triggers a forced full redraw of the search buffer (full DB scan via with-elfeed-db-visit, erase-buffer, re-render all matching entries). With N feeds, this produces N forced full redraws fired back-to-back as run-at-time 0 timer events, each blocking the main thread.

With ~300 feeds, this freezes Emacs intermittently for 30-60 seconds.

Fix

All changes are in elfeed-search.el:

  1. Debounce non-forced calls. elfeed-search-update now dispatches: when force is non-nil it redraws immediately (preserving existing behavior for interactive g, filter changes, mode init); when force is nil it schedules a trailing-edge debounce timer. If another call arrives before the timer fires, it resets. This coalesces hundreds of rapid callbacks into a handful of redraws.

  2. Fix the argument mismatch. A new wrapper elfeed-search--update-debounced discards the URL argument from the hook and calls elfeed-search-update with no arguments, entering the debounce path instead of the forced path. This replaces elfeed-search-update on elfeed-update-hooks.

  3. Timer cleanup. The debounce timer is canceled in elfeed-search--unload when the search buffer is killed.

Result

With 317 feeds: ~5-10 redraws instead of 317+. Emacs remains fully responsive throughout the update. Interactive refresh (g), filter changes, and live filtering are unaffected.

Backward compatibility

  • elfeed-update-hooks contract unchanged: hook still fires once per feed completion
  • elfeed-search-update-hook unchanged: still fires after every actual buffer update
  • elfeed-search-update still accepts optional force with same semantics
  • elfeed-search-update--force still forces immediate update
  • Custom elfeed-search-print-entry-function implementations are unaffected

Test plan

  • Byte-compiles cleanly (zero new warnings)
  • All 39 existing tests pass
  • G in search buffer: feeds fetch without freezing, buffer updates after completions settle
  • g in search buffer: immediate forced refresh (no debounce)
  • Live filter (s): still updates immediately as you type
  • elfeed-search-set-filter: immediate update

When elfeed fetches feeds, each completion callback triggers
`elfeed-search-update' via `elfeed-update-hooks'.  The URL string
passed by `run-hook-with-args' was interpreted as a truthy `force'
argument, causing every callback to force a full buffer redraw
(DB scan + erase + re-render all entries).  With hundreds of feeds
this produced hundreds of forced redraws back-to-back, freezing Emacs.

Split `elfeed-search-update' into a dispatcher: forced calls (interactive
`g', filter changes, mode init) redraw immediately; non-forced calls
schedule a trailing-edge debounce timer (`elfeed-search-update-delay',
default 1.5s).  A new `elfeed-search--update-debounced' wrapper on
`elfeed-update-hooks' discards the URL argument so callbacks enter the
debounce path.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Suggestion: Only redraw search buffer after refresh completes

1 participant