dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

flymake.el (74201B)


      1 ;;; flymake.el --- A universal on-the-fly syntax checker  -*- lexical-binding: t; -*-
      2 
      3 ;; Copyright (C) 2003-2021 Free Software Foundation, Inc.
      4 
      5 ;; Author: Pavel Kobyakov <pk_at_work@yahoo.com>
      6 ;; Maintainer: João Távora <joaotavora@gmail.com>
      7 ;; Version: 1.2.1
      8 ;; Keywords: c languages tools
      9 ;; Package-Requires: ((emacs "26.1") (eldoc "1.1.0") (project "0.7.1"))
     10 
     11 ;; This is a GNU ELPA :core package.  Avoid functionality that is not
     12 ;; compatible with the version of Emacs recorded above.
     13 
     14 ;; This file is part of GNU Emacs.
     15 
     16 ;; GNU Emacs is free software: you can redistribute it and/or modify
     17 ;; it under the terms of the GNU General Public License as published by
     18 ;; the Free Software Foundation, either version 3 of the License, or
     19 ;; (at your option) any later version.
     20 
     21 ;; GNU Emacs is distributed in the hope that it will be useful, but
     22 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
     23 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     24 ;; General Public License for more details.
     25 
     26 ;; You should have received a copy of the GNU General Public License
     27 ;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
     28 
     29 ;;; Commentary:
     30 ;;
     31 ;; Flymake is a minor Emacs mode performing on-the-fly syntax checks.
     32 ;;
     33 ;; Flymake collects diagnostic information for multiple sources,
     34 ;; called backends, and visually annotates the relevant portions in
     35 ;; the buffer.
     36 ;;
     37 ;; This file contains the UI for displaying and interacting with the
     38 ;; results produced by these backends, as well as entry points for
     39 ;; backends to hook on to.
     40 ;;
     41 ;; The main interactive entry point is the `flymake-mode' minor mode,
     42 ;; which periodically and automatically initiates checks as the user
     43 ;; is editing the buffer.  The variables `flymake-no-changes-timeout',
     44 ;; `flymake-start-on-flymake-mode' give finer control over the events
     45 ;; triggering a check, as does the interactive command  `flymake-start',
     46 ;; which immediately starts a check.
     47 ;;
     48 ;; Shortly after each check, a summary of collected diagnostics should
     49 ;; appear in the mode-line.  If it doesn't, there might not be a
     50 ;; suitable Flymake backend for the current buffer's major mode, in
     51 ;; which case Flymake will indicate this in the mode-line.  The
     52 ;; indicator will be `!' (exclamation mark), if all the configured
     53 ;; backends errored (or decided to disable themselves) and `?'
     54 ;; (question mark) if no backends were even configured.
     55 ;;
     56 ;; For programmers interested in writing a new Flymake backend, the
     57 ;; docstring of `flymake-diagnostic-functions', the Flymake manual,
     58 ;; and the code of existing backends are probably a good starting
     59 ;; point.
     60 ;;
     61 ;; The user wishing to customize the appearance of error types should
     62 ;; set properties on the symbols associated with each diagnostic type.
     63 ;; The standard diagnostic symbols are `:error', `:warning' and
     64 ;; `:note' (though a specific backend may define and use more).  The
     65 ;; following properties can be set:
     66 ;;
     67 ;; * `flymake-bitmap', an image displayed in the fringe according to
     68 ;; `flymake-fringe-indicator-position'.  The value actually follows
     69 ;; the syntax of `flymake-error-bitmap' (which see).  It is overridden
     70 ;; by any `before-string' overlay property.
     71 ;;
     72 ;; * `flymake-severity', a non-negative integer specifying the
     73 ;; diagnostic's severity.  The higher, the more serious.  If the
     74 ;; overlay property `priority' is not specified, `severity' is used to
     75 ;; set it and help sort overlapping overlays.
     76 ;;
     77 ;; * `flymake-overlay-control', an alist ((OVPROP . VALUE) ...) of
     78 ;; further properties used to affect the appearance of Flymake
     79 ;; annotations.  With the exception of `category' and `evaporate',
     80 ;; these properties are applied directly to the created overlay.  See
     81 ;; Info Node `(elisp)Overlay Properties'.
     82 ;;
     83 ;; * `flymake-category', a symbol whose property list is considered a
     84 ;; default for missing values of any other properties.  This is useful
     85 ;; to backend authors when creating new diagnostic types that differ
     86 ;; from an existing type by only a few properties.  The category
     87 ;; symbols `flymake-error', `flymake-warning' and `flymake-note' make
     88 ;; good candidates for values of this property.
     89 ;;
     90 ;; For instance, to omit the fringe bitmap displayed for the standard
     91 ;; `:note' type, set its `flymake-bitmap' property to nil:
     92 ;;
     93 ;;   (put :note 'flymake-bitmap nil)
     94 ;;
     95 ;; To change the face for `:note' type, add a `face' entry to its
     96 ;; `flymake-overlay-control' property.
     97 ;;
     98 ;;   (push '(face . highlight) (get :note 'flymake-overlay-control))
     99 ;;
    100 ;; If you push another alist entry in front, it overrides the previous
    101 ;; one.  So this effectively removes the face from `:note'
    102 ;; diagnostics.
    103 ;;
    104 ;;   (push '(face . nil) (get :note 'flymake-overlay-control))
    105 ;;
    106 ;; To erase customizations and go back to the original look for
    107 ;; `:note' types:
    108 ;;
    109 ;;   (cl-remf (symbol-plist :note) 'flymake-overlay-control)
    110 ;;   (cl-remf (symbol-plist :note) 'flymake-bitmap)
    111 ;;
    112 ;;; Code:
    113 
    114 (require 'cl-lib)
    115 (require 'thingatpt) ; end-of-thing
    116 (require 'warnings) ; warning-numeric-level, display-warning
    117 (require 'compile) ; for some faces
    118 ;; We need the next require to avoid compiler warnings and run-time
    119 ;; errors about mouse-wheel-up/down-event in builds --without-x, where
    120 ;; mwheel is not preloaded.
    121 (require 'mwheel)
    122 ;; when-let*, if-let*, hash-table-keys, hash-table-values:
    123 (eval-when-compile (require 'subr-x))
    124 (require 'project)
    125 
    126 (defgroup flymake nil
    127   "Universal on-the-fly syntax checker."
    128   :version "23.1"
    129   :link '(custom-manual "(flymake) Top")
    130   :group 'tools)
    131 
    132 (defcustom flymake-error-bitmap '(flymake-double-exclamation-mark
    133                                   compilation-error)
    134   "Bitmap (a symbol) used in the fringe for indicating errors.
    135 The value may also be a list of two elements where the second
    136 element specifies the face for the bitmap.  For possible bitmap
    137 symbols, see `fringe-bitmaps'.  See also `flymake-warning-bitmap'.
    138 
    139 The option `flymake-fringe-indicator-position' controls how and where
    140 this is used."
    141   :version "24.3"
    142   :type '(choice (symbol :tag "Bitmap")
    143                  (list :tag "Bitmap and face"
    144                        (symbol :tag "Bitmap")
    145                        (face :tag "Face"))))
    146 
    147 (defcustom flymake-warning-bitmap '(exclamation-mark compilation-warning)
    148   "Bitmap (a symbol) used in the fringe for indicating warnings.
    149 The value may also be a list of two elements where the second
    150 element specifies the face for the bitmap.  For possible bitmap
    151 symbols, see `fringe-bitmaps'.  See also `flymake-error-bitmap'.
    152 
    153 The option `flymake-fringe-indicator-position' controls how and where
    154 this is used."
    155   :version "24.3"
    156   :type '(choice (symbol :tag "Bitmap")
    157                  (list :tag "Bitmap and face"
    158                        (symbol :tag "Bitmap")
    159                        (face :tag "Face"))))
    160 
    161 (defcustom flymake-note-bitmap '(exclamation-mark compilation-info)
    162   "Bitmap (a symbol) used in the fringe for indicating info notes.
    163 The value may also be a list of two elements where the second
    164 element specifies the face for the bitmap.  For possible bitmap
    165 symbols, see `fringe-bitmaps'.  See also `flymake-error-bitmap'.
    166 
    167 The option `flymake-fringe-indicator-position' controls how and where
    168 this is used."
    169   :version "26.1"
    170   :type '(choice (symbol :tag "Bitmap")
    171                  (list :tag "Bitmap and face"
    172                        (symbol :tag "Bitmap")
    173                        (face :tag "Face"))))
    174 
    175 (defcustom flymake-fringe-indicator-position 'left-fringe
    176   "The position to put Flymake fringe indicator.
    177 The value can be nil (do not use indicators), `left-fringe' or `right-fringe'.
    178 See `flymake-error-bitmap' and `flymake-warning-bitmap'."
    179   :version "24.3"
    180   :type '(choice (const left-fringe)
    181 		 (const right-fringe)
    182 		 (const :tag "No fringe indicators" nil)))
    183 
    184 (make-obsolete-variable 'flymake-start-syntax-check-on-newline
    185 		        "can check on newline in post-self-insert-hook"
    186                         "27.1")
    187 
    188 (defcustom flymake-no-changes-timeout 0.5
    189   "Time to wait after last change before automatically checking buffer.
    190 If nil, never start checking buffer automatically like this."
    191   :type '(choice (number :tag "Timeout in seconds")
    192                  (const :tag "No check on timeout" nil)))
    193 
    194 (defcustom flymake-gui-warnings-enabled t
    195   "Enables/disables GUI warnings."
    196   :type 'boolean)
    197 (make-obsolete-variable 'flymake-gui-warnings-enabled
    198 			"it no longer has any effect." "26.1")
    199 
    200 (define-obsolete-variable-alias 'flymake-start-syntax-check-on-find-file
    201   'flymake-start-on-flymake-mode "26.1")
    202 
    203 (defcustom flymake-start-on-flymake-mode t
    204   "If non-nil, start syntax check when `flymake-mode' is enabled.
    205 Specifically, start it when the buffer is actually displayed."
    206   :version "26.1"
    207   :type 'boolean)
    208 
    209 (defcustom flymake-start-on-save-buffer t
    210   "If non-nil, start syntax check when a buffer is saved.
    211 Specifically, start it when the saved buffer is actually displayed."
    212   :version "27.1"
    213   :type 'boolean)
    214 
    215 (defcustom flymake-log-level -1
    216   "Obsolete and ignored variable."
    217   :type 'integer)
    218 (make-obsolete-variable 'flymake-log-level
    219 			"it is superseded by `warning-minimum-log-level.'"
    220                         "26.1")
    221 
    222 (defcustom flymake-wrap-around t
    223   "If non-nil, moving to errors wraps around buffer boundaries."
    224   :version "26.1"
    225   :type 'boolean)
    226 
    227 (defcustom flymake-suppress-zero-counters :warning
    228   "Control appearance of zero-valued diagnostic counters in mode line.
    229 
    230 If set to t, suppress all zero counters.  If set to a severity
    231 symbol like `:warning' (the default) suppress zero counters less
    232 severe than that severity, according to `warning-numeric-level'.
    233 If set to nil, don't suppress any zero counters."
    234   :type 'symbol)
    235 
    236 (when (fboundp 'define-fringe-bitmap)
    237   (define-fringe-bitmap 'flymake-double-exclamation-mark
    238     (vector #b00000000
    239             #b00000000
    240             #b00000000
    241             #b00000000
    242             #b01100110
    243             #b01100110
    244             #b01100110
    245             #b01100110
    246             #b01100110
    247             #b01100110
    248             #b01100110
    249             #b01100110
    250             #b00000000
    251             #b01100110
    252             #b00000000
    253             #b00000000
    254             #b00000000)))
    255 
    256 (defvar-local flymake-timer nil
    257   "Timer for starting syntax check.")
    258 
    259 (defvar-local flymake-check-start-time nil
    260   "Time at which syntax check was started.")
    261 
    262 (defun flymake--log-1 (level sublog msg &rest args)
    263   "Do actual work for `flymake-log'."
    264   (let (;; never popup the log buffer
    265         (warning-minimum-level :emergency)
    266         (warning-type-format
    267          (format " [%s %s]"
    268                  (or sublog 'flymake)
    269                  (current-buffer))))
    270     (display-warning (list 'flymake sublog)
    271                      (apply #'format-message msg args)
    272                      (if (numberp level)
    273                          (or (nth level
    274                                   '(:emergency :error :warning :debug :debug) )
    275                              :error)
    276                        level)
    277                      "*Flymake log*")))
    278 
    279 (defun flymake-switch-to-log-buffer ()
    280   "Go to the *Flymake log* buffer."
    281   (interactive)
    282   (switch-to-buffer "*Flymake log*"))
    283 
    284 ;;;###autoload
    285 (defmacro flymake-log (level msg &rest args)
    286   "Log, at level LEVEL, the message MSG formatted with ARGS.
    287 LEVEL is passed to `display-warning', which is used to display
    288 the warning.  If this form is included in a file,
    289 the generated warning contains an indication of the file that
    290 generated it."
    291   (let* ((file (if (fboundp 'macroexp-file-name)
    292                    (macroexp-file-name)
    293                  (and (not load-file-name)
    294                       (bound-and-true-p byte-compile-current-file))))
    295          (sublog (if (stringp file)
    296                      (intern
    297                       (file-name-nondirectory
    298                        (file-name-sans-extension file))))))
    299     `(flymake--log-1 ,level ',sublog ,msg ,@args)))
    300 
    301 (defun flymake-error (text &rest args)
    302   "Format TEXT with ARGS and signal an error for Flymake."
    303   (let ((msg (apply #'format-message text args)))
    304     (flymake-log :error msg)
    305     (error (concat "[Flymake] " msg))))
    306 
    307 (cl-defstruct (flymake--diag
    308                (:constructor flymake--diag-make))
    309   locus beg end type text backend data overlay-properties overlay
    310   ;; FIXME: See usage of these two in `flymake--highlight-line'.
    311   ;; Ideally they wouldn't be needed.
    312   orig-beg orig-end)
    313 
    314 ;;;###autoload
    315 (defun flymake-make-diagnostic (locus
    316                                 beg
    317                                 end
    318                                 type
    319                                 text
    320                                 &optional data
    321                                 overlay-properties)
    322   "Make a Flymake diagnostic for LOCUS's region from BEG to END.
    323 LOCUS is a buffer object or a string designating a file name.
    324 
    325 TYPE is a diagnostic symbol and TEXT is string describing the
    326 problem detected in this region.  DATA is any object that the
    327 caller wishes to attach to the created diagnostic for later
    328 retrieval with `flymake-diagnostic-data'.
    329 
    330 If LOCUS is a buffer BEG and END should be buffer positions
    331 inside it.  If LOCUS designates a file, BEG and END should be a
    332 cons (LINE . COL) indicating a file position.  In this second
    333 case, END may be ommited in which case the region is computed
    334 using `flymake-diag-region' if the diagnostic is appended to an
    335 actual buffer.
    336 
    337 OVERLAY-PROPERTIES is an alist of properties attached to the
    338 created diagnostic, overriding the default properties and any
    339 properties listed in the `flymake-overlay-control' property of
    340 the diagnostic's type symbol."
    341   (when (stringp locus)
    342     (setq locus (expand-file-name locus)))
    343   (flymake--diag-make :locus locus :beg beg :end end
    344                       :type type :text text :data data
    345                       :overlay-properties overlay-properties
    346                       :orig-beg beg
    347                       :orig-end end))
    348 
    349 ;;;###autoload
    350 (defun flymake-diagnostics (&optional beg end)
    351   "Get Flymake diagnostics in region determined by BEG and END.
    352 
    353 If neither BEG or END is supplied, use whole accessible buffer,
    354 otherwise if BEG is non-nil and END is nil, consider only
    355 diagnostics at BEG."
    356   (mapcar (lambda (ov) (overlay-get ov 'flymake-diagnostic))
    357           (flymake--overlays :beg beg :end end)))
    358 
    359 (defmacro flymake--diag-accessor (public internal thing)
    360   "Make PUBLIC an alias for INTERNAL, add doc using THING."
    361   `(defsubst ,public (diag)
    362      ,(format "Get Flymake diagnostic DIAG's %s." (symbol-name thing))
    363      (,internal diag)))
    364 
    365 (flymake--diag-accessor flymake-diagnostic-text flymake--diag-text text)
    366 (flymake--diag-accessor flymake-diagnostic-type flymake--diag-type type)
    367 (flymake--diag-accessor flymake-diagnostic-backend flymake--diag-backend backend)
    368 (flymake--diag-accessor flymake-diagnostic-data flymake--diag-data data)
    369 (flymake--diag-accessor flymake-diagnostic-beg flymake--diag-beg beg)
    370 (flymake--diag-accessor flymake-diagnostic-end flymake--diag-end end)
    371 (flymake--diag-accessor flymake-diagnostic-buffer flymake--diag-locus locus)
    372 
    373 (cl-defun flymake--overlays (&key beg end filter compare key)
    374   "Get flymake-related overlays.
    375 If BEG is non-nil and END is nil, consider only `overlays-at'
    376 BEG.  Otherwise consider `overlays-in' the region comprised by BEG
    377 and END, defaulting to the whole buffer.  Remove all that do not
    378 verify FILTER, a function, and sort them by COMPARE (using KEY)."
    379   (save-restriction
    380     (widen)
    381     (let ((ovs (cl-remove-if-not
    382                 (lambda (ov)
    383                   (and (overlay-get ov 'flymake-diagnostic)
    384                        (or (not filter)
    385                            (funcall filter ov))))
    386                 (if (and beg (null end))
    387                     (overlays-at beg t)
    388                   (overlays-in (or beg (point-min))
    389                                (or end (point-max)))))))
    390       (if compare
    391           (cl-sort ovs compare :key (or key
    392                                         #'identity))
    393         ovs))))
    394 
    395 (defface flymake-error
    396   '((((supports :underline (:style wave)))
    397      :underline (:style wave :color "Red1"))
    398     (t
    399      :inherit error))
    400   "Face used for marking error regions."
    401   :version "24.4")
    402 
    403 (defface flymake-warning
    404   '((((supports :underline (:style wave)))
    405      :underline (:style wave :color "deep sky blue"))
    406     (t
    407      :inherit warning))
    408   "Face used for marking warning regions."
    409   :version "24.4")
    410 
    411 (defface flymake-note
    412   '((((supports :underline (:style wave)))
    413      :underline (:style wave :color "yellow green"))
    414     (t
    415      :inherit warning))
    416   "Face used for marking note regions."
    417   :version "26.1")
    418 
    419 (define-obsolete-face-alias 'flymake-warnline 'flymake-warning "26.1")
    420 (define-obsolete-face-alias 'flymake-errline 'flymake-error "26.1")
    421 
    422 ;;;###autoload
    423 (defun flymake-diag-region (buffer line &optional col)
    424   "Compute BUFFER's region (BEG . END) corresponding to LINE and COL.
    425 If COL is nil, return a region just for LINE.  Return nil if the
    426 region is invalid.  This function saves match data."
    427   (condition-case-unless-debug _err
    428       (with-current-buffer buffer
    429         (let ((line (min (max line 1)
    430                          (line-number-at-pos (point-max) 'absolute))))
    431           (save-excursion
    432             (save-match-data
    433               (goto-char (point-min))
    434               (forward-line (1- line))
    435               (cl-flet ((fallback-bol
    436                          ()
    437                          (back-to-indentation)
    438                          (if (eobp)
    439                              (line-beginning-position 0)
    440                            (point)))
    441                         (fallback-eol
    442                          (beg)
    443                          (progn
    444                            (end-of-line)
    445                            (skip-chars-backward " \t\f\n" beg)
    446                            (if (eq (point) beg)
    447                                (line-beginning-position 2)
    448                              (point)))))
    449                 (if (and col (cl-plusp col))
    450                     (let* ((beg (progn (forward-char (1- col))
    451                                        (point)))
    452                            (sexp-end (or (ignore-errors (end-of-thing 'sexp))
    453                                          (ignore-errors (end-of-thing 'symbol))))
    454                            (end (or (and sexp-end
    455                                          (not (= sexp-end beg))
    456                                          sexp-end)
    457                                     (and (< (goto-char (1+ beg)) (point-max))
    458                                          (point)))))
    459                       (if end
    460                           (cons beg end)
    461                         (cons (setq beg (fallback-bol))
    462                               (fallback-eol beg))))
    463                   (let* ((beg (fallback-bol))
    464                          (end (fallback-eol beg)))
    465                     (cons beg end))))))))
    466     (error (flymake-log :warning "Invalid region line=%s col=%s" line col)
    467            nil)))
    468 
    469 (defvar flymake-diagnostic-functions nil
    470   "Special hook of Flymake backends that check a buffer.
    471 
    472 The functions in this hook diagnose problems in a buffer's
    473 contents and provide information to the Flymake user interface
    474 about where and how to annotate problems diagnosed in a buffer.
    475 
    476 Each backend function must be prepared to accept an arbitrary
    477 number of arguments:
    478 
    479 * the first argument is always REPORT-FN, a callback function
    480   detailed below;
    481 
    482 * the remaining arguments are keyword-value pairs in the
    483   form (:KEY VALUE :KEY2 VALUE2...).
    484 
    485 Currently, Flymake may provide these keyword-value pairs:
    486 
    487 * `:recent-changes', a list of recent changes since the last time
    488   the backend function was called for the buffer.  An empty list
    489   indicates that no changes have been recorded.  If it is the
    490   first time that this backend function is called for this
    491   activation of `flymake-mode', then this argument isn't provided
    492   at all (i.e. it's not merely nil).
    493 
    494   Each element is in the form (BEG END TEXT) where BEG and END
    495   are buffer positions, and TEXT is a string containing the text
    496   contained between those positions (if any) after the change was
    497   performed.
    498 
    499 * `:changes-start' and `:changes-end', the minimum and maximum
    500   buffer positions touched by the recent changes.  These are only
    501   provided if `:recent-changes' is also provided.
    502 
    503 Whenever Flymake or the user decides to re-check the buffer,
    504 backend functions are called as detailed above and are expected
    505 to initiate this check, but aren't required to complete it before
    506 exiting: if the computation involved is expensive, especially for
    507 large buffers, that task can be scheduled for the future using
    508 asynchronous processes or other asynchronous mechanisms.
    509 
    510 In any case, backend functions are expected to return quickly or
    511 signal an error, in which case the backend is disabled.  Flymake
    512 will not try disabled backends again for any future checks of
    513 this buffer.  To reset the list of disabled backends, turn
    514 `flymake-mode' off and on again, or interactively call
    515 `flymake-start' with a prefix argument.
    516 
    517 If the function returns, Flymake considers the backend to be
    518 \"running\".  If it has not done so already, the backend is
    519 expected to call the function REPORT-FN with a single argument
    520 REPORT-ACTION also followed by an optional list of keyword-value
    521 pairs in the form (:REPORT-KEY VALUE :REPORT-KEY2 VALUE2...).
    522 
    523 Currently accepted values for REPORT-ACTION are:
    524 
    525 * A (possibly empty) list of diagnostic objects created with
    526   `flymake-make-diagnostic', causing Flymake to delete all
    527   previous diagnostic annotations in the buffer and create new
    528   ones from this list.
    529 
    530   A backend may call REPORT-FN repeatedly in this manner, but
    531   only until Flymake considers that the most recently requested
    532   buffer check is now obsolete because, say, buffer contents have
    533   changed in the meantime.  The backend is only given notice of
    534   this via a renewed call to the backend function.  Thus, to
    535   prevent making obsolete reports and wasting resources, backend
    536   functions should first cancel any ongoing processing from
    537   previous calls.
    538 
    539 * The symbol `:panic', signaling that the backend has encountered
    540   an exceptional situation and should be disabled.
    541 
    542 Currently accepted REPORT-KEY arguments are:
    543 
    544 * `:explanation' value should give user-readable details of
    545   the situation encountered, if any.
    546 
    547 * `:force': value should be a boolean suggesting that Flymake
    548   consider the report even if it was somehow unexpected.
    549 
    550 * `:region': a cons (BEG . END) of buffer positions indicating
    551   that the report applies to that region only.  Specifically,
    552   this means that Flymake will only delete diagnostic annotations
    553   of past reports if they intersect the region by at least one
    554   character.")
    555 
    556 (put 'flymake-diagnostic-functions 'safe-local-variable #'null)
    557 
    558 (put :error 'flymake-category 'flymake-error)
    559 (put :warning 'flymake-category 'flymake-warning)
    560 (put :note 'flymake-category 'flymake-note)
    561 
    562 (defvar flymake-diagnostic-types-alist '() "")
    563 (make-obsolete-variable
    564  'flymake-diagnostic-types-alist
    565  "Set properties on the diagnostic symbols instead.  See Info
    566 Node `(Flymake)Flymake error types'"
    567  "27.1")
    568 
    569 (put 'flymake-error 'face 'flymake-error)
    570 (put 'flymake-error 'flymake-bitmap 'flymake-error-bitmap)
    571 (put 'flymake-error 'severity (warning-numeric-level :error))
    572 (put 'flymake-error 'mode-line-face 'compilation-error)
    573 (put 'flymake-error 'flymake-type-name "error")
    574 
    575 (put 'flymake-warning 'face 'flymake-warning)
    576 (put 'flymake-warning 'flymake-bitmap 'flymake-warning-bitmap)
    577 (put 'flymake-warning 'severity (warning-numeric-level :warning))
    578 (put 'flymake-warning 'mode-line-face 'compilation-warning)
    579 (put 'flymake-warning 'flymake-type-name "warning")
    580 
    581 (put 'flymake-note 'face 'flymake-note)
    582 (put 'flymake-note 'flymake-bitmap 'flymake-note-bitmap)
    583 (put 'flymake-note 'severity (warning-numeric-level :debug))
    584 (put 'flymake-note 'mode-line-face 'compilation-info)
    585 (put 'flymake-note 'flymake-type-name "note")
    586 
    587 (defun flymake--lookup-type-property (type prop &optional default)
    588   "Look up PROP for diagnostic TYPE.
    589 If TYPE doesn't declare PROP in its plist or in the symbol of its
    590 associated `flymake-category' return DEFAULT."
    591   ;; This function also consults `flymake-diagnostic-types-alist' for
    592   ;; backward compatibility.
    593   ;;
    594   (if (plist-member (symbol-plist type) prop)
    595       ;; allow nil values to survive
    596       (get type prop)
    597     (let (alist)
    598       (or
    599        (alist-get
    600         prop (setq
    601               alist
    602               (alist-get type flymake-diagnostic-types-alist)))
    603        (when-let* ((cat (or
    604                          (get type 'flymake-category)
    605                          (alist-get 'flymake-category alist)))
    606                    (plist (and (symbolp cat)
    607                                (symbol-plist cat)))
    608                    (cat-probe (plist-member plist prop)))
    609          (cadr cat-probe))
    610        default))))
    611 
    612 (defun flymake--severity (type)
    613   "Get the severity for diagnostic TYPE."
    614   (flymake--lookup-type-property type 'severity
    615                                  (warning-numeric-level :error)))
    616 
    617 (defun flymake--fringe-overlay-spec (bitmap &optional recursed)
    618   (if (and (symbolp bitmap)
    619            (boundp bitmap)
    620            (not recursed))
    621       (flymake--fringe-overlay-spec
    622        (symbol-value bitmap) t)
    623     (and flymake-fringe-indicator-position
    624          bitmap
    625          (propertize "!" 'display
    626                      (cons flymake-fringe-indicator-position
    627                            (if (listp bitmap)
    628                                bitmap
    629                              (list bitmap)))))))
    630 
    631 (defun flymake--equal-diagnostic-p (a b)
    632   "Tell if A and B are equivalent `flymake--diag' objects."
    633   (or (eq a b)
    634       (cl-loop for comp in '(flymake--diag-end
    635                              flymake--diag-beg
    636                              flymake-diagnostic-type
    637                              flymake-diagnostic-backend
    638                              flymake-diagnostic-text)
    639                always (equal (funcall comp a) (funcall comp b)))))
    640 
    641 (cl-defun flymake--highlight-line (diagnostic &optional foreign)
    642   "Attempt to overlay DIAGNOSTIC in current buffer.
    643 
    644 FOREIGN says if DIAGNOSTIC is \"foreign\" to the current buffer,
    645 i.e. managed by another buffer where `flymake-mode' is also
    646 active.
    647 
    648 This function mayskip overlay creation if a diagnostic which is
    649 the same as DIAGNOSTIC is already highlighted
    650 (in the sense of `flymake--equal-diagnostic-p').  In that case
    651 the action to take depends on FOREIGN.  If nil the existing
    652 overlay is deleted, else no overlay is created.
    653 
    654 Return nil or the overlay created."
    655   (let* ((type (or (flymake-diagnostic-type diagnostic)
    656                    :error))
    657          (beg (flymake--diag-beg diagnostic))
    658          (end (flymake--diag-end diagnostic))
    659          (convert (lambda (cell)
    660                     (flymake-diag-region (current-buffer)
    661                                          (car cell)
    662                                          (cdr cell))))
    663          ov)
    664     ;; Convert (LINE . COL) forms of `flymake--diag-beg' and
    665     ;; `flymake--diag-end'.  Record the converted positions.
    666     ;;
    667     (cond ((and (consp beg) (not (null end)))
    668            (setq beg (car (funcall convert beg)))
    669            (when (consp end)
    670              (setq end (car (funcall convert end)))))
    671           ((consp beg)
    672            (cl-destructuring-bind (a . b) (funcall convert beg)
    673              (setq beg a end b))))
    674     (setf (flymake--diag-beg diagnostic) beg
    675           (flymake--diag-end diagnostic) end)
    676     ;; Try to fix the remedy the situation if there is the same
    677     ;; diagnostic is already registered in the same place, which only
    678     ;; happens for clashes between domestic and foreign diagnostics
    679     (cl-loop for e in (flymake-diagnostics beg end)
    680              when (flymake--equal-diagnostic-p e diagnostic)
    681              ;; FIXME.  This is an imperfect heuristic.  Ideally, we'd
    682              ;; want to delete no overlays and keep annotating the
    683              ;; superseded foreign in an overlay but hide it from most
    684              ;; `flymake-diagnostics' calls.  If the target buffer is
    685              ;; killed we can keep the "latent" state of the foreign
    686              ;; diagnostic (with filename and updated line/col info).
    687              ;; If it is revisited the foreign diagnostic can be
    688              ;; revived again.
    689              do (if foreign
    690                     (cl-return-from flymake--highlight-line nil)
    691                   (setf (flymake--diag-beg e)
    692                         (flymake--diag-orig-beg e)
    693                         (flymake--diag-end e)
    694                         (flymake--diag-orig-end e))
    695                   (delete-overlay (flymake--diag-overlay e))))
    696     (setq ov (make-overlay end beg))
    697     (setf (flymake--diag-beg diagnostic) (overlay-start ov)
    698           (flymake--diag-end diagnostic) (overlay-end ov))
    699     ;; First set `category' in the overlay
    700     ;;
    701     (overlay-put ov 'category
    702                  (flymake--lookup-type-property type 'flymake-category))
    703     ;; Now "paint" the overlay with all the other non-category
    704     ;; properties.
    705     (cl-loop
    706      for (ov-prop . value) in
    707      (append (reverse
    708               (flymake--diag-overlay-properties diagnostic))
    709              (reverse ; ensure earlier props override later ones
    710               (flymake--lookup-type-property type 'flymake-overlay-control))
    711              (alist-get type flymake-diagnostic-types-alist))
    712      do (overlay-put ov ov-prop value))
    713     ;; Now ensure some essential defaults are set
    714     ;;
    715     (cl-flet ((default-maybe
    716                 (prop value)
    717                 (unless (plist-member (overlay-properties ov) prop)
    718                   (overlay-put ov prop (flymake--lookup-type-property
    719                                         type prop value)))))
    720       (default-maybe 'face 'flymake-error)
    721       (default-maybe 'before-string
    722         (flymake--fringe-overlay-spec
    723          (flymake--lookup-type-property
    724           type
    725           'flymake-bitmap
    726           (alist-get 'bitmap (alist-get type ; backward compat
    727                                         flymake-diagnostic-types-alist)))))
    728       (default-maybe 'help-echo
    729         (lambda (window _ov pos)
    730           (with-selected-window window
    731             (mapconcat
    732              #'flymake-diagnostic-text
    733              (flymake-diagnostics pos)
    734              "\n"))))
    735       (default-maybe 'severity (warning-numeric-level :error))
    736       ;; Use (PRIMARY . SECONDARY) priority, to avoid clashing with
    737       ;; `region' face, for example (bug#34022).
    738       (default-maybe 'priority (cons nil (+ 40 (overlay-get ov 'severity)))))
    739     ;; Some properties can't be overridden.
    740     ;;
    741     (overlay-put ov 'evaporate t)
    742     (overlay-put ov 'flymake-diagnostic diagnostic)
    743     (setf (flymake--diag-overlay diagnostic) ov)
    744     ov))
    745 
    746 ;; Nothing in Flymake uses this at all any more, so this is just for
    747 ;; third-party compatibility.
    748 (define-obsolete-function-alias 'flymake-display-warning 'message-box "26.1")
    749 
    750 (defvar-local flymake--state nil
    751   "State of a buffer's multiple Flymake backends.
    752 The keys to this hash table are functions as found in
    753 `flymake-diagnostic-functions'.  The values are structures
    754 of the type `flymake--state', with these slots:
    755 
    756 `running', a symbol to keep track of a backend's replies via its
    757 REPORT-FN argument.  A backend is running if this key is
    758 present.  If nil, Flymake isn't expecting any replies from the
    759 backend.
    760 
    761 `diags', a (possibly empty) list of recent diagnostic objects
    762 created by the backend with `flymake-make-diagnostic'.
    763 
    764 `reported-p', a boolean indicating if the backend has replied
    765 since it last was contacted.
    766 
    767 `disabled', a string with the explanation for a previous
    768 exceptional situation reported by the backend, nil if the
    769 backend is operating normally.
    770 
    771 `foreign-diags', a hash table of buffers/files to
    772 collections of diagnostics outside the buffer where this
    773 `flymake--state' pertains.")
    774 
    775 (cl-defstruct (flymake--state
    776                (:constructor flymake--make-backend-state))
    777   running reported-p disabled diags (foreign-diags
    778                                      (make-hash-table)))
    779 
    780 (defmacro flymake--with-backend-state (backend state-var &rest body)
    781   "Bind BACKEND's STATE-VAR to its state, run BODY."
    782   (declare (indent 2) (debug (sexp sexp &rest form)))
    783   (let ((b (make-symbol "b")))
    784     `(let* ((,b ,backend)
    785             (,state-var
    786              (or (gethash ,b flymake--state)
    787                  (puthash ,b (flymake--make-backend-state)
    788                           flymake--state))))
    789        ,@body)))
    790 
    791 (defun flymake-is-running ()
    792   "Tell if Flymake has running backends in this buffer."
    793   (flymake-running-backends))
    794 
    795 ;; FIXME: clone of `isearch-intersects-p'! Make this an util.
    796 (defun flymake--intersects-p (start0 end0 start1 end1)
    797   "Return t if regions START0..END0 and START1..END1 intersect."
    798   (or (and (>= start0 start1) (<  start0 end1))
    799       (and (>  end0 start1)   (<= end0 end1))
    800       (and (>= start1 start0) (<  start1 end0))
    801       (and (>  end1 start0)   (<= end1 end0))))
    802 
    803 (cl-defun flymake--handle-report
    804     (backend token report-action
    805              &key explanation force region
    806              &allow-other-keys
    807              &aux
    808              (state (or (gethash backend flymake--state)
    809                         (error "Can't find state for %s in `flymake--state'"
    810                                backend)))
    811              expected-token)
    812   "Handle reports from BACKEND identified by TOKEN.
    813 BACKEND, REPORT-ACTION and EXPLANATION, and FORCE conform to the
    814 calling convention described in
    815 `flymake-diagnostic-functions' (which see).  Optional FORCE says
    816 to handle a report even if TOKEN was not expected.  REGION is
    817 a (BEG . END) pair of buffer positions indicating that this
    818 report applies to that region."
    819   (cond
    820    ((null state)
    821     (flymake-error
    822      "Unexpected report from unknown backend %s" backend))
    823    ((flymake--state-disabled state)
    824     (flymake-error
    825      "Unexpected report from disabled backend %s" backend))
    826    ((progn
    827       (setq expected-token (flymake--state-running state))
    828       (null expected-token))
    829     ;; should never happen
    830     (flymake-error "Unexpected report from stopped backend %s" backend))
    831    ((not (or (eq expected-token token)
    832              force))
    833     (flymake-error "Obsolete report from backend %s with explanation %s"
    834                    backend explanation))
    835    ((eq :panic report-action)
    836     (flymake--disable-backend backend explanation))
    837    ((not (listp report-action))
    838     (flymake--disable-backend backend
    839                               (format "Unknown action %S" report-action))
    840     (flymake-error "Expected report, but got unknown key %s" report-action))
    841    (t
    842     (flymake--publish-diagnostics report-action
    843                                   :backend backend
    844                                   :state state
    845                                   :region region)
    846     (when flymake-check-start-time
    847       (flymake-log :debug "backend %s reported %d diagnostics in %.2f second(s)"
    848                    backend
    849                    (length report-action)
    850                    (float-time
    851                     (time-since flymake-check-start-time))))))
    852   (setf (flymake--state-reported-p state) t)
    853   (flymake--update-diagnostics-listings (current-buffer)))
    854 
    855 (defun flymake--clear-foreign-diags (state)
    856   (maphash (lambda (_buffer diags)
    857              (cl-loop for d in diags
    858                       when (flymake--diag-overlay d)
    859                       do (delete-overlay it)))
    860            (flymake--state-foreign-diags state))
    861   (clrhash (flymake--state-foreign-diags state)))
    862 
    863 (defvar-local flymake-mode nil)
    864 
    865 (cl-defun flymake--publish-diagnostics (diags &key backend state region)
    866   "Helper for `flymake--handle-report'.
    867 Publish DIAGS, which contain diagnostics for the current buffer
    868 and other buffers."
    869   (dolist (d diags) (setf (flymake--diag-backend d) backend))
    870   (save-restriction
    871     (widen)
    872     ;; First, clean up.  Remove diagnostics from bookeeping lists and
    873     ;; their overlays from buffers.
    874     ;;
    875     (cond
    876      (;; If there is a `region' arg, only affect the diagnostics whose
    877       ;; overlays are in a certain region.  Discard "foreign"
    878       ;; diagnostics.
    879       region
    880       (cl-loop for diag in (flymake--state-diags state)
    881                for ov = (flymake--diag-overlay diag)
    882                if (or (not (overlay-buffer ov))
    883                       (flymake--intersects-p
    884                        (overlay-start ov) (overlay-end ov)
    885                        (car region) (cdr region)))
    886                do (delete-overlay ov)
    887                else collect diag into surviving
    888                finally (setf (flymake--state-diags state)
    889                              surviving)))
    890      (;; Else, if this is the first report, zero all lists and delete
    891       ;; all associated overlays.
    892       (not (flymake--state-reported-p state))
    893       (cl-loop for diag in (flymake--state-diags state)
    894                for ov = (flymake--diag-overlay diag)
    895                when ov do (delete-overlay ov))
    896       (setf (flymake--state-diags state) nil)
    897       ;; Also clear all overlays for `foreign-diags' in all other
    898       ;; buffers.
    899       (flymake--clear-foreign-diags state))
    900      (;; If this is not the first report, do no cleanup.
    901        t))
    902 
    903     ;; Now place new overlays for all diagnostics: "domestic"
    904     ;; diagnostics are for the current buffer; "foreign" may be for a
    905     ;; some other live buffer or for a file name that hasn't a buffer
    906     ;; yet.  If a foreign diagnostic is for a buffer, convert to a
    907     ;; file name, protecting it against that buffer's killing.
    908     ;;
    909     (cl-loop
    910      for d in diags
    911      for locus = (flymake--diag-locus d)
    912      do (cond ((eq locus (current-buffer))
    913                (push d (flymake--state-diags state))
    914                (flymake--highlight-line d))
    915               (t
    916                (when (or (buffer-live-p locus)
    917                          (setq locus (find-buffer-visiting locus)))
    918                  (with-current-buffer locus
    919                    (when flymake-mode (flymake--highlight-line d 'foreign))
    920                    ;; Ensure locus of a foreign diag is always a file-name
    921                    ;; string, even if created from a buffer.
    922                    (setf (flymake--diag-locus d) (buffer-file-name))))
    923                (cl-assert (stringp (flymake--diag-locus d)))
    924                (push d (gethash (flymake--diag-locus d)
    925                                 (flymake--state-foreign-diags state))))))))
    926 
    927 (defun flymake-make-report-fn (backend &optional token)
    928   "Make a suitable anonymous report function for BACKEND.
    929 BACKEND is used to help Flymake distinguish different diagnostic
    930 sources.  If provided, TOKEN helps Flymake distinguish between
    931 different runs of the same backend."
    932   (let ((buffer (current-buffer)))
    933     (lambda (&rest args)
    934       (when (buffer-live-p buffer)
    935         (with-current-buffer buffer
    936           (apply #'flymake--handle-report backend token args))))))
    937 
    938 (defun flymake--collect (fn &optional message-prefix)
    939   "Collect Flymake backends matching FN.
    940 If MESSAGE-PREFIX, echo a message using that prefix."
    941   (unless flymake--state
    942     (user-error "Flymake is not initialized"))
    943   (let (retval)
    944     (maphash (lambda (backend state)
    945                (when (funcall fn state) (push backend retval)))
    946              flymake--state)
    947     (when message-prefix
    948       (message "%s%s"
    949                message-prefix
    950                (mapconcat (lambda (s) (format "%s" s))
    951                           retval ", ")))
    952     retval))
    953 
    954 (defun flymake-running-backends ()
    955   "Compute running Flymake backends in current buffer."
    956   (interactive)
    957   (flymake--collect #'flymake--state-running
    958                     (and (called-interactively-p 'interactive)
    959                          "Running backends: ")))
    960 
    961 (defun flymake-disabled-backends ()
    962   "Compute disabled Flymake backends in current buffer."
    963   (interactive)
    964   (flymake--collect #'flymake--state-disabled
    965                     (and (called-interactively-p 'interactive)
    966                          "Disabled backends: ")))
    967 
    968 (defun flymake-reporting-backends ()
    969   "Compute reporting Flymake backends in current buffer."
    970   (interactive)
    971   (flymake--collect #'flymake--state-reported-p
    972                     (and (called-interactively-p 'interactive)
    973                          "Reporting backends: ")))
    974 
    975 (defun flymake--disable-backend (backend &optional explanation)
    976   "Disable BACKEND because EXPLANATION.
    977 If it is running also stop it."
    978   (flymake-log :warning "Disabling backend %s because %s" backend explanation)
    979   (flymake--with-backend-state backend state
    980     (setf (flymake--state-running state) nil
    981           (flymake--state-disabled state) explanation
    982           (flymake--state-reported-p state) t)))
    983 
    984 (defun flymake--run-backend (backend &optional args)
    985   "Run the backend BACKEND, re-enabling if necessary.
    986 ARGS is a keyword-value plist passed to the backend along
    987 with a report function."
    988   (flymake-log :debug "Running backend %s" backend)
    989   (let ((run-token (cl-gensym "backend-token")))
    990     (flymake--with-backend-state backend state
    991       (setf (flymake--state-running state) run-token
    992             (flymake--state-disabled state) nil
    993             (flymake--state-reported-p state) nil))
    994     ;; FIXME: Should use `condition-case-unless-debug' here, but don't
    995     ;; for two reasons: (1) that won't let me catch errors from inside
    996     ;; `ert-deftest' where `debug-on-error' appears to be always
    997     ;; t. (2) In cases where the user is debugging elisp somewhere
    998     ;; else, and using flymake, the presence of a frequently
    999     ;; misbehaving backend in the global hook (most likely the legacy
   1000     ;; backend) will trigger an annoying backtrace.
   1001     ;;
   1002     (condition-case err
   1003         (apply backend (flymake-make-report-fn backend run-token)
   1004                args)
   1005       (error
   1006        (flymake--disable-backend backend err)))))
   1007 
   1008 (defvar-local flymake--recent-changes nil
   1009   "Recent changes collected by `flymake-after-change-function'.")
   1010 (defvar flymake-mode)
   1011 
   1012 (defun flymake-start (&optional deferred force)
   1013   "Start a syntax check for the current buffer.
   1014 DEFERRED is a list of symbols designating conditions to wait for
   1015 before actually starting the check.  If it is nil (the list is
   1016 empty), start it immediately, else defer the check to when those
   1017 conditions are met.  Currently recognized conditions are
   1018 `post-command', for waiting until the current command is over,
   1019 `on-display', for waiting until the buffer is actually displayed
   1020 in a window.  If DEFERRED is t, wait for all known conditions.
   1021 
   1022 With optional FORCE run even disabled backends.
   1023 
   1024 Interactively, with a prefix arg, FORCE is t."
   1025   (interactive (list nil current-prefix-arg))
   1026   (let ((deferred (if (eq t deferred)
   1027                       '(post-command on-display)
   1028                     deferred))
   1029         (buffer (current-buffer)))
   1030     (cl-labels
   1031         ((start-post-command
   1032           ()
   1033           (remove-hook 'post-command-hook #'start-post-command
   1034                        nil)
   1035           ;; The buffer may have disappeared already, e.g. because of
   1036           ;; code like `(with-temp-buffer (python-mode) ...)'.
   1037           (when (buffer-live-p buffer)
   1038             (with-current-buffer buffer
   1039               (flymake-start (remove 'post-command deferred) force))))
   1040          (start-on-display
   1041           ()
   1042           (remove-hook 'window-configuration-change-hook #'start-on-display
   1043                        'local)
   1044           (flymake-start (remove 'on-display deferred) force)))
   1045       (cond ((and (memq 'post-command deferred)
   1046                   this-command)
   1047              (add-hook 'post-command-hook
   1048                        #'start-post-command
   1049                        'append nil))
   1050             ((and (memq 'on-display deferred)
   1051                   (not (get-buffer-window (current-buffer))))
   1052              (add-hook 'window-configuration-change-hook
   1053                        #'start-on-display
   1054                        'append 'local))
   1055             (flymake-mode
   1056              (setq flymake-check-start-time (float-time))
   1057              (let ((backend-args
   1058                     (and
   1059                      flymake--recent-changes
   1060                      (list :recent-changes
   1061                            flymake--recent-changes
   1062                            :changes-start
   1063                            (cl-reduce
   1064                             #'min (mapcar #'car flymake--recent-changes))
   1065                            :changes-end
   1066                            (cl-reduce
   1067                             #'max (mapcar #'cadr flymake--recent-changes))))))
   1068                (setq flymake--recent-changes nil)
   1069                (run-hook-wrapped
   1070                 'flymake-diagnostic-functions
   1071                 (lambda (backend)
   1072                   (cond
   1073                    ((and (not force)
   1074                          (flymake--with-backend-state backend state
   1075                            (flymake--state-disabled state)))
   1076                     (flymake-log :debug "Backend %s is disabled, not starting"
   1077                                  backend))
   1078                    (t
   1079                     (flymake--run-backend backend backend-args)))
   1080                   nil))))))))
   1081 
   1082 (defvar flymake-mode-map
   1083   (let ((map (make-sparse-keymap))) map)
   1084   "Keymap for `flymake-mode'.")
   1085 
   1086 ;;;###autoload
   1087 (define-minor-mode flymake-mode
   1088   "Toggle Flymake mode on or off.
   1089 
   1090 Flymake is an Emacs minor mode for on-the-fly syntax checking.
   1091 Flymake collects diagnostic information from multiple sources,
   1092 called backends, and visually annotates the buffer with the
   1093 results.
   1094 
   1095 Flymake performs these checks while the user is editing.
   1096 The customization variables `flymake-start-on-flymake-mode',
   1097 `flymake-no-changes-timeout' determine the exact circumstances
   1098 whereupon Flymake decides to initiate a check of the buffer.
   1099 
   1100 The commands `flymake-goto-next-error' and
   1101 `flymake-goto-prev-error' can be used to navigate among Flymake
   1102 diagnostics annotated in the buffer.
   1103 
   1104 The visual appearance of each type of diagnostic can be changed
   1105 by setting properties `flymake-overlay-control', `flymake-bitmap'
   1106 and `flymake-severity' on the symbols of diagnostic types (like
   1107 `:error', `:warning' and `:note').
   1108 
   1109 Activation or deactivation of backends used by Flymake in each
   1110 buffer happens via the special hook
   1111 `flymake-diagnostic-functions'.
   1112 
   1113 Some backends may take longer than others to respond or complete,
   1114 and some may decide to disable themselves if they are not
   1115 suitable for the current buffer.  The commands
   1116 `flymake-running-backends', `flymake-disabled-backends' and
   1117 `flymake-reporting-backends' summarize the situation, as does the
   1118 special *Flymake log* buffer."  :group 'flymake :lighter
   1119   flymake-mode-line-format :keymap flymake-mode-map
   1120   (cond
   1121    ;; Turning the mode ON.
   1122    (flymake-mode
   1123     (add-hook 'after-change-functions 'flymake-after-change-function nil t)
   1124     (add-hook 'after-save-hook 'flymake-after-save-hook nil t)
   1125     (add-hook 'kill-buffer-hook 'flymake-kill-buffer-hook nil t)
   1126     (add-hook 'eldoc-documentation-functions 'flymake-eldoc-function t t)
   1127 
   1128     ;; If Flymake happened to be already already ON, we must cleanup
   1129     ;; existing diagnostic overlays, lest we forget them by blindly
   1130     ;; reinitializing `flymake--state' in the next line.
   1131     ;; See https://github.com/joaotavora/eglot/issues/223.
   1132     (mapc #'delete-overlay (flymake--overlays))
   1133     (setq flymake--state (make-hash-table))
   1134     (setq flymake--recent-changes nil)
   1135 
   1136     (when flymake-start-on-flymake-mode (flymake-start t))
   1137 
   1138     ;; Other diagnostic sources may already target this buffer's file
   1139     ;; before we turned on: these sources may be of two types...
   1140     (let ((source (current-buffer))
   1141           (bfn buffer-file-name))
   1142       ;; 1. For `flymake-list-only-diagnostics': here, we do nothing.
   1143       ;; FIXME: We could remove the corresponding entry from that
   1144       ;; variable, as we assume that new diagnostics will come in soon
   1145       ;; via the brand new `flymake-mode' setup.  For simplicity's
   1146       ;; sake, we have opted to leave the backend for now.
   1147       nil
   1148       ;; 2. other buffers where a backend has created "foreign"
   1149       ;; diagnostics and pointed them here.  We must highlight them in
   1150       ;; this buffer, i.e. create overlays for them.  Those other
   1151       ;; buffers and backends are still responsible for them, i.e. the
   1152       ;; current buffer does not "own" these foreign diags.
   1153       (dolist (buffer (buffer-list))
   1154         (with-current-buffer buffer
   1155           (when (and flymake-mode flymake--state)
   1156             (maphash (lambda (_backend state)
   1157                        (maphash (lambda (file diags)
   1158                                   (when (or (eq file source)
   1159                                             (string= bfn (expand-file-name file)))
   1160                                     (with-current-buffer source
   1161                                       (mapc (lambda (diag)
   1162                                               (flymake--highlight-line diag
   1163                                                                        'foreign))
   1164                                             diags))))
   1165                                 (flymake--state-foreign-diags state)))
   1166                      flymake--state))))))
   1167 
   1168    ;; Turning the mode OFF.
   1169    (t
   1170     (remove-hook 'after-change-functions 'flymake-after-change-function t)
   1171     (remove-hook 'after-save-hook 'flymake-after-save-hook t)
   1172     (remove-hook 'kill-buffer-hook 'flymake-kill-buffer-hook t)
   1173     ;;+(remove-hook 'find-file-hook (function flymake-find-file-hook) t)
   1174     (remove-hook 'eldoc-documentation-functions 'flymake-eldoc-function t)
   1175 
   1176     (when flymake-timer
   1177       (cancel-timer flymake-timer)
   1178       (setq flymake-timer nil))
   1179     (mapc #'delete-overlay (flymake--overlays))
   1180     (when flymake--state
   1181       (maphash (lambda (_backend state)
   1182                  (flymake--clear-foreign-diags state))
   1183                flymake--state)))
   1184    ;; turning Flymake on or off has consequences for listings
   1185    (flymake--update-diagnostics-listings (current-buffer))))
   1186 
   1187 (defun flymake--schedule-timer-maybe ()
   1188   "(Re)schedule an idle timer for checking the buffer.
   1189 Do it only if `flymake-no-changes-timeout' is non-nil."
   1190   (when flymake-timer (cancel-timer flymake-timer))
   1191   (when flymake-no-changes-timeout
   1192     (setq
   1193      flymake-timer
   1194      (run-with-idle-timer
   1195       ;; This can use time-convert instead of seconds-to-time,
   1196       ;; once we can assume Emacs 27 or later.
   1197       (seconds-to-time flymake-no-changes-timeout)
   1198       nil
   1199       (lambda (buffer)
   1200         (when (buffer-live-p buffer)
   1201           (with-current-buffer buffer
   1202             (when (and flymake-mode
   1203                        flymake-no-changes-timeout)
   1204 	      (flymake-log
   1205                :debug "starting syntax check after idle for %s seconds"
   1206                flymake-no-changes-timeout)
   1207 	      (flymake-start t))
   1208             (setq flymake-timer nil))))
   1209       (current-buffer)))))
   1210 
   1211 ;;;###autoload
   1212 (defun flymake-mode-on ()
   1213   "Turn Flymake mode on."
   1214   (flymake-mode 1))
   1215 
   1216 ;;;###autoload
   1217 (defun flymake-mode-off ()
   1218   "Turn Flymake mode off."
   1219   (flymake-mode 0))
   1220 
   1221 (make-obsolete 'flymake-mode-on 'flymake-mode "26.1")
   1222 (make-obsolete 'flymake-mode-off 'flymake-mode "26.1")
   1223 
   1224 (defun flymake-after-change-function (start stop _len)
   1225   "Start syntax check for current buffer if it isn't already running.
   1226 START and STOP and LEN are as in `after-change-functions'."
   1227   (let((new-text (buffer-substring start stop)))
   1228     (push (list start stop new-text) flymake--recent-changes)
   1229     (flymake--schedule-timer-maybe)))
   1230 
   1231 (defun flymake-after-save-hook ()
   1232   (when flymake-start-on-save-buffer
   1233     (flymake-log :debug "starting syntax check as buffer was saved")
   1234     (flymake-start t)))
   1235 
   1236 (defun flymake-kill-buffer-hook ()
   1237   ;; Explicitly set flymake off, because that does a lot of useful
   1238   ;; cleanup.
   1239   (flymake-mode -1))
   1240 
   1241 (defun flymake-find-file-hook ()
   1242   (unless (or flymake-mode
   1243               (null flymake-diagnostic-functions))
   1244     (flymake-mode)
   1245     (flymake-log :warning "Turned on in `flymake-find-file-hook'")))
   1246 
   1247 (defun flymake-eldoc-function (report-doc &rest _)
   1248   "Document diagnostics at point.
   1249 Intended for `eldoc-documentation-functions' (which see)."
   1250   (let ((diags (flymake-diagnostics (point))))
   1251     (when diags
   1252       (funcall report-doc
   1253                (mapconcat #'flymake-diagnostic-text diags "\n")))))
   1254 
   1255 (defun flymake-goto-next-error (&optional n filter interactive)
   1256   "Go to Nth next Flymake diagnostic that matches FILTER.
   1257 Interactively, always move to the next diagnostic.  With a prefix
   1258 arg, skip any diagnostics with a severity less than `:warning'.
   1259 
   1260 If `flymake-wrap-around' is non-nil and no more next diagnostics,
   1261 resumes search from top.
   1262 
   1263 FILTER is a list of diagnostic types.  Only diagnostics with
   1264 matching severities matching are considered.  If nil (the
   1265 default) no filter is applied."
   1266   ;; TODO: let filter be a number, a severity below which diags are
   1267   ;; skipped.
   1268   (interactive (list 1
   1269                      (if current-prefix-arg
   1270                          '(:error :warning))
   1271                      t))
   1272   (let* ((n (or n 1))
   1273          (ovs (flymake--overlays :filter
   1274                                  (lambda (ov)
   1275                                    (let ((diag (overlay-get
   1276                                                 ov
   1277                                                 'flymake-diagnostic)))
   1278                                      (and diag
   1279                                           (or
   1280                                            (not filter)
   1281                                            (cl-find
   1282                                             (flymake--severity
   1283                                              (flymake-diagnostic-type diag))
   1284                                             filter :key #'flymake--severity)))))
   1285                                  :compare (if (cl-plusp n) #'< #'>)
   1286                                  :key #'overlay-start))
   1287          (tail (cl-member-if (lambda (ov)
   1288                                (if (cl-plusp n)
   1289                                    (> (overlay-start ov)
   1290                                       (point))
   1291                                  (< (overlay-start ov)
   1292                                     (point))))
   1293                              ovs))
   1294          (chain (if flymake-wrap-around
   1295                     (if tail
   1296                         (progn (setcdr (last tail) ovs) tail)
   1297                       (and ovs (setcdr (last ovs) ovs)))
   1298                   tail))
   1299          (target (nth (1- n) chain)))
   1300     (cond (target
   1301            (goto-char (overlay-start target))
   1302            (when interactive
   1303              (message
   1304               "%s"
   1305               (funcall (overlay-get target 'help-echo)
   1306                        (selected-window) target (point)))))
   1307           (interactive
   1308            (user-error "No more Flymake diagnostics%s"
   1309                        (if filter
   1310                            (format " of %s severity"
   1311                                    (mapconcat #'symbol-name filter ", ")) ""))))))
   1312 
   1313 (defun flymake-goto-prev-error (&optional n filter interactive)
   1314   "Go to Nth previous Flymake diagnostic that matches FILTER.
   1315 Interactively, always move to the previous diagnostic.  With a
   1316 prefix arg, skip any diagnostics with a severity less than
   1317 `:warning'.
   1318 
   1319 If `flymake-wrap-around' is non-nil and no more previous
   1320 diagnostics, resumes search from bottom.
   1321 
   1322 FILTER is a list of diagnostic types.  Only diagnostics with
   1323 matching severities matching are considered.  If nil (the
   1324 default) no filter is applied."
   1325   (interactive (list 1 (if current-prefix-arg
   1326                            '(:error :warning))
   1327                      t))
   1328   (flymake-goto-next-error (- (or n 1)) filter interactive))
   1329 
   1330 
   1331 ;;; Mode-line and menu
   1332 ;;;
   1333 (easy-menu-define flymake-menu flymake-mode-map "Flymake"
   1334   '("Flymake"
   1335     [ "Go to next problem"      flymake-goto-next-error t ]
   1336     [ "Go to previous problem"  flymake-goto-prev-error t ]
   1337     [ "Check now"               flymake-start t ]
   1338     [ "List all problems"       flymake-show-diagnostics-buffer t ]
   1339     "--"
   1340     [ "Go to log buffer"        flymake-switch-to-log-buffer t ]
   1341     [ "Turn off Flymake"        flymake-mode t ]))
   1342 
   1343 (defcustom flymake-mode-line-format
   1344   '(" " flymake-mode-line-title flymake-mode-line-exception
   1345     flymake-mode-line-counters)
   1346   "Mode line construct for customizing Flymake information."
   1347   :type '(repeat (choice string symbol)))
   1348 
   1349 (defcustom flymake-mode-line-counter-format
   1350   '("["
   1351     flymake-mode-line-error-counter
   1352     flymake-mode-line-warning-counter
   1353     flymake-mode-line-note-counter "]")
   1354   "Mode-line construct for formatting Flymake diagnostic counters.
   1355 This is a suitable place for placing the `flymake-error-counter',
   1356 `flymake-warning-counter' and `flymake-note-counter' constructs.
   1357 Separating each of these with space is not necessary."
   1358   :type '(repeat (choice string symbol)))
   1359 
   1360 (defvar flymake-mode-line-title '(:eval (flymake--mode-line-title))
   1361   "Mode-line construct to show Flymake's mode name and menu.")
   1362 
   1363 (defvar flymake-mode-line-exception '(:eval (flymake--mode-line-exception))
   1364   "Mode-line construct to report on exceptional Flymake status.")
   1365 
   1366 (defvar flymake-mode-line-counters '(:eval (flymake--mode-line-counters))
   1367   "Mode-line construct for counting Flymake diagnostics.
   1368 The counters are only placed if some Flymake backend initialized
   1369 correctly.")
   1370 
   1371 (defvar flymake-mode-line-error-counter
   1372   `(:eval (flymake--mode-line-counter :error t)))
   1373 (defvar flymake-mode-line-warning-counter
   1374   `(:eval (flymake--mode-line-counter :warning)))
   1375 (defvar flymake-mode-line-note-counter
   1376   `(:eval (flymake--mode-line-counter :note)))
   1377 
   1378 (put 'flymake-mode-line-format 'risky-local-variable t)
   1379 (put 'flymake-mode-line-title 'risky-local-variable t)
   1380 (put 'flymake-mode-line-exception 'risky-local-variable t)
   1381 (put 'flymake-mode-line-counters 'risky-local-variable t)
   1382 (put 'flymake-mode-line-error-counter 'risky-local-variable t)
   1383 (put 'flymake-mode-line-warning-counter 'risky-local-variable t)
   1384 (put 'flymake-mode-line-note-counter 'risky-local-variable t)
   1385 
   1386 (defun flymake--mode-line-title ()
   1387   `(:propertize
   1388     "Flymake"
   1389     mouse-face mode-line-highlight
   1390     help-echo
   1391     ,(lambda (&rest _)
   1392        (concat
   1393         (format "%s known backends\n" (hash-table-count flymake--state))
   1394         (format "%s running\n" (length (flymake-running-backends)))
   1395         (format "%s disabled\n" (length (flymake-disabled-backends)))
   1396         "mouse-1: Display minor mode menu\n"
   1397         "mouse-2: Show help for minor mode"))
   1398     keymap
   1399     ,(let ((map (make-sparse-keymap)))
   1400        (define-key map [mode-line down-mouse-1]
   1401          flymake-menu)
   1402        (define-key map [mode-line down-mouse-3]
   1403          flymake-menu)
   1404        (define-key map [mode-line mouse-2]
   1405          (lambda ()
   1406            (interactive)
   1407            (describe-function 'flymake-mode)))
   1408        map)))
   1409 
   1410 (defun flymake--mode-line-exception ()
   1411   "Helper for `flymake-mode-line-exception'."
   1412   (pcase-let* ((running) (reported)
   1413                (`(,ind ,face ,explain)
   1414                 (cond ((zerop (hash-table-count flymake--state))
   1415                        '("?" nil "No known backends"))
   1416                       ((cl-set-difference
   1417                         (setq running (flymake-running-backends))
   1418                         (setq reported (flymake-reporting-backends)))
   1419                        `("Wait" compilation-mode-line-run
   1420                          ,(format "Waiting for %s running backend(s)"
   1421                                   (length (cl-set-difference running reported)))))
   1422                       ((and (flymake-disabled-backends) (null running))
   1423                        '("!" compilation-mode-line-run
   1424                          "All backends disabled"))
   1425                       (t
   1426                        '(nil nil nil)))))
   1427     (when ind
   1428       `(":"
   1429         (:propertize ,ind face ,face
   1430                      help-echo ,explain
   1431                      keymap ,(let ((map (make-sparse-keymap)))
   1432                                (define-key map [mode-line mouse-1]
   1433                                  'flymake-switch-to-log-buffer)
   1434                                map))))))
   1435 
   1436 (defun flymake--mode-line-counters ()
   1437   (when (flymake-running-backends) flymake-mode-line-counter-format))
   1438 
   1439 (defun flymake--mode-line-counter (type &optional no-space)
   1440   "Compute number of diagnostics in buffer with TYPE's severity.
   1441 TYPE is usually keyword `:error', `:warning' or `:note'."
   1442   (let ((count 0)
   1443         (face (flymake--lookup-type-property type
   1444                                              'mode-line-face
   1445                                              'compilation-error)))
   1446     (dolist (d (flymake-diagnostics))
   1447       (when (= (flymake--severity type)
   1448                (flymake--severity (flymake-diagnostic-type d)))
   1449         (cl-incf count)))
   1450     (when (or (cl-plusp count)
   1451               (cond ((eq flymake-suppress-zero-counters t)
   1452                      nil)
   1453                     (flymake-suppress-zero-counters
   1454                      (>= (flymake--severity type)
   1455                          (warning-numeric-level
   1456                           flymake-suppress-zero-counters)))
   1457                     (t t)))
   1458       `(,(if no-space "" '(:propertize " "))
   1459         (:propertize
   1460          ,(format "%d" count)
   1461          face ,face
   1462          mouse-face mode-line-highlight
   1463          keymap
   1464          ,(let ((map (make-sparse-keymap)))
   1465             (define-key map (vector 'mode-line
   1466                                     mouse-wheel-down-event)
   1467               (lambda (event)
   1468                 (interactive "e")
   1469                 (with-selected-window (posn-window (event-start event))
   1470                   (flymake-goto-prev-error 1 (list type) t))))
   1471             (define-key map (vector 'mode-line
   1472                                     mouse-wheel-up-event)
   1473               (lambda (event)
   1474                 (interactive "e")
   1475                 (with-selected-window (posn-window (event-start event))
   1476                   (flymake-goto-next-error 1 (list type) t))))
   1477             map))))))
   1478 
   1479 ;;; Per-buffer diagnostic listing
   1480 
   1481 (defvar-local flymake--diagnostics-buffer-source nil)
   1482 
   1483 (defvar flymake-diagnostics-buffer-mode-map
   1484   (let ((map (make-sparse-keymap)))
   1485     (define-key map (kbd "RET") 'flymake-goto-diagnostic)
   1486     (define-key map (kbd "SPC") 'flymake-show-diagnostic)
   1487     map))
   1488 
   1489 (defun flymake-show-diagnostic (pos &optional other-window)
   1490   "Show location of diagnostic at POS."
   1491   (interactive (list (point) t))
   1492   (let* ((id (or (tabulated-list-get-id pos)
   1493                  (user-error "Nothing at point")))
   1494          (diag (plist-get id :diagnostic))
   1495          (locus (flymake--diag-locus diag))
   1496          (beg (flymake--diag-beg diag))
   1497          (end (flymake--diag-end diag))
   1498          (visit (lambda (b e)
   1499                   (goto-char b)
   1500                   (pulse-momentary-highlight-region (point)
   1501                                                     (or e (line-end-position))
   1502                                                     'highlight))))
   1503     (with-current-buffer (cond ((bufferp locus) locus)
   1504                                (t (find-file-noselect locus)))
   1505       (with-selected-window
   1506           (display-buffer (current-buffer) other-window)
   1507         (cond (;; an annotated diagnostic (most common case), or a
   1508                ;; non-annotated buffer diag
   1509                (number-or-marker-p beg)
   1510                (funcall visit beg end))
   1511               (;; a non-annotated file diag (TODO: could use `end'
   1512                ;; here, too)
   1513                (pcase-let ((`(,bbeg . ,bend)
   1514                             (flymake-diag-region (current-buffer)
   1515                                                  (car beg)
   1516                                                  (cdr beg))))
   1517                  (funcall visit bbeg bend)))))
   1518       (current-buffer))))
   1519 
   1520 (defun flymake-goto-diagnostic (pos)
   1521   "Show location of diagnostic at POS.
   1522 POS can be a buffer position or a button"
   1523   (interactive "d")
   1524   (pop-to-buffer
   1525    (flymake-show-diagnostic (if (button-type pos) (button-start pos) pos))))
   1526 
   1527 (defun flymake--tabulated-entries-1 (diags project-root)
   1528   "Helper for `flymake--diagnostic-buffer-entries'.
   1529 PROJECT-ROOT indicates that each entry should be preceded by the
   1530 filename of the diagnostic relative to that directory."
   1531   (cl-loop
   1532    for diag in diags
   1533    for locus = (flymake-diagnostic-buffer diag)
   1534    for file = (if (bufferp locus)
   1535                   (buffer-file-name locus)
   1536                 locus)
   1537    for overlay = (flymake--diag-overlay diag)
   1538    for (line . col) =
   1539    (cond (;; has live overlay, use overlay for position
   1540           (and overlay (overlay-buffer overlay))
   1541           (with-current-buffer (overlay-buffer overlay)
   1542             (save-excursion
   1543               (goto-char (overlay-start overlay))
   1544               (cons (line-number-at-pos)
   1545                     (- (point)
   1546                        (line-beginning-position))))))
   1547          (;; diagnostic not annotated, maybe foreign, check for cons
   1548           (consp (flymake--diag-beg diag))
   1549           (flymake--diag-beg diag))
   1550          (;; may still be a valid foreign diagnostic
   1551           (consp (flymake--diag-orig-beg diag))
   1552           (flymake--diag-orig-beg diag))
   1553          (;; somehow dead annotated diagnostic, ignore/give up
   1554           t nil))
   1555    for type = (flymake-diagnostic-type diag)
   1556    for backend = (flymake-diagnostic-backend diag)
   1557    for bname = (or (ignore-errors (symbol-name backend))
   1558                    "(anonymous function)")
   1559    for data-vec = `[,(format "%s" line)
   1560                     ,(format "%s" col)
   1561                     ,(propertize (format "%s"
   1562                                          (flymake--lookup-type-property
   1563                                           type 'flymake-type-name type))
   1564                                  'face (flymake--lookup-type-property
   1565                                         type 'mode-line-face 'flymake-error))
   1566                     ,(propertize
   1567                       (if bname
   1568                           (replace-regexp-in-string "\\(.\\)[^-]+\\(-\\|$\\)"
   1569                                                     "\\1\\2" bname)
   1570                         "(anon)")
   1571                       'help-echo (format "From `%s' backend" backend))
   1572                     (,(replace-regexp-in-string "\n.*" ""
   1573                                                 (flymake-diagnostic-text diag))
   1574                      mouse-face highlight
   1575                      help-echo "mouse-2: visit this diagnostic"
   1576                      face nil
   1577                      action flymake-goto-diagnostic
   1578                      mouse-action flymake-goto-diagnostic)]
   1579    when (and line col) collect
   1580    (list (list :diagnostic diag
   1581                :line line
   1582                :severity (flymake--lookup-type-property
   1583                           type
   1584                           'severity (warning-numeric-level :error)))
   1585          (if project-root
   1586              (vconcat `[(,(file-name-nondirectory file)
   1587                          help-echo ,(file-relative-name file project-root)
   1588                          face nil
   1589                          mouse-face highlight
   1590                          action flymake-goto-diagnostic
   1591                          mouse-action flymake-goto-diagnostic )]
   1592                       data-vec)
   1593            data-vec))))
   1594 
   1595 (defun flymake--diagnostics-buffer-entries ()
   1596   "Get tabulated list entries for current tabulated list buffer.
   1597 Expects `flymake--diagnostics-buffer-entries' to be bound to a
   1598 buffer."
   1599   ;; Do nothing if 'flymake--diagnostics-buffer-source' has not yet
   1600   ;; been set to a valid buffer.  This could happen when this function
   1601   ;; is called too early.  For example 'global-display-line-numbers-mode'
   1602   ;; calls us from its mode hook, when the diagnostic buffer has just
   1603   ;; been created by 'flymake-show-diagnostics-buffer', but is not yet
   1604   ;; set up properly (Bug#40529).
   1605   (when (bufferp flymake--diagnostics-buffer-source)
   1606     (with-current-buffer flymake--diagnostics-buffer-source
   1607       (when flymake-mode
   1608         (flymake--tabulated-entries-1 (flymake-diagnostics) nil)))))
   1609 
   1610 (defvar flymake--diagnostics-base-tabulated-list-format
   1611   `[("Line" 5 ,(lambda (l1 l2)
   1612                  (< (plist-get (car l1) :line)
   1613                     (plist-get (car l2) :line)))
   1614      :right-align t)
   1615     ("Col" 3 nil :right-align t)
   1616     ("Type" 8 ,(lambda (l1 l2)
   1617                  (< (plist-get (car l1) :severity)
   1618                     (plist-get (car l2) :severity))))
   1619     ("Backend" 8 t)
   1620     ("Message" 0 t)])
   1621 
   1622 (define-derived-mode flymake-diagnostics-buffer-mode tabulated-list-mode
   1623   "Flymake diagnostics"
   1624   "A mode for listing Flymake diagnostics."
   1625   (setq tabulated-list-format flymake--diagnostics-base-tabulated-list-format)
   1626   (setq tabulated-list-entries
   1627         'flymake--diagnostics-buffer-entries)
   1628   (tabulated-list-init-header))
   1629 
   1630 (defun flymake--diagnostics-buffer-name ()
   1631   (format "*Flymake diagnostics for `%s'*" (current-buffer)))
   1632 
   1633 (define-obsolete-function-alias 'flymake-show-diagnostics-buffer
   1634   'flymake-show-buffer-diagnostics "1.2.1")
   1635 
   1636 (defun flymake-show-buffer-diagnostics ()
   1637   "Show a list of Flymake diagnostics for current buffer."
   1638   (interactive)
   1639   (let* ((name (flymake--diagnostics-buffer-name))
   1640          (source (current-buffer))
   1641          (target (or (get-buffer name)
   1642                      (with-current-buffer (get-buffer-create name)
   1643                        (flymake-diagnostics-buffer-mode)
   1644                        (current-buffer)))))
   1645     (with-current-buffer target
   1646       (setq flymake--diagnostics-buffer-source source)
   1647       (display-buffer (current-buffer))
   1648       (revert-buffer))))
   1649 
   1650 
   1651 ;;; Per-project diagnostic listing
   1652 ;;;
   1653 
   1654 (defvar flymake-list-only-diagnostics nil
   1655   "Diagnostics list meant for listing, not highlighting.
   1656 This variable holds an alist ((FILE-NAME . DIAGS) ...) where
   1657 FILE-NAME is a string holding an absolute file name and DIAGS is
   1658 a list of diagnostic objects created with with
   1659 `flymake-make-diagnostic'.  These diagnostics are never annotated
   1660 as overlays in actual buffers: they merely serve as temporary
   1661 stand-ins for more accurate diagnostics that are produced once
   1662 the file they refer to is visited and `flymake-mode' is turned on
   1663 in the resulting buffer.
   1664 
   1665 Flymake backends that somehow gain sporadic information about
   1666 diagnostics in neighbouring files may freely modify this variable
   1667 by adding or removing entries to for those files.  If the
   1668 information about those neighbouring files is acquired repeatedly
   1669 and reliably, it may be more sensible to report them as
   1670 \"foreign\" diagnostics instead.
   1671 
   1672 Commands such as `flymake-show-project-diagnostics' will include
   1673 some of this variable's contents the diagnostic listings.")
   1674 
   1675 (defvar-local flymake--project-diagnostic-list-project nil)
   1676 
   1677 (define-derived-mode flymake-project-diagnostics-mode tabulated-list-mode
   1678   "Flymake diagnostics"
   1679   "A mode for listing Flymake diagnostics."
   1680   (setq tabulated-list-format
   1681         (vconcat [("File" 25 t)]
   1682                  flymake--diagnostics-base-tabulated-list-format))
   1683   (setq tabulated-list-entries
   1684         'flymake--project-diagnostics-entries)
   1685   (tabulated-list-init-header))
   1686 
   1687 (cl-defun flymake--project-diagnostics (&optional (project (project-current)))
   1688   "Get all known relevant diagnostics for PROJECT."
   1689   (let* ((root (project-root project))
   1690          (visited-buffers (cl-remove-if-not #'buffer-file-name (project-buffers project)))
   1691          buffer-annotated-diags
   1692          relevant-foreign-diags
   1693          list-only-diags
   1694          annotated-diag-files)
   1695     (setq buffer-annotated-diags
   1696           (cl-loop for buf in visited-buffers
   1697                    for diags = (with-current-buffer buf
   1698                                  (flymake-diagnostics))
   1699                    when diags do
   1700                    (push (buffer-file-name buf) annotated-diag-files)
   1701                    append (cl-sort diags #'< :key #'flymake-diagnostic-beg)))
   1702     (cl-loop
   1703      for buf in visited-buffers
   1704      do (with-current-buffer buf
   1705           (when (and flymake-mode flymake--state)
   1706             (maphash
   1707              (lambda (_backend state)
   1708                (maphash
   1709                 (lambda (foreign-file diags)
   1710                   (setq foreign-file (expand-file-name foreign-file))
   1711                   ;; FIXME: This is not right if more than one visited
   1712                   ;; source targets the same foreign file.  Don't
   1713                   ;; think we can get away without some kind of
   1714                   ;; `cl-remove-duplicates' here that utilizes
   1715                   ;; `flymake--equal-diagnostic-p'.
   1716                   (unless (member foreign-file annotated-diag-files)
   1717                     (push foreign-file annotated-diag-files)
   1718                     (setq relevant-foreign-diags
   1719                           (append relevant-foreign-diags
   1720                                   diags))))
   1721                 (flymake--state-foreign-diags state)))
   1722              flymake--state))))
   1723     (setq list-only-diags
   1724           (cl-loop for (file-name . diags) in flymake-list-only-diagnostics
   1725                    if (and (string-prefix-p (expand-file-name root) file-name)
   1726                            (not (member file-name annotated-diag-files)))
   1727                    append diags))
   1728     (append buffer-annotated-diags relevant-foreign-diags list-only-diags)))
   1729 
   1730 (defun flymake--project-diagnostics-entries ()
   1731   (let ((p (project-current)))
   1732     (flymake--tabulated-entries-1 (flymake--project-diagnostics p)
   1733                                   (project-root p))))
   1734 
   1735 (defun flymake--project-diagnostics-buffer (root)
   1736   (get-buffer-create (format "*Flymake diagnostics for `%s'*" root)))
   1737 
   1738 (defun flymake-show-project-diagnostics ()
   1739   "Show a list of Flymake diagnostics for the current project."
   1740   (interactive)
   1741   (let* ((prj (project-current))
   1742          (root (project-root prj))
   1743          (buffer (flymake--project-diagnostics-buffer root)))
   1744     (with-current-buffer buffer
   1745       (flymake-project-diagnostics-mode)
   1746       (setq-local flymake--project-diagnostic-list-project prj)
   1747       (display-buffer (current-buffer))
   1748       (revert-buffer))))
   1749 
   1750 (defun flymake--update-diagnostics-listings (buffer)
   1751   "Update diagnostics listings somehow relevant to BUFFER"
   1752   (dolist (probe (buffer-list))
   1753     (with-current-buffer probe
   1754       (when (or (and (eq major-mode 'flymake-project-diagnostics-mode)
   1755                      flymake--project-diagnostic-list-project
   1756                      (buffer-file-name buffer)
   1757                      (memq buffer
   1758                       (project-buffers flymake--project-diagnostic-list-project)))
   1759                 (and (eq major-mode 'flymake-diagnostics-buffer-mode)
   1760                      (eq flymake--diagnostics-buffer-source buffer)))
   1761         (revert-buffer)))))
   1762 
   1763 (provide 'flymake)
   1764 
   1765 (require 'flymake-proc)
   1766 
   1767 ;;; flymake.el ends here