This is a continuation of my post Using Eldoc with
Magit where I've shown how to define a
function to be added to eldoc-documentation-functions
in
Magit status or log buffers which shows the author and
diffstat of the commit under point. That function has served me well until it
started to produce hangs in emacs at work.
The hangs began when I've started working with a branch which implements a major architectural restructuring of great parts of our code-base at work. That branch has been created more than two years ago and it has thousands of commits not in other branches. However, there's a merge cascade so that all fixes to release branches will eventually also be merged into that branch.
As it turned out, git show
can become quite slow (up to several seconds) when
invoked on a merge commit between very divergent branches. So whenever point
was on a merge commit, Eldoc would run my function doing a git show
shell
command and block until that finished.
Long story short: Eldoc in Emacs 28.1 and above has support for asynchronous
documentation functions in terms that a documentation function now receives a
callback
function which it should call with the documentation string once
it's computed. In my last post, I've
just ignored it.
So without further ado, here's the workhorse function which given a callback
and a commit
(which actually might be any object git show
can operate on)
computes the author/date/diffstat information asynchronously and then calls
callback
with it.
(defvar th/eldoc-git-show-stat--process nil)
(defun th/eldoc-git-show-stat (callback commit)
"Compute diffstat for COMMIT asynchronously, then call CALLBACK with it."
;; Kill the possibly still running old process and its buffer.
(when (processp th/eldoc-git-show-stat--process)
(let ((buf (process-buffer th/eldoc-git-show-stat--process)))
(when (process-live-p th/eldoc-git-show-stat--process)
(let (confirm-kill-processes)
(kill-process th/eldoc-git-show-stat--process)))
(when (buffer-live-p buf)
(kill-buffer buf))))
;; Spawn a new "git show" process.
(let* ((cmd (list "git" "--no-pager" "show"
"--no-color"
;; Author Name <author@email.com>, <date-and-time>
"--format=format:%an <%ae>, %aD"
"--stat=80"
commit)))
;; An async eldoc-documentation-function must also return a non-nil,
;; non-string result if it's applicable for computing a documentation
;; string, so we set and return the new process here.
(setq th/eldoc-git-show-stat--process
(make-process
:name "eldoc-git-show"
:buffer (generate-new-buffer " *git-show-stat*")
:noquery t
:command cmd
:sentinel (lambda (proc event)
(when (eq (process-status proc) 'exit)
(with-current-buffer (process-buffer proc)
(goto-char (point-min))
(put-text-property (point-min)
(line-end-position)
'face 'bold)
(funcall callback (buffer-string)))))))))
To use the above function as an eldoc documentation function in
Magit status and log buffers, the following function
th/magit-eldoc-for-commit
is defined and added buffer-locally to buffers of
the respective magit modes.
(defvar th/magit-eldoc-last-commit nil)
(defun th/magit-eldoc-for-commit (callback)
(let ((commit (magit-commit-at-point)))
(when (and commit
(not (equal commit th/magit-eldoc-last-commit)))
(setq th/magit-eldoc-last-commit commit)
(th/eldoc-git-show-stat callback commit))))
(defun th/magit-eldoc-setup ()
(add-hook 'eldoc-documentation-functions
#'th/magit-eldoc-for-commit nil t))
(add-hook 'magit-status-mode-hook #'th/magit-eldoc-setup)
(add-hook 'magit-log-mode-hook #'th/magit-eldoc-setup)
So with these changes in place, operating in Magit buffers containing merge commits of very diverged branches won't hang my Emacs anymore.
What a relief!
EDIT 2024-12-09: There's now a package implementing eldoc support for commits, see eldoc-diffstat.
Back to the blog by tags or to the overview of all posts.
Also, you can subscribe to my rss feed.