Compare commits

...

8 commits

Author SHA1 Message Date
João Távora
1f48b56f8b Re-organize and rewrite parts of the Flymake manual
bug#50244

* doc/misc/flymake.texi (Starting Flymake): New section.
(Finding diagnostics): New section, now contains info previously in
"Listing diagnostics"
(Mode line status): Renamed from "Mode-line syntax check status"
(Troubleshooting): Renamed from "Backend exceptions".
(Flymake error types): Tweak phrasing.
2021-09-13 19:03:38 +01:00
João Távora
44824d616b Add support for project-wide diagnostics in Flymake (bug#50244)
This is done with two new concepts: "foreign diagnostics" and
"list-only diagnostics".  The manual has been updated with a
description of these new concepts.

* doc/misc/flymake.texi (Flymake utility functions):
Explain creation of foreign diagnostics.
(Foreign and list-only diagnostics): New subsection.
(Listing diagnostics): New subsection.

* lisp/progmodes/flymake.el
(Version): Bump to 1.2.1
(project): Require project.
(flymake--diag): Add new slots 'orig-beg' and 'orig-end'.  Rename
'buffer' slot to 'locus'.
(flymake-make-diagnostic): Rework docstring.  Accept stringp
LOCUS arg.
(flymake-diagnostic-beg, flymake-diagnostic-end)
(flymake-diagnostic-buffer):  Simplify definition.
(flymake--equal-diagnostic-p): New helper
(flymake--highlight-line): Rework.  Accept FOREIGN arg.
(flymake--state): Work docstring. Add new slot 'foreign-diags'
(flymake--handle-report): Call
flymake--update-diagnostics-listings.
(flymake--handle-report): New helper.
(flymake--mode): Forward declare.
(flymake--handle-report): Rework for foreign diagnostics.
(flymake-mode): When turning on, notice any Flymake diagnostics
for current buffer.  When turning off update diagnostics listings.
(flymake-kill-buffer-hook): Turn off flymake explicitly before
killing.
(flymake--mode-line-counter): Use flymake-diagnostics to collect
diagnostics.
(flymake-show-diagnostic): Visit buffer of file-specific
diagnostic.
(flymake--tabulated-entries-1): New helper extracted from
flymake--diagnostic-buffer-entries.
(flymake--diagnostics-buffer-entries): Rework.
(flymake--diagnostics-base-tabulated-list-format): New helper.
(flymake--diagnostics-buffer-name): Adjust.
(flymake-list-only-diagnostics): New variable.
(flymake--project-diagnostic-list-project): New variable.
(flymake--clear-list-only-diagnostics): New helper.
(flymake-project-diagnostics-mode): New major mode.
(flymake--project-diagnostics)
(flymake--project-diagnostics-entries)
(flymake--project-diagnostics-buffer): New helpers.
(flymake-show-project-diagnostics): New command.
(flymake--update-diagnostics-listings): New helper.
(flymake-show-buffer-diagnostics): Renamed from flymake-diagnostics-buffer.

* etc/NEWS: Mention change.
2021-09-13 19:03:38 +01:00
João Távora
96304eadad Bump lisp/progmodes/project.el version to 0.7.1
Amont other things exposes the new project-buffers generic function to
ELPA users.

* lisp/progmodes/project.el (Version): Bump to 0.7.1
2021-09-13 19:03:38 +01:00
João Távora
dcdbb7486b Keep and report "foreign" diangnostics in flymake-cc Flymake backend
This includes diagnostics for .h files that sprang up when checking a
c file.  Those diagnostics are reported to the Flymake infrastructure
which does not (yet) do anything with them.

This includes a change to the test fixtures, too.

* lisp/progmodes/flymake-cc.el (flymake-cc--make-diagnostics): Rework

* test/lisp/progmodes/flymake-resources/another-problematic-file.c:
  New file.

* test/lisp/progmodes/flymake-resources/some-problems.h:
  Add a function declaration..
2021-09-13 18:40:58 +01:00
João Távora
ac24adc376 Abbreviate Flymake backend name in flymake-show-diagnostics-buffer
* lisp/progmodes/flymake.el (flymake--diagnostics-buffer-entries):
(flymake-diagnostics-buffer-mode): Report abbreviated backend, too.
2021-09-13 18:40:58 +01:00
João Távora
2a02ac1fda Unbreak M-x compile-defun of functions using flymake-log
* lisp/progmodes/flymake.el (flymake-log): Check if compilation unit
  is indeed a string before treating it as a file name.
2021-09-13 18:40:58 +01:00
João Távora
0c1cb42b5b Refactor some Flymake functions
* lisp/progmodes/flymake.el (flymake-diagnostic-buffer): New
helper.
(flymake-diagnostic-beg, flymake-diagnostic-end): Tweak docstring.
(flymake--handle-report): Simplify.
(flymake--publish-diagnostics): Helper for flymake--handle-report.
(flymake--mode-line-counter, flymake-show-diagnostic)
(flymake--diagnostics-buffer-entries): Use
flymake-diagnostic-buffer, flymake-diagonstic-type,
flymake-diagnostic-beg.
2021-09-13 18:40:56 +01:00
João Távora
c6bc40137f Rename flymake--backend-state to flymake--state
The previous name was confusing and akward and dreadful to type and
read.

* lisp/progmodes/flymake.el (flymake--state): Rename from
flymake--backend-state.
(flymake--with-backend-state): Use flymake--state.
(flymake--handle-report): Use flymake--state.
(flymake--collect): Use flymake--state.
(flymake-running-backends): Use flymake--state.
(flymake--disable-backend): Use flymake--state.
(flymake--run-backend): Use flymake--state.
(flymake-start): Use flymake--state.
(flymake-mode): Use flymake--state.
(flymake--mode-line-title): Use flymake--state.
(flymake--mode-line-exception): Use flymake--state.
(flymake--mode-line-counter): Use flymake--state.
2021-09-13 18:39:17 +01:00
7 changed files with 754 additions and 302 deletions

View file

@ -1,18 +1,33 @@
\input texinfo @c -*-texinfo; coding: utf-8 -*-
@comment %**start of header
@setfilename ../../info/flymake.info
@set VERSION 1.0
@set UPDATED June 2018
@set VERSION 1.2
@set UPDATED September 2021
@settitle GNU Flymake @value{VERSION}
@include docstyle.texi
@include ../emacs/docstyle.texi
@syncodeindex pg cp
@syncodeindex vr cp
@syncodeindex fn cp
@comment %**end of header
@copying
This manual is for GNU Flymake (version @value{VERSION}, @value{UPDATED}),
which is a universal on-the-fly syntax checker for GNU Emacs.
This manual is for GNU Flymake (version @value{VERSION}, @value{UPDATED}).
Flymake is a universal on-the-fly syntax checker for Emacs. When
enabled, Flymake contacts one or more source @dfn{backends} to
collects information about problems in the buffer, called
@dfn{diagnostics}, and visually annotates them with a special face.
The mode line display overall status including totals for different
types of diagnostics.
To learn about using Flymake, @xref{Using Flymake}.
Flymake is designed to be easily extended to support new backends via
an Elisp interface. @xref{Extending Flymake}
Historically, Flymake used to accept diagnostics from a single
backend. Although obsolete, it is still functional. To learn how to
use and customize it, @xref{The legacy Proc backend}.
Copyright @copyright{} 2004--2021 Free Software Foundation, Inc.
@ -41,6 +56,7 @@ modify this GNU manual.''
@page
@vskip 0pt plus 1filll
@insertcopying
@end titlepage
@contents
@ -64,19 +80,34 @@ modify this GNU manual.''
@cindex overview of flymake
@cindex using flymake
Flymake is a universal on-the-fly buffer checker implemented as an
Emacs minor mode. To use Flymake, you must first activate
@code{flymake-mode} by using the command @kbd{flymake-mode}.
Flymake is only useful if at least one @dfn{backend} is configured to
provide the buffer-checking service. This is done via the hook
@code{flymake-diagnostic-functions}. @xref{Hooks,Hooks,, emacs, The
Emacs Editor}.
When enabled, Flymake collects information about problems in the
buffer, called @dfn{diagnostics}, from one or more different sources,
or @dfn{backends}, and then visually annotates the buffer by
highlighting problematic buffer regions with a special face.
It's possible that some major modes or a third-party package has
already setup this hook for you, by adding @dfn{backend functions} to
@code{flymake-diagnostic-functions}. If you know Elisp you may also
write your own Flymake backend functions. @xref{Backend functions}.
It also displays an overall buffer status in the mode line containing
totals for different types of diagnostics.
@menu
* Starting Flymake::
* Finding diagnostics::
* Mode line status::
* Troubleshooting::
* Customizable variables::
@end menu
Syntax check is done ``on-the-fly''. It is started whenever
@node Starting Flymake
@section Starting Flymake
@cindex Starting Flymake
To use Flymake, the minor-mode @code{flymake-mode} must be activated.
If it's not, use the command @kbd{flymake-mode} to toggle it on. The
mode line should indicate its presence via an indicator (@pxref{Mode
line status}).
Syntax checks happen ``on-the-fly''. Each check is started whenever:
@itemize @bullet
@item
@ -90,50 +121,56 @@ nil;
@item
some changes were made to the buffer more than @code{0.5} seconds ago
(the delay is configurable in @code{flymake-no-changes-timeout}).
@item
When the user invokes the command @code{flymake-start}.
@end itemize
Syntax check can also be started manually by typing the @kbd{M-x
flymake-start @key{RET}} command.
If the check detected errors or warnings, the respective buffer
regions are highlighted. You can place point on those regions and use
@kbd{C-h .} (@code{display-local-help}) to see what the specific
problem was. Alternatively, hovering the mouse on those regions
should also display a tool-tip with the same information.
regions are highlighted. @xref{Finding diagnostics} for how to
learn what the problems are.
@node Finding diagnostics
@section Finding diagnostics
@cindex Read diagnostic message
If Flymake has highlighted the buffer, you may hover the mouse on the
highlighted regions to learn what the specific problem
is. Alternatively, place point on the highlighted regions and use the
commands @code{eldoc} or @code{display-local-help}.
@cindex Next and previous diagnostic
If the diagnostics are outside the visible region of the buffer,
@code{flymake-goto-next-error} and @code{flymake-goto-prev-error} are
commands that allow easy navigation to the next/previous erroneous
regions, respectively. It might be a good idea to map them to @kbd{M-n}
and @kbd{M-p} in @code{flymake-mode}, by adding to your init file:
let you navigate to the next/previous errorenous regions,
respectively. It might be a good idea to map them to @kbd{M-n} and
@kbd{M-p} in @code{flymake-mode}, by adding to your init file:
@lisp
(define-key flymake-mode-map (kbd "M-n") 'flymake-goto-next-error)
(define-key flymake-mode-map (kbd "M-p") 'flymake-goto-prev-error)
@end lisp
Flymake is a universal syntax checker in the sense that it's easily
extended to support new backends (@pxref{Extending Flymake}).
@cindex Listing diagnostics
Sometimes it is useful to have a detailed overview of the diagnostics
in your files without having to jump to each one to one. The commands
@code{flymake-show-buffer-diagnostics} and
@code{flymake-show-project-diagnostics} are designed to handle this
situation. When invoked, they bring up a separate buffer containing a
detailed structured listing of multiple diagnostics in the current
buffer or for the current project, respectively (@pxref{Projects,,,
emacs, The Emacs Editor}).
Historically, Flymake used to accept diagnostics from a single
backend, albeit a reasonably flexible one.
The listings is continuously updated as you edit source code, adding or
removing lines as you make or correct mistakes. Each line of this
listing includes the type of the diagnostic, its line and column in
the file as well as the diagnostic message. You may sort the listing
by each of these columns.
This backend isn't (yet) obsolete, and so is still available as a
fallback and active by default (@pxref{The legacy Proc backend}). It works by
selecting a syntax check tool from a preconfigured list (compiler for
C@t{++} files, @command{perl} for Perl files, etc.), and executing it in the
background, passing it a temporary file which is a copy of the current
buffer, and parsing the output for known error/warning message
patterns.
@menu
* Syntax check statuses::
* Backend exceptions::
* Customizable variables::
@end menu
@node Syntax check statuses
@section Syntax check statuses
@cindex Syntax check statuses
@node Mode line status
@section Mode line status
@cindex Flymake mode line
@cindex Syntax check status
When enabled, Flymake displays its status in the mode line, which
provides a visual summary of diagnostic collection. It may also hint
@ -157,7 +194,7 @@ delay and Flymake will resume normal operation soon.
@item @code{!}
@tab All the configured Flymake backends have disabled themselves: Flymake
cannot annotate the buffer and action from the user is needed to
investigate and remedy the situation (@pxref{Backend exceptions}).
investigate and remedy the situation (@pxref{Troubleshooting}).
@item @code{?}
@tab There are no applicable Flymake backends for this buffer, thus Flymake
@ -166,17 +203,24 @@ and add a new backend (@pxref{Extending Flymake}).
@end multitable
@node Backend exceptions
@section Backend exceptions
@cindex backend exceptions
If you would like to customize the appearance of the mode-line, you
can use the variables @code{flymake-mode-line-format} and
@code{flymake-mode-line-counter-format} for that purpose.
@xref{Customizable variables}.
@node Troubleshooting
@section Troubleshooting
@cindex Troubleshooting
@cindex Backend exceptions
@cindex disabled backends
@cindex backends, disabled
Some backends may take longer than others to respond or complete, and
some may decide to @emph{disable} themselves if they are not suitable
for the current buffer or encounter some unavoidable problem. A
disabled backend is not tried again for future checks of the current
buffer.
As Flymake supports multiple simutaneously active external backends,
is becomes useful monitor their status. For example, some backends
may take longer than others to respond or complete, and some may
decide to @emph{disable} themselves if they are not suitable for the
current buffer or encounter some unavoidable problem. A disabled
backend is not tried again for future checks of the current buffer.
@findex flymake-reporting-backends
@findex flymake-running-backends
@ -186,18 +230,23 @@ The commands @code{flymake-reporting-backends},
show the backends currently used and those which are disabled.
@cindex reset disabled backends
Toggling @code{flymake-mode} off and on again, or invoking
@code{flymake-start} with a prefix argument is one way to reset the
disabled backend list, so that they will be tried again in the next check.
Sometimes, re-starting a backend that disabled itself is useful after
some external roadblock has been removed (for example after the user
installed a needed syntax-check program). Invoking
@code{flymake-start} with a prefix argument is a way to reset the
disabled backend list, so that they will be tried again in the next
check. Manually toggle @code{flymake-mode} off and on again also
works.
@cindex logging
@cindex flymake logging
Flymake also uses a simple logging facility for indicating important
points in the control flow. The logging facility sends logging
messages to the @file{*Flymake log*} buffer. The information logged
can be used for resolving various problems related to Flymake. For
convenience, a shortcut to this buffer can be found in Flymake's menu,
accessible from the top menu bar or just left of the status indicator.
Flymake uses a simple logging facility for indicating important points
in the control flow. The logging facility sends logging messages to
the @file{*Flymake log*} buffer. The logged information can be used
for resolving various problems related to Flymake. For convenience, a
shortcut to this buffer can be found in Flymake's menu, accessible
from the top menu bar or just left of the status indicator. The
command @code{flymake-switch-to-log-buffer} is another alternative.
@vindex warning-minimum-log-level
@vindex warning-minimum-level
@ -217,7 +266,7 @@ configuration of the Flymake user interface.
Format to use for the Flymake mode line indicator.
@item flymake-mode-line-counter-format
Mode-line construct for formatting Flymake diagnostic counters inside
mode line construct for formatting Flymake diagnostic counters inside
the Flymake mode line indicator.
@item flymake-no-changes-timeout
@ -270,10 +319,10 @@ Flymake can primarily be extended in one of two ways:
@enumerate
@item
By changing the look and feel of the annotations produced by the
different backends.
different backends. @xref{Flymake error types}.
@item
By adding a new buffer-checking backend.
By adding a new buffer-checking backend. @xref{Backend functions}.
@end enumerate
The following sections discuss each approach in detail.
@ -288,10 +337,12 @@ The following sections discuss each approach in detail.
@cindex customizing error types
@cindex error types, customization
To customize the appearance of error types, set properties on the
symbols associated with each diagnostic type. The standard diagnostic
symbols are @code{:error}, @code{:warning} and @code{:note} (though
the backend may define more, @pxref{Backend functions}).
To customize the appearance of error types, the user must set
properties on the symbols associated with each diagnostic type.
The three standard diagnostic keyowrd symbols -- @code{:error},
@code{:warning} and @code{:note} -- have pre-configured appearances.
However a backend may define more (@pxref{Backend functions}).
The following properties can be set:
@ -489,7 +540,7 @@ manual}) or other asynchronous mechanisms.
In any case, backend functions are expected to return quickly or
signal an error, in which case the backend is disabled
(@pxref{Backend exceptions}).
(@pxref{Troubleshooting}).
If the function returns, Flymake considers the backend to be
@dfn{running}. If it has not done so already, the backend is expected
@ -540,6 +591,7 @@ reports targeting other parts of the buffer remain valid.
@menu
* Flymake utility functions::
* Foreign and list-only diagnostics::
* An annotated example backend::
@end menu
@ -551,20 +603,26 @@ reports targeting other parts of the buffer remain valid.
Before delivering them to Flymake, backends create diagnostic objects
by calling the function @code{flymake-make-diagnostic}.
@deffn Function flymake-make-diagnostic buffer beg end type text
Make a Flymake diagnostic for @var{buffer}'s region from @var{beg} to
@var{end}. @var{type} is a diagnostic symbol (@pxref{Flymake error
types}), and @var{text} is a description of the problem detected in
this region. Currently, it is unspecified behavior to make
diagnostics for buffers other than the buffer that the Flymake backend
is responsible for.
@deffn Function flymake-make-diagnostic locus beg end type text &optional data
Make a Flymake diagnostic for the region of text in @var{locus}'s
delimited by @var{beg} and @var{end}. @var{type} is a diagnostic
symbol (@pxref{Flymake error types}), and @var{text} is a description
of the problem detected in this region. Most commonly @var{locus} is
the buffer object designating for the current buffer being
syntax-checked. However, it may be a string nameing a file relative
to the current working directory. @xref{Foreign and list-only
diagnostics} for when this may be useful. Depending on the type of
@var{locus}, @var{beg} and @var{end} are both either buffer positions
or conses (@var{line} . @var{col}) which specify the line and column
of the diagnostic's start and end positions, respectively.
@end deffn
@cindex access diagnostic object
These objects' properties can be accessed with the functions
@code{flymake-diagnostic-backend}, @code{flymake-diagnostic-buffer},
@code{flymake-diagnostic-text}, @code{flymake-diagnostic-beg},
@code{flymake-diagnostic-end} and @code{flymake-diagnostic-type}.
@code{flymake-diagnostic-end}, @code{flymake-diagnostic-type} and
@code{flymake-diagnostic-data}.
Additionally, the function @code{flymake-diagnostics} will collect
such objects in the region you specify.
@ -595,7 +653,7 @@ elisp, The Emacs Lisp Reference Manual}).
@cindex add a log message
For troubleshooting purposes, backends may record arbitrary
exceptional or erroneous situations into the Flymake log
buffer (@pxref{Backend exceptions}):
buffer (@pxref{Troubleshooting}):
@deffn Macro flymake-log level msg &optional args
Log, at level @var{level}, the message @var{msg} formatted with
@ -604,6 +662,59 @@ Log, at level @var{level}, the message @var{msg} formatted with
used to display the warning in Flymake's log buffer.
@end deffn
@node Foreign and list-only diagnostics
@subsection Foreign and list-only diagnostics
@cindex create diagnostic object for other buffer
It is possible for a given backend's implementation to use
@code{flymake-make-diagnostic} to create diagnostics for buffers or
files other than the ``source'' buffer where Flymake was enabled. For
instance, this is useful when a given backend has access to
information about the health of neighboring files that are not yet
visited or whose diagnostics depend on the current buffer's state.
There are two alternative ways to go about doing this:
@enumerate
@item
@cindex foreign diagnostics
@cindex domestic diagnostics
If the information about neighboring diagnostics is obtained
regularly, like when each syntax-checking iteration of a @code{.c}
file also reports a number of associated problems in an neighboring
@code{.h} file, it is better to create so-called ``foreign''
diagnostics. This is done by passing a file name to
@code{flymake-make-diagnostic} (@pxref{Flymake utility functions}).
Then, the resulting object is simply reported along with the other
``domestic'' diagnostics for the source buffer (@pxref{Backend
functions}). If the neighboring file is visited as a buffer and
Flymake is active there, a number of supplemental annotations will
appear and automatically update whenever as the ``source'' buffer is
syntax-checked.
@item
@cindex list-only diagnostics
If information about neighboring diagnostics is obtained infrequently,
like when running a time-consuming and sporadic check of a large
project, it is easier for the backend to modify the global variable
@code{flymake-list-only-diagnostics}. Flymake will look up this
variable when asked to compile project-wide lists of diagnostics. The
backend should add one or more alist entries that look like
(@var{file-name} . @var{diags}). @var{file-name} is the absolute name
of the neighboring file presumed not to be visited in Emacs already,
as that would mean that that buffer contains more up-to-date
information on its diagnostics. @var{diags} is a list of diagnostic
objects. If the neighboring file @var{file-name} is visited as a
buffer and Flymake is activated there, this does @emph{not} produce
annotations for @var{diags}, as Flymake assumes that the Flymake
activation in the new buffer will take care of that. To avoid
confusion, that action also deletes the corresponding entries for
@var{file-name} in @code{flymake-list-only-diagnostics}. If the
buffer is killed, it is up to the backend to re-add an alist entry to
the variable again with ``fresher'' information gathered from the last
syntax check.
@end enumerate
@node An annotated example backend
@subsection An annotated example backend
@cindex example of backend
@ -1038,7 +1149,7 @@ correct @file{file.h}.
First matching master file found stops the search. The master file is then
patched and saved to disk. In case no master file is found, syntax check is
aborted, and corresponding status (@samp{!}) is reported in the mode line.
@xref{Syntax check statuses}.
@xref{Mode line status}.
@node Getting the include directories
@section Getting the include directories

