]> git.eshelyaron.com Git - emacs.git/commitdiff
Flymake and backends exchange hints abouts changed regions
authorJoão Távora <joaotavora@gmail.com>
Fri, 15 Jun 2018 11:17:37 +0000 (12:17 +0100)
committerJoão Távora <joaotavora@gmail.com>
Fri, 15 Jun 2018 13:59:42 +0000 (14:59 +0100)
* lisp/progmodes/flymake.el (flymake--delete-own-overlays): Accept
BEG and END.  Rename from flymake-delete-own-overlays.
(flymake-diagnostic-functions): Describe :region, :recent-changes
in docstring.
(flymake--handle-report): Accept REGION.
(flymake--run-backend): Accept optional ARGS to pass to backend
fn.
(flymake--recent-changes): New buffer-local variable.
(flymake-start): Call flymake--run-backend with recent changes.
(flymake-mode): Initialize flymake--recent-changes.  Call
flymake--delete-own-overlays.
(flymake-after-change-function): Collect recent changes.

* doc/misc/flymake.texi (Backend functions): Describe
:recent-changes and :region.

* etc/NEWS (Flymake): Mention improvements in backend communication.

doc/misc/flymake.texi
etc/NEWS
lisp/progmodes/flymake.el

index 502d408f2b8da1ea6dcd4bfd02517211851c37e2..1e7a5e82c618ad0fad73da72ff97944c2aac6114 100644 (file)
@@ -437,18 +437,35 @@ calling convention: one for calls made by Flymake into the backend via
 the backend function, the other in the reverse direction via a
 callback.  To be usable, backends must adhere to both.
 
-Backend functions must accept an arbitrary number of arguments:
+The first argument passed to a backend function is always
+@var{report-fn}, a callback function detailed below.  Beyond it,
+functions must be prepared to accept (and possibly ignore) an
+arbitrary number of keyword-value pairs of the form
+@w{@code{(@var{:key} @var{value} @var{:key2} @var{value2}...)}}.
+
+Currently, Flymake may pass the following keywords and values to the
+backend function:
 
 @itemize
-@item
-the first argument is always @var{report-fn}, a callback function
-detailed below;
 
-@item
-the remaining arguments are keyword-value pairs of the form
-@w{@code{(@var{:key} @var{value} @var{:key2} @var{value2}...)}}.
-Currently, Flymake provides no such arguments, but backend functions
-must be prepared to accept (and possibly ignore) any number of them.
+@item @code{:recent-changes}
+The value is a list recent changes since the last time the backend
+function was called for the buffer.  If the list is empty, this
+indicates that no changes have been recorded.  If it is the first time
+that this backend function is called for this activation of
+@code{flymake-mode}, then this argument isn't provided at all
+(i.e. it's not merely nil).
+
+Each element is in the form (@var{beg} @var{end} @var{text}) where
+@var{beg} and @var{end} are buffer positions, and @var{text} is a
+string containing the text contained between those positions (if any),
+after the change was performed.
+
+@item @code{:changes-start} and @code{:changes-end}
+The value is, repectively, the minimum and maximum buffer positions
+touched by the recent changes.  These are provided for convenience and
+only if @code{:recent-changes} is also provided.
+
 @end itemize
 
 Whenever Flymake or the user decide to re-check the buffer, backend
@@ -504,6 +521,11 @@ details of the situation encountered, if any.
 @code{:force}, whose value should be a boolean suggesting
 that Flymake consider the report even if it was somehow
 unexpected.
+
+@item
+@code{:region}, a cons (@var{beg} . @var{end}) of buffer positions
+indicating that the report applies to that region and that previous
+reports targeting other parts of the buffer remain valid.
 @end itemize
 
 @menu
index 50433eb7f286e17dc9abdb65de3e26522c78dd46..e89402db1315998138452cee03d8c289edf00216 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -193,6 +193,10 @@ You should instead set properties on known diagnostic symbols, like
 *** New customizable variable 'flymake-start-on-save-buffer'
 Control whether Flymake starts checking the buffer on save.
 
+*** Flymake and backend functions may exchange hints about buffer changes
+This enables more efficient backends.  See the docstring of
+'flymake-diagnostic-functions' or the Flymake manual for details.
+
 ** Package
 *** New 'package-quickstart' feature
 When 'package-quickstart' is non-nil, package.el precomputes a big autoloads
index fdb22ccaf3461a0d44c8a92ed503a677ae5c58da..e3c07fc898c67a3f8362264de63d0f5b977a2988 100644 (file)
@@ -354,9 +354,9 @@ verify FILTER, a function, and sort them by COMPARE (using KEY)."
                                         #'identity))
         ovs))))
 
