;;; cider-error.el --- Route game-loop exceptions into cider's stacktrace buffer -*- lexical-binding: t -*- ;;; Commentary: ;; The game loop runs on its own thread, so anything it throws never reaches ;; the nREPL session that started it -- cider's stacktrace buffer stays empty ;; and you get an unreadable `println' in the REPL instead. ;; ;; `engine/report-exception!' stashes the throwable in `engine/last-error' and ;; prints a marker line. This hook watches REPL output for that marker, ;; swallows it, and asks the REPL to rethrow the stashed exception -- now on ;; the session's own thread, so cider renders it properly. ;; ;; Load with: (load "/path/to/project/cider-error.el") ;;; Code: (require 'cider-client) (defconst clj-game-error-marker "SIAM-REPORT-EXCEPTION" "Line `engine/report-exception!' prints to announce a stashed exception. Deliberately does not contain EMACS-CIDER-REPORT-EXCEPTION, which a global hook elsewhere matches for a different project's exception var.") ;; `last-error' is a private var holding an atom, so both forms deref twice: ;; once through the var-quote to reach the atom, once to reach the throwable. (defconst clj-game-error-deref "@@#'engine/last-error") (defconst clj-game-error-throw "(when-let [t @@#'engine/last-error] (throw t))") (defvar clj-game-error--last nil "Printed form of the exception most recently handed to cider.") (defun clj-game-throw-last-error () "Rethrow the stashed exception over nREPL so cider renders it. Does nothing if that exception is already on show in *cider-error*." (interactive) (let* ((error-str (nrepl-dict-get (cider-nrepl-sync-request:eval clj-game-error-deref) "value")) (same-error-p (and (get-buffer "*cider-error*") (equal clj-game-error--last error-str)))) (unless same-error-p (setq clj-game-error--last error-str) (cider-interactive-eval clj-game-error-throw)))) (defun clj-game-error-preoutput (output) "Swallow the marker line in OUTPUT and rethrow; pass anything else through." (if (string-match-p clj-game-error-marker output) (progn (clj-game-throw-last-error) "") output)) ;; Named, so the dir-locals `eval' re-firing on every file open replaces this ;; hook entry rather than stacking duplicates of it. (add-hook 'cider-repl-preoutput-hook #'clj-game-error-preoutput) (provide 'clj-game-error) ;;; cider-error.el ends here