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
@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
#'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)))
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
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)
(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)
(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))
(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
;; 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
'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)
(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)))
(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)
(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))