-(defun flymake-delete-own-overlays (&optional filter)
-  "Delete all Flymake overlays in BUFFER."
-  (mapc #'delete-overlay (flymake--overlays :filter filter)))
+(defun flymake--delete-own-overlays (&optional filter beg end)
+  "Delete Flymake overlays matching FILTER between BEG and END."
+  (mapc #'delete-overlay (flymake--overlays :filter filter :beg beg :end end)))
 
 (defface flymake-error
   '((((supports :underline (:style wave)))
@@ -444,9 +444,25 @@ number of arguments:
   detailed below;
 
 * the remaining arguments are keyword-value pairs in the
-  form (:KEY VALUE :KEY2 VALUE2...).  Currently, Flymake provides
-  no such arguments, but backend functions must be prepared to
-  accept and possibly ignore any number of them.
+  form (:KEY VALUE :KEY2 VALUE2...).
+
+Currently, Flymake may provide these keyword-value pairs:
+
+* `:recent-changes', a list of recent changes since the last time
+  the backend function was called for the buffer.  An empty list
+  indicates that no changes have been reocrded.  If it is the
+  first time that this backend function is called for this
+  activation of `flymake-mode', then this argument isn't provided
+  at all (i.e. it's not merely nil).
+
+  Each element is in the form (BEG END TEXT) where BEG and END
+  are buffer positions, and text is a string containing the text
+  contained between those positions (if any) after the change was
+  performed.
+
+* `:changes-start' and `:changes-end' the minimum and maximum
+  buffer positions touched by the recent changes.  These are only
+  provided if `:recent-changes' is also provided.
 
 Whenever Flymake or the user decides to re-check the buffer,
 backend functions are called as detailed above and are expected
@@ -491,7 +507,11 @@ Currently accepted REPORT-KEY arguments are:
   the situation encountered, if any.
 
 * `:force': value should be a boolean suggesting that Flymake
-  consider the report even if it was somehow unexpected.")
+  consider the report even if it was somehow unexpected.
+
+* `:region': a cons (BEG . END) of buffer positions indicating
+  that the report applies to that region and that previous
+  reports targeting other buffer regions are still valid.")
 
 (put 'flymake-diagnostic-functions 'safe-local-variable #'null)
 
@@ -657,13 +677,15 @@ backend is operating normally.")
   (flymake-running-backends))
 
 (cl-defun flymake--handle-report (backend token report-action
-                                          &key explanation force
+                                          &key explanation force region
                                           &allow-other-keys)
   "Handle reports from BACKEND identified by TOKEN.
-BACKEND, REPORT-ACTION and EXPLANATION, and FORCE conform to the calling
-convention described in `flymake-diagnostic-functions' (which
-see). Optional FORCE says to handle a report even if TOKEN was
-not expected."
+BACKEND, REPORT-ACTION and EXPLANATION, and FORCE conform to the
+calling convention described in
+`flymake-diagnostic-functions' (which see).  Optional FORCE says
+to handle a report even if TOKEN was not expected.  REGION is
+a (BEG . END) pair of buffer positions indicating that this
+report applies to that region."
   (let* ((state (gethash backend flymake--backend-state))
          (first-report (not (flymake--backend-state-reported-p state))))
     (setf (flymake--backend-state-reported-p state) t)
@@ -695,13 +717,18 @@ not expected."
         (setq new-diags report-action)
         (save-restriction
           (widen)
-          ;; only delete overlays if this is the first report
-          (when first-report
-            (flymake-delete-own-overlays
-             (lambda (ov)
-               (eq backend
-                   (flymake--diag-backend
-                    (overlay-get ov 'flymake-diagnostic))))))
+          ;; Decide whether to delete some of this backend's overlays
+          (let ((ov-filter
+                 (lambda (ov)
+                   (eq backend
+                       (flymake--diag-backend
+                        (overlay-get ov 'flymake-diagnostic))))))
+            (cond
+             (region       (flymake--delete-own-overlays ov-filter
+                                                         (car region)
+                                                         (cdr region)))
+             (first-report (flymake--delete-own-overlays ov-filter))))
+          ;; Now make new ones
           (mapc (lambda (diag)
                   (flymake--highlight-line diag)
                   (setf (flymake--diag-backend diag) backend))
@@ -776,8 +803,10 @@ If it is running also stop it."
           (flymake--backend-state-disabled state) explanation
           (flymake--backend-state-reported-p state) t)))
 
-(defun flymake--run-backend (backend)
-  "Run the backend BACKEND, reenabling if necessary."
+(defun flymake--run-backend (backend &optional args)
+  "Run the backend BACKEND, re-enabling if necessary.
+ARGS is a keyword-value plist passed to the backend along
+with a report function."
   (flymake-log :debug "Running backend %s" backend)
   (let ((run-token (cl-gensym "backend-token")))
     (flymake--with-backend-state backend state
@@ -794,16 +823,19 @@ If it is running also stop it."
     ;; backend) will trigger an annoying backtrace.
     ;;
     (condition-case err
-        (funcall backend
-                 (flymake-make-report-fn backend run-token))
+        (apply backend (flymake-make-report-fn backend run-token)
+               args)
       (error
        (flymake--disable-backend backend err)))))
 
+(defvar-local flymake--recent-changes nil
+  "Recent changes collected by `flymake-after-change-function'.")
+
 (defun flymake-start (&optional deferred force)
   "Start a syntax check for the current buffer.
 DEFERRED is a list of symbols designating conditions to wait for
 before actually starting the check.  If it is nil (the list is
-empty), start it immediately, else defer the check to when those
+                                                       empty), start it immediately, else defer the check to when those
 conditions are met.  Currently recognized conditions are
 `post-command', for waiting until the current command is over,
 `on-display', for waiting until the buffer is actually displayed
@@ -844,18 +876,30 @@ Interactively, with a prefix arg, FORCE is t."
                        'append 'local))
             (t
              (setq flymake-check-start-time (float-time))
-             (run-hook-wrapped
-              'flymake-diagnostic-functions
-              (lambda (backend)
-                (cond
-                 ((and (not force)
-                       (flymake--with-backend-state backend state
-                         (flymake--backend-state-disabled state)))
-                  (flymake-log :debug "Backend %s is disabled, not starting"
-                               backend))
-                 (t
-                  (flymake--run-backend backend)))
-                nil)))))))
+             (let ((backend-args
+                    (and
+                     flymake--recent-changes
+                     (list :recent-changes
+                           flymake--recent-changes
+                           :changes-start
+                           (cl-reduce
+                            #'min (mapcar #'car flymake--recent-changes))
+                           :changes-end
+                           (cl-reduce
+                            #'max (mapcar #'cadr flymake--recent-changes))))))
+               (setq flymake--recent-changes nil)
+               (run-hook-wrapped
+                'flymake-diagnostic-functions
+                (lambda (backend)
+                  (cond
+                   ((and (not force)
+                         (flymake--with-backend-state backend state
+                           (flymake--backend-state-disabled state)))
+                    (flymake-log :debug "Backend %s is disabled, not starting"
+                                 backend))
+                   (t
+                    (flymake--run-backend backend backend-args)))
+                  nil))))))))
 
 (defvar flymake-mode-map
   (let ((map (make-sparse-keymap))) map)
@@ -908,6 +952,7 @@ special *Flymake log* buffer."  :group 'flymake :lighter
     (add-hook 'kill-buffer-hook 'flymake-kill-buffer-hook nil t)
 
     (setq flymake--backend-state (make-hash-table))
+    (setq flymake--recent-changes nil)
 
     (when flymake-start-on-flymake-mode (flymake-start t)))
 
@@ -918,7 +963,7 @@ special *Flymake log* buffer."  :group 'flymake :lighter
     (remove-hook 'kill-buffer-hook 'flymake-kill-buffer-hook t)
     ;;+(remove-hook 'find-file-hook (function flymake-find-file-hook) t)
 
-    (flymake-delete-own-overlays)
+    (flymake--delete-own-overlays)
 
     (when flymake-timer
       (cancel-timer flymake-timer)
@@ -960,8 +1005,10 @@ Do it only if `flymake-no-changes-timeout' is non-nil."
 (make-obsolete 'flymake-mode-off 'flymake-mode "26.1")
 
 (defun flymake-after-change-function (start stop _len)
-  "Start syntax check for current buffer if it isn't already running."
+  "Start syntax check for current buffer if it isn't already running.
+START and STOP and LEN are as in `after-change-functions'."
   (let((new-text (buffer-substring start stop)))
+    (push (list start stop new-text) flymake--recent-changes)
     (when (and flymake-start-syntax-check-on-newline (equal new-text "\n"))
       (flymake-log :debug "starting syntax check as new-line has been seen")
       (flymake-start t))