emacs-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Emacs-diffs] scratch/flymake-refactor 26f1e0c 27/52: Replace flymake-ba


From: João Távora
Subject: [Emacs-diffs] scratch/flymake-refactor 26f1e0c 27/52: Replace flymake-backends with flymake-diagnostic-functions
Date: Sun, 1 Oct 2017 12:40:47 -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



reply via email to

[Prev in Thread] Current Thread [Next in Thread]