[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Emacs-diffs] scratch/flymake-refactor 26f1e0c 02/12: Replace flymake-ba
From: |
João Távora |
Subject: |
[Emacs-diffs] scratch/flymake-refactor 26f1e0c 02/12: Replace flymake-backends with flymake-diagnostic-functions |
Date: |
Wed, 27 Sep 2017 13:47:28 -0400 (EDT) |
branch: scratch/flymake-refactor
commit 26f1e0cf0201dc2b6b724c4bf540180bc46c9629
Author: João Távora <address@hidden>
Commit: João Távora <address@hidden>
Replace flymake-backends with flymake-diagnostic-functions
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-ui.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-ui.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-ui.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--cancelled-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--cancel-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--cancelled-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-ui.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.
---
lisp/progmodes/flymake-proc.el | 210 +++++++++++++------------
lisp/progmodes/flymake-ui.el | 286 +++++++++++++++++++++--------------
test/lisp/progmodes/flymake-tests.el | 121 ++++++++++++++-
3 files changed, 405 insertions(+), 212 deletions(-)
diff --git a/lisp/progmodes/flymake-proc.el b/lisp/progmodes/flymake-proc.el
index 96700ef..8992b1d 100644
--- a/lisp/progmodes/flymake-proc.el
+++ b/lisp/progmodes/flymake-proc.el
@@ -102,9 +102,15 @@ NAME is the file name function to use, default
`flymake-proc-get-real-file-name'
(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)
@@ -118,13 +124,6 @@ NAME is the file name function to use, default
`flymake-proc-get-real-file-name'
(flymake-log 3 "file %s, init=%s" file-name (car mode-and-masks))
mode-and-masks))
-(defun flymake-can-syntax-check-buffer ()
- "Determine whether we can syntax check current buffer.
-Return nil if we cannot, non-nil if
-we can."
- (and buffer-file-name
- (if (flymake-proc--get-init-function buffer-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))))
@@ -451,7 +450,9 @@ Create parent directories as needed."
"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
@@ -482,52 +483,56 @@ Create parent directories as needed."
(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-unless-debug 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.
@@ -680,32 +685,47 @@ expression. A match indicates `:warning' type, otherwise
(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 flymake-is-running)
- (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)
-
- (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."
@@ -720,15 +740,18 @@ expression. A match indicates `:warning' type, otherwise
: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)
@@ -742,22 +765,16 @@ expression. A match indicates `:warning' type, otherwise
(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)
@@ -766,7 +783,7 @@ expression. A match indicates `:warning' type, otherwise
(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
@@ -896,11 +913,11 @@ Return full-name. Names are real, not patched."
"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)))))
@@ -916,7 +933,7 @@ Return full-name. Names are real, not patched."
(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)))))
@@ -1054,10 +1071,8 @@ Use CREATE-TEMP-F for creating temp copy."
;;;; Hook onto flymake-ui
-(add-to-list 'flymake-backends
- `(flymake-can-syntax-check-buffer
- .
- flymake-proc-start-syntax-check))
+(add-to-list 'flymake-diagnostic-functions
+ 'flymake-proc-start-syntax-check)
;;;;
@@ -1244,9 +1259,6 @@ Return its components if so, nil otherwise.")
(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.")
diff --git a/lisp/progmodes/flymake-ui.el b/lisp/progmodes/flymake-ui.el
index 6678759..608a2b4 100644
--- a/lisp/progmodes/flymake-ui.el
+++ b/lisp/progmodes/flymake-ui.el
@@ -35,6 +35,7 @@
(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."
@@ -110,17 +111,6 @@ See `flymake-error-bitmap' and `flymake-warning-bitmap'."
:group 'flymake
:type 'integer)
-(defcustom flymake-backends '()
- "Ordered list of backends providing syntax check information for a buffer.
-Value is an alist of conses (PREDICATE . CHECKER). Both PREDICATE
-and CHECKER are functions called without arguments and within the
-the buffer in which `flymake-mode' was enabled. PREDICATE is
-expected to (quickly) return t or nil if the buffer can be
-syntax-checked by CHECKER, in which case it can then perform more
-morose operations, possibly asynchronously. After it's done,
-CHECKER must invoke `flymake-report' to display the results of
-the syntax check." :group 'flymake :type 'alist)
-
(defvar-local flymake-timer nil
"Timer for starting syntax check.")
@@ -130,9 +120,6 @@ the syntax check." :group 'flymake :type 'alist)
(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'.")
-
(defun flymake-log (level text &rest args)
"Log a message at level LEVEL.
If LEVEL is higher than `flymake-log-level', the message is
@@ -145,7 +132,7 @@ are the string substitutions (see the function `format')."
(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
@@ -191,9 +178,9 @@ verify FILTER, sort them by COMPARE (using KEY)."
#'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)))
@@ -257,6 +244,55 @@ Or nil if the region is invalid."
(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
. ((category . flymake-error)))
@@ -353,16 +389,11 @@ with flymake-specific meaning can also be used.
(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))
@@ -404,68 +435,116 @@ with flymake-specific meaning can also be used.
(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--backend nil
- "The currently active backend selected by `flymake-mode'")
-
-(defun flymake--can-syntax-check-buffer (buffer)
- (catch 'done
- (dolist (candidate flymake-backends)
- (when (with-current-buffer buffer (funcall (car candidate)))
- (throw 'done (cdr candidate))))))
+(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)
+ (setq flymake--running-backends (delq backend flymake--running-backends))))
+
+(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--start-syntax-check (&optional deferred)
- (cl-labels ((start
- ()
- (remove-hook 'post-command-hook #'start 'local)
- (setq flymake-check-start-time (float-time))
- (funcall flymake--backend)))
+ (cl-labels
+ ((remove
+ (backend)
+ (setq flymake--running-backends
+ (delq backend flymake--running-backends)))
+ (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 1 "Backend %s still running, not restarting"
+ backend))
+ ((memq backend flymake--disabled-backends)
+ (flymake-log 1 "Backend %s is disabled, not starting"
+ backend))
+ (t
+ (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))
+ (remove backend))
+ (error
+ (flymake--disable-backend backend :error
+ err)
+ (remove backend))))))))
(if (and deferred
this-command)
(add-hook 'post-command-hook #'start 'append 'local)
@@ -473,41 +552,30 @@ with flymake-specific meaning can also be used.
;;;###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
- (let* ((backend (flymake--can-syntax-check-buffer (current-buffer))))
- (cond
- ((not backend)
- (flymake-log 2 "flymake cannot check syntax in buffer %s"
(buffer-name)))
- (t
- (setq flymake--backend backend)
-
- (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 "" "")
-
- (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)))))
- )
- )
+ (cond
+ ((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)
+
+ (flymake--update-lighter "*" "*")
+
+ (setq flymake-timer
+ (run-at-time nil 1 'flymake-on-timer-event (current-buffer)))
+
+ (when flymake-start-syntax-check-on-find-file
+ (flymake--start-syntax-check)))))
;; Turning the mode OFF.
(t
- (setq flymake--backend nil)
-
(remove-hook 'after-change-functions 'flymake-after-change-function t)
(remove-hook 'after-save-hook 'flymake-after-save-hook t)
(remove-hook 'kill-buffer-hook 'flymake-kill-buffer-hook t)
@@ -517,9 +585,7 @@ with flymake-specific meaning can also be used.
(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 ()
@@ -555,8 +621,8 @@ with flymake-specific meaning can also be used.
;;;###autoload
(defun flymake-find-file-hook ()
- (when (and (not (local-variable-p 'flymake-mode (current-buffer)))
- (flymake--can-syntax-check-buffer (current-buffer)))
+ (unless (or flymake-mode
+ (null flymake-diagnostic-functions))
(flymake-mode)
(flymake-log 3 "automatically turned ON flymake mode")))
diff --git a/test/lisp/progmodes/flymake-tests.el
b/test/lisp/progmodes/flymake-tests.el
index 76ec31b..521d045 100644
--- a/test/lisp/progmodes/flymake-tests.el
+++ b/test/lisp/progmodes/flymake-tests.el
@@ -1,4 +1,4 @@
-;;; 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.
@@ -53,7 +53,7 @@ SEVERITY-PREDICATE is used to setup
(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
@@ -63,7 +63,7 @@ SEVERITY-PREDICATE is used to setup
;; 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)))
@@ -130,6 +130,121 @@ SEVERITY-PREDICATE is used to setup
(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
- [Emacs-diffs] scratch/flymake-refactor d68448f 01/12: More cleanup before advancing to backend redesign, (continued)
- [Emacs-diffs] scratch/flymake-refactor d68448f 01/12: More cleanup before advancing to backend redesign, João Távora, 2017/09/27
- [Emacs-diffs] scratch/flymake-refactor 245114e 08/12: Fix autoload conflict between flymake.el and flymake-ui.el, João Távora, 2017/09/27
- [Emacs-diffs] scratch/flymake-refactor 879dcef 04/12: Misc cleanup in flymake-proc.el, João Távora, 2017/09/27
- [Emacs-diffs] scratch/flymake-refactor e1b913f 11/12: Re-implement wraparound for flymake-goto-next-error, João Távora, 2017/09/27
- [Emacs-diffs] scratch/flymake-refactor 8e42a5d 06/12: Cleanup some flymake-ui.el internals, João Távora, 2017/09/27
- [Emacs-diffs] scratch/flymake-refactor 3dfe11c 03/12: Simplify flymake logging and erroring., João Távora, 2017/09/27
- [Emacs-diffs] scratch/flymake-refactor 820b460 10/12: Add interactive flymake-start function, João Távora, 2017/09/27
- [Emacs-diffs] scratch/flymake-refactor 4fea8a9 05/12: Allow filtering in flymake-goto-[next/prev]-error, João Távora, 2017/09/27
- [Emacs-diffs] scratch/flymake-refactor 4e2cbaa 07/12: Fancy mode-line construct for flymake-mode, João Távora, 2017/09/27
- [Emacs-diffs] scratch/flymake-refactor 3b6c736 12/12: Start rewriting flymake manual, João Távora, 2017/09/27
- [Emacs-diffs] scratch/flymake-refactor 26f1e0c 02/12: Replace flymake-backends with flymake-diagnostic-functions,
João Távora <=
- [Emacs-diffs] scratch/flymake-refactor 7a22358 09/12: A couple of new flymake backends for emacs-lisp-mode, João Távora, 2017/09/27