diff --git a/lisp/progmodes/flymake-proc.el b/lisp/progmodes/flymake-proc.el index 05f2cab1af6..55f00955341 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,11 +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-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)))) @@ -450,7 +451,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 @@ -481,52 +484,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 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))) + (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))))))) - (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))))) +(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. @@ -679,34 +686,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 (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." @@ -721,15 +741,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) @@ -743,22 +766,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))))) + (flymake-proc--panic :make-process-error 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 () +(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) @@ -767,7 +784,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 @@ -897,11 +914,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))))) @@ -917,7 +934,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))))) @@ -1053,6 +1070,11 @@ Use CREATE-TEMP-F for creating temp copy." (list "val" (flymake-proc-init-create-temp-buffer-copy 'flymake-proc-create-temp-inplace)))) + +;;;; Hook onto flymake-ui +(add-to-list 'flymake-diagnostic-functions + 'flymake-proc-start-syntax-check) + ;;;; @@ -1238,9 +1260,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.el b/lisp/progmodes/flymake.el index ea9e7c92ead..a3cec8d3dd9 100644 --- a/lisp/progmodes/flymake.el +++ b/lisp/progmodes/flymake.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." @@ -119,15 +120,6 @@ See `flymake-error-bitmap' and `flymake-warning-bitmap'." (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 @@ -140,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 @@ -186,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))) @@ -252,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 . ((flymake-category . flymake-error))) @@ -376,15 +417,11 @@ If TYPE doesn't declare PROP in either (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 (not flymake-is-running) + (when (and (not (flymake-is-running)) flymake-last-change-time (> (- (float-time) flymake-last-change-time) flymake-no-changes-timeout)) @@ -426,59 +463,123 @@ If TYPE doesn't declare PROP in either (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) +(defvar-local flymake-lighter 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))) +(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) @@ -486,33 +587,27 @@ If TYPE doesn't declare PROP in either ;;;###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 @@ -525,9 +620,7 @@ If TYPE doesn't declare PROP in either (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 () @@ -563,8 +656,8 @@ If TYPE doesn't declare PROP in either ;;;###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"))) @@ -599,8 +692,5 @@ If TYPE doesn't declare PROP in either (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 diff --git a/test/lisp/progmodes/flymake-tests.el b/test/lisp/progmodes/flymake-tests.el index 5ecc87fc7e6..c2deb1dc5c7 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