1 ;;; flymake.el --- A universal on-the-fly syntax checker -*- lexical-binding: t; -*-
3 ;; Copyright (C) 2003-2017 Free Software Foundation, Inc.
5 ;; Author: Pavel Kobyakov <pk_at_work@yahoo.com>
6 ;; Maintainer: Leo Liu <sdl.web@gmail.com>
8 ;; Keywords: c languages tools
10 ;; This file is part of GNU Emacs.
12 ;; GNU Emacs is free software: you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation, either version 3 of the License, or
15 ;; (at your option) any later version.
17 ;; GNU Emacs is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ;; GNU General Public License for more details.
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
27 ;; Flymake is a minor Emacs mode performing on-the-fly syntax checks.
29 ;; Flymake collects diagnostic information for multiple sources,
30 ;; called backends, and visually annotates the relevant portions in
33 ;; This file contains the UI for displaying and interacting with the
34 ;; results produced by these backends, as well as entry points for
35 ;; backends to hook on to.
37 ;; The main entry points are `flymake-mode' and `flymake-start'
39 ;; The docstrings of these variables are relevant to understanding how
40 ;; Flymake works for both the user and the backend programmer:
42 ;; * `flymake-diagnostic-functions'
43 ;; * `flymake-diagnostic-types-alist'
48 (require 'thingatpt
) ; end-of-thing
49 (require 'warnings
) ; warning-numeric-level, display-warning
50 (require 'compile
) ; for some faces
51 (require 'subr-x
) ; when-let*, if-let*, hash-table-keys, hash-table-values
54 "Universal on-the-fly syntax checker."
56 :link
'(custom-manual "(flymake) Top")
59 (defcustom flymake-error-bitmap
'(flymake-double-exclamation-mark
61 "Bitmap (a symbol) used in the fringe for indicating errors.
62 The value may also be a list of two elements where the second
63 element specifies the face for the bitmap. For possible bitmap
64 symbols, see `fringe-bitmaps'. See also `flymake-warning-bitmap'.
66 The option `flymake-fringe-indicator-position' controls how and where
69 :type
'(choice (symbol :tag
"Bitmap")
70 (list :tag
"Bitmap and face"
71 (symbol :tag
"Bitmap")
74 (defcustom flymake-warning-bitmap
'(exclamation-mark compilation-warning
)
75 "Bitmap (a symbol) used in the fringe for indicating warnings.
76 The value may also be a list of two elements where the second
77 element specifies the face for the bitmap. For possible bitmap
78 symbols, see `fringe-bitmaps'. See also `flymake-error-bitmap'.
80 The option `flymake-fringe-indicator-position' controls how and where
83 :type
'(choice (symbol :tag
"Bitmap")
84 (list :tag
"Bitmap and face"
85 (symbol :tag
"Bitmap")
88 (defcustom flymake-note-bitmap
'(exclamation-mark compilation-info
)
89 "Bitmap (a symbol) used in the fringe for indicating info notes.
90 The value may also be a list of two elements where the second
91 element specifies the face for the bitmap. For possible bitmap
92 symbols, see `fringe-bitmaps'. See also `flymake-error-bitmap'.
94 The option `flymake-fringe-indicator-position' controls how and where
97 :type
'(choice (symbol :tag
"Bitmap")
98 (list :tag
"Bitmap and face"
99 (symbol :tag
"Bitmap")
100 (face :tag
"Face"))))
102 (defcustom flymake-fringe-indicator-position
'left-fringe
103 "The position to put Flymake fringe indicator.
104 The value can be nil (do not use indicators), `left-fringe' or `right-fringe'.
105 See `flymake-error-bitmap' and `flymake-warning-bitmap'."
107 :type
'(choice (const left-fringe
)
109 (const :tag
"No fringe indicators" nil
)))
111 (defcustom flymake-start-syntax-check-on-newline t
112 "Start syntax check if newline char was added/removed from the buffer."
115 (defcustom flymake-no-changes-timeout
0.5
116 "Time to wait after last change before automatically checking buffer.
117 If nil, never start checking buffer automatically like this."
120 (defcustom flymake-gui-warnings-enabled t
121 "Enables/disables GUI warnings."
123 (make-obsolete-variable 'flymake-gui-warnings-enabled
124 "it no longer has any effect." "26.1")
126 (defcustom flymake-start-syntax-check-on-find-file t
127 "Start syntax check on find file."
130 (defcustom flymake-log-level -
1
131 "Obsolete and ignored variable."
133 (make-obsolete-variable 'flymake-log-level
134 "it is superseded by `warning-minimum-log-level.'"
137 (defcustom flymake-wrap-around t
138 "If non-nil, moving to errors wraps around buffer boundaries."
141 (define-fringe-bitmap 'flymake-double-exclamation-mark
160 (defvar-local flymake-timer nil
161 "Timer for starting syntax check.")
163 (defvar-local flymake-check-start-time nil
164 "Time at which syntax check was started.")
166 (defun flymake--log-1 (level sublog msg
&rest args
)
167 "Do actual work for `flymake-log'."
168 (let (;; never popup the log buffer
169 (warning-minimum-level :emergency
)
174 (display-warning (list 'flymake sublog
)
175 (apply #'format-message msg args
)
178 '(:emergency
:error
:warning
:debug
:debug
) )
184 (defmacro flymake-log
(level msg
&rest args
)
185 "Log, at level LEVEL, the message MSG formatted with ARGS.
186 LEVEL is passed to `display-warning', which is used to display
187 the warning. If this form is included in a byte-compiled file,
188 the generated warning contains an indication of the file that
190 (let* ((compile-file (and (boundp 'byte-compile-current-file
)
191 (symbol-value 'byte-compile-current-file
)))
194 (not load-file-name
))
196 (file-name-nondirectory
197 (file-name-sans-extension compile-file
))))))
198 `(flymake--log-1 ,level
',sublog
,msg
,@args
)))
200 (defun flymake-error (text &rest args
)
201 "Format TEXT with ARGS and signal an error for flymake."
202 (let ((msg (apply #'format-message text args
)))
203 (flymake-log :error msg
)
204 (error (concat "[Flymake] " msg
))))
206 (cl-defstruct (flymake--diag
207 (:constructor flymake--diag-make
))
208 buffer beg end type text backend
)
211 (defun flymake-make-diagnostic (buffer
216 "Make a Flymake diagnostic for BUFFER's region from BEG to END.
217 TYPE is a key to `flymake-diagnostic-types-alist' and TEXT is a
218 description of the problem detected in this region."
219 (flymake--diag-make :buffer buffer
:beg beg
:end end
:type type
:text text
))
221 (defun flymake-ler-make-ler (file line type text
&optional full-file
)
222 (let* ((file (or full-file file
))
223 (buf (find-buffer-visiting file
)))
224 (unless buf
(flymake-error "No buffer visiting %s" file
))
225 (pcase-let* ((`(,beg .
,end
)
226 (with-current-buffer buf
227 (flymake-diag-region line nil
))))
228 (flymake-make-diagnostic buf beg end type text
))))
230 (make-obsolete 'flymake-ler-make-ler
'flymake-make-diagnostic
"26.1")
232 (cl-defun flymake--overlays (&key beg end filter compare key
)
233 "Get flymake-related overlays.
234 If BEG is non-nil and END is nil, consider only `overlays-at'
235 BEG. Otherwise consider `overlays-in' the region comprised by BEG
236 and END, defaulting to the whole buffer. Remove all that do not
237 verify FILTER, a function, and sort them by COMPARE (using KEY)."
240 (let ((ovs (cl-remove-if-not
242 (and (overlay-get ov
'flymake
)
244 (funcall filter ov
))))
245 (if (and beg
(null end
))
247 (overlays-in (or beg
(point-min))
248 (or end
(point-max)))))))
250 (cl-sort ovs compare
:key
(or key
254 (defun flymake-delete-own-overlays (&optional filter
)
255 "Delete all Flymake overlays in BUFFER."
256 (mapc #'delete-overlay
(flymake--overlays :filter filter
)))
258 (defface flymake-error
259 '((((supports :underline
(:style wave
)))
260 :underline
(:style wave
:color
"Red1"))
263 "Face used for marking error regions."
266 (defface flymake-warning
267 '((((supports :underline
(:style wave
)))
268 :underline
(:style wave
:color
"deep sky blue"))
271 "Face used for marking warning regions."
274 (defface flymake-note
275 '((((supports :underline
(:style wave
)))
276 :underline
(:style wave
:color
"yellow green"))
279 "Face used for marking note regions."
282 (define-obsolete-face-alias 'flymake-warnline
'flymake-warning
"26.1")
283 (define-obsolete-face-alias 'flymake-errline
'flymake-error
"26.1")
285 (defun flymake-diag-region (line &optional col
)
286 "Compute region (BEG . END) corresponding to LINE and COL.
287 If COL is nil, return a region just for LINE.
288 Return nil if the region is invalid."
289 (condition-case-unless-debug _err
290 (let ((line (min (max line
1)
291 (line-number-at-pos (point-max) 'absolute
))))
293 (goto-char (point-min))
294 (forward-line (1- line
))
295 (cl-flet ((fallback-bol
296 () (progn (back-to-indentation) (point)))
301 (skip-chars-backward " \t\f\t\n" beg
)
303 (line-beginning-position 2)
305 (if (and col
(cl-plusp col
))
306 (let* ((beg (progn (forward-char (1- col
))
308 (sexp-end (ignore-errors (end-of-thing 'sexp
)))
309 (end (or (and sexp-end
310 (not (= sexp-end beg
))
312 (ignore-errors (goto-char (1+ beg
)))))
314 (fallback-eol beg
))))
315 (cons (if end beg
(fallback-bol))
317 (let* ((beg (fallback-bol))
318 (end (fallback-eol beg
)))
320 (error (flymake-error "Invalid region line=%s col=%s" line col
))))
322 (defvar flymake-diagnostic-functions nil
323 "Special hook of Flymake backends that check a buffer.
325 The functions in this hook diagnose problems in a buffer’s
326 contents and provide information to the Flymake user interface
327 about where and how to annotate problems diagnosed in a buffer.
329 Whenever Flymake or the user decides to re-check the buffer, each
330 function is called with an arbitrary number of arguments:
332 * the first argument is always REPORT-FN, a callback function
335 * the remaining arguments are keyword-value pairs in the
336 form (:KEY VALUE :KEY2 VALUE2...). Currently, Flymake provides
337 no such arguments, but backend functions must be prepared to
338 accept to accept and possibly ignore any number of them.
340 Backend functions are expected to initiate the buffer check, but
341 aren't required to complete it check before exiting: if the
342 computation involved is expensive, especially for large buffers,
343 that task can be scheduled for the future using asynchronous
344 processes or other asynchronous mechanisms.
346 In any case, backend functions are expected to return quickly or
347 signal an error, in which case the backend is disabled. Flymake
348 will not try disabled backends again for any future checks of
349 this buffer. Certain commands, like turning `flymake-mode' off
350 and on again, reset the list of disabled backends.
352 If the function returns, Flymake considers the backend to be
353 \"running\". If it has not done so already, the backend is
354 expected to call the function REPORT-FN with a single argument
355 REPORT-ACTION also followed by an optional list of keyword-value
356 pairs in the form (:REPORT-KEY VALUE :REPORT-KEY2 VALUE2...).
358 Currently accepted values for REPORT-ACTION are:
360 * A (possibly empty) list of diagnostic objects created with
361 `flymake-make-diagnostic', causing Flymake to annotate the
362 buffer with this information.
364 A backend may call REPORT-FN repeatedly in this manner, but
365 only until Flymake considers that the most recently requested
366 buffer check is now obsolete because, say, buffer contents have
367 changed in the meantime. The backend is only given notice of
368 this via a renewed call to the backend function. Thus, to
369 prevent making obsolete reports and wasting resources, backend
370 functions should first cancel any ongoing processing from
373 * The symbol `:panic', signaling that the backend has encountered
374 an exceptional situation and should be disabled.
376 Currently accepted REPORT-KEY arguments are:
378 * ‘:explanation’: value should give user-readable details of
379 the situation encountered, if any.
381 * ‘:force’: value should be a boolean suggesting that the Flymake
382 considers the report even if was somehow unexpected.")
384 (defvar flymake-diagnostic-types-alist
386 .
((flymake-category . flymake-error
)))
388 .
((flymake-category . flymake-warning
)))
390 .
((flymake-category . flymake-note
))))
391 "Alist ((KEY . PROPS)*) of properties of Flymake error types.
392 KEY can be anything passed as `:type' to `flymake-diag-make'.
394 PROPS is an alist of properties that are applied, in order, to
395 the diagnostics of each type. The recognized properties are:
397 * Every property pertaining to overlays, except `category' and
398 `evaporate' (see Info Node `(elisp)Overlay Properties'), used
399 affect the appearance of Flymake annotations.
401 * `bitmap', an image displayed in the fringe according to
402 `flymake-fringe-indicator-position'. The value actually
403 follows the syntax of `flymake-error-bitmap' (which see). It
404 is overridden by any `before-string' overlay property.
406 * `severity', a non-negative integer specifying the diagnostic's
407 severity. The higher, the more serious. If the overlay
408 priority `priority' is not specified, `severity' is used to set
409 it and help sort overlapping overlays.
411 * `flymake-category', a symbol whose property list is considered
412 as a default for missing values of any other properties. This
413 is useful to backend authors when creating new diagnostic types
414 that differ from an existing type by only a few properties.")
416 (put 'flymake-error
'face
'flymake-error
)
417 (put 'flymake-error
'bitmap
'flymake-error-bitmap
)
418 (put 'flymake-error
'severity
(warning-numeric-level :error
))
419 (put 'flymake-error
'mode-line-face
'compilation-error
)
421 (put 'flymake-warning
'face
'flymake-warning
)
422 (put 'flymake-warning
'bitmap
'flymake-warning-bitmap
)
423 (put 'flymake-warning
'severity
(warning-numeric-level :warning
))
424 (put 'flymake-warning
'mode-line-face
'compilation-warning
)
426 (put 'flymake-note
'face
'flymake-note
)
427 (put 'flymake-note
'bitmap
'flymake-note-bitmap
)
428 (put 'flymake-note
'severity
(warning-numeric-level :debug
))
429 (put 'flymake-note
'mode-line-face
'compilation-info
)
431 (defun flymake--lookup-type-property (type prop
&optional default
)
432 "Look up PROP for TYPE in `flymake-diagnostic-types-alist'.
433 If TYPE doesn't declare PROP in either
434 `flymake-diagnostic-types-alist' or in the symbol of its
435 associated `flymake-category' return DEFAULT."
436 (let ((alist-probe (assoc type flymake-diagnostic-types-alist
)))
438 (let* ((alist (cdr alist-probe
))
439 (prop-probe (assoc prop alist
)))
442 (if-let* ((cat (assoc-default 'flymake-category alist
))
443 (plist (and (symbolp cat
)
445 (cat-probe (plist-member plist prop
)))
451 (defun flymake--fringe-overlay-spec (bitmap &optional recursed
)
452 (if (and (symbolp bitmap
)
455 (flymake--fringe-overlay-spec
456 (symbol-value bitmap
) t
)
457 (and flymake-fringe-indicator-position
459 (propertize "!" 'display
460 (cons flymake-fringe-indicator-position
465 (defun flymake--highlight-line (diagnostic)
466 "Highlight buffer with info in DIAGNOSTIC."
467 (when-let* ((ov (make-overlay
468 (flymake--diag-beg diagnostic
)
469 (flymake--diag-end diagnostic
))))
470 ;; First set `category' in the overlay, then copy over every other
473 (let ((alist (assoc-default (flymake--diag-type diagnostic
)
474 flymake-diagnostic-types-alist
)))
475 (overlay-put ov
'category
(assoc-default 'flymake-category alist
))
476 (cl-loop for
(k . v
) in alist
477 unless
(eq k
'category
)
478 do
(overlay-put ov k v
)))
479 ;; Now ensure some essential defaults are set
481 (cl-flet ((default-maybe
483 (unless (or (plist-member (overlay-properties ov
) prop
)
484 (let ((cat (overlay-get ov
487 (plist-member (symbol-plist cat
) prop
))))
488 (overlay-put ov prop value
))))
489 (default-maybe 'bitmap
'flymake-error-bitmap
)
490 (default-maybe 'face
'flymake-error
)
491 (default-maybe 'before-string
492 (flymake--fringe-overlay-spec
493 (overlay-get ov
'bitmap
)))
494 (default-maybe 'help-echo
495 (lambda (_window _ov pos
)
498 (let ((diag (overlay-get ov
'flymake--diagnostic
)))
499 (flymake--diag-text diag
)))
500 (flymake--overlays :beg pos
)
502 (default-maybe 'severity
(warning-numeric-level :error
))
503 (default-maybe 'priority
(+ 100 (overlay-get ov
'severity
))))
504 ;; Some properties can't be overridden.
506 (overlay-put ov
'evaporate t
)
507 (overlay-put ov
'flymake t
)
508 (overlay-put ov
'flymake--diagnostic diagnostic
)))
510 ;; Nothing in Flymake uses this at all any more, so this is just for
511 ;; third-party compatibility.
512 (define-obsolete-function-alias 'flymake-display-warning
'message-box
"26.1")
514 (defvar-local flymake--backend-state nil
515 "Buffer-local hash table of a Flymake backend's state.
516 The keys to this hash table are functions as found in
517 `flymake-diagnostic-functions'. The values are structures
518 of the type `flymake--backend-state', with these slots
520 `running', a symbol to keep track of a backend's replies via its
521 REPORT-FN argument. A backend is running if this key is
522 present. If the key is absent if the backend isn't expecting any
523 replies from the backend.
525 `diags', a (possibly empty) list of diagnostic objects created
526 with `flymake-make-diagnostic'. This key is absent if the
527 backend hasn't reported anything yet.
529 `reported-p', a boolean indicating if the backend has replied
530 since it last was contacted.
532 `disabled', a string with the explanation for a previous
533 exceptional situation reported by the backend. If this key is
534 present the backend is disabled.")
536 (cl-defstruct (flymake--backend-state
537 (:constructor flymake--make-backend-state
))
538 running reported-p disabled diags
)
540 (defmacro flymake--with-backend-state
(backend state-var
&rest body
)
541 "Bind BACKEND's STATE-VAR to its state, run BODY."
542 (declare (indent 2) (debug (sexp sexp
&rest form
)))
543 (let ((b (make-symbol "b")))
544 `(let* ((,b
,backend
)
546 (or (gethash ,b flymake--backend-state
)
547 (puthash ,b
(flymake--make-backend-state)
548 flymake--backend-state
))))
551 (defun flymake-is-running ()
552 "Tell if Flymake has running backends in this buffer"
553 (flymake-running-backends))
555 (cl-defun flymake--handle-report (backend token report-action
556 &key explanation force
558 "Handle reports from BACKEND identified by TOKEN.
560 BACKEND, REPORT-ACTION and EXPLANATION, and FORCE conform to the calling
561 convention described in `flymake-diagnostic-functions' (which
562 see). Optional FORCE says to handle a report even if TOKEN was
564 (let* ((state (gethash backend flymake--backend-state
))
565 (first-report (not (flymake--backend-state-reported-p state
))))
566 (setf (flymake--backend-state-reported-p state
) t
)
572 "Unexpected report from unknown backend %s" backend
))
573 ((flymake--backend-state-disabled state
)
575 "Unexpected report from disabled backend %s" backend
))
577 (setq expected-token
(flymake--backend-state-running state
))
578 (null expected-token
))
579 ;; should never happen
580 (flymake-error "Unexpected report from stopped backend %s" backend
))
581 ((and (not (eq expected-token token
))
583 (flymake-error "Obsolete report from backend %s with explanation %s"
584 backend explanation
))
585 ((eq :panic report-action
)
586 (flymake--disable-backend backend explanation
))
587 ((not (listp report-action
))
588 (flymake--disable-backend backend
589 (format "Unknown action %S" report-action
))
590 (flymake-error "Expected report, but got unknown key %s" report-action
))
592 (setq new-diags report-action
)
595 ;; only delete overlays if this is the first report
597 (flymake-delete-own-overlays
600 (flymake--diag-backend
601 (overlay-get ov
'flymake--diagnostic
))))))
603 (flymake--highlight-line diag
)
604 (setf (flymake--diag-backend diag
) backend
))
606 (setf (flymake--backend-state-diags state
)
607 (append new-diags
(flymake--backend-state-diags state
)))
608 (when flymake-check-start-time
609 (flymake-log :debug
"backend %s reported %d diagnostics in %.2f second(s)"
612 (- (float-time) flymake-check-start-time
)))))))))
614 (defun flymake-make-report-fn (backend &optional token
)
615 "Make a suitable anonymous report function for BACKEND.
616 BACKEND is used to help Flymake distinguish different diagnostic
617 sources. If provided, TOKEN helps Flymake distinguish between
618 different runs of the same backend."
619 (let ((buffer (current-buffer)))
621 (when (buffer-live-p buffer
)
622 (with-current-buffer buffer
623 (apply #'flymake--handle-report backend token args
))))))
625 (defun flymake--collect (fn)
627 (maphash (lambda (backend state
)
628 (when (funcall fn state
) (push backend retval
)))
629 flymake--backend-state
)
632 (defun flymake-running-backends ()
633 "Compute running Flymake backends in current buffer."
634 (flymake--collect #'flymake--backend-state-running
))
636 (defun flymake-disabled-backends ()
637 "Compute disabled Flymake backends in current buffer."
638 (flymake--collect #'flymake--backend-state-disabled
))
640 (defun flymake-reporting-backends ()
641 "Compute reporting Flymake backends in current buffer."
642 (flymake--collect #'flymake--backend-state-reported-p
))
644 (defun flymake--disable-backend (backend &optional explanation
)
645 "Disable BACKEND because EXPLANATION.
646 If is is running also stop it."
647 (flymake-log :warning
"Disabling backend %s because %s" backend explanation
)
648 (flymake--with-backend-state backend state
649 (setf (flymake--backend-state-running state
) nil
650 (flymake--backend-state-disabled state
) explanation
651 (flymake--backend-state-reported-p state
) t
)))
653 (defun flymake--run-backend (backend)
654 "Run the backend BACKEND, reenabling if necessary."
655 (flymake-log :debug
"Running backend %s" backend
)
656 (let ((run-token (cl-gensym "backend-token")))
657 (flymake--with-backend-state backend state
658 (setf (flymake--backend-state-running state
) run-token
659 (flymake--backend-state-disabled state
) nil
660 (flymake--backend-state-diags state
) nil
661 (flymake--backend-state-reported-p state
) nil
))
662 ;; FIXME: Should use `condition-case-unless-debug' here, for don't
663 ;; for two reasons: (1) that won't let me catch errors from inside
664 ;; `ert-deftest' where `debug-on-error' appears to be always
665 ;; t. (2) In cases where the user is debugging elisp somewhere
666 ;; else, and using flymake, the presence of a frequently
667 ;; misbehaving backend in the global hook (most likely the legacy
668 ;; backend) will trigger an annoying backtrace.
672 (flymake-make-report-fn backend run-token
))
674 (flymake--disable-backend backend err
)))))
676 (defun flymake-start (&optional deferred force
)
677 "Start a syntax check.
678 Start it immediately, or after current command if DEFERRED is
679 non-nil. With optional FORCE run even disabled backends.
681 Interactively, with a prefix arg, FORCE is t."
682 (interactive (list nil current-prefix-arg
))
686 (remove-hook 'post-command-hook
#'start
'local
)
687 (setq flymake-check-start-time
(float-time))
689 'flymake-diagnostic-functions
693 (flymake--with-backend-state backend state
694 (flymake--backend-state-disabled state
)))
695 (flymake-log :debug
"Backend %s is disabled, not starting"
698 (flymake--run-backend backend
)))
702 (add-hook 'post-command-hook
#'start
'append
'local
)
705 (defvar flymake-mode-map
706 (let ((map (make-sparse-keymap))) map
)
707 "Keymap for `flymake-mode'")
710 (define-minor-mode flymake-mode nil
711 :group
'flymake
:lighter flymake--mode-line-format
:keymap flymake-mode-map
713 ;; Turning the mode ON.
716 ((not flymake-diagnostic-functions
)
717 (flymake-error "No backends to check buffer %s" (buffer-name)))
719 (add-hook 'after-change-functions
'flymake-after-change-function nil t
)
720 (add-hook 'after-save-hook
'flymake-after-save-hook nil t
)
721 (add-hook 'kill-buffer-hook
'flymake-kill-buffer-hook nil t
)
723 (setq flymake--backend-state
(make-hash-table))
725 (when flymake-start-syntax-check-on-find-file
728 ;; Turning the mode OFF.
730 (remove-hook 'after-change-functions
'flymake-after-change-function t
)
731 (remove-hook 'after-save-hook
'flymake-after-save-hook t
)
732 (remove-hook 'kill-buffer-hook
'flymake-kill-buffer-hook t
)
733 ;;+(remove-hook 'find-file-hook (function flymake-find-file-hook) t)
735 (flymake-delete-own-overlays)
738 (cancel-timer flymake-timer
)
739 (setq flymake-timer nil
)))))
741 (defun flymake--schedule-timer-maybe ()
742 "(Re)schedule an idle timer for checking the buffer.
743 Do it only if `flymake-no-changes-timeout' is non-nil."
744 (when flymake-timer
(cancel-timer flymake-timer
))
745 (when flymake-no-changes-timeout
749 (seconds-to-time flymake-no-changes-timeout
)
752 (when (buffer-live-p buffer
)
753 (with-current-buffer buffer
754 (when (and flymake-mode
755 flymake-no-changes-timeout
)
757 :debug
"starting syntax check after idle for %s seconds"
758 flymake-no-changes-timeout
)
760 (setq flymake-timer nil
))))
764 (defun flymake-mode-on ()
765 "Turn Flymake mode on."
769 (defun flymake-mode-off ()
770 "Turn Flymake mode off."
773 (make-obsolete 'flymake-mode-on
'flymake-mode
"26.1")
774 (make-obsolete 'flymake-mode-off
'flymake-mode
"26.1")
776 (defun flymake-after-change-function (start stop _len
)
777 "Start syntax check for current buffer if it isn't already running."
778 (let((new-text (buffer-substring start stop
)))
779 (when (and flymake-start-syntax-check-on-newline
(equal new-text
"\n"))
780 (flymake-log :debug
"starting syntax check as new-line has been seen")
781 (flymake-start 'deferred
))
782 (flymake--schedule-timer-maybe)))
784 (defun flymake-after-save-hook ()
786 (flymake-log :debug
"starting syntax check as buffer was saved")
789 (defun flymake-kill-buffer-hook ()
791 (cancel-timer flymake-timer
)
792 (setq flymake-timer nil
)))
794 (defun flymake-find-file-hook ()
795 (unless (or flymake-mode
796 (null flymake-diagnostic-functions
))
798 (flymake-log :warning
"Turned on in `flymake-find-file-hook'")))
800 (defun flymake-goto-next-error (&optional n filter interactive
)
801 "Go to Nth next Flymake error in buffer matching FILTER.
803 Interactively, always move to the next error. Interactively, and
804 with a prefix arg, skip any diagnostics with a severity less than
807 If ‘flymake-wrap-around’ is non-nil, resumes search from top
810 FILTER is a list of diagnostic types found in
811 `flymake-diagnostic-types-alist', or nil, if no filter is to be
813 ;; TODO: let filter be a number, a severity below which diags are
816 (if current-prefix-arg
820 (ovs (flymake--overlays :filter
822 (let ((diag (overlay-get
824 'flymake--diagnostic
)))
827 (memq (flymake--diag-type diag
)
829 :compare
(if (cl-plusp n
) #'< #'>)
830 :key
#'overlay-start
))
831 (tail (cl-member-if (lambda (ov)
833 (> (overlay-start ov
)
835 (< (overlay-start ov
)
838 (chain (if flymake-wrap-around
840 (progn (setcdr (last tail
) ovs
) tail
)
841 (and ovs
(setcdr (last ovs
) ovs
)))
843 (target (nth (1- n
) chain
)))
845 (goto-char (overlay-start target
))
848 (funcall (overlay-get target
'help-echo
)
851 (user-error "No more Flymake errors%s"
853 (format " of types %s" filter
)
856 (defun flymake-goto-prev-error (&optional n filter interactive
)
857 "Go to Nth previous Flymake error in buffer matching FILTER.
859 Interactively, always move to the previous error. Interactively,
860 and with a prefix arg, skip any diagnostics with a severity less
863 If ‘flymake-wrap-around’ is non-nil, resumes search from top
866 FILTER is a list of diagnostic types found in
867 `flymake-diagnostic-types-alist', or nil, if no filter is to be
869 (interactive (list 1 (if current-prefix-arg
872 (flymake-goto-next-error (- (or n
1)) filter interactive
))
875 ;;; Mode-line fanciness
877 (defvar flymake--mode-line-format
`(:eval
(flymake--mode-line-format)))
879 (put 'flymake--mode-line-format
'risky-local-variable t
)
881 (defun flymake--mode-line-format ()
882 "Produce a pretty minor mode indicator."
883 (let* ((known (hash-table-keys flymake--backend-state
))
884 (running (flymake-running-backends))
885 (disabled (flymake-disabled-backends))
886 (reported (flymake-reporting-backends))
887 (diags-by-type (make-hash-table))
888 (all-disabled (and disabled
(null running
)))
889 (some-waiting (cl-set-difference running reported
)))
890 (maphash (lambda (_b state
)
893 (gethash (flymake--diag-type diag
)
895 (flymake--backend-state-diags state
)))
896 flymake--backend-state
)
897 `((:propertize
" Flymake"
898 mouse-face mode-line-highlight
900 ,(concat (format "%s known backends\n" (length known
))
901 (format "%s running\n" (length running
))
902 (format "%s disabled\n" (length disabled
))
903 "mouse-1: go to log buffer ")
905 ,(let ((map (make-sparse-keymap)))
906 (define-key map
[mode-line mouse-1
]
909 (switch-to-buffer "*Flymake log*")))
911 ,@(pcase-let ((`(,ind
,face
,explain
)
913 `("?" mode-line
"No known backends"))
915 `("Wait" compilation-mode-line-run
916 ,(format "Waiting for %s running backends"
919 `("!" compilation-mode-line-run
920 "All backends disabled"))
927 help-echo
,explain
)))))
928 ,@(unless (or all-disabled
931 for
(type . severity
)
932 in
(cl-sort (mapcar (lambda (type)
933 (cons type
(flymake--lookup-type-property
936 (warning-numeric-level :error
))))
937 (cl-union (hash-table-keys diags-by-type
)
941 for diags
= (gethash type diags-by-type
)
942 for face
= (flymake--lookup-type-property type
946 (>= severity
(warning-numeric-level :warning
)))
947 collect
`(:propertize
948 ,(format "%d" (length diags
))
950 mouse-face mode-line-highlight
952 ,(let ((map (make-sparse-keymap))
954 (define-key map
[mode-line mouse-4
]
957 (flymake-goto-prev-error 1 (list type
) t
)))
958 (define-key map
[mode-line mouse-5
]
961 (flymake-goto-next-error 1 (list type
) t
)))
964 ,(concat (format "%s diagnostics of type %s\n"
965 (propertize (format "%d"
968 (propertize (format "%s" type
)
970 "mouse-4/mouse-5: previous/next of this type\n"))
974 ,@(cl-loop for
(a . rest
) on forms by
#'cdr
975 collect a when rest collect
977 (:propertize
"]")))))))
981 (require 'flymake-proc
)
983 ;;; flymake.el ends here