Subject: bug#50244: 28.0.50; Support project-wide diagnostics reports in flymake.el

From: Theodor Thornhill <[email-masked]>
Date: Sat, 23 Oct 2021 21:22:08 +0200

Hi João!

(sent this mail some days ago, but the bug was archived, so not sure if
it worked.  Trying again.  If this is only noise, then I'm sorry, but
hopefully ok considering i found a typo in the old patch :))

I finally did get to test this out a little, and I seem to have hit some
small snags I can't really understand.  I'd love some pointers as to how
to continue!

(the patch I currently use is attached at the bottom)

Ok, so what happens is:

- There is no backend set on the list-only-diagnostics as for now, and I
  cannot for the life of me see how to set them, as it doesn't look like
  eglot itself sets it.  Is there some reflection going on here that I
  have missed?

- The function in question is riddled with fallbacks and failure
  checking.  Are we sure we need all that?  I see most of the commits
  relating to the range checking is from 2018, so they may not be
  relevant anymore?

- The diff as supplied appends the list-only-diagnostics just fine to
  the flymake buffer, but I believe since the backend isn't set, eglot
  doesn't report the proper diagnostics when I visit the file.  I have
  to make a bogus edit to trigger the eglot-flymake-backend function.
  (This could just as well be some unrelated eglot/elmls issue...)

- What would be the best way to get the proper line and column?  Now I
  simply use the range, but considering there's a lot of error handling
  there I guess that is a little risky?  I did have a version earlier
  using with-temp-buffer and finding the correct positions, but that
  seemed more complicated than needed.

I'd love to get your thoughts on this :)

Theodor

diff --git a/eglot.el b/eglot.el
index bf9cf25..47c7b74 100644
--- a/eglot.el
+++ b/eglot.el
@@ -1776,9 +1776,14 @@ COMMAND is a symbol naming the command."
   (_server (_method (eql telemetry/event)) &rest _any)
   "Handle notification telemetry/event") ;; noop, use events buffer
 
+(defun eglot--diag-type (severity)
+  (cond ((<= severity 1) 'eglot-error)
+        ((= severity 2)  'eglot-warning)
+        (t          'eglot-note)))
+
 (cl-defmethod eglot-handle-notification
   (server (_method (eql textDocument/publishDiagnostics)) &key uri diagnostics
-          &allow-other-keys) ; FIXME: doesn't respect `eglot-strict-mode'
+          &allow-other-keys)        ; FIXME: doesn't respect `eglot-strict-mode'
   "Handle notification publishDiagnostics"
   (if-let ((buffer (find-buffer-visiting (eglot--uri-to-path uri))))
       (with-current-buffer buffer
@@ -1787,9 +1792,7 @@ COMMAND is a symbol naming the command."
          collect (eglot--dbind ((Diagnostic) range message severity source)
                      diag-spec
                    (setq message (concat source ": " message))
-                   (pcase-let
-                       ((sev severity)
-                        (`(,beg . ,end) (eglot--range-region range)))
+                   (pcase-let ((`(,beg . ,end) (eglot--range-region range)))
                      ;; Fallback to `flymake-diag-region' if server
                      ;; botched the range
                      (when (= beg end)
@@ -1807,17 +1810,29 @@ COMMAND is a symbol naming the command."
                           (setq end
                                 (point-at-eol
                                  (1+ (plist-get (plist-get range :end) :line)))))))
-                     (eglot--make-diag (current-buffer) beg end
-                                       (cond ((<= sev 1) 'eglot-error)
-                                             ((= sev 2)  'eglot-warning)
-                                             (t          'eglot-note))
-                                       message `((eglot-lsp-diag . ,diag-spec)))))
+                     (eglot--make-diag
+                      (current-buffer) beg end
+                      (eglot--diag-type severity)
+                      message `((eglot-lsp-diag . ,diag-spec)))))
          into diags
          finally (cond (eglot--current-flymake-report-fn
                         (eglot--report-to-flymake diags))
                        (t
                         (setq eglot--unreported-diagnostics (cons t diags))))))
-    (jsonrpc--debug server "Diagnostics received for unvisited %s" uri)))
+    (cl-loop
+     with path = (expand-file-name (eglot--uri-to-path uri))
+     for diag-spec across diagnostics
+     collect (eglot--dbind ((Diagnostic) range message severity source) diag-spec
+               (setq message (concat source ": " message))
+               (let* ((start (plist-get range :start))
+                      (line (1+ (plist-get start :line)))
+                      (char (1+ (plist-get start :character))))
+                 (eglot--make-diag path (cons line char) nil (eglot--diag-type severity) message)))
+     into diags
+     finally
+     (setq flymake-list-only-diagnostics
+           (assoc-delete-all path flymake-list-only-diagnostics #'string=))
+     (push (cons path diags) flymake-list-only-diagnostics))))
 
 (cl-defun eglot--register-unregister (server things how)
   "Helper for `registerCapability'.