Eglot: improve diagnostic-reporting performance

After a change in the buffer has occured, it is often the case
that Flymake is quicker to ask for diagnostics than the server
is to supply them to us.  If we're still stuck with old outdated
diagnostics, don't forward them to Flymake, even if it eagerly
asks us for them.

* etc/EGLOT-NEWS (Changes in upcoming Eglot): Announce changes.

* lisp/progmodes/eglot.el
(eglot--diagnostics): Rework.
(eglot--report-to-flymake): Also take version.
(eglot-handle-notification textDocument/publishDiagnostics)
(eglot--managed-mode)
(eglot-flymake-backend): Tweak call to eglot--report-to-flymake.
This commit is contained in:
João Távora 2025-04-29 12:20:51 +01:00
parent 3c47139b8f
commit 7ae275f04c
2 changed files with 33 additions and 18 deletions

View file

@ -47,6 +47,13 @@ The composition of Eglot's mode line can be fully customized by adding
or removing symbols and strings from the customizable variable
'eglot-mode-line-format'
** Improved diagnostic-reporting performance and bugfixes (bug#77588)
Eglot remembers the LSP document version to which diagonstics reported
by the LSP server pertain. This helps in skipping useless or harmful
updates, avoiding flakiness with code actions and flickering overlays
when the buffer is changed.
* Changes in Eglot 1.18 (20/1/2025)

View file

@ -2228,7 +2228,7 @@ Use `eglot-managed-p' to determine if current buffer is managed.")
do (set (make-local-variable var) saved-binding))
(remove-function (local 'imenu-create-index-function) #'eglot-imenu)
(when eglot--current-flymake-report-fn
(eglot--report-to-flymake nil)
(eglot--report-to-flymake nil nil)
(setq eglot--current-flymake-report-fn nil))
(run-hooks 'eglot-managed-mode-hook)
(let ((server eglot--cached-server))
@ -2268,7 +2268,10 @@ Use `eglot-managed-p' to determine if current buffer is managed.")
(jsonrpc-error "No current JSON-RPC connection")))
(defvar-local eglot--diagnostics nil
"Flymake diagnostics for this buffer.")
"A cons (DIAGNOSTICS . VERSION) for current buffer.
DIAGNOSTICS is a list of Flymake diagnostics objects. VERSION is the
LSP Document version reported for DIAGNOSTICS (comparable to
`eglot--versioned-identifier') or nil if server didn't bother.")
(defvar revert-buffer-preserve-modes)
(defun eglot--after-revert-hook ()
@ -2699,8 +2702,11 @@ expensive cached value of `file-truename'.")
initially
(if (and version (/= version eglot--versioned-identifier))
(cl-return))
(setq flymake-list-only-diagnostics
(assoc-delete-all path flymake-list-only-diagnostics))
(setq
;; if no explicit version received, assume it's current.
version eglot--versioned-identifier
flymake-list-only-diagnostics
(assoc-delete-all path flymake-list-only-diagnostics))
for diag-spec across diagnostics
collect (eglot--dbind ((Diagnostic) range code message severity source tags)
diag-spec
@ -2740,9 +2746,9 @@ expensive cached value of `file-truename'.")
;; starts on idle-timer (github#958)
(not (null flymake-no-changes-timeout))
eglot--current-flymake-report-fn)
(eglot--report-to-flymake diags))
(eglot--report-to-flymake diags version))
(t
(setq eglot--diagnostics diags)))))
(setq eglot--diagnostics (cons diags version))))))
(cl-loop
for diag-spec across diagnostics
collect (eglot--dbind ((Diagnostic) code range message severity source) diag-spec
@ -3134,22 +3140,24 @@ may be called multiple times (respecting the protocol of
`flymake-diagnostic-functions')."
(cond (eglot--managed-mode
(setq eglot--current-flymake-report-fn report-fn)
(eglot--report-to-flymake eglot--diagnostics))
(eglot--report-to-flymake (car eglot--diagnostics)
(cdr eglot--diagnostics)))
(t
(funcall report-fn nil))))
(defun eglot--report-to-flymake (diags)
(defun eglot--report-to-flymake (diags version)
"Internal helper for `eglot-flymake-backend'."
(save-restriction
(widen)
(funcall eglot--current-flymake-report-fn diags
;; If the buffer hasn't changed since last
;; call to the report function, flymake won't
;; delete old diagnostics. Using :region
;; keyword forces flymake to delete
;; them (github#159).
:region (cons (point-min) (point-max))))
(setq eglot--diagnostics diags))
(when (or (null version) (= version eglot--versioned-identifier))
(save-restriction
(widen)
(funcall eglot--current-flymake-report-fn diags
;; If the buffer hasn't changed since last
;; call to the report function, flymake won't
;; delete old diagnostics. Using :region
;; keyword forces flymake to delete
;; them (github#159).
:region (cons (point-min) (point-max)))))
(setq eglot--diagnostics (cons diags version)))
(defun eglot-xref-backend () "Eglot xref backend." 'eglot)