Lay groundwork for multiple active backends in the same buffer.
Backends are lisp functions called when flymake-mode sees fit. They
are responsible for examining the current buffer and telling
flymake.el, via return value, if they can syntax check it.
Backends should return quickly and inexpensively, but they are also
passed a REPORT-FN argument which they may or may not call
asynchronously after performing more expensive work.
REPORT-FN's calling convention stipulates that a backend calls it with
a list of diagnostics as argument, or, alternatively, with a symbol
denoting an exceptional situation, usually some panic resulting from a
misconfigured backend. In keeping with legacy behaviour,
flymake.el's response to a panic is to disable the issuing backend.
The flymake--diag object representing a diagnostic now also keeps
information about its source backend. Among other uses, this allows
flymake to selectively cleanup overlays based on which backend is
updating its diagnostics.
* lisp/progmodes/flymake-proc.el (flymake-proc--report-fn):
New dynamic variable.
(flymake-proc--process): New variable.
(flymake-can-syntax-check-buffer): Remove.
(flymake-proc--process-sentinel): Simplify. Use
unwind-protect. Affect flymake-proc--processes here.
Bind flymake-proc--report-fn.
(flymake-proc--process-filter): Bind flymake-proc--report-fn.
(flymake-proc--post-syntax-check): Delete
(flymake-proc-start-syntax-check): Take mandatory
report-fn. Rewrite. Bind flymake-proc--report-fn.
(flymake-proc--process-sentinel): Rewrite and simplify.
(flymake-proc--panic): New helper.
(flymake-proc--start-syntax-check-process): Record report-fn
in process. Use flymake-proc--panic.
(flymake-proc-stop-all-syntax-checks): Use mapc. Don't affect
flymake-proc--processes here. Record interruption reason.
(flymake-proc--init-find-buildfile-dir)
(flymake-proc--init-create-temp-source-and-master-buffer-copy):
Use flymake-proc--panic.
(flymake-diagnostic-functions): Add
flymake-proc-start-syntax-check.
(flymake-proc-compile): Call
flymake-proc-stop-all-syntax-checks with a reason.
* lisp/progmodes/flymake.el (flymake-backends): Delete.
(flymake-check-was-interrupted): Delete.
(flymake--diag): Add backend slot.
(flymake-delete-own-overlays): Take optional filter arg.
(flymake-diagnostic-functions): New user-visible variable.
(flymake--running-backends, flymake--disabled-backends): New
buffer-local variables.
(flymake-is-running): Now a function, not a variable.
(flymake-mode-line, flymake-mode-line-e-w)
(flymake-mode-line-status): Delete.
(flymake-lighter): flymake's minor-mode "lighter".
(flymake-report): Delete.
(flymake--backend): Delete.
(flymake--can-syntax-check-buffer): Delete.
(flymake--handle-report, flymake--disable-backend)
(flymake--run-backend, flymake--run-backend): New helpers.
(flymake-make-report-fn): Make a lambda.
(flymake--start-syntax-check): Iterate
flymake-diagnostic-functions.
(flymake-mode): Use flymake-lighter. Simplify. Initialize
flymake--running-backends and flymake--disabled-backends.
(flymake-find-file-hook): Simplify.
* test/lisp/progmodes/flymake-tests.el
(flymake-tests--call-with-fixture): Use flymake-is-running the
function. Check if flymake-mode already active before activating it.
Add a thorough test for flymake multiple backends
* lisp/progmodes/flymake.el (flymake--start-syntax-check):
Don't use condition-case-unless-debug, use condition-case
* test/lisp/progmodes/flymake-tests.el
(flymake-tests--assert-set): New helper macro.
(dummy-backends): New test.
(const :tag "flymake-proc-get-real-file-name" nil)
function))))
+(defvar-local flymake-proc--process nil
+ "Currently active flymake process for a buffer, if any.")
+
(defvar flymake-proc--processes nil
"List of currently active flymake processes.")
+(defvar flymake-proc--report-fn nil
+ "If bound, function used to report back to flymake's UI.")
+
(defun flymake-proc--get-file-name-mode-and-masks (file-name)
"Return the corresponding entry from `flymake-proc-allowed-file-name-masks'."
(unless (stringp file-name)
(flymake-log 3 "file %s, init=%s" file-name (car mode-and-masks))
mode-and-masks))
-(defun flymake-proc-can-syntax-check-file (file-name)
- "Determine whether we can syntax check FILE-NAME.
-Return nil if we cannot, non-nil if we can."
- (if (flymake-proc-get-init-function file-name) t nil))
-
(defun flymake-proc--get-init-function (file-name)
"Return init function to be used for the file."
(let* ((init-f (nth 0 (flymake-proc--get-file-name-mode-and-masks file-name))))
"Parse STRING and collect diagnostics info."
(flymake-log 3 "received %d byte(s) of output from process %d"
(length string) (process-id proc))
- (let ((output-buffer (process-get proc 'flymake-proc--output-buffer)))
+ (let ((output-buffer (process-get proc 'flymake-proc--output-buffer))
+ (flymake-proc--report-fn
+ (process-get proc 'flymake-proc--report-fn)))
(when (and (buffer-live-p (process-buffer proc))
output-buffer)
(with-current-buffer output-buffer
(process-put proc 'flymake-proc--unprocessed-mark
(point-marker))))))))
-(defun flymake-proc--process-sentinel (process _event)
+(defun flymake-proc--process-sentinel (proc _event)
"Sentinel for syntax check buffers."
- (when (memq (process-status process) '(signal exit))
- (let* ((exit-status (process-exit-status process))
- (command (process-command process))
- (source-buffer (process-buffer process))
- (cleanup-f (flymake-proc--get-cleanup-function
- (buffer-file-name source-buffer))))
-
+ (when (memq (process-status proc) '(signal exit))
+ (let* ((exit-status (process-exit-status proc))
+ (command (process-command proc))
+ (source-buffer (process-buffer proc))
+ (flymake-proc--report-fn (process-get proc
+ 'flymake-proc--report-fn))
+ (cleanup-f (flymake-proc--get-cleanup-function
+ (buffer-file-name source-buffer)))
+ (diagnostics (process-get
+ proc
+ 'flymake-proc--collected-diagnostics))
+ (interrupted (process-get proc 'flymake-proc--interrupted)))
(flymake-log 2 "process %d exited with code %d"
- (process-id process) exit-status)
- (unless (> flymake-log-level 2)
- (kill-buffer (process-get process 'flymake-proc--output-buffer)))
- (condition-case err
- (progn
+ (process-id proc) exit-status)
+ (unwind-protect
+ (when (buffer-live-p source-buffer)
(flymake-log 3 "cleaning up using %s" cleanup-f)
- (when (buffer-live-p source-buffer)
- (with-current-buffer source-buffer
- (funcall cleanup-f)))
-
- (delete-process process)
- (setq flymake-proc--processes (delq process flymake-proc--processes))
-
- (when (buffer-live-p source-buffer)
- (with-current-buffer source-buffer
- (flymake-proc--post-syntax-check
- exit-status command
- (process-get process 'flymake-proc--collected-diagnostics))
- (setq flymake-is-running nil))))
- (error
- (let ((err-str (format "Error in process sentinel for buffer %s: %s"
- source-buffer (error-message-string err))))
- (flymake-log 0 err-str)
- (with-current-buffer source-buffer
- (setq flymake-is-running nil))))))))
-
-(defun flymake-proc--post-syntax-check (exit-status command diagnostics)
- (if (equal 0 exit-status)
- (flymake-report diagnostics)
- (if flymake-check-was-interrupted
- (flymake-report-status nil "") ;; STOPPED
- (if (null diagnostics)
- (flymake-report-fatal-status
- "CFGERR"
- (format "Configuration error has occurred while running %s" command))
- (flymake-report diagnostics)))))
+ (with-current-buffer source-buffer
+ (funcall cleanup-f)
+ (cond ((equal 0 exit-status)
+ (funcall flymake-proc--report-fn diagnostics))
+ (interrupted
+ (flymake-proc--panic :stopped interrupted))
+ ((null diagnostics)
+ ;; non-zero exit but no errors is strange
+ (flymake-proc--panic
+ :configuration-error
+ (format "Command %s errored, but no diagnostics"
+ command)))
+ (diagnostics
+ (funcall flymake-proc--report-fn diagnostics)))))
+ (delete-process proc)
+ (setq flymake-proc--processes
+ (delq proc flymake-proc--processes))
+ (unless (> flymake-log-level 2)
+ (kill-buffer (process-get proc 'flymake-proc--output-buffer)))))))
+
+(defun flymake-proc--panic (problem explanation)
+ "Tell flymake UI about a fatal PROBLEM with this backend.
+May only be called in a dynamic environment where
+`flymake-proc--dynamic-report-fn' is bound"
+ (flymake-log 0 "%s: %s" problem explanation)
+ (if (and (boundp 'flymake-proc--report-fn)
+ flymake-proc--report-fn)
+ (funcall flymake-proc--report-fn :panic
+ :explanation (format "%s: %s" problem explanation))
+ (error "Trouble telling flymake-ui about problem %s(%s)"
+ problem explanation)))
(defun flymake-proc-reformat-err-line-patterns-from-compile-el (original-list)
"Grab error line patterns from ORIGINAL-LIST in compile.el format.
(error
(flymake-log 1 "Failed to delete dir %s, error ignored" dir-name))))
-(defun flymake-proc-start-syntax-check ()
+
+(defun flymake-proc-start-syntax-check (report-fn &optional interactive)
"Start syntax checking for current buffer."
- (interactive)
- (flymake-log 3 "flymake is running: %s" flymake-is-running)
- (when (not (and flymake-is-running
- (flymake-proc-can-syntax-check-file buffer-file-name)))
- (when (or (not flymake-proc-compilation-prevents-syntax-check)
- (not (flymake-proc--compilation-is-running))) ;+ (flymake-rep-ort-status buffer "COMP")
+ ;; Interactively, behave as if flymake had invoked us through its
+ ;; `flymake-diagnostic-functions' with a suitable ID so flymake can
+ ;; clean up consistently
+ (interactive (list (flymake-make-report-fn 'flymake-proc-start-syntax-check)
+ t))
+ (cond
+ ((process-live-p flymake-proc--process)
+ (when interactive
+ (user-error
+ "There's already a flymake process running in this buffer")))
+ ((and buffer-file-name
+ ;; Since we write temp files in current dir, there's no point
+ ;; trying if the directory is read-only (bug#8954).
+ (file-writable-p (file-name-directory buffer-file-name))
+ (or (not flymake-proc-compilation-prevents-syntax-check)
+ (not (flymake-proc--compilation-is-running))))
+ (let ((init-f (flymake-proc--get-init-function buffer-file-name)))
+ (unless init-f (error "Can find a suitable init function"))
(flymake-proc--clear-buildfile-cache)
(flymake-proc--clear-project-include-dirs-cache)
- (setq flymake-check-was-interrupted nil)
- (setq flymake-check-start-time (float-time))
-
- (let* ((source-file-name buffer-file-name)
- (init-f (flymake-proc--get-init-function source-file-name))
- (cleanup-f (flymake-proc--get-cleanup-function source-file-name))
+ (let* ((flymake-proc--report-fn report-fn)
+ (cleanup-f (flymake-proc--get-cleanup-function buffer-file-name))
(cmd-and-args (funcall init-f))
(cmd (nth 0 cmd-and-args))
(args (nth 1 cmd-and-args))
(dir (nth 2 cmd-and-args)))
- (if (not cmd-and-args)
- (progn
- (flymake-log 0 "init function %s for %s failed, cleaning up" init-f source-file-name)
- (funcall cleanup-f))
- (progn
- (setq flymake-last-change-time nil)
- (flymake-proc--start-syntax-check-process cmd args dir)))))))
+ (cond ((not cmd-and-args)
+ (progn
+ (flymake-log 0 "init function %s for %s failed, cleaning up"
+ init-f buffer-file-name)
+ (funcall cleanup-f)))
+ (t
+ (setq flymake-last-change-time nil)
+ (flymake-proc--start-syntax-check-process cmd
+ args
+ dir)
+ t)))))))
(defun flymake-proc--start-syntax-check-process (cmd args dir)
"Start syntax check process."
:noquery t
:filter 'flymake-proc--process-filter
:sentinel 'flymake-proc--process-sentinel))))
- (setf (process-get process 'flymake-proc--output-buffer)
- (generate-new-buffer
- (format " *flymake output for %s*" (current-buffer))))
+ (process-put process 'flymake-proc--output-buffer
+ (generate-new-buffer
+ (format " *flymake output for %s*" (current-buffer))))
+ (process-put process 'flymake-proc--report-fn
+ flymake-proc--report-fn)
+
+ (setq-local flymake-proc--process process)
(push process flymake-proc--processes)
(setq flymake-is-running t)
(setq flymake-last-change-time nil)
- (flymake-report-status nil "*")
(flymake-log 2 "started process %d, command=%s, dir=%s"
(process-id process) (process-command process)
default-directory)
(cleanup-f (flymake-proc--get-cleanup-function source-file-name)))
(flymake-log 0 err-str)
(funcall cleanup-f)
- (flymake-report-fatal-status "PROCERR" err-str)))))
-
-(defun flymake-proc--kill-process (proc)
- "Kill process PROC."
- (kill-process proc)
- (let* ((buf (process-buffer proc)))
- (when (buffer-live-p buf)
- (with-current-buffer buf
- (setq flymake-check-was-interrupted t))))
- (flymake-log 1 "killed process %d" (process-id proc)))
-
-(defun flymake-proc-stop-all-syntax-checks ()
+ (flymake-proc--panic :make-process-error err-str)))))
+
+(defun flymake-proc-stop-all-syntax-checks (&optional reason)
"Kill all syntax check processes."
- (interactive)
- (while flymake-proc--processes
- (flymake-proc--kill-process (pop flymake-proc--processes))))
+ (interactive (list "Interrupted by user"))
+ (mapc (lambda (proc)
+ (kill-process proc)
+ (process-put proc 'flymake-proc--interrupted reason)
+ (flymake-log 2 "killed process %d" (process-id proc)))
+ flymake-proc--processes))
(defun flymake-proc--compilation-is-running ()
(and (boundp 'compilation-in-progress)
(defun flymake-proc-compile ()
"Kill all flymake syntax checks, start compilation."
(interactive)
- (flymake-proc-stop-all-syntax-checks)
+ (flymake-proc-stop-all-syntax-checks "Stopping for proper compilation")
(call-interactively 'compile))
;;;; general init-cleanup and helper routines
"Find buildfile, store its dir in buffer data and return its dir, if found."
(let* ((buildfile-dir
(flymake-proc--find-buildfile buildfile-name
- (file-name-directory source-file-name))))
+ (file-name-directory source-file-name))))
(if buildfile-dir
(setq flymake-proc--base-dir buildfile-dir)
(flymake-log 1 "no buildfile (%s) for %s" buildfile-name source-file-name)
- (flymake-report-fatal-status
+ (flymake-proc--panic
"NOMK" (format "No buildfile (%s) found for %s"
buildfile-name source-file-name)))))
(if (not master-and-temp-master)
(progn
(flymake-log 1 "cannot find master file for %s" source-file-name)
- (flymake-report-status "!" "") ; NOMASTER
+ (flymake-proc--panic "NOMASTER" "") ; NOMASTER
nil)
(setq flymake-proc--master-file-name (nth 0 master-and-temp-master))
(setq flymake-proc--temp-master-file-name (nth 1 master-and-temp-master)))))
(list "val" (flymake-proc-init-create-temp-buffer-copy
'flymake-proc-create-temp-inplace))))
+\f
+;;;; Hook onto flymake-ui
+(add-to-list 'flymake-diagnostic-functions
+ 'flymake-proc-start-syntax-check)
+
\f
;;;;
(define-obsolete-function-alias 'flymake-start-syntax-check
'flymake-proc-start-syntax-check "26.1"
"Start syntax checking for current buffer.")
- (define-obsolete-function-alias 'flymake-kill-process
- 'flymake-proc--kill-process "26.1"
- "Kill process PROC.")
(define-obsolete-function-alias 'flymake-stop-all-syntax-checks
'flymake-proc-stop-all-syntax-checks "26.1"
"Kill all syntax check processes.")
(require 'cl-lib)
(require 'thingatpt) ; end-of-thing
(require 'warnings) ; warning-numeric-level
+(eval-when-compile (require 'subr-x)) ; when-let*, if-let*
(defgroup flymake nil
"Universal on-the-fly syntax checker."
(defvar-local flymake-check-start-time nil
"Time at which syntax check was started.")
-(defvar-local flymake-check-was-interrupted nil
- "Non-nil if syntax check was killed by `flymake-compile'.")
-
-(defvar-local flymake-err-info nil
- "Sorted list of line numbers and lists of err info in the form (file, err-text).")
-
-(defvar-local flymake-new-err-info nil
- "Same as `flymake-err-info', effective when a syntax check is in progress.")
-
(defun flymake-log (level text &rest args)
"Log a message at level LEVEL.
If LEVEL is higher than `flymake-log-level', the message is
(cl-defstruct (flymake--diag
(:constructor flymake--diag-make))
- buffer beg end type text)
+ buffer beg end type text backend)
(defun flymake-make-diagnostic (buffer
beg
#'identity))
ovs)))))
-(defun flymake-delete-own-overlays ()
+(defun flymake-delete-own-overlays (&optional filter)
"Delete all flymake overlays in BUFFER."
- (mapc #'delete-overlay (flymake--overlays)))
+ (mapc #'delete-overlay (flymake--overlays :filter filter)))
(defface flymake-error
'((((supports :underline (:style wave)))
(error (flymake-log 4 "Invalid region for diagnostic %s")
nil)))
+(defvar flymake-diagnostic-functions nil
+ "List of flymake backends i.e. sources of flymake diagnostics.
+
+This variable holds an arbitrary number of \"backends\" or
+\"checkers\" providing the flymake UI's \"frontend\" with
+information about where and how to annotate problems diagnosed in
+a buffer.
+
+Backends are lisp functions sharing a common calling
+convention. Whenever flymake decides it is time to re-check the
+buffer, each backend is called with a single argument, a
+REPORT-FN callback, detailed below. Backend functions are first
+expected to quickly and inexpensively announce the feasibility of
+checking the buffer (i.e. they aren't expected to immediately
+start checking the buffer):
+
+* If the backend function returns nil, flymake forgets about this
+ backend for the current check, but will call it again the next
+ time;
+
+* If the backend function returns non-nil, flymake expects this backend to
+ check the buffer and call its REPORT-FN callback function. If
+ the computation involved is inexpensive, the backend function
+ may do so synchronously before returning. If it is not, it may
+ do so after retuning, using idle timers, asynchronous
+ processes or other asynchronous mechanisms.
+
+* If the backend function signals an error, it is disabled, i.e. flymake
+ will not attempt it again for this buffer until `flymake-mode'
+ is turned off and on again.
+
+When calling REPORT-FN, the first argument passed to it decides
+how to proceed. Recognized values are:
+
+* A (possibly empty) list of objects created with
+ `flymake-make-diagnostic', causing flymake to annotate the
+ buffer with this information and consider the backend has
+ having finished its check normally.
+
+* The symbol `:progress', signalling that the backend is still
+ working and will call REPORT-FN again in the future.
+
+* The symbol `:panic', signalling that the backend has
+ encountered an exceptional situation and should be disabled.
+
+In the latter cases, it is also possible to provide REPORT-FN
+with a string as the keyword argument `:explanation'. The string
+should give human-readable details of the situation.")
+
(defvar flymake-diagnostic-types-alist
`((:error
. ((flymake-category . flymake-error)))
(overlay-put ov 'flymake-overlay t)
(overlay-put ov 'flymake--diagnostic diagnostic)))
-
-(defvar-local flymake-is-running nil
- "If t, flymake syntax check process is running for the current buffer.")
-
(defun flymake-on-timer-event (buffer)
"Start a syntax check for buffer BUFFER if necessary."
(when (buffer-live-p buffer)
(with-current-buffer buffer
(when (and flymake-mode
- (not flymake-is-running)
flymake-last-change-time
(> (- (float-time) flymake-last-change-time)
flymake-no-changes-timeout))
(when choice (goto-char (overlay-start choice)))))
;; flymake minor mode declarations
-(defvar-local flymake-mode-line nil)
-(defvar-local flymake-mode-line-e-w nil)
-(defvar-local flymake-mode-line-status nil)
-
-(defun flymake-report-status (e-w &optional status)
- "Show status in mode line."
- (when e-w
- (setq flymake-mode-line-e-w e-w))
- (when status
- (setq flymake-mode-line-status status))
- (let* ((mode-line " Flymake"))
- (when (> (length flymake-mode-line-e-w) 0)
- (setq mode-line (concat mode-line ":" flymake-mode-line-e-w)))
- (setq mode-line (concat mode-line flymake-mode-line-status))
- (setq flymake-mode-line mode-line)
- (force-mode-line-update)))
+(defvar-local flymake-lighter nil)
+
+(defun flymake--update-lighter (info &optional extended)
+ "Update Flymake’s \"lighter\" with INFO and EXTENDED."
+ (setq flymake-lighter (format " Flymake(%s%s)"
+ info
+ (if extended
+ (format ",%s" extended)
+ ""))))
;; Nothing in flymake uses this at all any more, so this is just for
;; third-party compatibility.
(define-obsolete-function-alias 'flymake-display-warning 'message-box "26.1")
-(defun flymake-report-fatal-status (status warning)
- "Display a warning and switch flymake mode off."
- ;; This first message was always shown by default, and flymake-log
- ;; does nothing by default, hence the use of message.
- ;; Another option is display-warning.
- (if (< flymake-log-level 0)
- (message "Flymake: %s. Flymake will be switched OFF" warning))
- (flymake-mode 0)
- (flymake-log 0 "switched OFF Flymake mode for buffer %s due to fatal status %s, warning %s"
- (buffer-name) status warning))
+(defvar-local flymake--running-backends nil
+ "List of currently active flymake backends.
+An active backend is a member of `flymake-diagnostic-functions'
+that has been invoked but hasn't reported any final status yet.")
-(defun flymake-report (diagnostics)
- (save-restriction
- (widen)
- (flymake-delete-own-overlays)
- (mapc #'flymake--highlight-line diagnostics)
- (let ((err-count (cl-count-if #'flymake--diag-errorp diagnostics))
- (warn-count (cl-count-if-not #'flymake--diag-errorp diagnostics)))
- (when flymake-check-start-time
- (flymake-log 2 "%s: %d error(s), %d other(s) in %.2f second(s)"
- (buffer-name) err-count warn-count
- (- (float-time) flymake-check-start-time)))
- (if (null diagnostics)
- (flymake-report-status "" "")
- (flymake-report-status (format "%d/%d" err-count warn-count) "")))))
+(defvar-local flymake--disabled-backends nil
+ "List of currently disabled flymake backends.
+A backend is disabled if it reported `:panic'.")
+
+(defun flymake-is-running ()
+ "Tell if flymake has running backends in this buffer"
+ flymake--running-backends)
+
+(defun flymake--disable-backend (backend action &optional explanation)
+ (cl-pushnew backend flymake--disabled-backends)
+ (flymake-log 0 "Disabled the backend %s due to reports of %s (%s)"
+ backend action explanation))
+
+(cl-defun flymake--handle-report (backend action &key explanation)
+ "Handle reports from flymake backend identified by BACKEND."
+ (cond
+ ((not (memq backend flymake--running-backends))
+ (error "Ignoring unexpected report from backend %s" backend))
+ ((eq action :progress)
+ (flymake-log 3 "Backend %s reports progress: %s" backend explanation))
+ ((eq :panic action)
+ (flymake--disable-backend backend action explanation))
+ ((listp action)
+ (let ((diagnostics action))
+ (save-restriction
+ (widen)
+ (flymake-delete-own-overlays
+ (lambda (ov)
+ (eq backend
+ (flymake--diag-backend
+ (overlay-get ov 'flymake--diagnostic)))))
+ (mapc (lambda (diag)
+ (flymake--highlight-line diag)
+ (setf (flymake--diag-backend diag) backend))
+ diagnostics)
+ (let ((err-count (cl-count-if #'flymake--diag-errorp diagnostics))
+ (warn-count (cl-count-if-not #'flymake--diag-errorp
+ diagnostics)))
+ (when flymake-check-start-time
+ (flymake-log 2 "%d error(s), %d other(s) in %.2f second(s)"
+ err-count warn-count
+ (- (float-time) flymake-check-start-time)))
+ (if (null diagnostics)
+ (flymake--update-lighter "[ok]")
+ (flymake--update-lighter
+ (format "%d/%d" err-count warn-count)))))))
+ (t
+ (flymake--disable-backend "?"
+ :strange
+ (format "unknown action %s (%s)"
+ action explanation))))
+ (unless (eq action :progress)
+ (flymake--stop-backend backend)))
+
+(defun flymake-make-report-fn (backend)
+ "Make a suitable anonymous report function for BACKEND.
+BACKEND is used to help flymake distinguish diagnostic
+sources."
+ (lambda (&rest args)
+ (apply #'flymake--handle-report backend args)))
+
+(defun flymake--stop-backend (backend)
+ "Stop the backend BACKEND."
+ (setq flymake--running-backends (delq backend flymake--running-backends)))
+
+(defun flymake--run-backend (backend)
+ "Run the backend BACKEND."
+ (push backend flymake--running-backends)
+ ;; FIXME: Should use `condition-case-unless-debug'
+ ;; here, but that won't let me catch errors during
+ ;; testing where `debug-on-error' is always t
+ (condition-case err
+ (unless (funcall backend
+ (flymake-make-report-fn backend))
+ (flymake--stop-backend backend))
+ (error
+ (flymake--disable-backend backend :error
+ err)
+ (flymake--stop-backend backend))))
(defun flymake--start-syntax-check (&optional deferred)
- (cl-labels ((start
- ()
- (remove-hook 'post-command-hook #'start 'local)
- (setq flymake-check-start-time (float-time))
- (flymake-proc-start-syntax-check)))
+ "Start a syntax check.
+Start it immediately, or after current command if DEFERRED is
+non-nil."
+ (cl-labels
+ ((start
+ ()
+ (remove-hook 'post-command-hook #'start 'local)
+ (setq flymake-check-start-time (float-time))
+ (dolist (backend flymake-diagnostic-functions)
+ (cond ((memq backend flymake--running-backends)
+ (flymake-log 2 "Backend %s still running, not restarting"
+ backend))
+ ((memq backend flymake--disabled-backends)
+ (flymake-log 2 "Backend %s is disabled, not starting"
+ backend))
+ (t
+ (flymake--run-backend backend))))))
(if (and deferred
this-command)
(add-hook 'post-command-hook #'start 'append 'local)
;;;###autoload
(define-minor-mode flymake-mode nil
- :group 'flymake :lighter flymake-mode-line
+ :group 'flymake :lighter flymake-lighter
+ (setq flymake--running-backends nil
+ flymake--disabled-backends nil)
(cond
-
;; Turning the mode ON.
(flymake-mode
(cond
- ((not buffer-file-name)
- (message "Flymake unable to run without a buffer file name"))
- ((not (flymake-can-syntax-check-file buffer-file-name))
- (flymake-log 2 "flymake cannot check syntax in buffer %s" (buffer-name)))
+ ((not flymake-diagnostic-functions)
+ (error "flymake cannot check syntax in buffer %s" (buffer-name)))
(t
(add-hook 'after-change-functions 'flymake-after-change-function nil t)
(add-hook 'after-save-hook 'flymake-after-save-hook nil t)
(add-hook 'kill-buffer-hook 'flymake-kill-buffer-hook nil t)
- ;;+(add-hook 'find-file-hook 'flymake-find-file-hook)
- (flymake-report-status "" "")
+ (flymake--update-lighter "*" "*")
(setq flymake-timer
(run-at-time nil 1 'flymake-on-timer-event (current-buffer)))
- (when (and flymake-start-syntax-check-on-find-file
- ;; Since we write temp files in current dir, there's no point
- ;; trying if the directory is read-only (bug#8954).
- (file-writable-p (file-name-directory buffer-file-name)))
- (with-demoted-errors
- (flymake--start-syntax-check))))))
+ (when flymake-start-syntax-check-on-find-file
+ (flymake--start-syntax-check)))))
;; Turning the mode OFF.
(t
(when flymake-timer
(cancel-timer flymake-timer)
- (setq flymake-timer nil))
-
- (setq flymake-is-running nil))))
+ (setq flymake-timer nil)))))
;;;###autoload
(defun flymake-mode-on ()
;;;###autoload
(defun flymake-find-file-hook ()
- (when (and (not (local-variable-p 'flymake-mode (current-buffer)))
- (flymake-can-syntax-check-file buffer-file-name))
+ (unless (or flymake-mode
+ (null flymake-diagnostic-functions))
(flymake-mode)
(flymake-log 3 "automatically turned ON flymake mode")))
(provide 'flymake)
-(declare-function flymake-proc-start-syntax-check "flymake-proc")
-(declare-function flymake-can-syntax-check-file "flymake-proc")
-
(require 'flymake-proc)
;;; flymake.el ends here
-;;; flymake-tests.el --- Test suite for flymake
+;;; flymake-tests.el --- Test suite for flymake -*- lexical-binding: t -*-
;; Copyright (C) 2011-2017 Free Software Foundation, Inc.
(when sev-pred-supplied-p
(setq-local flymake-proc-diagnostic-type-pred severity-predicate))
(goto-char (point-min))
- (flymake-mode 1)
+ (unless flymake-mode (flymake-mode 1))
;; Weirdness here... http://debbugs.gnu.org/17647#25
;; ... meaning `sleep-for', and even
;; `accept-process-output', won't suffice as ways to get
;; reading an input event, so, as a workaround, use a dummy
;; `read-event' with a very short timeout.
(unless noninteractive (read-event "" nil 0.1))
- (while (and flymake-is-running (< (setq i (1+ i)) 10))
+ (while (and (flymake-is-running) (< (setq i (1+ i)) 10))
(unless noninteractive (read-event "" nil 0.1))
(sleep-for (+ 0.5 flymake-no-changes-timeout)))
(funcall fn)))
(should (eq 'flymake-error (face-at-point)))
(should-error (flymake-goto-next-error nil t)) ))
+(defmacro flymake-tests--assert-set (set
+ should
+ should-not)
+ (declare (indent 1))
+ `(progn
+ ,@(cl-loop
+ for s in should
+ collect `(should (memq ,s ,set)))
+ ,@(cl-loop
+ for s in should-not
+ collect `(should-not (memq ,s ,set)))))
+
+(ert-deftest dummy-backends ()
+ "Test GCC warning via function predicate."
+ (with-temp-buffer
+ (cl-labels
+ ((diagnose
+ (report-fn type words)
+ (funcall
+ report-fn
+ (cl-loop
+ for word in words
+ append
+ (save-excursion
+ (goto-char (point-min))
+ (cl-loop while (word-search-forward word nil t)
+ collect (flymake-make-diagnostic
+ (current-buffer)
+ (match-beginning 0)
+ (match-end 0)
+ type
+ (concat word " is wrong")))))))
+ (error-backend
+ (report-fn)
+ (run-with-timer
+ 0.5 nil
+ #'diagnose report-fn :error '("manha" "prognata")))
+ (warning-backend
+ (report-fn)
+ (run-with-timer
+ 0.5 nil
+ #'diagnose report-fn :warning '("ut" "dolor")))
+ (sync-backend
+ (report-fn)
+ (diagnose report-fn :note '("quis" "commodo")))
+ (refusing-backend
+ (_report-fn)
+ nil)
+ (panicking-backend
+ (report-fn)
+ (run-with-timer
+ 0.5 nil
+ report-fn :panic :explanation "The spanish inquisition!"))
+ (crashing-backend
+ (_report-fn)
+ ;; HACK: Shoosh log during tests
+ (setq-local warning-minimum-log-level :emergency)
+ (error "crashed")))
+ (insert "Lorem ipsum dolor sit amet, consectetur adipiscing
+ elit, sed do eiusmod tempor incididunt ut labore et dolore
+ manha aliqua. Ut enim ad minim veniam, quis nostrud
+ exercitation ullamco laboris nisi ut aliquip ex ea commodo
+ consequat. Duis aute irure dolor in reprehenderit in
+ voluptate velit esse cillum dolore eu fugiat nulla
+ pariatur. Excepteur sint occaecat cupidatat non prognata
+ sunt in culpa qui officia deserunt mollit anim id est
+ laborum.")
+ (let ((flymake-diagnostic-functions
+ (list #'error-backend #'warning-backend #'sync-backend
+ #'refusing-backend #'panicking-backend
+ #'crashing-backend
+ )))
+ (flymake-mode)
+ ;; FIXME: accessing some flymake-ui's internals here...
+ (flymake-tests--assert-set flymake--running-backends
+ (#'error-backend #'warning-backend #'panicking-backend)
+ (#'sync-backend #'crashing-backend #'refusing-backend))
+
+ (flymake-tests--assert-set flymake--disabled-backends
+ (#'crashing-backend)
+ (#'error-backend #'warning-backend #'sync-backend
+ #'panicking-backend #'refusing-backend))
+
+ (cl-loop repeat 10 while (flymake-is-running)
+ unless noninteractive do (read-event "" nil 0.1)
+ do (sleep-for (+ 0.5 flymake-no-changes-timeout)))
+
+ (should (eq flymake--running-backends '()))
+
+ (flymake-tests--assert-set flymake--disabled-backends
+ (#'crashing-backend #'panicking-backend)
+ (#'error-backend #'warning-backend #'sync-backend
+ #'refusing-backend))
+
+ (goto-char (point-min))
+ (flymake-goto-next-error)
+ (should (eq 'flymake-warning (face-at-point))) ; dolor
+ (flymake-goto-next-error)
+ (should (eq 'flymake-warning (face-at-point))) ; ut
+ (flymake-goto-next-error)
+ (should (eq 'flymake-error (face-at-point))) ; manha
+ (flymake-goto-next-error)
+ (should (eq 'flymake-warning (face-at-point))) ; Ut
+ (flymake-goto-next-error)
+ (should (eq 'flymake-note (face-at-point))) ; quis
+ (flymake-goto-next-error)
+ (should (eq 'flymake-warning (face-at-point))) ; ut
+ (flymake-goto-next-error)
+ (should (eq 'flymake-note (face-at-point))) ; commodo
+ (flymake-goto-next-error)
+ (should (eq 'flymake-warning (face-at-point))) ; dolor
+ (flymake-goto-next-error)
+ (should (eq 'flymake-error (face-at-point))) ; prognata
+ (should-error (flymake-goto-next-error nil t))))))
+
(provide 'flymake-tests)
;;; flymake.el ends here