-;;; bug-hunter.el --- Hunt down errors in elisp files -*- lexical-binding: t; -*-
+;;; bug-hunter.el --- Hunt down errors by bisecting elisp files -*- lexical-binding: t; -*-
;; Copyright (C) 2015 Free Software Foundation, Inc.
;; Author: Artur Malabarba <emacs@endlessparentheses.com>
-;; URL: http://github.com/Malabarba/elisp-bug-hunter
-;; Version: 0.5
+;; URL: https://github.com/Malabarba/elisp-bug-hunter
+;; Version: 1.3.1
;; Keywords: lisp
;; Package-Requires: ((seq "1.3") (cl-lib "0.5"))
;; ,----
;; | M-x bug-hunter-init-file RET i
;; `----
-;; The Bug Hunter will start a separate Emacs frame several times, and
-;; then it will ask you each time whether that frame presented the
+;; The Bug Hunter will start a separate Emacs instance several times, and
+;; then it will ask you each time whether that instance presented the
;; problem you have. After doing this about 5--12 times, you’ll be given
;; the results.
;;
(defconst bug-hunter--interactive-explanation
"You have asked to do an interactive hunt, here's how it goes.
-1) I will start a new Emacs frame.
+1) I will start a new Emacs instance, which opens a new frame.
2) You will try to reproduce your problem on the new frame.
3) When you’re done, close that frame.
4) I will ask you if you managed to reproduce the problem.
(defconst bug-hunter--assertion-reminder
"Remember, the assertion must be an expression that returns
non-nil in your current (problematic) Emacs state, AND that
-returns nil on a clean Emacs instance."
+returns nil on a clean Emacs instance.
+If you're unsure how to write an assertion, you can try the interactive
+hunt instead, or see some examples in the Readme:
+ https://github.com/Malabarba/elisp-bug-hunter"
"Printed to the user if they provide a bad assertion.")
(defvar bug-hunter--current-head nil
nil)
(end-of-file `(bug-caught (end-of-file) ,line ,col))
(invalid-read-syntax `(bug-caught ,er ,line ,col))
- (error (error "Ran into an error we don't understand, please file a bug report: %S" er)))
+ (error
+ (if (string= (elt er 1) "Invalid modifier in string")
+ `(bug-caught (invalid-modifier) ,line ,col)
+ (error "Ran into an error we don't understand, please file a bug report: %S" er))))
(nreverse out))))
(defun bug-hunter--read-contents (file)
(cl-case (car error)
(end-of-file
"There's a missing closing parenthesis, the expression on this line never ends.")
+ (invalid-modifier (concat "There's a string on this line with an invalid modifier."
+ "\n A \"modifier\" is a \\ followed by a few characters."
+ "\n For example, \\C-; is an invalid modifier."))
(invalid-read-syntax
(let ((char (cadr error)))
(if (member char '("]" ")"))
"")
(when (> size 16)
(format "\n ... %s omitted expressions ...\n\n"
- (- size 14)))
+ (- size 14)))
(when (> size 16)
(mapconcat (lambda (x) (bug-hunter--pretty-format x 4))
(seq-drop forms (- size 7)) "")))))
(concat "The assertion returned the following value here:\n"
(bug-hunter--pretty-format (cadr error) 4)))
(t (format "The following error was signaled here:\n %S"
- error))))
+ error))))
(when expression
(bug-hunter--report " Caused by the following expression:\n%s"
(bug-hunter--pretty-format expression 4)))
(delete-file file-name))))
(defun bug-hunter--run-form-interactively (form)
- "Run FORM in a graphical frame and ask user about the outcome."
+ "Run FORM in a graphical instance and ask user about the outcome."
(let ((file-name (bug-hunter--print-to-temp (list 'prin1 form))))
(unwind-protect
(bug-hunter--run-emacs file-name "-Q")
(delete-file file-name))
- (y-or-n-p "Did you find the problem/bug in this instance? ")))
+ (y-or-n-p "Did you find the problem/bug in this instance (if you encounter some other issue, answer `n')? ")))
(defun bug-hunter--wrap-forms-for-eval (forms)
"Return FORMS wrapped in initialization code."
"Execute FORMS in the background and test ASSERTION.
See `bug-hunter' for a description on the ASSERTION.
-If ASSERTION is 'interactive, the form is run through
+If ASSERTION is `interactive', the form is run through
`bug-hunter--run-form-interactively'. Otherwise, a slightly
modified version of the form combined with ASSERTION is run
through `bug-hunter--run-form'."
(vector (length safe) ret-val))
(ret-val
(apply #'bug-hunter--bisect
- assertion
- safe
- (bug-hunter--split head)))
+ assertion
+ safe
+ (bug-hunter--split head)))
;; Issue in the tail.
(t (apply #'bug-hunter--bisect
- assertion
- (append safe head)
- ;; If tail has length 1, we already know where the issue is,
- ;; but we still do this to get the return value.
- (bug-hunter--split tail))))))
+ assertion
+ (append safe head)
+ ;; If tail has length 1, we already know where the issue is,
+ ;; but we still do this to get the return value.
+ (bug-hunter--split tail))))))
(defun bug-hunter--bisect-start (forms assertion)
"Run a bisection search on list of FORMS using ASSERTION.
;; Prepare buffer, and make sure they've seen it.
(switch-to-buffer (bug-hunter--init-report-buffer assertion bug-hunter--estimate))
(when (eq assertion 'interactive)
- (read-char-choice "Please the instructions above and type 6 when ready. " '(?6)))
+ (read-char-choice "Please read the instructions above and type 6 when ready. " '(?6)))
(cond
;; Check for errors when reading the init file.
(if assertion
(concat "The assertion returned nil after loading the entire file.\n"
bug-hunter--assertion-reminder)
- "No errors signaled after loading the entire file. If you're
-looking for something that's not an error, you need to provide an
-assertion. See this link for some examples:
- https://github.com/Bruce-Connor/elisp-bug-hunter")
+ "No errors signaled after loading the entire file.
+If you're looking for something that's not an error, use the
+interactive hunt instead of the error hunt. If you have some
+elisp proficiency, you can also use the assertion hunt, see this
+link for some examples:
+ https://github.com/Malabarba/elisp-bug-hunter")
(or assertion "")))
;; Make sure we're in a forest, not a volcano.
(if assertion
(concat "Assertion returned non-nil even on emacs -Q:"
bug-hunter--assertion-reminder)
- "Detected a signaled error even on emacs -Q. I'm sorry, but there
-is something seriously wrong with your Emacs installation.
-There's nothing more I can do here.")
+ "Detected a signaled error even on emacs -Q. This could mean three
+things things:
+1. The problem happens inside `package-initialize'.
+2. You wrote the assertion wrong.
+3. There's something seriously wrong with your Emacs installation.")
(or assertion "")))
(t
form.
The user may decide to not provide input, in which case
-'interactive is returned. Note, this is different from the user
-typing `RET' at an empty prompt, in which case nil is returned."
- (pcase (read-char-choice bug-hunter--hunt-type-prompt '(?i ?e ?a))
- (`?i 'interactive)
+`interactive' is returned. Note, this is different from the user
+typing RET at an empty prompt, in which case nil is returned."
+ (pcase (read-char-choice (if (display-graphic-p)
+ bug-hunter--hunt-type-prompt
+ (replace-regexp-in-string "To bisect interactively,.*\n" ""
+ bug-hunter--hunt-type-prompt))
+ '(?i ?e ?a))
+ (`?i
+ (unless (display-graphic-p)
+ (user-error "Sorry, but `interactive' bisection needs a graphical frame"))
+ 'interactive)
(`?e nil)
(_
(require 'simple)
(minibuffer-with-setup-hook
(lambda ()
(add-hook 'completion-at-point-functions
- #'elisp-completion-at-point nil t)
+ (if (fboundp 'elisp-completion-at-point)
+ #'elisp-completion-at-point
+ (with-no-warnings
+ #'lisp-completion-at-point))
+ nil t)
(run-hooks 'eval-expression-minibuffer-setup-hook))
(insert
(read-from-minibuffer