"Current size of the buffer, as far as this library knows.
This is used to try and detect cases where buffer modifications are \"lost\".")
+(defvar track-changes--trace nil
+ "Ring holding a trace of recent calls to the API.
+Each call is recorded as a (BUFFER-NAME . BACKTRACE).")
+
;;;; Exposed API.
(defvar track-changes-record-errors
;; annoy the user too much about errors.
(string-match "\\..*\\." emacs-version)
"If non-nil, keep track of errors in `before/after-change-functions' calls.
-The errors are kept in `track-changes--error-log'.")
+The errors are kept in `track-changes--error-log'.
+If set to `trace', then we additionally keep a trace of recent calls to the API.")
(cl-defun track-changes-register ( signal &key nobefore disjoint immediate)
"Register a new tracker whose change-tracking function is SIGNAL.
that may block, do as little work as possible, ...
When IMMEDIATE is non-nil, the SIGNAL should probably not always call
`track-changes-fetch', since that would defeat the purpose of this library."
+ (track-changes--trace)
(when (and nobefore disjoint)
;; FIXME: Without `before-change-functions', we can discover
;; a disjoint change only after the fact, which is not good enough.
Trackers can consume resources (especially if `track-changes-fetch' is
not called), so it is good practice to unregister them when you don't
need them any more."
+ (track-changes--trace)
(unless (memq id track-changes--trackers)
(error "Unregistering a non-registered tracker: %S" id))
(setq track-changes--trackers (delq id track-changes--trackers))
If no changes occurred since the last time, it doesn't call FUNC and
returns nil, otherwise it returns the value returned by FUNC
and re-enable the TRACKER corresponding to ID."
+ (track-changes--trace)
(cl-assert (memq id track-changes--trackers))
(unless (equal track-changes--buffer-size (buffer-size))
(track-changes--recover-from-error
;;;; Auxiliary functions.
+(defun track-change--backtrace (n &optional base)
+ (let ((frames nil))
+ (catch 'done
+ (mapbacktrace (lambda (&rest frame)
+ (if (>= (setq n (- n 1)) 0)
+ (push frame frames)
+ (push '... frames)
+ (throw 'done nil)))
+ (or base #'track-change--backtrace)))
+ (nreverse frames)))
+
+(defun track-changes--trace ()
+ (when (eq 'trace track-changes-record-errors)
+ (require 'ring)
+ (declare-function ring-insert "ring" (ring item))
+ (declare-function make-ring "ring" (size))
+ (unless track-changes--trace
+ (setq track-changes--trace (make-ring 10)))
+ (ring-insert track-changes--trace
+ (cons (buffer-name)
+ (track-change--backtrace
+ 10 #'track-changes--trace)))))
+
(defun track-changes--clean-state ()
(cond
((null track-changes--state)
(defvar track-changes--error-log ()
"List of errors encountered.
-Each element is a triplet (BUFFER-NAME BACKTRACE RECENT-KEYS).")
+Each element is a tuple [BUFFER-NAME BACKTRACE RECENT-KEYS TRACE].
+where both RECENT-KEYS and TRACE are sorted oldest-first and
+backtraces have the deepest frame first.")
(defun track-changes--recover-from-error (&optional info)
;; We somehow got out of sync. This is usually the result of a bug
(message "Recovering from confusing calls to `before/after-change-functions'!")
(warn "Missing/incorrect calls to `before/after-change-functions'!!
Details logged to `track-changes--error-log'")
- (push (list (buffer-name) info
- (let* ((bf (backtrace-frames
- #'track-changes--recover-from-error))
- (tail (nthcdr 50 bf)))
- (when tail (setcdr tail '...))
- bf)
- (let ((rk (recent-keys 'include-cmds)))
- (if (< (length rk) 20) rk (substring rk -20))))
+ (push (vector (buffer-name) info
+ (track-change--backtrace
+ 50 #'track-changes--recover-from-error)
+ (let ((rk (recent-keys 'include-cmds)))
+ (if (< (length rk) 20) rk (substring rk -20)))
+ (when (and (eq 'trace track-changes-record-errors)
+ (fboundp 'ring-elements))
+ (apply #'vector
+ (nreverse (ring-elements track-changes--trace)))))
track-changes--error-log))
(setq track-changes--before-clean 'unset)
(setq track-changes--buffer-size (buffer-size))
(setq track-changes--state (track-changes--state)))
(defun track-changes--before (beg end)
+ (track-changes--trace)
(cl-assert track-changes--state)
(cl-assert (<= beg end))
(let* ((size (- end beg))
(buffer-substring-no-properties old-bend new-bend)))))))))
(defun track-changes--after (beg end len)
+ (track-changes--trace)
(cl-assert track-changes--state)
(and (eq track-changes--before-clean 'unset)
(not track-changes--before-no)