Wednesday, November 21, 2007

Restoring Emacs layout

When I work in EMACS I have a specific layout that I use. I have about five frames open each dedicated to some task. One will have three dired windows and an eshell window. Others will have project files open or source files. Restoring this layout is a pain whenever I have to restart emacs. So I decided to automate this layout restoration. I found revive.el that does almost what I wanted so I wrote some code that uses revive to save the state of all the frames that are currently open and then restore that state.

(defun emacs-save-layout ();TODO: handle eshell
"save the frame and window layout to ~/.emacs-layout. Requires revive.el."
(let ((out-name (concat *HOME* "/.emacs-layout"))
(frames (frame-list))
(configs nil)
(buffs (sorted-buffer-list))
(filtered-buffs nil)
(s-buffs nil))
(dolist (b buffs)
(let ((file-name (buffer-file-name b)))
(when (and file-name
(> (length file-name) 0))
(setq filtered-buffs (cons file-name filtered-buffs)))))
(when filtered-buffs (setq filtered-buffs (reverse filtered-buffs)))
(dolist (frame frames)
(select-frame frame)
(setq configs (cons (current-window-configuration-printable) configs)))
(setq configs (cons filtered-buffs configs))
(write-region (with-output-to-string (prin1 configs)) nil out-name)))

(defun emacs-load-layout ();TODO: handle eshell buffers.
"Load the layout saved by emacs-save-layout. Requires revive.el."
(let* ((in-name (concat *HOME* "/.emacs-layout"))
(config-count 0)
(frames (frame-list))
(configs nil)
(frame-count (length frames))
(buffs nil))
(insert-file-contents-literally in-name)
(setq buffs (read (current-buffer)))
(setq configs (rest buffs))
(setq buffs (first buffs)))
(dolist (b buffs)
(find-file-noselect b)
(message "Loading buffer %s" b))
(setq config-count (length configs))
(message "Config count is %s" config-count)
(unless (>= frame-count config-count)
(dotimes (i (- config-count frame-count))
(setq frames (frame-list))
(setq frame-count (length frames))
(message "frame-count is %s" frame-count))
(defun it (lconfigs lframes)
(when (and lconfigs lframes)
(select-frame (first lframes))
(restore-window-configuration (first lconfigs))
(it (rest lconfigs) (rest lframes))))
(it configs frames)))


Nova Viator said...

i'm trying to use your script, as revive doesn't save some of the buffers like *SCLang*Help* and stuff. in fact it does save them, but is unable re resume them. so, with your script i only get an error:

let*: Symbol's value as variable is void: *HOME*

any ideas?

Mike said...

Sorry it took me so long to get back to you but for some reason I didn't get notified of the post.

To answer your question:

*HOME* is a global variable that points to my home directory. In my home directory I have an elisp directory that contains all the elisp files I use in my emacs configuration. I use emacs in Windows, OS X and Linux so I have code that goes through my environment variables and loads them into emacs. This is one of those that gets loaded. There's probably better ways to do this but this way works for me.