<feed xmlns='http://www.w3.org/2005/Atom'>
<title>dotemacs/tests/test-modeline-config-vc-cache.el, branch main</title>
<subtitle>My Emacs configuration
</subtitle>
<id>https://git.cjennings.net/dotemacs/atom?h=main</id>
<link rel='self' href='https://git.cjennings.net/dotemacs/atom?h=main'/>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/'/>
<updated>2026-05-04T01:24:14+00:00</updated>
<entry>
<title>perf: cache modeline VC data per buffer</title>
<updated>2026-05-04T01:24:14+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-05-04T01:24:14+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/dotemacs/commit/?id=ba1a0249bfbc61ba3590ec0c9cd8b5568980ab22'/>
<id>urn:sha1:ba1a0249bfbc61ba3590ec0c9cd8b5568980ab22</id>
<content type='text'>
The custom modeline's VC `:eval` form was calling `vc-backend`, `vc-working-revision`, `vc-git--symbolic-ref`, and `vc-state` on every redisplay. Mode-line eval runs every keystroke. For a large git repo or a TRAMP buffer over SSH, the round-trip cost shows up as visible input lag.

I split the inline form into helpers and added a buffer-local cache. `cj/modeline-vc-info` returns the cached plist when its TTL hasn't expired and the cache key still matches. The TTL defaults to 5 seconds via `cj/modeline-vc-cache-ttl`. Save and revert hooks invalidate the cache so the user sees state changes promptly. The render path (`cj/modeline-vc-render`) is now a separate function so it can be tested without touching VC at all.

Remote files are skipped by default. `cj/modeline-vc-show-remote` opts back in for cases where TRAMP VC is fast enough to be worth it.

Measured on this repo: uncached reads were about 2.4 ms each, cached reads were about 0.0025 ms each, and remote-skipped reads pay only the cheap `file-remote-p` check.

I added five tests in `tests/test-modeline-config-vc-cache.el`: cache reuse within TTL (backend called once for two reads), refresh after TTL expiry (called twice), remote-file bypass (no backend call, nil result), cache clear (buffer-locals reset to nil), and render output (branch text + face metadata preserved).
</content>
</entry>
</feed>
