(defvar-local eglot--outstanding-inlay-regions-timer nil
"Helper timer for `eglot--update-hints'.")
-(defun eglot--update-hints (from to)
+(cl-defun eglot--update-hints (from to)
"Jit-lock function for Eglot inlay hints."
+ ;; XXX: We're relying on knowledge of jit-lock internals here.
+ ;; Comparing `jit-lock-context-unfontify-pos' (if non-nil) to
+ ;; `point-max' tells us whether this call to `jit-lock-functions'
+ ;; happens after `jit-lock-context-timer' has just run.
+ (when (and jit-lock-context-unfontify-pos
+ (/= jit-lock-context-unfontify-pos (point-max)))
+ (cl-return-from eglot--update-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))
- ;; HACK: We're relying on knowledge of jit-lock internals here. The
- ;; condition comparing `jit-lock-context-unfontify-pos' to
- ;; `point-max' is a heuristic for telling whether this call to
- ;; `jit-lock-functions' happens after `jit-lock-context-timer' has
- ;; just run. Only after this delay should we start the smoothing
- ;; timer that will eventually call `eglot--update-hints-1' with the
- ;; coalesced region. I wish we didn't need the timer, but sometimes
- ;; a lot of "non-contextual" calls come in all at once and do verify
- ;; the condition. Notice it is a 0 second timer though, so we're
- ;; not introducing any more delay over jit-lock's timers.
- (when (= jit-lock-context-unfontify-pos (point-max))
- (if timer (cancel-timer timer))
- (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)))))))))
+ ;; XXX: Then there is a smoothing timer. I wish we didn't need it,
+ ;; but sometimes a lot of calls come in all at once and do make it
+ ;; past the check above. Notice it is a 0 second timer though, so
+ ;; we're not introducing any more delay over jit-lock's timers.
+ (when timer (cancel-timer timer))
+ (setq timer (run-at-time
+ 0 nil
+ (lambda (buf)
+ (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)))
+ (current-buffer)))))
(defun eglot--update-hints-1 (from to)
"Do most work for `eglot--update-hints', including LSP request."