[comp.emacs] package to save context between Emacs sessions

kyle@xanth.UUCP (Kyle Jones) (11/13/87)

When I switched from Gosling to GNU Emacs, one feature I really missed was the
ability save and recover the context of an Emacs session.  That is, just by
typing 'emacs' in a directory in which I had used Emacs previously, Emacs
would 'remember' the windows and visited files that were visible in my last
Emacs session in that directory and recover them.  The window dot positions
were also remembered, thus I could pick up exactly where I left off.

Attached is a package to implement this in GNU Emacs Lisp.  The package is
named "ultra.el" here because the main function used to be called
`ultra-save-excursion'.  This package works by modifying the binding of the
Lisp variable `top-level' and the function definition of `kill-emacs'.
Because of the modification to `top-level' this package MUST be preloaded
before dumping Emacs and must be loaded after startup.el.  Both these
conditions can be satisfied by having "ultra" loaded from the site-init.el
file.

Once the package is installed, you must set up your .emacs file to set the
Lisp variable `auto-save-and-recover-context' to be non-nil to enable the new
feature.  You do NOT want to set this variable non-nil in the package; temacs
will dump core upon exit if you do.  It is also a good idea to set the
variable `inhibit-startup-message' non-nil in your .emacs, otherwise you'll
have to type a key (or wait 120 seconds) before the startup message goes away.

This package is running here under Emacs 18.46 on a 4.3 BSD system.

kyle jones  <kyle@odu.edu>  old dominion university, norfolk, va  usa
------------------------------------------------------------------------------
;;; Save Emacs windows between editing sessions.
;;; Copyright (C) 1987 Kyle E. Jones
;;;
;;; This software may be redistributed provided this notice appears unchanged
;;; on all copies and that the further free redistribution of this software is
;;; not restricted in any way.
;;;
;;; This software is distributed 'as is', without warranties of any kind.

;;; NOTE: In order for this package to operate properly it must be pre-loaded
;;;       and dumped.  It cannot be autoloaded or loaded into a dumped Emacs
;;;       and work properly.  Also it must be loaded after startup.el.

(defconst save-context-version "Norma Jean Baker"
  "A unique string which is placed at the beginning of every saved context
file.  If the string at the beginning of the context file doesn't match the
value of this variable the `recover-context' command will ignore the file's
contents.")

(defvar auto-save-and-recover-context nil
  "*If non-nil the `save-context' command will always be run before Emacs is
exited.  Also upon Emacs startup, if this variable is non-nil and Emacs is
passed no command line arguments, `recover-context' will be run.")

(defvar save-context-predicate
  (function (lambda (w)
	      (and (buffer-file-name (window-buffer w))
		   (not (string-match "^\\(/usr\\)?/tmp/"
				      (buffer-file-name (window-buffer w)))))))
  "*Value is a predicate function which determines which windows' contexts
are saved.  When the `save-context' command is invoked, this function will
be called once for each existing Emacs window.  The function should accept
one argument which will be a window object, and should return non-nil if
the window's context should be saved.")


;; kill-emacs' function definition must be saved
(if (not (fboundp 'just-kill-emacs))
    (fset 'just-kill-emacs (symbol-function 'kill-emacs)))

;; Change top-level to (recover-context) if appropriate
(setq top-level
      (list 'prog1 top-level
	    '(if (and auto-save-and-recover-context
		      (null (cdr command-line-args))) (recover-context))))

