branch: externals/corfu
commit 190aec96b1895b21d011ad8869ebaaf72f29ecf9
Author: Daniel Mendler <m...@daniel-mendler.de>
Commit: Daniel Mendler <m...@daniel-mendler.de>

    Guard Corfu hooks to automatically show stack traces
---
 CHANGELOG.org |  5 ++++
 README.org    | 31 +++++++--------------
 corfu.el      | 87 +++++++++++++++++++++++++++++++++++++----------------------
 3 files changed, 69 insertions(+), 54 deletions(-)

diff --git a/CHANGELOG.org b/CHANGELOG.org
index 9716383758..b26ee0a0e2 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 1870291576..17b419b6c4 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 93cc0981ce..0b617e4033 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.

Reply via email to