View file

@ -2522,6 +2522,14 @@ images are marked.
** Flymake mode
+++
*** New command 'flymake-show-project-diagnostics'
This lists all diagnostics for buffers in the currently active project
in a buffer similar to the one obtained by the
'flymake-show-buffer-diagnostics', which lists for only one buffer.
For backends supporting it, 'flymake-show-project-diagnostics' may
also list diagnostics for files that have not yet been visited.
+++
*** New user options to customize Flymake's mode-line.
The new user option 'flymake-mode-line-format' is a mix of strings and

View file

@ -61,23 +61,34 @@ SOURCE."
(cl-loop
while
(search-forward-regexp
"^\\(In file included from \\)?<stdin>:\\([0-9]+\\)\\(?::\\([0-9]+\\)\\)?:\n?\\(.*\\): \\(.*\\)$"
"^\\(In file included from \\)?\\([^ :]+\\):\\([0-9]+\\)\\(?::\\([0-9]+\\)\\)?:\n?\\(.*\\): \\(.*\\)$"
nil t)
for msg = (match-string 5)
for (beg . end) = (flymake-diag-region
source
(string-to-number (match-string 2))
(and (match-string 3) (string-to-number (match-string 3))))
for msg = (match-string 6)
for locus = (match-string 2)
for line = (string-to-number (match-string 3))
for col = (ignore-errors (string-to-number (match-string 4)))
for source-buffer = (and (string= locus "<stdin>") source)
for type = (if (match-string 1)
:error
(assoc-default
(match-string 4)
'(("error" . :error)
("note" . :note)
("warning" . :warning))
#'string-match
:error))
collect (flymake-make-diagnostic source beg end type msg)))
(save-match-data
(assoc-default
(match-string 5)
'(("error" . :error)
("note" . :note)
("warning" . :warning))
#'string-match
:error)))
for diag =
(cond (source-buffer
(pcase-let ((`(,beg . ,end)
(flymake-diag-region source-buffer line col)))
(flymake-make-diagnostic source-buffer beg end type msg)))
(t (flymake-make-diagnostic locus (cons line col) nil type msg)))
collect diag
;; If "In file included from..." matched, then move to end of that
;; line. This helps us collect the diagnostic at its .h locus,
;; too.
when (match-end 1) do (goto-char (match-end 2))))
(defun flymake-cc-use-special-make-target ()
"Command for checking a file via a CHK_SOURCES Make target."

View file

@ -4,9 +4,9 @@
;; Author: Pavel Kobyakov <pk_at_work@yahoo.com>
;; Maintainer: João Távora <joaotavora@gmail.com>
;; Version: 1.1.1
;; Version: 1.2.1
;; Keywords: c languages tools
;; Package-Requires: ((emacs "26.1") (eldoc "1.1.0"))
;; Package-Requires: ((emacs "26.1") (eldoc "1.1.0") (project "0.7.1"))
;; This is a GNU ELPA :core package. Avoid functionality that is not
;; compatible with the version of Emacs recorded above.
@ -121,6 +121,7 @@
(require 'mwheel)
;; when-let*, if-let*, hash-table-keys, hash-table-values:
(eval-when-compile (require 'subr-x))
(require 'project)
(defgroup flymake nil
"Universal on-the-fly syntax checker."
@ -291,7 +292,7 @@ generated it."
(macroexp-file-name)
(and (not load-file-name)
(bound-and-true-p byte-compile-current-file))))
(sublog (if file
(sublog (if (stringp file)
(intern
(file-name-nondirectory
(file-name-sans-extension file))))))
@ -305,35 +306,51 @@ generated it."
(cl-defstruct (flymake--diag
(:constructor flymake--diag-make))
buffer beg end type text backend data overlay-properties overlay)
locus beg end type text backend data overlay-properties overlay
;; FIXME: See usage of these two in `flymake--highlight-line'.
;; Ideally they wouldn't be needed.
orig-beg orig-end)
;;;###autoload
(defun flymake-make-diagnostic (buffer
(defun flymake-make-diagnostic (locus
beg
end
type
text
&optional data
overlay-properties)
"Make a Flymake diagnostic for BUFFER's region from BEG to END.
"Make a Flymake diagnostic for LOCUS's region from BEG to END.
LOCUS is a buffer object or a string designating a file name.
TYPE is a diagnostic symbol and TEXT is string describing the
problem detected in this region. DATA is any object that the
caller wishes to attach to the created diagnostic for later
retrieval.
retrieval with `flymake-diagnostic-data'.
If LOCUS is a buffer BEG and END should be buffer positions
inside it. If LOCUS designates a file, BEG and END should be a
cons (LINE . COL) indicating a file position. In this second
case, END may be ommited in which case the region is computed
using `flymake-diag-region' if the diagnostic is appended to an
actual buffer.
OVERLAY-PROPERTIES is an alist of properties attached to the
created diagnostic, overriding the default properties and any
properties of `flymake-overlay-control' of the diagnostic's
type."
(flymake--diag-make :buffer buffer :beg beg :end end
properties listed in the `flymake-overlay-control' property of
the diagnostic's type symbol."
(when (stringp locus)
(setq locus (expand-file-name locus)))
(flymake--diag-make :locus locus :beg beg :end end
:type type :text text :data data
:overlay-properties overlay-properties))
:overlay-properties overlay-properties
:orig-beg beg
:orig-end end))
;;;###autoload
(defun flymake-diagnostics (&optional beg end)
"Get Flymake diagnostics in region determined by BEG and END.
If neither BEG or END is supplied, use the whole buffer,
If neither BEG or END is supplied, use whole accessible buffer,
otherwise if BEG is non-nil and END is nil, consider only
diagnostics at BEG."
(mapcar (lambda (ov) (overlay-get ov 'flymake-diagnostic))
@ -345,27 +362,13 @@ diagnostics at BEG."
,(format "Get Flymake diagnostic DIAG's %s." (symbol-name thing))
(,internal diag)))
(flymake--diag-accessor flymake-diagnostic-buffer flymake--diag-buffer buffer)
(flymake--diag-accessor flymake-diagnostic-text flymake--diag-text text)
(flymake--diag-accessor flymake-diagnostic-type flymake--diag-type type)
(flymake--diag-accessor flymake-diagnostic-backend flymake--diag-backend backend)
(flymake--diag-accessor flymake-diagnostic-data flymake--diag-data backend)
(defun flymake-diagnostic-beg (diag)
"Get Flymake diagnostic DIAG's start position.
This position only be queried after DIAG has been reported to Flymake."
(let ((overlay (flymake--diag-overlay diag)))
(unless overlay
(error "DIAG %s not reported to Flymake yet" diag))
(overlay-start overlay)))
(defun flymake-diagnostic-end (diag)
"Get Flymake diagnostic DIAG's end position.
This position only be queried after DIAG has been reported to Flymake."
(let ((overlay (flymake--diag-overlay diag)))
(unless overlay
(error "DIAG %s not reported to Flymake yet" diag))
(overlay-end overlay)))
(flymake--diag-accessor flymake-diagnostic-data flymake--diag-data data)
(flymake--diag-accessor flymake-diagnostic-beg flymake--diag-beg beg)
(flymake--diag-accessor flymake-diagnostic-end flymake--diag-end end)
(flymake--diag-accessor flymake-diagnostic-buffer flymake--diag-locus locus)
(cl-defun flymake--overlays (&key beg end filter compare key)
"Get flymake-related overlays.
@ -625,13 +628,74 @@ associated `flymake-category' return DEFAULT."
bitmap
(list bitmap)))))))
(defun flymake--highlight-line (diagnostic)
"Highlight buffer with info in DIAGNOSTIC."
(let ((type (or (flymake--diag-type diagnostic)
:error))
(ov (make-overlay
(flymake--diag-beg diagnostic)
(flymake--diag-end diagnostic))))
(defun flymake--equal-diagnostic-p (a b)
"Tell if A and B are equivalent `flymake--diag' objects."
(or (eq a b)
(cl-loop for comp in '(flymake--diag-end
flymake--diag-beg
flymake-diagnostic-type
flymake-diagnostic-backend
flymake-diagnostic-text)
always (equal (funcall comp a) (funcall comp b)))))
(cl-defun flymake--highlight-line (diagnostic &optional foreign)
"Attempt to overlay DIAGNOSTIC in current buffer.
FOREIGN says if DIAGNOSTIC is \"foreign\" to the current buffer,
i.e. managed by another buffer where `flymake-mode' is also
active.
This function mayskip overlay creation if a diagnostic which is
the same as DIAGNOSTIC is already highlighted
(in the sense of `flymake--equal-diagnostic-p'). In that case
the action to take depends on FOREIGN. If nil the existing
overlay is deleted, else no overlay is created.
Return nil or the overlay created."
(let* ((type (or (flymake-diagnostic-type diagnostic)
:error))
(beg (flymake--diag-beg diagnostic))
(end (flymake--diag-end diagnostic))
(convert (lambda (cell)
(flymake-diag-region (current-buffer)
(car cell)
(cdr cell))))
ov)
;; Convert (LINE . COL) forms of `flymake--diag-beg' and
;; `flymake--diag-end'. Record the converted positions.
;;
(cond ((and (consp beg) (not (null end)))
(setq beg (car (funcall convert beg)))
(when (consp end)
(setq end (car (funcall convert end)))))
((consp beg)
(cl-destructuring-bind (a . b) (funcall convert beg)
(setq beg a end b))))
(setf (flymake--diag-beg diagnostic) beg
(flymake--diag-end diagnostic) end)
;; Try to fix the remedy the situation if there is the same
;; diagnostic is already registered in the same place, which only
;; happens for clashes between domestic and foreign diagnostics
(cl-loop for e in (flymake-diagnostics beg end)
when (flymake--equal-diagnostic-p e diagnostic)
;; FIXME. This is an imperfect heuristic. Ideally, we'd
;; want to delete no overlays and keep annotating the
;; superseded foreign in an overlay but hide it from most
;; `flymake-diagnostics' calls. If the target buffer is
;; killed we can keep the "latent" state of the foreign
;; diagnostic (with filename and updated line/col info).
;; If it is revisited the foreign diagnostic can be
;; revived again.
do (if foreign
(cl-return-from flymake--highlight-line nil)
(setf (flymake--diag-beg e)
(flymake--diag-orig-beg e)
(flymake--diag-end e)
(flymake--diag-orig-end e))
(delete-overlay (flymake--diag-overlay e))))
(setq ov (make-overlay end beg))
(setf (flymake--diag-beg diagnostic) (overlay-start ov)
(flymake--diag-end diagnostic) (overlay-end ov))
;; First set `category' in the overlay
;;
(overlay-put ov 'category
@ -665,7 +729,7 @@ associated `flymake-category' return DEFAULT."
(lambda (window _ov pos)
(with-selected-window window
(mapconcat
#'flymake--diag-text
#'flymake-diagnostic-text
(flymake-diagnostics pos)
"\n"))))
(default-maybe 'severity (warning-numeric-level :error))
@ -676,17 +740,18 @@ associated `flymake-category' return DEFAULT."
;;
(overlay-put ov 'evaporate t)
(overlay-put ov 'flymake-diagnostic diagnostic)
(setf (flymake--diag-overlay diagnostic) ov)
ov))
;; Nothing in Flymake uses this at all any more, so this is just for
;; third-party compatibility.
(define-obsolete-function-alias 'flymake-display-warning 'message-box "26.1")
(defvar-local flymake--backend-state nil
"Buffer-local hash table of a Flymake backend's state.
(defvar-local flymake--state nil
"State of a buffer's multiple Flymake backends.
The keys to this hash table are functions as found in
`flymake-diagnostic-functions'. The values are structures
of the type `flymake--backend-state', with these slots:
of the type `flymake--state', with these slots:
`running', a symbol to keep track of a backend's replies via its
REPORT-FN argument. A backend is running if this key is
@ -701,11 +766,16 @@ since it last was contacted.
`disabled', a string with the explanation for a previous
exceptional situation reported by the backend, nil if the
backend is operating normally.")
backend is operating normally.
(cl-defstruct (flymake--backend-state
`foreign-diags', a hash table of buffers/files to
collections of diagnostics outside the buffer where this
`flymake--state' pertains.")
(cl-defstruct (flymake--state
(:constructor flymake--make-backend-state))
running reported-p disabled diags)
running reported-p disabled diags (foreign-diags
(make-hash-table)))
(defmacro flymake--with-backend-state (backend state-var &rest body)
"Bind BACKEND's STATE-VAR to its state, run BODY."
@ -713,9 +783,9 @@ backend is operating normally.")
(let ((b (make-symbol "b")))
`(let* ((,b ,backend)
(,state-var
(or (gethash ,b flymake--backend-state)
(or (gethash ,b flymake--state)
(puthash ,b (flymake--make-backend-state)
flymake--backend-state))))
flymake--state))))
,@body)))
(defun flymake-is-running ()
@ -730,9 +800,14 @@ backend is operating normally.")
(and (>= start1 start0) (< start1 end0))
(and (> end1 start0) (<= end1 end0))))
(cl-defun flymake--handle-report (backend token report-action
&key explanation force region
&allow-other-keys)
(cl-defun flymake--handle-report
(backend token report-action
&key explanation force region
&allow-other-keys
&aux
(state (or (gethash backend flymake--state)
(error "Can't find state for %s in `flymake--state'" backend)))
expected-token)
"Handle reports from BACKEND identified by TOKEN.
BACKEND, REPORT-ACTION and EXPLANATION, and FORCE conform to the
calling convention described in
@ -740,81 +815,112 @@ calling convention described in
to handle a report even if TOKEN was not expected. REGION is
a (BEG . END) pair of buffer positions indicating that this
report applies to that region."
(let* ((state (gethash backend flymake--backend-state))
first-report)
(unless state
(error "Can't find state for %s in `flymake--backend-state'" backend))
(setf first-report (not (flymake--backend-state-reported-p state)))
(setf (flymake--backend-state-reported-p state) t)
(let (expected-token
new-diags)
(cond
((null state)
(flymake-error
"Unexpected report from unknown backend %s" backend))
((flymake--backend-state-disabled state)
(flymake-error
"Unexpected report from disabled backend %s" backend))
((progn
(setq expected-token (flymake--backend-state-running state))
(null expected-token))
;; should never happen
(flymake-error "Unexpected report from stopped backend %s" backend))
((not (or (eq expected-token token)
force))
(flymake-error "Obsolete report from backend %s with explanation %s"
backend explanation))
((eq :panic report-action)
(flymake--disable-backend backend explanation))
((not (listp report-action))
(flymake--disable-backend backend
(format "Unknown action %S" report-action))
(flymake-error "Expected report, but got unknown key %s" report-action))
(t
(setq new-diags
(cl-remove-if-not
(lambda (diag) (eq (flymake--diag-buffer diag) (current-buffer)))
report-action))
(save-restriction
(widen)
;; Before adding to backend's diagnostic list, decide if
;; some or all must be deleted. When deleting, also delete
;; the associated overlay.
(cond
(region
(cl-loop for diag in (flymake--backend-state-diags state)
for ov = (flymake--diag-overlay diag)
if (or (not (overlay-buffer ov))
(flymake--intersects-p
(overlay-start ov) (overlay-end ov)
(car region) (cdr region)))
do (delete-overlay ov)
else collect diag into surviving
finally (setf (flymake--backend-state-diags state)
surviving)))
(first-report
(dolist (diag (flymake--backend-state-diags state))
(delete-overlay (flymake--diag-overlay diag)))
(setf (flymake--backend-state-diags state) nil)))
;; Now make new ones
(mapc (lambda (diag)
(let ((overlay (flymake--highlight-line diag)))
(setf (flymake--diag-backend diag) backend
(flymake--diag-overlay diag) overlay)))
new-diags)
(setf (flymake--backend-state-diags state)
(append new-diags (flymake--backend-state-diags state)))
(when flymake-check-start-time
(flymake-log :debug "backend %s reported %d diagnostics in %.2f second(s)"
backend
(length new-diags)
(float-time
(time-since flymake-check-start-time))))
(when (and (get-buffer (flymake--diagnostics-buffer-name))
(get-buffer-window (flymake--diagnostics-buffer-name))
(null (cl-set-difference (flymake-running-backends)
(flymake-reporting-backends))))
(flymake-show-diagnostics-buffer))))))))
(unless state
(error "Can't find state for %s in `flymake--state'" backend))
(cond
((null state)
(flymake-error
"Unexpected report from unknown backend %s" backend))
((flymake--state-disabled state)
(flymake-error
"Unexpected report from disabled backend %s" backend))
((progn
(setq expected-token (flymake--state-running state))
(null expected-token))
;; should never happen
(flymake-error "Unexpected report from stopped backend %s" backend))
((not (or (eq expected-token token)
force))
(flymake-error "Obsolete report from backend %s with explanation %s"
backend explanation))
((eq :panic report-action)
(flymake--disable-backend backend explanation))
((not (listp report-action))
(flymake--disable-backend backend
(format "Unknown action %S" report-action))
(flymake-error "Expected report, but got unknown key %s" report-action))
(t
(flymake--publish-diagnostics report-action
:backend backend
:state state
:region region)
(when flymake-check-start-time
(flymake-log :debug "backend %s reported %d diagnostics in %.2f second(s)"
backend
(length report-action)
(float-time
(time-since flymake-check-start-time))))))
(setf (flymake--state-reported-p state) t)
(flymake--update-diagnostics-listings (current-buffer)))
(defun flymake--clear-foreign-diags (state)
(maphash (lambda (_buffer diags)
(cl-loop for d in diags
when (flymake--diag-overlay d)
do (delete-overlay it)))
(flymake--state-foreign-diags state))
(clrhash (flymake--state-foreign-diags state)))
(defvar-local flymake-mode nil)
(cl-defun flymake--publish-diagnostics (diags &key backend state region)
"Helper for `flymake--handle-report'.
Publish DIAGS, which contain diagnostics for the current buffer
and other buffers."
(dolist (d diags) (setf (flymake--diag-backend d) backend))
(save-restriction
(widen)
;; First, clean up. Remove diagnostics from bookeeping lists and
;; their overlays from buffers.
;;
(cond
(;; If there is a `region' arg, only affect the diagnostics whose
;; overlays are in a certain region. Discard "foreign"
;; diagnostics.
region
(cl-loop for diag in (flymake--state-diags state)
for ov = (flymake--diag-overlay diag)
if (or (not (overlay-buffer ov))
(flymake--intersects-p
(overlay-start ov) (overlay-end ov)
(car region) (cdr region)))
do (delete-overlay ov)
else collect diag into surviving
finally (setf (flymake--state-diags state)
surviving)))
(;; Else, if this is the first report, zero all lists and delete
;; all associated overlays.
(not (flymake--state-reported-p state))
(cl-loop for diag in (flymake--state-diags state)
for ov = (flymake--diag-overlay diag)
when ov do (delete-overlay ov))
(setf (flymake--state-diags state) nil)
;; Also clear all overlays for `foreign-diags' in all other
;; buffers.
(flymake--clear-foreign-diags state))
(;; If this is not the first report, do no cleanup.
t))
;; Now place new overlays for all diagnostics: "domestic"
;; diagnostics are for the current buffer; "foreign" may be for a
;; some other live buffer or for a file name that hasn't a buffer
;; yet. If a foreign diagnostic is for a buffer, convert to a
;; file name, protecting it against that buffer's killing.
;;
(cl-loop
for d in diags
for locus = (flymake--diag-locus d)
do (cond ((eq locus (current-buffer))
(push d (flymake--state-diags state))
(flymake--highlight-line d))
(t
(when (or (buffer-live-p locus)
(setq locus (find-buffer-visiting locus)))
(with-current-buffer locus
(when flymake-mode (flymake--highlight-line d 'foreign))
(setf (flymake--diag-locus d) (buffer-file-name))))
(push d (gethash (flymake--diag-locus d)
(flymake--state-foreign-diags state))))))))
(defun flymake-make-report-fn (backend &optional token)
"Make a suitable anonymous report function for BACKEND.
@ -830,12 +936,12 @@ different runs of the same backend."
(defun flymake--collect (fn &optional message-prefix)
"Collect Flymake backends matching FN.
If MESSAGE-PREFIX, echo a message using that prefix."
(unless flymake--backend-state
(unless flymake--state
(user-error "Flymake is not initialized"))
(let (retval)
(maphash (lambda (backend state)
(when (funcall fn state) (push backend retval)))
flymake--backend-state)
flymake--state)
(when message-prefix
(message "%s%s"
message-prefix
@ -846,21 +952,21 @@ If MESSAGE-PREFIX, echo a message using that prefix."
(defun flymake-running-backends ()
"Compute running Flymake backends in current buffer."
(interactive)
(flymake--collect #'flymake--backend-state-running
(flymake--collect #'flymake--state-running
(and (called-interactively-p 'interactive)
"Running backends: ")))
(defun flymake-disabled-backends ()
"Compute disabled Flymake backends in current buffer."
(interactive)
(flymake--collect #'flymake--backend-state-disabled
(flymake--collect #'flymake--state-disabled
(and (called-interactively-p 'interactive)
"Disabled backends: ")))
(defun flymake-reporting-backends ()
"Compute reporting Flymake backends in current buffer."
(interactive)
(flymake--collect #'flymake--backend-state-reported-p
(flymake--collect #'flymake--state-reported-p
(and (called-interactively-p 'interactive)
"Reporting backends: ")))
@ -869,9 +975,9 @@ If MESSAGE-PREFIX, echo a message using that prefix."
If it is running also stop it."
(flymake-log :warning "Disabling backend %s because %s" backend explanation)
(flymake--with-backend-state backend state
(setf (flymake--backend-state-running state) nil
(flymake--backend-state-disabled state) explanation
(flymake--backend-state-reported-p state) t)))
(setf (flymake--state-running state) nil
(flymake--state-disabled state) explanation
(flymake--state-reported-p state) t)))
(defun flymake--run-backend (backend &optional args)
"Run the backend BACKEND, re-enabling if necessary.
@ -880,9 +986,9 @@ with a report function."
(flymake-log :debug "Running backend %s" backend)
(let ((run-token (cl-gensym "backend-token")))
(flymake--with-backend-state backend state
(setf (flymake--backend-state-running state) run-token
(flymake--backend-state-disabled state) nil
(flymake--backend-state-reported-p state) nil))
(setf (flymake--state-running state) run-token
(flymake--state-disabled state) nil
(flymake--state-reported-p state) nil))
;; FIXME: Should use `condition-case-unless-debug' here, but don't
;; for two reasons: (1) that won't let me catch errors from inside
;; `ert-deftest' where `debug-on-error' appears to be always
@ -964,7 +1070,7 @@ Interactively, with a prefix arg, FORCE is t."
(cond
((and (not force)
(flymake--with-backend-state backend state
(flymake--backend-state-disabled state)))
(flymake--state-disabled state)))
(flymake-log :debug "Backend %s is disabled, not starting"
backend))
(t
@ -1019,13 +1125,45 @@ special *Flymake log* buffer." :group 'flymake :lighter
;; If Flymake happened to be already already ON, we must cleanup
;; existing diagnostic overlays, lest we forget them by blindly
;; reinitializing `flymake--backend-state' in the next line.
;; reinitializing `flymake--state' in the next line.
;; See https://github.com/joaotavora/eglot/issues/223.
(mapc #'delete-overlay (flymake--overlays))
(setq flymake--backend-state (make-hash-table))
(setq flymake--state (make-hash-table))
(setq flymake--recent-changes nil)
(when flymake-start-on-flymake-mode (flymake-start t)))
(when flymake-start-on-flymake-mode (flymake-start t))
;; Other diagnostic sources may already target this buffer's file
;; before we turned on: these sources may be of two types...
(let ((source (current-buffer))
(bfn buffer-file-name))
;; 1. For `flymake-list-only-diagnostics': here, we do nothing.
;; FIXME: We could remove the corresponding entry from that
;; variable, as we assume that new diagnostics will come in soon
;; via the brand new `flymake-mode' setup. For simplicity's
;; sake, we have opted to leave the backend for now.
nil
;; 2. other buffers where a backend has created "foreign"
;; diagnostics and pointed them here. We must highlight them in
;; this buffer, i.e. create overlays for them. Those other
;; buffers and backends are still responsible for them, i.e. the
;; current buffer does not "own" these foreign diags.
(dolist (buffer (buffer-list))
(with-current-buffer buffer
(when (and flymake-mode flymake--state)
(maphash (lambda (_backend state)
(maphash (lambda (locus diags)
(when (or (eq locus source)
(and (stringp locus)
(string= bfn (expand-file-name
locus))))
(with-current-buffer source
(mapc (lambda (diag)
(flymake--highlight-line diag
'foreign))
diags))))
(flymake--state-foreign-diags state)))
flymake--state))))))
;; Turning the mode OFF.
(t
@ -1035,11 +1173,16 @@ special *Flymake log* buffer." :group 'flymake :lighter
;;+(remove-hook 'find-file-hook (function flymake-find-file-hook) t)
(remove-hook 'eldoc-documentation-functions 'flymake-eldoc-function t)
(mapc #'delete-overlay (flymake--overlays))
(when flymake-timer
(cancel-timer flymake-timer)
(setq flymake-timer nil)))))
(setq flymake-timer nil))
(mapc #'delete-overlay (flymake--overlays))
(when flymake--state
(maphash (lambda (_backend state)
(flymake--clear-foreign-diags state))
flymake--state)))
;; turning Flymake on or off has consequences for listings
(flymake--update-diagnostics-listings (current-buffer))))
(defun flymake--schedule-timer-maybe ()
"(Re)schedule an idle timer for checking the buffer.
@ -1091,9 +1234,9 @@ START and STOP and LEN are as in `after-change-functions'."
(flymake-start t)))
(defun flymake-kill-buffer-hook ()
(when flymake-timer
(cancel-timer flymake-timer)
(setq flymake-timer nil)))
;; Explicitly set flymake off, because that does a lot of useful
;; cleanup.
(flymake-mode -1))
(defun flymake-find-file-hook ()
(unless (or flymake-mode
@ -1137,7 +1280,7 @@ default) no filter is applied."
(not filter)
(cl-find
(flymake--severity
(flymake--diag-type diag))
(flymake-diagnostic-type diag))
filter :key #'flymake--severity)))))
:compare (if (cl-plusp n) #'< #'>)
:key #'overlay-start))
@ -1247,7 +1390,7 @@ correctly.")
help-echo
,(lambda (&rest _)
(concat
(format "%s known backends\n" (hash-table-count flymake--backend-state))
(format "%s known backends\n" (hash-table-count flymake--state))
(format "%s running\n" (length (flymake-running-backends)))
(format "%s disabled\n" (length (flymake-disabled-backends)))
"mouse-1: Display minor mode menu\n"
@ -1268,7 +1411,7 @@ correctly.")
"Helper for `flymake-mode-line-exception'."
(pcase-let* ((running) (reported)
(`(,ind ,face ,explain)
(cond ((zerop (hash-table-count flymake--backend-state))
(cond ((zerop (hash-table-count flymake--state))
'("?" nil "No known backends"))
((cl-set-difference
(setq running (flymake-running-backends))
@ -1300,13 +1443,10 @@ TYPE is usually keyword `:error', `:warning' or `:note'."
(face (flymake--lookup-type-property type
'mode-line-face
'compilation-error)))
(maphash (lambda
(_b state)
(dolist (d (flymake--backend-state-diags state))
(when (= (flymake--severity type)
(flymake--severity (flymake--diag-type d)))
(cl-incf count))))
flymake--backend-state)
(dolist (d (flymake-diagnostics))
(when (= (flymake--severity type)
(flymake--severity (flymake-diagnostic-type d)))
(cl-incf count)))
(when (or (cl-plusp count)
(cond ((eq flymake-suppress-zero-counters t)
nil)
@ -1336,7 +1476,7 @@ TYPE is usually keyword `:error', `:warning' or `:note'."
(flymake-goto-next-error 1 (list type) t))))
map))))))
;;; Diagnostics buffer
;;; Per-buffer diagnostic listing
(defvar-local flymake--diagnostics-buffer-source nil)
@ -1351,14 +1491,30 @@ TYPE is usually keyword `:error', `:warning' or `:note'."
(interactive (list (point) t))
(let* ((id (or (tabulated-list-get-id pos)
(user-error "Nothing at point")))
(diag (plist-get id :diagnostic)))
(with-current-buffer (flymake--diag-buffer diag)
(diag (plist-get id :diagnostic))
(locus (flymake--diag-locus diag))
(beg (flymake--diag-beg diag))
(end (flymake--diag-end diag))
(visit (lambda (b e)
(goto-char b)
(pulse-momentary-highlight-region (point)
(or e (line-end-position))
'highlight))))
(with-current-buffer (cond ((bufferp locus) locus)
(t (find-file-noselect locus)))
(with-selected-window
(display-buffer (current-buffer) other-window)
(goto-char (flymake--diag-beg diag))
(pulse-momentary-highlight-region (flymake-diagnostic-beg diag)
(flymake-diagnostic-end diag)
'highlight))
(cond (;; an annotated diagnostic (most common case), or a
;; non-annotated buffer diag
(number-or-marker-p beg)
(funcall visit beg end))
(;; a non-annotated file diag (TODO: could use `end'
;; here, too)
(pcase-let ((`(,bbeg . ,bend)
(flymake-diag-region (current-buffer)
(car beg)
(cdr beg))))
(funcall visit bbeg bend)))))
(current-buffer))))
(defun flymake-goto-diagnostic (pos)
@ -1368,65 +1524,115 @@ POS can be a buffer position or a button"
(pop-to-buffer
(flymake-show-diagnostic (if (button-type pos) (button-start pos) pos))))
(defun flymake--tabulated-entries-1 (diags project-root)
"Helper for `flymake--diagnostic-buffer-entries'.
PROJECT-ROOT indicates that each entry should be preceded by the
filename of the diagnostic relative to that directory."
(cl-loop
for diag in diags
for locus = (flymake-diagnostic-buffer diag)
for file = (if (bufferp locus)
(buffer-file-name locus)
locus)
for overlay = (flymake--diag-overlay diag)
for (line . col) =
(cond (;; has live overlay, use overlay for position
(and overlay (overlay-buffer overlay))
(with-current-buffer (overlay-buffer overlay)
(save-excursion
(goto-char (overlay-start overlay))
(cons (line-number-at-pos)
(- (point)
(line-beginning-position))))))
(;; diagnostic not annotated, maybe foreign, check for cons
(consp (flymake--diag-beg diag))
(flymake--diag-beg diag))
(;; may still be a valid foreign diagnostic
(consp (flymake--diag-orig-beg diag))
(flymake--diag-orig-beg diag))
(;; somehow dead annotated diagnostic, ignore/give up
t nil))
for type = (flymake-diagnostic-type diag)
for backend = (flymake-diagnostic-backend diag)
for bname = (or (ignore-errors (symbol-name backend))
"(anonymous function)")
for data-vec = `[,(format "%s" line)
,(format "%s" col)
,(propertize (format "%s"
(flymake--lookup-type-property
type 'flymake-type-name type))
'face (flymake--lookup-type-property
type 'mode-line-face 'flymake-error))
,(propertize
(if bname
(replace-regexp-in-string "\\(.\\)[^-]+\\(-\\|$\\)"
"\\1\\2" bname)
"(anon)")
'help-echo (format "From `%s' backend" backend))
(,(format "%s" (flymake-diagnostic-text diag))
mouse-face highlight
help-echo "mouse-2: visit this diagnostic"
face nil
action flymake-goto-diagnostic
mouse-action flymake-goto-diagnostic)]
when (and line col) collect
(list (list :diagnostic diag
:line line
:severity (flymake--lookup-type-property
type
'severity (warning-numeric-level :error)))
(if project-root
(vconcat `[(,(file-name-nondirectory file)
help-echo ,(file-relative-name file project-root)
face nil
mouse-face highlight
action flymake-goto-diagnostic
mouse-action flymake-goto-diagnostic )]
data-vec)
data-vec))))
(defun flymake--diagnostics-buffer-entries ()
"Get tabulated list entries for current tabulated list buffer.
Expects `flymake--diagnostics-buffer-entries' to be bound to a
buffer."
;; Do nothing if 'flymake--diagnostics-buffer-source' has not yet
;; been set to a valid buffer. This could happen when this function
;; is called too early. For example 'global-display-line-numbers-mode'
;; calls us from its mode hook, when the diagnostic buffer has just
;; been created by 'flymake-show-diagnostics-buffer', but is not yet
;; set up properly.
;; set up properly (Bug#40529).
(when (bufferp flymake--diagnostics-buffer-source)
(with-current-buffer flymake--diagnostics-buffer-source
(cl-loop for diag in
(cl-sort (flymake-diagnostics) #'< :key #'flymake-diagnostic-beg)
for (line . col) =
(save-excursion
(goto-char (flymake--diag-beg diag))
(cons (line-number-at-pos)
(- (point)
(line-beginning-position))))
for type = (flymake--diag-type diag)
collect
(list (list :diagnostic diag
:line line
:severity (flymake--lookup-type-property
type
'severity (warning-numeric-level :error)))
`[,(format "%s" line)
,(format "%s" col)
,(propertize (format "%s"
(flymake--lookup-type-property
type 'flymake-type-name type))
'face (flymake--lookup-type-property
type 'mode-line-face 'flymake-error))
(,(format "%s" (flymake--diag-text diag))
mouse-face highlight
help-echo "mouse-2: visit this diagnostic"
face nil
action flymake-goto-diagnostic
mouse-action flymake-goto-diagnostic)])))))
(when flymake-mode
(flymake--tabulated-entries-1 (flymake-diagnostics) nil)))))
(defvar flymake--diagnostics-base-tabulated-list-format
`[("Line" 5 ,(lambda (l1 l2)
(< (plist-get (car l1) :line)
(plist-get (car l2) :line)))
:right-align t)
("Col" 3 nil :right-align t)
("Type" 8 ,(lambda (l1 l2)
(< (plist-get (car l1) :severity)
(plist-get (car l2) :severity))))
("Backend" 8 t)
("Message" 0 t)])
(define-derived-mode flymake-diagnostics-buffer-mode tabulated-list-mode
"Flymake diagnostics"
"A mode for listing Flymake diagnostics."
(setq tabulated-list-format
`[("Line" 5 ,(lambda (l1 l2)
(< (plist-get (car l1) :line)
(plist-get (car l2) :line)))
:right-align t)
("Col" 3 nil :right-align t)
("Type" 8 ,(lambda (l1 l2)
(< (plist-get (car l1) :severity)
(plist-get (car l2) :severity))))
("Message" 0 t)])
(setq tabulated-list-format flymake--diagnostics-base-tabulated-list-format)
(setq tabulated-list-entries
'flymake--diagnostics-buffer-entries)
(tabulated-list-init-header))
(defun flymake--diagnostics-buffer-name ()
(format "*Flymake diagnostics for %s*" (current-buffer)))
(format "*Flymake diagnostics for `%s'*" (current-buffer)))
(defun flymake-show-diagnostics-buffer ()
(define-obsolete-function-alias 'flymake-show-diagnostics-buffer
'flymake-show-buffer-diagnostics "1.2.1")
(defun flymake-show-buffer-diagnostics ()
"Show a list of Flymake diagnostics for current buffer."
(interactive)
(let* ((name (flymake--diagnostics-buffer-name))
@ -1437,8 +1643,117 @@ POS can be a buffer position or a button"
(current-buffer)))))
(with-current-buffer target
(setq flymake--diagnostics-buffer-source source)
(revert-buffer)
(display-buffer (current-buffer)))))
(display-buffer (current-buffer))
(revert-buffer))))
;;; Per-project diagnostic listing
;;;
(defvar flymake-list-only-diagnostics nil
"Diagnostics list meant for listing, not highlighting.
This variable holds an alist ((FILE-NAME . DIAGS) ...) where
FILE-NAME is a string holding an absolute file name and DIAGS is
a list of diagnostic objects created with with
`flymake-make-diagnostic'. These diagnostics are never annotated
as overlays in actual buffers: they merely serve as temporary
stand-ins for more accurate diagnostics that are produced once
the file they refer to is visited and `flymake-mode' is turned on
in the resulting buffer.
Flymake backends that somehow gain sporadic information about
diagnostics in neighbouring files may freely modify this variable
at any time. If the information about those neighbouring files
is acquired repeatedly and reliably, it may be more sensible to
report them as \"foreign\" diagnostics instead.
Commands such as `flymake-show-project-diagnostics' will include
some of its contents in its diagnostic listing.")
(defvar-local flymake--project-diagnostic-list-project nil)
(define-derived-mode flymake-project-diagnostics-mode tabulated-list-mode
"Flymake diagnostics"
"A mode for listing Flymake diagnostics."
(setq tabulated-list-format
(vconcat [("File" 25 t)]
flymake--diagnostics-base-tabulated-list-format))
(setq tabulated-list-entries
'flymake--project-diagnostics-entries)
(tabulated-list-init-header))
(cl-defun flymake--project-diagnostics (&optional (project (project-current)))
"Get all known diagnostics for PROJECT."
(let* ((root (project-root project))
(visited (cl-remove-if-not #'buffer-file-name (project-buffers project)))
have-own-diags
buffer-annotated-diags
relevant-foreign-diags
list-only-diags)
(setq buffer-annotated-diags
(cl-loop for buf in visited
for diags = (with-current-buffer buf
(flymake-diagnostics))
when diags do (push buf have-own-diags)
append (cl-sort diags #'< :key #'flymake-diagnostic-beg)))
(cl-loop
for buf in visited
do (with-current-buffer buf
(when (and flymake-mode flymake--state)
(maphash
(lambda (_backend state)
(maphash
(lambda (foreign-locus diags)
(unless (cl-find-if
(lambda (b)
(or (eq b foreign-locus)
(and (stringp foreign-locus)
(equal (buffer-file-name b)
(expand-file-name foreign-locus)))))
have-own-diags)
(setq relevant-foreign-diags
(append relevant-foreign-diags
diags))))
(flymake--state-foreign-diags state)))
flymake--state))))
(setq list-only-diags
(cl-loop for (file-name . diags) in flymake-list-only-diagnostics
if (string-prefix-p (expand-file-name root) file-name)
append diags))
(append buffer-annotated-diags relevant-foreign-diags list-only-diags)))
(defun flymake--project-diagnostics-entries ()
(let ((p (project-current)))
(flymake--tabulated-entries-1 (flymake--project-diagnostics p)
(project-root p))))
(defun flymake--project-diagnostics-buffer (root)
(get-buffer-create (format "*Flymake diagnostics for `%s'*" root)))
(defun flymake-show-project-diagnostics ()
"Show a list of Flymake diagnostics for the current project."
(interactive)
(let* ((prj (project-current))
(root (project-root prj))
(buffer (flymake--project-diagnostics-buffer root)))
(with-current-buffer buffer
(flymake-project-diagnostics-mode)
(setq-local flymake--project-diagnostic-list-project prj)
(display-buffer (current-buffer))
(revert-buffer))))
(defun flymake--update-diagnostics-listings (buffer)
"Update diagnostics listings somehow relevant to BUFFER"
(dolist (probe (buffer-list))
(with-current-buffer probe
(when (or (and (eq major-mode 'flymake-project-diagnostics-mode)
flymake--project-diagnostic-list-project
(buffer-file-name buffer)
(memq buffer
(project-buffers flymake--project-diagnostic-list-project)))
(and (eq major-mode 'flymake-diagnostics-buffer-mode)
(eq flymake--diagnostics-buffer-source buffer)))
(revert-buffer)))))
(provide 'flymake)

View file

@ -1,7 +1,7 @@
;;; project.el --- Operations on the current project -*- lexical-binding: t; -*-
;; Copyright (C) 2015-2021 Free Software Foundation, Inc.
;; Version: 0.6.1
;; Version: 0.7.1
;; Package-Requires: ((emacs "26.1") (xref "1.0.2"))
;; This is a GNU ELPA :core package. Avoid using functionality that

View file

@ -0,0 +1,5 @@
#include "some-problems.h"
int frob(char* freb) {
return 42;
}

View file

@ -2,4 +2,6 @@
strange;
int frob(char);
sint main();