(defun kill-emacs (&optional query)
  "End this Emacs session.
Prefix ARG or optional first ARG non-nil means exit with no questions asked,
even if there are unsaved buffers.  If Emacs is running non-interactively
and ARG is an integer, then Emacs exits with ARG as its exit code.

If the variable `auto-save-and-restore-context' is non-nil,
the function save-context will be called first."
  (interactive "P")
  ;; check the purify flag.  try to save only if this is a dumped Emacs.
  ;; saving context from a undumped Emacs caused a NULL pointer to be
  ;; referenced through.  I'm not sure why.
  (if (and auto-save-and-recover-context (not purify-flag))
      (save-context))
  (just-kill-emacs query))

(defun save-context ()
  "Save context of all Emacs windows (files visited and position of point).
The information goes into a file called .emacs_<username> in the directory
where the Emacs session was started.  The context can be recovered with the
`recover-context' command, provided you are in the same directory where
the context was saved.

Window sizes and shapes are not saved, since these may not be recoverable
on terminals with a different number of rows and columns."
  (interactive)
  (catch 'forget-it
    (let ((dir (original-working-directory))
	  var
	  windows firstwin win
	  savebuf save-file-name
	  (name (user-login-name)))
      (setq save-file-name (concat dir ".emacs_" name))
      (if (not (file-writable-p save-file-name))
	  (throw 'forget-it nil))
      ;;
      ;; first make a list of all windows
      ;;
      (setq firstwin (selected-window))
      (setq windows (cons firstwin nil))
      (setq win (next-window firstwin))
      (while (not (equal win firstwin))
	(setq windows (cons win windows))
	(setq win (next-window win)))
      ;;
      ;; set up a buffer for the saved context information
      ;;
      (setq savebuf (create-file-buffer save-file-name))
      ; erase the savebuf without selecting it
      (copy-to-buffer savebuf (dot) (dot))
      ;;
      ;; traverse the list of windows, noting the buffers that the windows
      ;; are on.  Information is only saved about windows that are on buffers
      ;; that are visiting files.
      ;;
      (while windows
	(if (funcall save-context-predicate (car windows))
	    (progn
	      (prin1 (buffer-file-name (window-buffer (car windows))) savebuf)
	      (princ " " savebuf)
	      (prin1 (window-dot (car windows)) savebuf)
	      (princ "\n" savebuf)))
	(setq windows (cdr windows)))
      ;;
      ;; now if the savebuf isn't empty we add the version information
      ;; and an EOF sentinel and write out the saved context.
      ;; If the savebuf is empty, we don't create a file at all.
      ;; If there's an old saved context in this directory we attempt
      ;; to delete it.
      ;;
      (set-buffer savebuf)
      (if (/= (dot-max) (dot-min))
	  (progn
	    (goto-char (dot-min))
	    (prin1 save-context-version savebuf)
	    (insert "\n\n")
	    (goto-char (dot-max))
	    (insert "\nnil\n")
	    ;; I hate NFS!  Using (file-writable-p) as a check before writing
	    ;; is apt to fail on NFS filesystems, when root is involved.
	    (condition-case var
		(write-region (dot-min) (dot-max) save-file-name nil 'quiet)
	      (error nil))))
      (set-buffer-modified-p nil)
      (kill-buffer savebuf))))

(defun recover-context ()
   "Recover an Emacs context saved by `save-context' command.
Files that were visible in windows when the context was saved are visited and
dot is set in each window to what is was when the context was saved."
   (interactive)
   ;;
   ;; Set up some local variables.
   ;;
   (let ((dir (original-working-directory))
	 sexpr recbuf recover-file-name
	 (name (user-login-name)))
     (catch 'forget-it
       (setq recover-file-name (concat dir ".emacs_" name))
       ;;
       ;; if there's no saved context, forget it.
       ;;
       (if (not (file-exists-p recover-file-name))
	   (throw 'forget-it nil))
       ;;
       ;; create a temporary buffer and copy the saved context into it.
       ;;
       (setq recbuf (get-buffer-create "*Recovered Context*"))
       (set-buffer recbuf)
       (insert-file-contents recover-file-name t)
       ;;
       ;; If it's empty forget it.
       ;;
       (if (= (dot-min) (dot-max))
	   (progn (kill-buffer recbuf) (throw 'forget-it nil)))
       ;;
       ;; check the version and make sure it matches ours
       ;;
       (setq sexpr (read recbuf))
       (if (not (equal sexpr save-context-version))
	   (progn (kill-buffer recbuf) (throw 'forget-it nil)))
       ;;
       ;; Read first file name and do a find-file.  If this function
       ;; is invoked at Emacs startup time, doing this will get rid of the
       ;; *scratch* window.
       ;;
       (find-file (read recbuf))
       (goto-char (read recbuf))
       ;;
       ;; Now we loop doing find-file's and splliting windows
       ;; until we run out of file names.
       ;;
       (while (setq sexpr (read recbuf))
	 (select-window (get-largest-window))
	 (split-window)
	 (other-window 1)
	 (find-file sexpr)
	 (goto-char (read recbuf)))
       (kill-buffer recbuf)
       t)))


(defun original-working-directory ()
  (save-excursion
    (set-buffer (get-buffer-create "*scratch*"))
    default-directory))

maa@sei.cmu.edu (Mark Ardis) (11/16/87)

The Gnutest system performs a similar function, allowing one to save
the state of a session and then restore that state.  The user has some
control over which components are saved and restored.  For example,
one can specify which buffers, variables, and windows are included in
the set for each operation.
--
Mark A. Ardis
Software Engineering Institute
Carnegie-Mellon University
Pittsburgh, PA 15213
(412) 268-7636
maa@sei.cmu.edu