59 lines
2.4 KiB
EmacsLisp
59 lines
2.4 KiB
EmacsLisp
;;; 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
|