How to avoid errors in your dotfile
Did you ever add a new snippet of code to your .emacs and got an only partially
loaded dotfile ? Half of your customizations gone with the wind ?
I've encountered this behaviour various times, and I didn't feel happy seeing this.
The best thing you can do, is to avoid errors in your dotfile, but how ?
Well, what about testing them before you add them to your dotfile ?
There is noything mysterious about that, the only thing you have to do is
to put them into a buffer and execute the statements.
The *scratch* buffer is exactly for this.
Type your statements into this buffer and execute them by pressing C-x C-e
after the last parenthesis of every
form
you want to test.
This way you do some kind of single step testing if you have more than one
thing to evaluate. To shorten the procedure you can evaluate a complete region
by entering M-x eval-region .
If all works fine and you don't see any errors, there is a chance that the
code will also work in your dotfile.
Stop ! A chance ? Sorry, yes, only a chance.
Evaluating things in the *scratch* buffer means that you're already working
with Emacs thus all things should be setup properly. All vars initialized,
all the packages you use are available or waiting to be autoloaded.
Here are a few examples of what might happen in your dotfile:
- Your code uses a function which can be autoloaded and it works in
the *scratch* buffer. Add it to the top of your dotfile and it
will fail because of the missing autoload.
- You have setup a section for varaible customizing at the top of your
dotfile. You set a variable of a package to a value which fits to your needs.
The package is loaded at the end of your dotfile and sets the value back
to the package default.
- You already have customized the variable with customize. Same as above,
your dotfile entry might be gone, but this is
an other story.
The second and third example will not even produce an error in your
dotfile, Emacs will happily do what you have told him to do. You'll only
recognize it at the point where something doesn't work as expected.
That's not the case for the first example. The void symbol for the function
is recognized as an error and Emacs will stop reading the dotfile.
Most of the time this is a wanted behavior, because this way you recognize
that there is an error which should be corrected. On the other hand, there
might be situations where this is unwanted, because you know that there
might be an error. Using the same dotfile at work and at home but with
different packages or flavours of Emacs available might be such a situation.
It would be nice to catch these errors without them breaking your dotfile.
Error recovery
Reviewing one of the dotfiles
provided for publishing on the site I saw statements like this one:
(condition-case err
(display-time)
(error
(message "Cannot display time %s" (cdr err))))
Hey, what is this guy doing there ?
Looks very interesting, a kind of try/catch for elisp ? Having checked it in the
elisp manual, I saw that there is a possibility to protect the dotfile against
bad code. Reading more and trying some statements I recognized that it is restricted
to what is called a
form. Hmm, beeing no elisp professional, but just a dumb c/c++ and java programmer,
I've played a bit with it and saw that I can protect only what a c programmer would call
a statement.
Not much :((
Playing a bit more with it, I found a possibility to do the test for a bigger block:
(defmacro try-and-error ()
(setq tralala t) (setq bar tralali) (defun foo-mul (factor) (* foo factor)) nil )
(condition-case err
(try-and-error)
(error
(setq message-log-max t) (message "Ooops, try and error failed: %s" err)))
Adding the above to your dotfile will give you this message in your
messages buffer:
Ooops, try and error failed: (void-variable tralali)
The rest of your dotfile is loaded as expected. You're able to work
with your normal environment, even if the new things failed.
But hey, there are several drawbacks:
If you take a look at the defun, you'll recognize that it uses
foo, which is not defined. The condition-case statement doesn't
complain about this, the function is defined, not evaluated.
Nevertheless, it is useless, the first execution will lead to
a message like:
Symbol's value as variable is void: foo
The error strikes back :))
Btw.: A definition like the one above works in the same way
if it is not protected by condition-case, so it makes no sense
at all to protect it. It was only introduced to
show what can be done.
More important is the fact that Emacs, stopping to read the rest
of your dotfile if an error occurs, signals that there is
something odd in your dotfile. You have to search for it, but
you know that there is an error. Catching the error doesn't give
you this kind of feedback. Loading of your dotfile proceedes as
if nothing is wrong. You'll only recognize the error if you look
into the messages buffer or see that the new features don't work.
If you don't do anything to catch the error, you'll get the message
Error in init file: Symbol's value as variable is void: tralali
Making it part of the load strategy
Lawrence Buja has mailed the following:
After my .emacs file grew too large to comprehend, I split it up
into a number of different .el files grouping different functional
areas
Then I added safe-load function to continue loading the rest of
the .el files if any of the .el files fails to load correctly.
As you can see from the date on safe-load, it's been working
fine for me for years.
---start of .emacs ----------snip---------------snip-----------snip-------------------
(setq load-path (append '("/home/southern/emacs/fortran") load-path))
(setq load-path (append '("/home/southern/emacs") load-path))
( load "safe-load" nil t ) (safe-load "defaults" nil t ) (safe-load "hooks" nil t ) (safe-load "tools" nil t ) (safe-load "my-keys" nil t ) (safe-load "my-print" nil t ) (safe-load "compress" nil t ) (safe-load "all" nil t ) (safe-load "echistory" nil t ) (safe-load "ispell" nil t ) (safe-load "show-file-buffers" nil t ) (safe-load "server-start" nil t ) (safe-load "solaris-keys" nil t ) (start-server)
(set-mark-command nil)
---end of .emacs ----------snip---------------snip-----------snip-------------------
---start of safe-load.el ----------snip---------------snip-----------snip-------------
(defvar safe-load-error-list ""
"*List of files that reported errors when loaded via safe-load")
(defun safe-load (file &optional noerror nomessage nosuffix)
"Load a file. If error when loading, report back, wait for
a key stroke then continue on"
(interactive "f")
(condition-case nil (load file noerror nomessage nosuffix)
(error
(progn
(setq safe-load-error-list (concat safe-load-error-list " " file))
(message "****** [Return to continue] Error loading %s" safe-load-error-list )
(sleep-for 1)
nil))))
(defun safe-load-check ()
"Check for any previous safe-load loading errors. (safe-load.el)"
(interactive)
(if (string-equal safe-load-error-list "") ()
(message (concat "****** error loading: " safe-load-error-list))))
---end of safe-load.el ----------snip---------------snip-----------snip-------------
Conclusion
It's your decision:
- Beeing forced to correct the error Emacs found for you
- or to ignore it to have your environment beeing setup as usual
and you beeing responsible to look for errors that might have occured.
|