From 813dc6e664a294de518ce55b3f6379cd2059347e Mon Sep 17 00:00:00 2001 From: Gerry Agbobada Date: Wed, 14 Apr 2021 09:09:37 +0200 Subject: [PATCH] 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 --- modules/tools/lsp/+eglot.el | 2 + modules/tools/lsp/autoload/flycheck-eglot.el | 44 +++++++++++--------- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/modules/tools/lsp/+eglot.el b/modules/tools/lsp/+eglot.el index aaa5f285f..a941b8222 100644 --- a/modules/tools/lsp/+eglot.el +++ b/modules/tools/lsp/+eglot.el @@ -11,6 +11,8 @@ ;; NOTE We disable eglot-auto-display-help-buffer because :select t in ;; its popup rule causes eglot to steal focus too often. eglot-auto-display-help-buffer nil) + (when (featurep! :checkers syntax) + (setq eglot-stay-out-of '(flymake))) :config (set-popup-rule! "^\\*eglot-help" :size 0.15 :quit t :select t) diff --git a/modules/tools/lsp/autoload/flycheck-eglot.el b/modules/tools/lsp/autoload/flycheck-eglot.el index 0c6d008fa..b38f0623b 100644 --- a/modules/tools/lsp/autoload/flycheck-eglot.el +++ b/modules/tools/lsp/autoload/flycheck-eglot.el @@ -1,22 +1,27 @@ ;;; flycheck-eglot --- Hacky eglot support in flycheck -*- lexical-binding: t; -*- ;;; Commentary: ;; 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 -;; flycheck. +;; from the server, flycheck updates the reports. ;; -;; It works by creating an eglot-specific callback function, and using this as -;; the REPORT-FN argument of `eglot-flymake-backend', which internally registers -;; that lambda as the function to use whenever there is a publishDiagnostics method. -;; Calling `+lsp--flycheck-eglot-init' "too late" is not a problem, since if there -;; are any unreported/missed diagnostics, eglot ensures that the -;; REPORT-FN function is called immediately. +;; Thanks to: +;; - joaotavora for adding a handle to plug flycheck, and +;; - purcell for finding out the initial stub and the current implementation +;; +;; It works by creating a bridge function which can be used as the argument of +;; `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: + +(defvar-local +lsp--flycheck-eglot--current-errors nil) + (defun +lsp--flycheck-eglot-init (checker callback) "CHECKER is the checker (eglot). 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 ((flymake-diag->flycheck-err (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))) (flymake--diag-text diag) :end-pos (flymake--diag-end diag) - :checker checker + :checker 'eglot :buffer (current-buffer) :filename (buffer-file-name))))) - ;; NOTE: Setting up eglot to automatically create flycheck errors for the buffer. - ;; Internally, this sets the lambda as the callback to be used by eglot - ;; when it receives a publishDiagnostics method from the server - (eglot-flymake-backend - (lambda (flymake-diags &rest _) - (funcall callback - 'finished - (mapcar #'flymake-diag->flycheck-err flymake-diags)))))) + (setq +lsp--flycheck-eglot--current-errors + (mapcar #'flymake-diag->flycheck-err diags)) + ;; Call Flycheck to update the diagnostics annotations + (flycheck-buffer-deferred))) (defun +lsp--flycheck-eglot-available-p () (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 (defun +lsp-eglot-prefer-flycheck-h () (when eglot--managed-mode + (flymake-mode -1) (when-let ((current-checker (flycheck-get-checker-for-buffer))) (unless (equal current-checker 'eglot) (flycheck-add-next-checker 'eglot current-checker))) (flycheck-add-mode 'eglot major-mode) (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