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.