fix(:tools lsp): make eglot diags use flycheck with :checkers syntax

Research on how Flycheck work, and a pending fix from Eglot, allowed to
get a cleaner representation of how this "hack" works and make it more
resilient

Co-Authored-By: Steve Purcell <steve@sanityinc.com>
This commit is contained in:
Gerry Agbobada 2021-04-14 09:09:37 +02:00
parent 539ab8d1e9
commit 813dc6e664
No known key found for this signature in database
GPG key ID: BE26DBAFD866BE34
2 changed files with 26 additions and 20 deletions

View file

@ -11,6 +11,8 @@
;; NOTE We disable eglot-auto-display-help-buffer because :select t in ;; NOTE We disable eglot-auto-display-help-buffer because :select t in
;; its popup rule causes eglot to steal focus too often. ;; its popup rule causes eglot to steal focus too often.
eglot-auto-display-help-buffer nil) eglot-auto-display-help-buffer nil)
(when (featurep! :checkers syntax)
(setq eglot-stay-out-of '(flymake)))
:config :config
(set-popup-rule! "^\\*eglot-help" :size 0.15 :quit t :select t) (set-popup-rule! "^\\*eglot-help" :size 0.15 :quit t :select t)

View file

@ -1,22 +1,27 @@
;;; flycheck-eglot --- Hacky eglot support in flycheck -*- lexical-binding: t; -*- ;;; flycheck-eglot --- Hacky eglot support in flycheck -*- lexical-binding: t; -*-
;;; Commentary: ;;; Commentary:
;; This file sets up flycheck so that, when eglot receives a publishDiagnostics method ;; This file sets up flycheck so that, when eglot receives a publishDiagnostics method
;; from the server, then eglot calls a report function that creates diagnostics for ;; from the server, flycheck updates the reports.
;; flycheck.
;; ;;
;; It works by creating an eglot-specific callback function, and using this as ;; Thanks to:
;; the REPORT-FN argument of `eglot-flymake-backend', which internally registers ;; - joaotavora for adding a handle to plug flycheck, and
;; that lambda as the function to use whenever there is a publishDiagnostics method. ;; - purcell for finding out the initial stub and the current implementation
;; Calling `+lsp--flycheck-eglot-init' "too late" is not a problem, since if there ;;
;; are any unreported/missed diagnostics, eglot ensures that the ;; It works by creating a bridge function which can be used as the argument of
;; REPORT-FN function is called immediately. ;; `eglot-flymake-backend', which both consumes diagnostics and queue a call to
;; 'flycheck-buffer'
;; ;;
;; Note: as long as joaotavora/eglot#596 isn't fixed/dealt with, this checker cannot
;; work. Please check the issue on github for more context
;;; Code: ;;; Code:
(defvar-local +lsp--flycheck-eglot--current-errors nil)
(defun +lsp--flycheck-eglot-init (checker callback) (defun +lsp--flycheck-eglot-init (checker callback)
"CHECKER is the checker (eglot). "CHECKER is the checker (eglot).
CALLBACK is the function that we need to call when we are done, on all the errors." CALLBACK is the function that we need to call when we are done, on all the errors."
(eglot-flymake-backend #'+lsp--flycheck-eglot--on-diagnostics)
(funcall callback 'finished +lsp--flycheck-eglot--current-errors))
(defun +lsp--flycheck-eglot--on-diagnostics (diags &rest _)
(cl-labels (cl-labels
((flymake-diag->flycheck-err ((flymake-diag->flycheck-err
(diag) (diag)
@ -30,17 +35,13 @@ CALLBACK is the function that we need to call when we are done, on all the error
(_ (error "Unknown diagnostic type, %S" diag))) (_ (error "Unknown diagnostic type, %S" diag)))
(flymake--diag-text diag) (flymake--diag-text diag)
:end-pos (flymake--diag-end diag) :end-pos (flymake--diag-end diag)
:checker checker :checker 'eglot
:buffer (current-buffer) :buffer (current-buffer)
:filename (buffer-file-name))))) :filename (buffer-file-name)))))
;; NOTE: Setting up eglot to automatically create flycheck errors for the buffer. (setq +lsp--flycheck-eglot--current-errors
;; Internally, this sets the lambda as the callback to be used by eglot (mapcar #'flymake-diag->flycheck-err diags))
;; when it receives a publishDiagnostics method from the server ;; Call Flycheck to update the diagnostics annotations
(eglot-flymake-backend (flycheck-buffer-deferred)))
(lambda (flymake-diags &rest _)
(funcall callback
'finished
(mapcar #'flymake-diag->flycheck-err flymake-diags))))))
(defun +lsp--flycheck-eglot-available-p () (defun +lsp--flycheck-eglot-available-p ()
(bound-and-true-p eglot--managed-mode)) (bound-and-true-p eglot--managed-mode))
@ -56,11 +57,14 @@ CALLBACK is the function that we need to call when we are done, on all the error
(add-hook! 'eglot-managed-mode-hook (add-hook! 'eglot-managed-mode-hook
(defun +lsp-eglot-prefer-flycheck-h () (defun +lsp-eglot-prefer-flycheck-h ()
(when eglot--managed-mode (when eglot--managed-mode
(flymake-mode -1)
(when-let ((current-checker (flycheck-get-checker-for-buffer))) (when-let ((current-checker (flycheck-get-checker-for-buffer)))
(unless (equal current-checker 'eglot) (unless (equal current-checker 'eglot)
(flycheck-add-next-checker 'eglot current-checker))) (flycheck-add-next-checker 'eglot current-checker)))
(flycheck-add-mode 'eglot major-mode) (flycheck-add-mode 'eglot major-mode)
(flycheck-mode 1) (flycheck-mode 1)
(flymake-mode -1)))) ;; Call flycheck on initilization to make sure to display initial
;; errors
(flycheck-buffer-deferred))))
;;; flycheck-eglot.el ends here ;;; flycheck-eglot.el ends here