summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Mendler <mail@daniel-mendler.de>2025-05-11 18:51:44 +0200
committerDaniel Mendler <mail@daniel-mendler.de>2025-05-11 19:00:48 +0200
commit190aec96b1895b21d011ad8869ebaaf72f29ecf9 (patch)
tree3a3edf2d8d801d385dea9c2391d48746b91b866b
parentce216b9795258cbb178f57f1921cceaf296bb891 (diff)
Guard Corfu hooks to automatically show stack traces
-rw-r--r--CHANGELOG.org5
-rw-r--r--README.org31
-rw-r--r--corfu.el87
3 files changed, 69 insertions, 54 deletions
diff --git a/CHANGELOG.org b/CHANGELOG.org
index 9716383..b26ee0a 100644
--- a/CHANGELOG.org
+++ b/CHANGELOG.org
@@ -2,6 +2,11 @@
#+author: Daniel Mendler
#+language: en
+* Development
+
+- Guard Corfu hooks to automatically print stack traces in order to ease
+ debugging.
+
* Version 2.1 (2025-04-22)
- =corfu-history-duplicate= and =corfu-history-decay=: New customization options to
diff --git a/README.org b/README.org
index 1870291..17b419b 100644
--- a/README.org
+++ b/README.org
@@ -602,34 +602,21 @@ enhance your setup.
* Debugging Corfu
-When you observe an error in the =corfu--post-command= post command hook, you
-should install an advice to enforce debugging. This allows you to obtain a stack
-trace in order to narrow down the location of the error. The reason is that post
-command hooks are automatically disabled (and not debugged) by Emacs. Otherwise
-Emacs would become unusable, given that the hooks are executed after every
-command.
+Corfu will automatically print a stack trace to the =*Messages*= buffer when an
+error is detected. The stack trace allows you to narrow down the exact code
+location which caused the error.
-#+begin_src emacs-lisp
-(setq debug-on-error t)
-
-(defun force-debug (func &rest args)
- (condition-case e
- (apply func args)
- ((debug error) (signal (car e) (cdr e)))))
-
-(advice-add #'corfu--post-command :around #'force-debug)
-#+end_src
-
-When Capfs do not yield the expected result you can use ~cape-capf-debug~ to add
-debug messages to a Capf. The Capf will then produce a completion log in the
-messages buffer.
+When Capfs do not yield the expected result, you can wrap a Capf with
+~cape-capf-debug~ from the [[https://github.com/minad/cape][Cape]] package, creating a new Capf, which adds
+completion log messages for debugging. The completion log messages are added to
+the =*Messages*= buffer.
#+begin_src emacs-lisp
(setq completion-at-point-functions (list (cape-capf-debug #'cape-dict)))
#+end_src
-Note that you will sometimes find crashes inside Capfs. Such issues are bugs in
-the Capfs must be fixed there. They cannot be worked around in Corfu.
+Sometimes you will find errors inside Capfs. Such errors are bugs in the Capfs
+must be fixed there, since they Corfu cannot work around them.
* Contributions
diff --git a/corfu.el b/corfu.el
index 93cc098..0b617e4 100644
--- a/corfu.el
+++ b/corfu.el
@@ -856,13 +856,34 @@ the last command must be listed in `corfu-continue-commands'."
(unless (corfu--range-valid-p)
(corfu-quit)))
+(defun corfu--debug (&rest _)
+ "Debugger used by `corfu--guard'."
+ (require 'backtrace)
+ (declare-function backtrace-to-string "backtrace")
+ (declare-function backtrace-get-frames "backtrace")
+ (let ((inhibit-message t))
+ (message "Corfu detected an error:\n%s"
+ (backtrace-to-string (backtrace-get-frames #'corfu--debug))))
+ (let (message-log-max)
+ (message "%s %s"
+ (propertize "Corfu detected an error:" 'face 'error)
+ (substitute-command-keys "Press \\[view-echo-area-messages] to see the stack trace")))
+ nil)
+
+(defmacro corfu--guard (&rest body)
+ "Guard BODY showing a stack trace on error."
+ `(condition-case nil
+ (let ((debug-on-error t) (debugger #'corfu--debug)) ,@body)
+ ((debug error) nil)))
+
(defun corfu--post-command ()
"Refresh Corfu after last command."
- (if (corfu--continue-p)
- (corfu--exhibit)
- (corfu-quit))
- (when corfu-auto
- (corfu--auto-post-command)))
+ (corfu--guard
+ (if (corfu--continue-p)
+ (corfu--exhibit)
+ (corfu-quit))
+ (when corfu-auto
+ (corfu--auto-post-command))))
(defun corfu--goto (index)
"Go to candidate with INDEX."
@@ -993,36 +1014,38 @@ See `completion-in-region' for the arguments BEG, END, TABLE, PRED."
(defun corfu--auto-complete-deferred (&optional tick)
"Initiate auto completion if TICK did not change."
- (when (and (not completion-in-region-mode)
- (or (not tick) (equal tick (corfu--auto-tick))))
- (pcase (while-no-input ;; Interruptible Capf query
- (run-hook-wrapped 'completion-at-point-functions #'corfu--capf-wrapper))
- (`(,fun ,beg ,end ,table . ,plist)
- (let ((completion-in-region-mode-predicate
- (lambda ()
- (when-let ((newbeg (car-safe (funcall fun))))
- (= newbeg beg))))
- (completion-extra-properties plist))
- (corfu--setup beg end table (plist-get plist :predicate))
- (corfu--exhibit 'auto))))))
+ (corfu--guard
+ (when (and (not completion-in-region-mode)
+ (or (not tick) (equal tick (corfu--auto-tick))))
+ (pcase (while-no-input ;; Interruptible Capf query
+ (run-hook-wrapped 'completion-at-point-functions #'corfu--capf-wrapper))
+ (`(,fun ,beg ,end ,table . ,plist)
+ (let ((completion-in-region-mode-predicate
+ (lambda ()
+ (when-let ((newbeg (car-safe (funcall fun))))
+ (= newbeg beg))))
+ (completion-extra-properties plist))
+ (corfu--setup beg end table (plist-get plist :predicate))
+ (corfu--exhibit 'auto)))))))
(defun corfu--auto-post-command ()
"Post command hook which initiates auto completion."
- (cancel-timer corfu--auto-timer)
- (when (and (not completion-in-region-mode)
- (not defining-kbd-macro)
- (not buffer-read-only)
- (corfu--match-symbol-p corfu-auto-commands this-command)
- (corfu--popup-support-p))
- (if (<= corfu-auto-delay 0)
- (corfu--auto-complete-deferred)
- ;; Do not use `timer-set-idle-time' since this leads to
- ;; unpredictable pauses, in particular with `flyspell-mode'.
- (timer-set-time corfu--auto-timer
- (timer-relative-time nil corfu-auto-delay))
- (timer-set-function corfu--auto-timer #'corfu--auto-complete-deferred
- (list (corfu--auto-tick)))
- (timer-activate corfu--auto-timer))))
+ (corfu--guard
+ (cancel-timer corfu--auto-timer)
+ (when (and (not completion-in-region-mode)
+ (not defining-kbd-macro)
+ (not buffer-read-only)
+ (corfu--match-symbol-p corfu-auto-commands this-command)
+ (corfu--popup-support-p))
+ (if (<= corfu-auto-delay 0)
+ (corfu--auto-complete-deferred)
+ ;; Do not use `timer-set-idle-time' since this leads to
+ ;; unpredictable pauses, in particular with `flyspell-mode'.
+ (timer-set-time corfu--auto-timer
+ (timer-relative-time nil corfu-auto-delay))
+ (timer-set-function corfu--auto-timer #'corfu--auto-complete-deferred
+ (list (corfu--auto-tick)))
+ (timer-activate corfu--auto-timer)))))
(defun corfu--auto-tick ()
"Return the current tick/status of the buffer.