Failing Gracefully
Update: Just using display-buffer doesn’t work for me with emacsclient, so I’ve gone for a slightly more radical solution, altering the initial buffer. For some reason initial-buffer-choice seems to be heavily biased towards files, so you’ll need to replace it with (setq initial-buffer-choice (lambda () (get-buffer "*init errors*"))).
One of the more annoying problems with Emacs and its init file is that any error encountered while loading it will prohibit it from proceeding any further. There is a question on the Emacs Stackexchange, but none of the answers are really satisfactory as the usage of ignore-errors or with-demoted-errors keeps it from popping up a buffer with the errors. I’d ideally like the built-in error reporting to be extended to all errors encountered. One of the commenters remarks that this level of control is possible, given a list of individual code snippets to be evaluated.
Come to think of it, this is how my init.org does already work! As I don’t use Org for loading it, I’ve adapted Arthur Malabarba’s code to find the source blocks and evaluate them one block at a time. The basic logic can be summarized as follows:
(with-temp-buffer (insert-file "~/.emacs.d/init.org") (goto-char (point-min)) ;; jump to first relevant header (search-forward "\n* Init") (while (not (eobp)) (forward-line 1) (cond ;; evaluate code blocks ((looking-at "^#\\+BEGIN_SRC +emacs-lisp.*$") (let ((src-beg (match-end 0))) (search-forward "\n#+END_SRC") (eval-region src-beg (match-beginning 0)))) ;; finish on the next level-1 header ((looking-at "^\\* ") (goto-char (point-max))))))
To report errors, I need to wrap eval-region into condition-case and collect data for the error reporting. If any errors have been collected after the entire file was processed, a buffer with a report shall be popped up:
(let (errors) (with-temp-buffer (insert-file "~/.emacs.d/init.org") (goto-char (point-min)) ;; jump to first relevant header (search-forward "\n* Init") (while (not (eobp)) (forward-line 1) (cond ;; evaluate code blocks ((looking-at "^#\\+BEGIN_SRC +emacs-lisp.*$") (let (src-beg src-end) (condition-case error (progn (setq src-beg (match-end 0)) (search-forward "\n#+END_SRC") (setq src-end (match-beginning 0)) (eval-region src-beg (match-beginning 0))) (error (push (format "%s for:\n%s\n\n---\n" (error-message-string error) (buffer-substring src-beg src-end)) errors))))) ;; finish on the next level-1 header ((looking-at "^\\* ") (goto-char (point-max)))))) (when errors (with-current-buffer (get-buffer-create "*init erorrs*") (insert (format "%i error(s) found\n\n" (length errors))) (dolist (error (nreverse errors)) (insert error "\n")) (goto-char (point-min)) (special-mode)) (setq initial-buffer-choice (lambda () (get-buffer "*init errors*")))))
This could be improved in a number of ways, like by tracking the last heading and evaluating every single form in the code block on its own. Probably not worth it though.