Eglot: simplify inlay hints implementation with jit-lock
This implementation is much simpler than the one based on windows-scroll-functions. It's also supposedly safer, as long as jit-lock guarantees refontification of affected regions. It's not _trivially_ simple though, as simply adding 'eglot--update-hints-1' to jit-lock-functions, while possible, is going to request inlay hints from the LSP server for many small regions of the buffer, depending on what jit-lock thinks is best. So we keep coalescing these into a larger region until the time is suitable for a more bandwidth-efficient request. To do this, we use a jit-lock implementation detail, jit-lock-context-unfontify-pos, which is a proxy for knowing that the jit-lock-context-timer has run. Not sure how brittle it is, but it seems to work reasonably. We also get rid of the previous "get hints for entire buffer" implementation. * doc/misc/eglot.texi (Eglot Variables): Remove mention to deleted eglot-lazy-inlay-hints. * lisp/progmodes/eglot.el (eglot-lazy-inlay-hints) (eglot--inlay-hints-after-scroll) (eglot--inlay-hints-fully) (eglot--inlay-hints-lazily): Remove. (eglot--update-hints): Add function. (eglot-inlay-hints-mode): Simplify.
This commit is contained in:
parent
91e24c5b5a
commit
b0cbd5590b
2 changed files with 33 additions and 85 deletions
|
@ -883,14 +883,6 @@ this map. For example:
|
|||
(define-key eglot-mode-map (kbd "<f6>") 'xref-find-definitions)
|
||||
@end lisp
|
||||
|
||||
@item eglot-lazy-inlay-hints
|
||||
This variable controls the operation and performance of LSP Inlay
|
||||
Hints (@pxref{Eglot Features}). If non-@code{nil}, it specifies how
|
||||
much time to wait after a window is displayed or scrolled before
|
||||
requesting hints for that visible portion of a given buffer. If
|
||||
@code{nil}, inlay hints are always requested for the whole buffer,
|
||||
even for parts of it not currently visible.
|
||||
|
||||
@end vtable
|
||||
|
||||
Additional variables, which are relevant for customizing the server
|
||||
|
|
|
@ -3489,32 +3489,39 @@ If NOERROR, return predicate, else erroring function."
|
|||
(defface eglot-parameter-hint-face '((t (:inherit eglot-inlay-hint-face)))
|
||||
"Face used for parameter inlay hint overlays.")
|
||||
|
||||
(defcustom eglot-lazy-inlay-hints 0.3
|
||||
"If non-nil, restrict LSP inlay hints to visible portion of the buffer.
|
||||
(defvar-local eglot--outstanding-inlay-hints-region (cons nil nil)
|
||||
"Jit-lock-calculated (FROM . TO) region with potentially outdated hints")
|
||||
|
||||
Value is a number specifying how many seconds to wait after a
|
||||
window has been (re)scrolled before requesting new inlay hints
|
||||
for the now-visible portion of the buffer shown in the window.
|
||||
(defvar-local eglot--outstanding-inlay-regions-timer nil
|
||||
"Helper timer for `eglot--update-hints'")
|
||||
|
||||
If nil, then inlay hints are requested for the entire buffer.
|
||||
This could be slow.
|
||||
|
||||
This value is only meaningful if the minor mode
|
||||
`eglot-inlay-hints-mode' is turned on in a buffer."
|
||||
:type 'number
|
||||
:version "29.1")
|
||||
|
||||
(defun eglot--inlay-hints-fully ()
|
||||
(eglot--widening (eglot--update-hints-1 (point-min) (point-max))))
|
||||
|
||||
(cl-defun eglot--inlay-hints-lazily (&optional (buffer (current-buffer)))
|
||||
(eglot--when-live-buffer buffer
|
||||
(when eglot--managed-mode
|
||||
(dolist (window (get-buffer-window-list nil nil 'visible))
|
||||
(eglot--update-hints-1 (window-start window) (window-end window))))))
|
||||
(defun eglot--update-hints (from to)
|
||||
"Jit-lock function for Eglot inlay hints."
|
||||
(cl-symbol-macrolet ((region eglot--outstanding-inlay-hints-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))
|
||||
(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)))))))
|
||||
|
||||
(defun eglot--update-hints-1 (from to)
|
||||
"Request LSP inlay hints and annotate current buffer from FROM to TO."
|
||||
"Do most work for `eglot--update-hints', including LSP request."
|
||||
(let* ((buf (current-buffer))
|
||||
(paint-hint
|
||||
(eglot--lambda ((InlayHint) position kind label paddingLeft paddingRight)
|
||||
|
@ -3545,67 +3552,16 @@ This value is only meaningful if the minor mode
|
|||
(mapc paint-hint hints))))
|
||||
:deferred 'eglot--update-hints-1)))
|
||||
|
||||
(defun eglot--inlay-hints-after-scroll (window display-start)
|
||||
(cl-macrolet ((wsetq (sym val) `(set-window-parameter window ',sym ,val))
|
||||
(wgetq (sym) `(window-parameter window ',sym)))
|
||||
(let ((buf (window-buffer window))
|
||||
(timer (wgetq eglot--inlay-hints-timer))
|
||||
(last-display-start (wgetq eglot--last-inlay-hint-display-start)))
|
||||
(when (and eglot-lazy-inlay-hints
|
||||
;; FIXME: If `window' is _not_ the selected window,
|
||||
;; then for some unknown reason probably related to
|
||||
;; the overlays added later to the buffer, the scroll
|
||||
;; function will be called indefinitely. Not sure if
|
||||
;; an Emacs bug, but prevent useless duplicate calls
|
||||
;; by saving and examining `display-start' fixes it.
|
||||
(not (eql last-display-start display-start)))
|
||||
(when timer (cancel-timer timer))
|
||||
(wsetq eglot--last-inlay-hint-display-start
|
||||
display-start)
|
||||
(wsetq eglot--inlay-hints-timer
|
||||
(run-at-time
|
||||
eglot-lazy-inlay-hints
|
||||
nil (lambda ()
|
||||
(eglot--when-live-buffer buf
|
||||
(when (eq buf (window-buffer window))
|
||||
(eglot--update-hints-1 (window-start window)
|
||||
(window-end window))
|
||||
(wsetq eglot--inlay-hints-timer nil))))))))))
|
||||
|
||||
(defun eglot--inlay-hints-after-window-config-change ()
|
||||
(eglot--update-hints-1 (window-start) (window-end)))
|
||||
|
||||
(define-minor-mode eglot-inlay-hints-mode
|
||||
"Minor mode for annotating buffers with LSP server's inlay hints."
|
||||
:global nil
|
||||
(cond (eglot-inlay-hints-mode
|
||||
(cond
|
||||
((not (eglot--server-capable :inlayHintProvider))
|
||||
(if (eglot--server-capable :inlayHintProvider)
|
||||
(jit-lock-register #'eglot--update-hints 'contextual)
|
||||
(eglot--warn
|
||||
"No :inlayHintProvider support. Inlay hints will not work."))
|
||||
(eglot-lazy-inlay-hints
|
||||
(add-hook 'eglot--document-changed-hook
|
||||
#'eglot--inlay-hints-lazily t t)
|
||||
(add-hook 'window-scroll-functions
|
||||
#'eglot--inlay-hints-after-scroll nil t)
|
||||
(add-hook 'window-configuration-change-hook
|
||||
#'eglot--inlay-hints-after-window-config-change nil t)
|
||||
;; Maybe there isn't a window yet for current buffer,
|
||||
;; so `run-at-time' ensures this runs after redisplay.
|
||||
(run-at-time 0 nil #'eglot--inlay-hints-lazily))
|
||||
(t
|
||||
(add-hook 'eglot--document-changed-hook
|
||||
#'eglot--inlay-hints-fully nil t)
|
||||
(eglot--inlay-hints-fully))))
|
||||
"No :inlayHintProvider support. Inlay hints will not work.")))
|
||||
(t
|
||||
(remove-hook 'window-configuration-change-hook
|
||||
#'eglot--inlay-hints-after-window-config-change)
|
||||
(remove-hook 'eglot--document-changed-hook
|
||||
#'eglot--inlay-hints-lazily t)
|
||||
(remove-hook 'eglot--document-changed-hook
|
||||
#'eglot--inlay-hints-fully t)
|
||||
(remove-hook 'window-scroll-functions
|
||||
#'eglot--inlay-hints-after-scroll t)
|
||||
(jit-lock-unregister #'eglot--update-hints)
|
||||
(remove-overlays nil nil 'eglot--inlay-hint t))))
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue