branch: elpa/flycheck
commit 0ba62aea8f5189c67325c299943b3e14e7e3091f
Author: Bozhidar Batsov <[email protected]>
Commit: Bozhidar Batsov <[email protected]>
Document errors at point through Eldoc by default
The old default pushed error messages to the echo area on a timer via
display-message-or-buffer, popping the *Flycheck error messages* buffer
for long messages. Going through Eldoc composes with other Eldoc
sources like Eglot and honors Eldoc display customizations
(eldoc-echo-area-use-multiline-p, eldoc-box and friends).
The Eldoc backend only activates when flycheck-display-errors-function
has its new default value, so custom display functions and extensions
like flycheck-posframe keep working unchanged.
---
CHANGES.rst | 6 ++++
doc/user/error-interaction.rst | 22 ++++++++----
flycheck.el | 74 ++++++++++++++++++++++++++++++++++----
test/specs/test-customization.el | 4 +--
test/specs/test-error-display.el | 78 ++++++++++++++++++++++++++++++++++++++++
5 files changed, 169 insertions(+), 15 deletions(-)
diff --git a/CHANGES.rst b/CHANGES.rst
index 78e99f202f..340770e74c 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -12,6 +12,12 @@
the existing minimum-level filter (``f``); ``F`` resets all filters.
- The File and ID columns of the error list adapt their width to the
errors on display instead of truncating at fixed widths.
+- Errors at point are now documented through Eldoc by default, composing
+ with other Eldoc sources (e.g. Eglot) and honoring Eldoc display
+ customizations. The previous behavior remains available by setting
+ ``flycheck-display-errors-function`` to
+ ``flycheck-display-error-messages``; custom display functions and
+ extensions like flycheck-posframe keep working unchanged.
- [#1787]: Add the ``:handle-suspicious`` property to command checkers.
It lets a checker translate a "suspicious state" (a non-zero exit
status with no parsable errors, e.g. a missing dependency) into
diff --git a/doc/user/error-interaction.rst b/doc/user/error-interaction.rst
index 28944513c3..191e45b0d8 100644
--- a/doc/user/error-interaction.rst
+++ b/doc/user/error-interaction.rst
@@ -108,9 +108,10 @@ customising:
`flycheck-posframe`, to prevent `flycheck-posframe` from repeatedly
displaying
errors at point.
-By default Flycheck shows the error messages in the minibuffer or in a separate
-buffer if the minibuffer is too small to hold the whole error message but this
-behaviour is entirely customisable:
+By default Flycheck documents the errors at point through Eldoc, alongside
+other Eldoc sources such as Eglot. This honors your Eldoc customizations,
+e.g. `eldoc-echo-area-use-multiline-p` or alternative Eldoc frontends like
+`eldoc-box`. The behaviour is entirely customisable:
.. defcustom:: flycheck-display-errors-function
@@ -119,15 +120,22 @@ behaviour is entirely customisable:
The function is given the list of Flycheck errors to display as sole
argument
and shall display these errors to the user in some way.
-Flycheck provides two built-in functions for this option:
+Flycheck provides three built-in functions for this option:
+
+.. defun:: flycheck-display-errors-via-eldoc errors
+
+ The default. Errors at point are shown through Eldoc. Messages too
+ long for the echo area (see `eldoc-echo-area-use-multiline-p`) can be
+ read in full with :kbd:`M-x eldoc-doc-buffer`.
.. defun:: flycheck-display-error-messages errors
flycheck-display-error-messages-unless-error-list errors
Show error messages and IDs in the echo area or in a separate buffer if the
- echo area is too small (using `display-message-or-buffer` which see). The
- latter only displays errors when the :ref:`error list <flycheck-error-list>`
- is not visible. To enable it add the following to your :term:`init file`:
+ echo area is too small (using `display-message-or-buffer` which see). This
+ was the default before Flycheck 37. The latter only displays errors when
+ the :ref:`error list <flycheck-error-list>` is not visible. To enable it
+ add the following to your :term:`init file`:
.. code-block:: elisp
diff --git a/flycheck.el b/flycheck.el
index 6dfd9e4f55..45c434e4ef 100644
--- a/flycheck.el
+++ b/flycheck.el
@@ -431,21 +431,30 @@ Use floating point numbers to express fractions of
seconds."
:package-version '(flycheck . "0.15")
:safe #'numberp)
-(defcustom flycheck-display-errors-function #'flycheck-display-error-messages
+(defcustom flycheck-display-errors-function #'flycheck-display-errors-via-eldoc
"Function to display error messages.
If set to a function, call the function with the list of errors
to display as single argument. Each error is an instance of the
`flycheck-error' struct.
+With the default value `flycheck-display-errors-via-eldoc',
+errors at point are documented through Eldoc. This composes with
+other Eldoc sources (e.g. Eglot) and honors Eldoc display
+customizations such as `eldoc-echo-area-use-multiline-p' or
+alternative Eldoc frontends. Use \\[eldoc-doc-buffer] to read
+messages that don't fit into the echo area in full.
+
If set to nil, do not display errors at all."
:group 'flycheck
- :type '(choice (const :tag "Display error messages"
+ :type '(choice (const :tag "Display errors via Eldoc"
+ flycheck-display-errors-via-eldoc)
+ (const :tag "Display error messages"
flycheck-display-error-messages)
(const :tag "Display error messages only if no error list"
flycheck-display-error-messages-unless-error-list)
(function :tag "Error display function"))
- :package-version '(flycheck . "0.13")
+ :package-version '(flycheck . "37")
:risky t)
(defcustom flycheck-clear-displayed-errors-function
#'flycheck-clear-displayed-error-messages
@@ -3144,6 +3153,15 @@ ARG is ‘toggle’; disable the mode otherwise."
(flycheck-mode
(flycheck-clear)
(flycheck--sync-margin)
+ (add-hook 'eldoc-documentation-functions #'flycheck-eldoc-function nil t)
+ ;; `global-eldoc-mode' may have skipped this buffer because no
+ ;; documentation source was registered when it made its decision;
+ ;; give it another chance now that Flycheck provides one. Buffers
+ ;; where the user disabled Eldoc entirely are left alone; there the
+ ;; display timer picks up the slack.
+ (when (and (bound-and-true-p global-eldoc-mode)
+ (not (bound-and-true-p eldoc-mode)))
+ (turn-on-eldoc-mode))
(pcase-dolist (`(,hook . ,fn) (reverse flycheck-hooks-alist))
(add-hook hook fn nil 'local))
@@ -3165,6 +3183,8 @@ ARG is ‘toggle’; disable the mode otherwise."
(unless (eq flycheck-old-next-error-function :unset)
(setq next-error-function flycheck-old-next-error-function))
+ (remove-hook 'eldoc-documentation-functions #'flycheck-eldoc-function t)
+
(pcase-dolist (`(,hook . ,fn) flycheck-hooks-alist)
(remove-hook hook fn 'local))
@@ -5920,6 +5940,43 @@ avoid slowing down editing when the error list is
hidden."
;;; Displaying errors in the current buffer
+(defun flycheck--display-errors-via-eldoc-p ()
+ "Whether errors at point are displayed through Eldoc."
+ (eq flycheck-display-errors-function #'flycheck-display-errors-via-eldoc))
+
+(defun flycheck-display-errors-via-eldoc (_errors)
+ "Trigger Eldoc to document the errors at point.
+
+Eldoc computes its documentation from all of its registered
+sources, including `flycheck-eldoc-function', so refreshing it
+shows the Flycheck errors at point alongside e.g. Eglot's
+documentation. This works from any display entry point --
+interactive commands, error navigation, automatic display after a
+check -- whether or not variable `eldoc-mode' is enabled."
+ (eldoc-print-current-symbol-info))
+
+(defun flycheck-eldoc-function (callback &rest _ignored)
+ "Document the Flycheck errors at point by calling CALLBACK.
+
+Intended for `eldoc-documentation-functions', where command
+`flycheck-mode' registers it. Only active when
+`flycheck-display-errors-function' has its default value
+`flycheck-display-errors-via-eldoc', so that user customizations
+and third-party display packages keep working unchanged."
+ (when (and flycheck-mode (flycheck--display-errors-via-eldoc-p))
+ (when-let* ((errors (flycheck-overlay-errors-at (point))))
+ (funcall callback
+ (mapconcat
+ (lambda (err)
+ (let ((level (flycheck-error-level err)))
+ (concat
+ (propertize (symbol-name level)
+ 'face (flycheck-error-level-error-list-face
+ level))
+ ": "
+ (flycheck-error-format-message-and-id err))))
+ errors "\n")))))
+
(defun flycheck-display-errors (errors)
"Display ERRORS using `flycheck-display-errors-function'."
(when flycheck-display-errors-function
@@ -5957,9 +6014,14 @@ If there are no errors, clears the error messages at
point."
(defun flycheck-display-error-at-point-soon ()
"Display error messages at point, with a delay."
(flycheck-cancel-error-display-error-at-point-timer)
- (setq flycheck-display-error-at-point-timer
- (run-at-time flycheck-display-errors-delay nil
- 'flycheck-display-error-at-point)))
+ ;; When errors are displayed through Eldoc and `eldoc-mode' is active,
+ ;; its own post-command refresh covers this path; without `eldoc-mode'
+ ;; fall back to Flycheck's timer, which triggers the refresh itself
+ (unless (and (flycheck--display-errors-via-eldoc-p)
+ (bound-and-true-p eldoc-mode))
+ (setq flycheck-display-error-at-point-timer
+ (run-at-time flycheck-display-errors-delay nil
+ 'flycheck-display-error-at-point))))
(defun flycheck-handle-focus-change ()
diff --git a/test/specs/test-customization.el b/test/specs/test-customization.el
index 9fea51bd1e..571c3eb085 100644
--- a/test/specs/test-customization.el
+++ b/test/specs/test-customization.el
@@ -78,9 +78,9 @@
(describe "flycheck-display-errors-function"
- (it "defaults to display-error-messages"
+ (it "defaults to display via eldoc"
(expect flycheck-display-errors-function
- :to-be #'flycheck-display-error-messages)))
+ :to-be #'flycheck-display-errors-via-eldoc)))
(describe "flycheck-indication-mode"
diff --git a/test/specs/test-error-display.el b/test/specs/test-error-display.el
index d7aaaf6704..70f95e4177 100644
--- a/test/specs/test-error-display.el
+++ b/test/specs/test-error-display.el
@@ -27,6 +27,84 @@
(flycheck-display-errors (list err))
(expect displayed-errors :to-equal (list err)))))
+ (describe "flycheck-eldoc-function"
+
+ (it "documents the errors at point"
+ (flycheck-buttercup-with-temp-buffer
+ (insert "hello world")
+ (flycheck-mode)
+ (goto-char 3)
+ (flycheck-add-overlay
+ (flycheck-error-new-at 1 1 'warning "Watch out" :end-column 6))
+ (let (doc)
+ (flycheck-eldoc-function (lambda (string &rest _) (setq doc string)))
+ (expect doc :to-match "warning: Watch out"))))
+
+ (it "registers with eldoc in flycheck-mode"
+ (flycheck-buttercup-with-temp-buffer
+ (flycheck-mode 1)
+ (expect (member #'flycheck-eldoc-function
+ eldoc-documentation-functions)
+ :to-be-truthy)
+ (flycheck-mode -1)
+ (expect (member #'flycheck-eldoc-function
+ eldoc-documentation-functions)
+ :not :to-be-truthy)))
+
+ (it "stays inert when the display function is customized"
+ (flycheck-buttercup-with-temp-buffer
+ (insert "hello")
+ (flycheck-mode)
+ (flycheck-add-overlay (flycheck-error-new-at 1 1 'error "Boom"))
+ (goto-char 1)
+ (let ((flycheck-display-errors-function
+ #'flycheck-display-error-messages)
+ (called nil))
+ (flycheck-eldoc-function (lambda (&rest _) (setq called t)))
+ (expect called :not :to-be-truthy))))
+
+ (it "triggers an eldoc refresh from any display entry point"
+ (flycheck-buttercup-with-temp-buffer
+ (insert "hello")
+ (flycheck-mode)
+ (flycheck-add-overlay (flycheck-error-new-at 1 1 'error "Boom"))
+ (goto-char 1)
+ (let ((refreshed 0))
+ (cl-letf (((symbol-function 'eldoc-print-current-symbol-info)
+ (lambda (&rest _) (cl-incf refreshed))))
+ ;; The default display function refreshes Eldoc, so the
+ ;; interactive command, error navigation and automatic
+ ;; display keep working
+ (flycheck-display-error-at-point)
+ (expect refreshed :to-equal 1)))))
+
+ (it "enables eldoc-mode when global-eldoc-mode allows it"
+ (flycheck-buttercup-with-temp-buffer
+ (let ((global-eldoc-mode t))
+ (flycheck-mode 1)
+ (expect (bound-and-true-p eldoc-mode) :to-be-truthy)
+ (flycheck-mode -1))))
+
+ (it "keeps the display timer off only while eldoc-mode is active"
+ (flycheck-buttercup-with-temp-buffer
+ ;; `eldoc-mode' refuses to activate without a documentation
+ ;; source, so register Flycheck's first
+ (flycheck-mode 1)
+ (eldoc-mode 1)
+ (flycheck-display-error-at-point-soon)
+ (expect flycheck-display-error-at-point-timer :not :to-be-truthy)
+ (eldoc-mode -1)
+ ;; Without eldoc-mode the timer must pick up the slack
+ (flycheck-display-error-at-point-soon)
+ (expect flycheck-display-error-at-point-timer :to-be-truthy)
+ (flycheck-cancel-error-display-error-at-point-timer)
+ ;; The old default schedules the timer regardless
+ (let ((flycheck-display-errors-function
+ #'flycheck-display-error-messages))
+ (flycheck-display-error-at-point-soon)
+ (expect flycheck-display-error-at-point-timer :to-be-truthy)
+ (flycheck-cancel-error-display-error-at-point-timer)))))
+
(describe "flycheck-display-error-messages"
(it "displays error messages"