A Quick Elisp Tutorial
Emacs has a handy, but sometimes decried, feature called the "scratch" buffer. This is a special buffer which is created upon startup and allows the user to type in and evaluate Emacs Lisp code. Handy for editing tasks too specific (or not useful enough) to put into an function and handy for exploratory Emacs Lisp interactive development (although this development is just as easily accomplished in any file in Lisp mode).
One problem with *scratch* is that its tempting to put significant
bits of code (and other information) into it. This isn't a problem in
itself, but *scratch* isn't associated with a file, and its contents
are lost without warning when Emacs is closed. Today we'll modify the
default behavior of Emacs so that it saves the scratch buffer to a
file on exit and loads it back in on startup.
This will serve as a brief Elisp tutorial.
Preliminaries
If you are totally new to Emacs, I'd just put the code we are about to
write directly in your .emacs or .emacs.d/init.el file. These
files are executed when emacs starts, and since we need to ensure that
our behavior happens on startup, this is a reasonable place to put
code. After years of emacs usage, your .emacs will balloon into an
unspeakable cthonian mess, and you'll have to refactor it, but by then
you'll know what you need to for that process.
Let's start by declaring a few parameters:
(defvar persistent-scratch-filename
"~/.emacs-persistent-scratch"
"Location of *scratch* file contents for persistent-scratch.")
(defvar persistent-scratch-backup-directory
"~/.emacs-persistent-scratch-backups/"
"Location of backups of the *scratch* buffer contents for
persistent-scratch.")
defvar declares variables, associates them with a value (the second
term), and a doc-string (the third term). Providing a doc-string will
ensure that when these symbols appear in the result of an apropos
search, they will have a convenient description associated with them.
It also will help us out when we look at the code in 4 years. Don't forget to create these directories before trying the code! (Or, modify the code to check for their existence and create them otherwise!)
Our code is going to backup any version of the scratch contents before overwriting them, just in case something really important was hiding in there. That means we need to copy the current scratch file contents (not the contents of the scratch buffer, but the last version of it) to a backup file. That file needs to have a generated name. We'll use the date to disambiguate scratch backups:
(defun make-persistent-scratch-backup-name ()
"Create a filename to backup the current scratch file by
concatenating PERSISTENT-SCRATCH-BACKUP-DIRECTORY with the
current date and time."
(concat
persistent-scratch-backup-directory
(replace-regexp-in-string
(regexp-quote " ") "-" (current-time-string))))
Again, the initial string in the body of this function is
documentation. Emacs will show us this description when it displays
information about this function. concat concatenates strings,
current-time-string does what you'd think, and we use
replace-regexp-in-string to remove the spaces. regexp-quote
produces a regular expression which matches exactly its input string
and nothing else.
Saving the Contents of *scratch*
Next is our function to save the contents of the *scratch* buffer.
We will eventually place this function on a "hook" which ensures it
gets run every time emacs shuts down.
(defun save-persistent-scratch ()
"Write the contents of *scratch* to the file name
PERSISTENT-SCRATCH-FILENAME, making a backup copy in
PERSISTENT-SCRATCH-BACKUP-DIRECTORY."
(with-current-buffer (get-buffer "*scratch*")
(if (file-exists-p persistent-scratch-filename)
(copy-file persistent-scratch-filename
(make-persistent-scratch-backup-name)))
(write-region (point-min) (point-max)
persistent-scratch-filename)))
This function needs to work with the contents of the *scratch*
buffer, so it uses the special form with-current-buffer to create a
context where actions refer to the contents of that buffer.
We then check to see whether a file containing the previous scratch
buffer is present, and if it is we use copy-file to copy it to the
backup directory. We then use write-region to write the contents of
*scratch* from (point-min) to (point-max) (that is, the whole
thing), to the scratch file location.
Loading the Contents of *scratch*
(defun load-persistent-scratch ()
"Load the contents of PERSISTENT-SCRATCH-FILENAME into the
scratch buffer, clearing its contents first."
(if (file-exists-p persistent-scratch-filename)
(with-current-buffer (get-buffer "*scratch*")
(delete-region (point-min) (point-max))
(shell-command (format "cat %s" persistent-scratch-filename) (current-buffer)))))
(It has been correctly pointed out in the comments that insert-file is the more idiomatic way of getting file contents into a buffer, which I somehow forgot about!) We first check to see whether there is a file containing the previous
session's scratch contents. If there is, we switch to the scratch
buffer context with with-current-buffer and use shell-command and
cat to insert the contents into *scratch*.
Hooking It All In
Since we are writing this in our .emacs file, we can ensure that the
contents of the last session's *scratch* are read in by simply
writing:
(load-persistent-scratch)
This will get executed on emacs startup, just as we want. We have to
do something more complicated to ensure that save-persistent-scratch
is run whenever emacs exits.
To do this, we use one of emacs's many "hooks". A hook, in emacs
parlance, is a list of functions which emacs promises to run at
specific times or when specific events occur. The hook we need is
kill-emacs-hook. This hook is run whenever emacs is killed for any
reason.
We can use the special form push, which pushes an item onto the
start of a list, to add our function.
(push #'save-persistent-scratch kill-emacs-hook)
Note that save-persistent-scratch is preceded by a #'. This tells
emacs that we want the function associated with the symbol
save-persistent-scratch, not the value (of which there is none
anyway).
That is It!
That's it. Save this into your .emacs and you should now have a
scratch buffer which remembers its contents between sessions. Handy!
7 comments:
Hello,
On Emacs trunk I'm getting this error when exiting Emacs:
Debugger entered--Lisp error: (file-error "Opening output file" "No such file or directory" "/Users/my_user/Dropbox/.emacs-persistent-scratch-backups/Thu-Dec--1-10:04:02-2011")
copy-file("~/Dropbox/.emacs-persistent-scratch" "~/Dropbox/.emacs-persistent-scratch-backups/Thu-Dec--1-10:04:02-2011")
(if (file-exists-p persistent-scratch-filename) (copy-file persistent-scratch-filename (make-persistent-scratch-backup-name)))
(save-current-buffer (set-buffer (get-buffer "*scratch*")) (if (file-exists-p persistent-scratch-filename) (copy-file persistent-scratch-filename (make-persistent-scratch-backup-name))) (write-region (point-min) (point-max) persistent-scratch-filename))
(with-current-buffer (get-buffer "*scratch*") (if (file-exists-p persistent-scratch-filename) (copy-file persistent-scratch-filename (make-persistent-scratch-backup-name))) (write-region (point-min) (point-max) persistent-scratch-filename))
save-persistent-scratch()
kill-emacs()
save-buffers-kill-emacs(nil)
save-buffers-kill-terminal(nil)
call-interactively(save-buffers-kill-terminal nil nil)
Any ideas why?
Thanks!
S.
Siancu,
You probably need to create the directory in question before you run the extension for the first time. You can do that directly in elisp with the `make-directory` function.
I leave it as an exercise to the reader to extend this extension so that it checks for the existence of the directory, and makes it, on startup.
Usefull!
But 2 things to improve:
1. windows forbids filenames with ":", so this solution would be better: (format-time-string "%d%m%y_%H%M%S") instead of (current-time-string) which returns a time-string like 01:21:12.
2. (insert-file persistent-scratch-filename) instead of (shell-command (format "cat %s" persistent-scratch-filename) (current-buffer))
is much easier and also windows-compatible.
Excellent points!
I think the time string in persistent name should be in good format, such as year-month-day_hour-min-sec, here is my version:
(defun make-persistent-scratch-backup-name ()
"Create a filename to backup the current scratch file by
concatenating PERSISTENT-SCRATCH-BACKUP-DIRECTORY with the
current date and time."
(concat
persistent-scratch-backup-directory
(format-time-string "%y-%m-%d_%H-%M-%S_%s" (current-time)))))
To be precise, `insert-file' is for interactive use only. Use `insert-file-contents'.
Post a Comment