Emacs: desktop-save-mode without the automatic restore

Srijan Choudhary Srijan Choudhary
- 2 min read
Tagged: emacs

Emacs has a built-in way to save and restore sessions: the desktop.el library.

As the Emacs manual says, putting this in the init file enables automatic save and restore of sessions:

(desktop-save-mode 1)

But, I wanted a different behavior:

  • Automatically save the session
  • But don't restore it automatically

I wanted to only use the session feature to restore sessions that I closed by mistake, or where I forgot to finish something before quitting Emacs. By default, I wanted a fresh session every time.

But this also means that some session history has to be maintained; otherwise, a fresh session will start to write to the desktop file and then I will not be able to recover the old session.

By default, the way to do this is to not set desktop-save-mode, but manually call desktop-save when needed, and then call desktop-read when needed. But, if I forget to save, I cannot restore.

This elisp is what I came up with to make it work the way I want:

(defvar my/desktop-snapshot-keep 5
  "How many previous sessions to keep desktop snapshots for.")

(defun my/desktop--snapshots ()
  (nreverse (directory-files desktop-dirname t
                             (concat "\\`" (regexp-quote desktop-base-file-name)
                                     "\\.[0-9]\\{8\\}T[0-9]\\{6\\}\\'"))))

(defun my/desktop--take-snapshot ()
  (let ((live (desktop-full-file-name)))
    (when (file-exists-p live)
      (let ((dest (concat live (format-time-string
                                ".%Y%m%dT%H%M%S"
                                (file-attribute-modification-time
                                 (file-attributes live))))))
        (unless (file-exists-p dest)
          (copy-file live dest t t)))))
  (dolist (old (nthcdr my/desktop-snapshot-keep (my/desktop--snapshots)))
    (delete-file old)))

(defun my/desktop--label (file)
  (format "%s   %d buffers"
          (format-time-string "%Y-%m-%d %H:%M:%S"
                              (file-attribute-modification-time
                               (file-attributes file)))
          (with-temp-buffer
            (insert-file-contents file)
            (how-many "^(desktop-\\(?:create-buffer\\|append-buffer-args\\) "
                      (point-min) (point-max)))))

(add-hook 'emacs-startup-hook
          (lambda ()
            (require 'desktop)
            (setq desktop-dirname (expand-file-name user-emacs-directory))
            (my/desktop--take-snapshot)
            (desktop--get-file-modtime)
            (let ((owner (desktop-owner)))
              (unless (and owner (desktop--emacs-pid-running-p owner))
                (desktop-claim-lock)))
            (desktop-save-mode 1)))

(defun my/desktop-restore (file)
  "Restore the desktop snapshot FILE, prompting for one interactively."
  (interactive
   (progn
     (require 'desktop)
     (let* ((snapshots (or (my/desktop--snapshots)
                           (user-error "No desktop snapshots in %s"
                                       desktop-dirname)))
            (choices (mapcar (lambda (f) (cons (my/desktop--label f) f))
                             snapshots)))
       (list (cdr (assoc (completing-read "Restore desktop session: "
                                          choices nil t)
                         choices))))))
  (desktop-release-lock)
  (let ((desktop-base-file-name (file-name-nondirectory file)))
    (desktop-read (file-name-directory file)))
  (desktop--get-file-modtime))

Note that desktop-save-mode must not be enabled before after-init-hook fires, because that's where desktop.el decides to automatically restore. So, I enable it in emacs-startup-hook, which runs later. Other things inside my emacs-startup-hook lambda are workarounds for how desktop.el works, so they can be fragile if there are major changes to it.

The lock check avoids a stale lock from a crashed Emacs silently disabling auto-save, and naming snapshots after the desktop file's mtime means restarting without saving anything doesn't use up one of the five slots.

Here's what it looks like when I call my/desktop-restore:

Emacs minibuffer showing a list of sessions to choose for restoring. Each entry shows the timestamp of last snapshot and number of buffers in that session.

Of course, this can be made better by showing more information per session (like number of projects, names of last edited files or last visited buffers). Vertico/marginalia can show it nicely. Also maybe a section in emacs-dashboard to show recent sessions and click/enter to restore. Something for another day.

Reactions

2 likes