2025-09-15
       Tags: emacs
       
       Helpful [1]  is a fantastic Emacs package that drastically improves
       the builtin `help-mode`.
       One  of the particularly nice  features is finding references  to a
       particular symbol.
       Unfortunately it can be painfully slow in practice due  to actually
       parsing every loaded Elisp file:
       (require 'benchmark)
       (benchmark-elapse (elisp-refs-function #'car))
       24.001221
       
       Instead  of  walking the ASTs of  every file, why not  do  a  regex
       search?
       
       It sacrifices correctness (and  completely ignores  the type of the
       symbol), but  it turns  out  to be  orders of magnitude  faster  in
       practice.
       Bonus  points for  using  a fast  tool like ripgrep and extra bonus
       points for completing  the work asynchronously so as not  to  block
       Emacs's main thread.
       
           I  don't  mean  to  denigrate elisp-refs  [2];  the  author
           clearly has put a lot of thought into performance  and it's
           only  natural  that using an  approach  that  heavily  cuts
           corners  together with  a  tool  implemented  in  optimized
           machine code instead of Elisp (which is especially hampered
           by GC performance) will lead to faster results.
           I'm a  ripgrep  junkie and I prefer it  for  grokking  most
           codebases,  but the speed  comes  at the cost of  having to
           sift through many false positives.
       
       I use the wonderful deadgrep [3] package to do just that:
       (when (locate-library "deadgrep")
         (require 'deadgrep)
         (fset 'deadgrep--arguments-orig (symbol-function #'deadgrep--arguments))
         (define-advice helpful--all-references (:override (button) fysh/use-deadgrep)
           (cl-letf* (((symbol-function 'deadgrep--arguments)
                       (lambda (&rest args)
                         `("--follow" "--type=elisp" "--type=gzip" "--search-zip" ,@(butlast (apply #'deadgrep--arguments-orig args)) ,lisp-directory ,(-first (lambda (p) (string-suffix-p "/share/emacs/site-lisp" p)) load-path)))))
             (deadgrep (symbol-name (button-get button 'symbol)) default-directory))))
       
       The code is quite hacky because deadgrep is  not designed to  allow
       passing multiple directories in a single  search, but this gets the
       job done.
       (with-temp-buffer
         (let ((button (make-button (point-min) (point-max)))
               (time-to-draw)
               (time-to-completion))
           (button-put button 'symbol #'car)
       
           (setq time-to-completion (benchmark-elapse
                                      (setq time-to-draw (benchmark-elapse (helpful--all-references button)))
                                      (while deadgrep--running (sit-for 0.1 'nodisp))))
           (format "Time to first draw: %s\nTime to completion: %s" time-to-draw time-to-completion)))
       Time to first draw: 0.084542
       Time to completion: 38.184606
       
       In this  pathological case  the  total  time  is slower than  using
       `elisp-refs-function` because  there are almost
       four  times as  many  matches  because  of  comments/docstrings and
       partial matches  like  `mapcar`  or  `car-safe`
       (including   symbols   that   aren't  functions  like   `byte-
       car`).
       The  major   difference  is   that   while   the   performance   of
       `elisp-refs-*`  functions[^fn:1]   are  roughly
       constant regardless  of the total number of references to a symbol,
       using ripgrep is significantly faster for terms with fewer than 10k
       matches  (not  to  mention  that   you   can  browse   the  results
       immediately).
       
       If  you want to remove  the  partial  matches,  you  could use  the
       following advice instead:
       (define-advice helpful--all-references (:override (button) fysh/use-deadgrep)
         (cl-letf* (((symbol-function 'deadgrep--arguments)
                     (lambda (&rest args)
                       `("--follow" "--type=elisp" "--type=gzip" "--search-zip" ,@(butlast (apply #'deadgrep--arguments-orig args) 3) "--no-fixed-strings" "--" ,(car args) ,lisp-directory ,(-first (lambda (p) (string-suffix-p "/share/emacs/site-lisp" p)) load-path...
           (deadgrep (format "(\\(|#?'| )(%s) " (symbol-name (button-get button 'symbol))) default-directory)))
       
       This unfortunately will highlight  the entire match instead of just
       the capturing group, so I prefer not to use it (althgough the speed
       is mostly the same, if not a bit faster).
       
       [^fn:1]: `elisp-refs-symbol` is faster than its
       counterparts due to reduced implementation complexity
       
       References:
 (HTM)   [1] Helpful
 (HTM)   [2] elisp-refs
 (HTM)   [3] deadgrep
       >=================================================================<
       
 (DIR) Blog
 (DIR) Writeups
 (DIR) jp
       
       copyright 2026 George Huebner
 (HTM) email