diff options
| author | Craig Jennings <c@cjennings.net> | 2026-05-03 20:24:14 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-05-03 20:24:14 -0500 |
| commit | ba1a0249bfbc61ba3590ec0c9cd8b5568980ab22 (patch) | |
| tree | 9246df151665b1661b0772284d2b5ade0c381be6 /tests/test-modeline-config-vc-cache.el | |
| parent | ff8071ff274342f270de32ac3a8b282b6f75adaa (diff) | |
| download | dotemacs-ba1a0249bfbc61ba3590ec0c9cd8b5568980ab22.tar.gz dotemacs-ba1a0249bfbc61ba3590ec0c9cd8b5568980ab22.zip | |
perf: cache modeline VC data per buffer
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).
Diffstat (limited to 'tests/test-modeline-config-vc-cache.el')
| -rw-r--r-- | tests/test-modeline-config-vc-cache.el | 102 |
1 files changed, 102 insertions, 0 deletions
diff --git a/tests/test-modeline-config-vc-cache.el b/tests/test-modeline-config-vc-cache.el new file mode 100644 index 00000000..b6aafbfb --- /dev/null +++ b/tests/test-modeline-config-vc-cache.el @@ -0,0 +1,102 @@ +;;; test-modeline-config-vc-cache.el --- Tests for modeline VC cache -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for the custom modeline VC helper behavior. The modeline evaluates +;; often, so expensive VC calls should be cached and remote files should be +;; skipped by default. + +;;; Code: + +(require 'ert) +(require 'cl-lib) + +(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) + +(require 'modeline-config) + +(ert-deftest test-modeline-config-vc-info-caches-per-buffer () + "Repeated VC info reads in one buffer should reuse the cache." + (let ((cj/modeline-vc-cache-ttl 60) + (backend-calls 0)) + (with-temp-buffer + (setq buffer-file-name "/tmp/project/file.el") + (cl-letf (((symbol-function 'float-time) + (lambda (&optional _time) 100.0)) + ((symbol-function 'file-remote-p) + (lambda (&rest _args) nil)) + ((symbol-function 'vc-backend) + (lambda (_file) (cl-incf backend-calls) 'Git)) + ((symbol-function 'vc-working-revision) + (lambda (_file _backend) "main-revision")) + ((symbol-function 'vc-git--symbolic-ref) + (lambda (_file) "main")) + ((symbol-function 'vc-state) + (lambda (_file _backend) 'edited))) + (should (equal (cj/modeline-vc-info) + '(:branch "main" :state edited))) + (should (equal (cj/modeline-vc-info) + '(:branch "main" :state edited))) + (should (= backend-calls 1)))))) + +(ert-deftest test-modeline-config-vc-info-refreshes-after-ttl () + "Expired VC cache entries should be refreshed." + (let ((cj/modeline-vc-cache-ttl 5) + (backend-calls 0) + (times '(100.0 106.0))) + (with-temp-buffer + (setq buffer-file-name "/tmp/project/file.el") + (cl-letf (((symbol-function 'float-time) + (lambda (&optional _time) (or (pop times) 106.0))) + ((symbol-function 'file-remote-p) + (lambda (&rest _args) nil)) + ((symbol-function 'vc-backend) + (lambda (_file) (cl-incf backend-calls) 'Git)) + ((symbol-function 'vc-working-revision) + (lambda (_file _backend) "main-revision")) + ((symbol-function 'vc-git--symbolic-ref) + (lambda (_file) "main")) + ((symbol-function 'vc-state) + (lambda (_file _backend) 'up-to-date))) + (should (equal (cj/modeline-vc-info) + '(:branch "main" :state up-to-date))) + (should (equal (cj/modeline-vc-info) + '(:branch "main" :state up-to-date))) + (should (= backend-calls 2)))))) + +(ert-deftest test-modeline-config-vc-info-skips-remote-files-by-default () + "Remote files should not call VC unless explicitly enabled." + (let ((backend-calls 0)) + (with-temp-buffer + (setq buffer-file-name "/ssh:host:/tmp/project/file.el") + (cl-letf (((symbol-function 'file-remote-p) + (lambda (&rest _args) t)) + ((symbol-function 'vc-backend) + (lambda (_file) (cl-incf backend-calls) 'Git))) + (should-not (cj/modeline-vc-info)) + (should (= backend-calls 0)))))) + +(ert-deftest test-modeline-config-vc-cache-clear-resets-buffer-cache () + "Clearing the VC cache should remove buffer-local cached values." + (with-temp-buffer + (setq cj/modeline-vc-cache-key '("/tmp/project/file.el") + cj/modeline-vc-cache-time 100.0 + cj/modeline-vc-cache-value '(:branch "main" :state edited)) + (cj/modeline-vc-cache-clear) + (should-not cj/modeline-vc-cache-key) + (should-not cj/modeline-vc-cache-time) + (should-not cj/modeline-vc-cache-value))) + +(ert-deftest test-modeline-config-vc-render-formats-branch-and-state () + "VC rendering should keep the branch text and state face metadata." + (cl-letf (((symbol-function 'cj/modeline-string-cut-middle) + (lambda (str) str))) + (let ((rendered (cj/modeline-vc-render '(:branch "feature/cache" + :state edited)))) + (should (string-match-p "feature/cache" rendered)) + (should (text-property-any 0 (length rendered) + 'face 'vc-edited-state rendered)) + (should (text-property-any 0 (length rendered) + 'mouse-face 'mode-line-highlight rendered))))) + +(provide 'test-modeline-config-vc-cache) +;;; test-modeline-config-vc-cache.el ends here |
