Eglot: consider "pulled" and "pushed" diagnostics separately

This is chiefly for the benefit of rust-analyzer which uses both
mechanisms.

Per https://github.com/joaotavora/eglot/discussions/1562

* lisp/progmodes/eglot.el
(eglot--flymake-report-fn): Rename from eglot--flymake-push-report-fn.
(eglot--pulled-diagnostics)
(eglot--pushed-diagnostics): Rework from eglot--diagnostics.
(eglot--diagnostics): Remove.
(eglot--managed-mode): Tweak.
(eglot--maybe-activate-editing-mode): Tweak.
(eglot-handle-notification): Set eglot--pushed-diagnostics
only.  Call eglot--flymake-report.
(eglot-flymake-backend): Use eglot--flymake-report and
eglot--flymake-report-fn.
(eglot--flymake-pull): Rework.
(eglot--flymake-report): Renam from eglot--flymake-push and reworked.
This commit is contained in:
João Távora 2026-01-02 01:16:22 +00:00
parent ad500a13e4
commit da4c693e0b

View file

@ -2175,7 +2175,7 @@ and just return it. PROMPT shouldn't end with a question mark."
(define-key map [remap display-local-help] #'eldoc-doc-buffer)
map))
(defvar-local eglot--flymake-push-report-fn nil
(defvar-local eglot--flymake-report-fn nil
"Current flymake report function for this buffer.")
(defvar-local eglot--saved-bindings nil
@ -2223,13 +2223,16 @@ Use `eglot-managed-p' to determine if current buffer is managed.")
(defvar eglot--highlights nil "Overlays for `eglot-highlight-eldoc-function'.")
(defvar-local eglot--diagnostics nil
"A list (DIAGNOSTICS VERSION RESULT-ID) for current buffer.
(defvar-local eglot--pulled-diagnostics nil
"A list (DIAGNOSTICS RESULT-ID) \"pulled\" for current buffer.
DIAGNOSTICS is a list of Flymake diagnostics objects. RESULT-ID
identifies this diagnostic result as is used for incremental updates.")
(defvar-local eglot--pushed-diagnostics nil
"A list (DIAGNOSTICS VERSION) \"pushed\" for current buffer.
DIAGNOSTICS is a list of Flymake diagnostics objects. VERSION is the
LSP Document version reported for DIAGNOSTICS (comparable to
`eglot--docver') or nil if server didn't bother. RESULT-ID is an
optional string identifying this diagnostic result for pull
diagnostics, used for incremental updates.")
`eglot--docver') or nil if server didn't bother.")
(defvar-local eglot--suggestion-overlay (make-overlay 0 0)
"Overlay for `eglot-code-action-suggestion'.")
@ -2309,10 +2312,11 @@ diagnostics, used for incremental updates.")
(cl-loop for (var . saved-binding) in eglot--saved-bindings
do (set (make-local-variable var) saved-binding))
(remove-function (local 'imenu-create-index-function) #'eglot-imenu)
(when eglot--flymake-push-report-fn
(setq eglot--diagnostics nil)
(eglot--flymake-push)
(setq eglot--flymake-push-report-fn nil))
(when eglot--flymake-report-fn
(setq eglot--pulled-diagnostics nil
eglot--pushed-diagnostics nil)
(eglot--flymake-report)
(setq eglot--flymake-report-fn nil))
(run-hooks 'eglot-managed-mode-hook)
(let ((server eglot--cached-server))
(setq eglot--cached-server nil)
@ -2367,7 +2371,8 @@ If it is activated, also signal textDocument/didOpen."
;; Called when `revert-buffer-in-progress-p' is t but
;; `revert-buffer-preserve-modes' is nil.
(when (and buffer-file-name (eglot-current-server))
(setq eglot--diagnostics nil)
(setq eglot--pulled-diagnostics nil
eglot--pushed-diagnostics nil)
(eglot--managed-mode)
(eglot--signal-textDocument/didOpen)
;; Run user hook after 'textDocument/didOpen' so server knows
@ -2788,11 +2793,11 @@ expensive cached value of `file-truename'.")
collect (eglot--flymake-make-diag diag-spec version)
into diags
finally
(setq eglot--diagnostics (list diags version nil))
(setq eglot--pushed-diagnostics (list diags version))
(when (not (null flymake-no-changes-timeout ))
;; only add to current report if Flymake
;; starts on idle-timer (github#957)
(eglot--flymake-push))))
(eglot--flymake-report))))
(cl-loop
for diag-spec across diagnostics
collect (eglot--dbind ((Diagnostic) code range message severity source) diag-spec
@ -3234,26 +3239,26 @@ publishes diagnostics. Between calls to this function, REPORT-FN
may be called multiple times (respecting the protocol of
`flymake-diagnostic-functions')."
(cond (eglot--managed-mode
(setq eglot--flymake-push-report-fn report-fn)
(setq eglot--flymake-report-fn report-fn)
(cond
;; Use pull diagnostics if server supports it
((eglot-server-capable :diagnosticProvider)
(eglot--flymake-pull))
;; Otherwise push whatever we might have, and wait for
;; `textDocument/publishDiagnostics'.
(t (eglot--flymake-push))))
(t (eglot--flymake-report))))
(t
(funcall report-fn nil))))
(cl-defun eglot--flymake-pull (&aux (server (eglot--current-server-or-lose))
(origin (current-buffer)))
"Pull diagnostics from server, for all managed buffers.
When response arrives call registered `eglot--flymake-push-report-fn'."
When response arrives call registered `eglot--flymake-report-fn'."
(cl-flet
((pull-for (buf &optional then)
(with-current-buffer buf
(let ((version eglot--docver)
(prev-result-id (nth 2 eglot--diagnostics)))
(prev-result-id (cadr eglot--pulled-diagnostics)))
(eglot--async-request
server
:textDocument/diagnostic
@ -3266,16 +3271,15 @@ When response arrives call registered `eglot--flymake-push-report-fn'."
(eglot--when-live-buffer buf
(pcase kind
("full"
(setq eglot--diagnostics
(setq eglot--pulled-diagnostics
(list
(cl-loop
for spec across items
collect (eglot--flymake-make-diag spec version))
version
resultId))
(eglot--flymake-push))
(eglot--flymake-report))
("unchanged"
(when (eq buf origin) (eglot--flymake-push 'void)))))
(when (eq buf origin) (eglot--flymake-report 'void)))))
(when then (funcall then)))
:hint :textDocument/diagnostic)))))
;; JT@2025-12-15: No known server yet supports "relatedDocuments" so
@ -3288,15 +3292,18 @@ When response arrives call registered `eglot--flymake-push-report-fn'."
(mapc #'pull-for
(remove origin (eglot--managed-buffers server))))))))
(cl-defun eglot--flymake-push
(&optional void &aux (diags (nth 0 eglot--diagnostics))
(version (nth 1 eglot--diagnostics)))
"Push previously collected diagnostics to `eglot--flymake-push-report-fn'.
(cl-defun eglot--flymake-report
(&optional void
&aux
(diags (append (car eglot--pulled-diagnostics)
(car eglot--pushed-diagnostics)))
(version (cadr eglot--pushed-diagnostics)))
"Push previously collected diagnostics to `eglot--flymake-report-fn'.
If VOID, knowingly push a dummy do-nothing update."
(unless eglot--flymake-push-report-fn
(unless eglot--flymake-report-fn
;; Occasionally called from contexts where report-fn not setup, such
;; as a `didOpen''ed but yet undisplayed buffer.
(cl-return-from eglot--flymake-push))
(cl-return-from eglot--flymake-report))
(eglot--widening
(if (or void (and version (< version eglot--docver)))
;; Here, we don't have anything interesting to give to Flymake: we
@ -3305,9 +3312,9 @@ If VOID, knowingly push a dummy do-nothing update."
;; we're alive and clear a possible "Wait" state. We hackingly
;; achieve this by reporting an empty list and making sure it
;; pertains to a 0-length region.
(funcall eglot--flymake-push-report-fn nil
(funcall eglot--flymake-report-fn nil
:region (cons (point-min) (point-min)))
(funcall eglot--flymake-push-report-fn diags
(funcall eglot--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