]> git.eshelyaron.com Git - emacs.git/commitdiff
Eglot: fix jit-lock inlay hint bugs
authorJoão Távora <joaotavora@gmail.com>
Fri, 24 Feb 2023 14:48:01 +0000 (14:48 +0000)
committerJoão Távora <joaotavora@gmail.com>
Fri, 24 Feb 2023 14:48:01 +0000 (14:48 +0000)
One of the bugs was straightforward.  The timer function of
eglot--update-hints must set the correct buffer.

The other is much more odd.  When using Eglot on Emacs's own
src/coding.c, the jit-lock code starts calling its jit-functions over
and over again with the same sequence of arguments, like so:

======================================================================
1 -> (eglot--update-hints 63551 65051)
1 <- eglot--update-hints: [nil 25592 52026 4
======================================================================
1 -> (eglot--update-hints 65051 66551)
1 <- eglot--update-hints: [nil 25592 52026 4
======================================================================
1 -> (eglot--update-hints-1 63551 66551)
1 <- eglot--update-hints-1: nil
======================================================================
1 -> (eglot--update-hints 63551 65051)
1 <- eglot--update-hints: [nil 25592 52026 4
======================================================================
1 -> (eglot--update-hints 65051 66551)
1 <- eglot--update-hints: [nil 25592 52026 5
======================================================================
1 -> (eglot--update-hints-1 63551 66551)
1 <- eglot--update-hints-1: nil

This continues forever at a very fast rate and saturates the LSP
channel.

At first I thought that it was because eglot--update-hints-1 is
actually causing the buffer to be modified with overlays sometime in
the future, but it is not so!  It seems that merely calling

   (goto-char (eglot--lsp-position-to-point position))

(from the LSP request handler in eglot--update-hints-1) will cause
this bug.

* lisp/progmodes/eglot.el (eglot--update-hints): Fix bugs.

lisp/progmodes/eglot.el

index e20d209332d0ffa6df8a11dfee30142eb3daa7ae..2b9d44f84e67db656aa103894d3f466fa6eccddc 100644 (file)
@@ -3492,12 +3492,15 @@ If NOERROR, return predicate, else erroring function."
 (defvar-local eglot--outstanding-inlay-hints-region (cons nil nil)
   "Jit-lock-calculated (FROM . TO) region with potentially outdated hints")
 
+(defvar-local eglot--outstanding-inlay-hints-last-region nil)
+
 (defvar-local eglot--outstanding-inlay-regions-timer nil
   "Helper timer for `eglot--update-hints'")
 
 (defun eglot--update-hints (from to)
   "Jit-lock function for Eglot inlay hints."
   (cl-symbol-macrolet ((region eglot--outstanding-inlay-hints-region)
+                       (last-region eglot--outstanding-inlay-hints-last-region)
                        (timer eglot--outstanding-inlay-regions-timer))
     (setcar region (min (or (car region) (point-max)) from))
     (setcdr region (max (or (cdr region) (point-min)) to))
@@ -3513,12 +3516,28 @@ If NOERROR, return predicate, else erroring function."
     ;; not introducing any more delay over jit-lock's timers.
     (when (= jit-lock-context-unfontify-pos (point-max))
       (if timer (cancel-timer timer))
-      (setq timer (run-at-time
-                   0 nil
-                   (lambda ()
-                     (eglot--update-hints-1 (max (car region) (point-min))
-                                            (min (cdr region) (point-max)))
-                     (setq region (cons nil nil) timer nil)))))))
+      (let ((buf (current-buffer)))
+        (setq timer (run-at-time
+                     0 nil
+                     (lambda ()
+                       (eglot--when-live-buffer buf
+                         ;; HACK: In some pathological situations
+                         ;; (Emacs's own coding.c, for example),
+                         ;; jit-lock is calling `eglot--update-hints'
+                         ;; repeatedly with same sequence of
+                         ;; arguments, which leads to
+                         ;; `eglot--update-hints-1' being called with
+                         ;; the same region repeatedly.  This happens
+                         ;; even if the hint-painting code does
+                         ;; nothing else other than widen, narrow,
+                         ;; move point then restore these things.
+                         ;; Possible Emacs bug, but this fixes it.
+                         (unless (equal last-region region)
+                           (eglot--update-hints-1 (max (car region) (point-min))
+                                                  (min (cdr region) (point-max)))
+                           (setq last-region region))
+                         (setq region (cons nil nil)
+                               timer nil)))))))))
 
 (defun eglot--update-hints-1 (from to)
   "Do most work for `eglot--update-hints', including LSP request."