Per-project sessions
projectile-session-mode is a global minor mode that gives every project
its own tab-bar
tab, so each project keeps its own window layout and you flip between
projects by flipping between tabs.
(projectile-session-mode +1)
Enabling the mode turns on tab-bar-mode and makes project switching
tab-aware:
-
Switching to a project that already has a tab selects that tab, restoring the project’s live window layout, rather than re-opening a file and clobbering it.
-
Switching to a project for the first time opens a fresh tab dedicated to it, names the tab after the project, and populates it by running
projectile-session-default-action(by defaultprojectile-find-file).
If you enable the mode while already visiting a project, the tab you’re on is adopted for that project instead of being left unowned.
Tabs are named after their project. Two checkouts that share a name
(e.g. two foo directories) are disambiguated by prepending a
parent-directory component, so you get work/foo and home/foo. You can
override the naming entirely via projectile-session-tab-name-function,
a function that receives a project root and returns the tab name.
Disabling projectile-session-mode restores
projectile-switch-project-action to whatever it was when you enabled
the mode; it leaves tab-bar-mode and any existing tabs in place.
The session commands live on the w sub-prefix of the Projectile command
map, and are also reachable from projectile-dispatch and the Projectile
menu:
| Keybinding | Command |
|---|---|
s-p w s |
|
s-p w S |
|
s-p w r |
|
s-p w R |
|
s-p w f |
|
s-p w b |
|
Switching buffers within a session
projectile-session-switch-to-buffer (s-p w b) completes over just
the buffers of the current tab’s project, falling back to a plain
switch-to-buffer when the current tab holds no project. It is a separate
command rather than a global remapping of switch-to-buffer, so plain
switch-to-buffer still sees every buffer.
| This mode makes the aging persp-projectile unnecessary for tab-based project workflows. |
Persisting sessions across restarts
A project’s tab keeps its layout only while Emacs is running. You can also persist a project’s session (its window layout plus the buffers it shows) to disk and restore it in a later Emacs:
-
projectile-session-savewrites the current frame’s layout and buffers as the current project’s session. Since it reads the live frame, it saves whichever project’s tab is selected. -
projectile-session-save-allsaves every open project’s session in one go, selecting each project tab in turn (and returning you to the tab you started on). Unlike autosave below, it runs regardless ofprojectile-session-autosave. -
projectile-session-restorerecreates that project’s buffers and puts the saved layout back. -
projectile-session-restore-allreopens every saved project’s session, each into its own tab (see below). -
projectile-session-forgetdeletes a project’s saved session file.
Sessions are stored one file per project under
projectile-session-directory (by default a projectile-sessions/
directory under user-emacs-directory). Each file is named after the
project and keyed by a hash of its root, so distinct roots never collide
and the same root maps to the same file across restarts.
With projectile-session-restore-on-switch (on by default), switching to a
project that has no open tab but does have a saved session restores that
session instead of running projectile-session-default-action.
Set projectile-session-autosave to save automatically: the outgoing
project’s session is saved when you switch away from it, and every open
project’s session is saved when Emacs exits. Layouts with no serializable
buffer are skipped.
(setq projectile-session-autosave t)
Restoring every session at once
projectile-session-restore-all (s-p w R) reopens every saved
project’s session in one go, each into its own tab. A project that already
has an open tab is left alone rather than duplicated, so it’s safe to run
repeatedly. A session whose files are all gone (say the project was moved
or deleted) recreates nothing; its scratch tab is closed again instead of
being left empty, and it doesn’t count toward the number restored. When
everything is done you land on the first project that was restored.
To have Emacs reopen your saved projects automatically at startup, enable
the mode and set projectile-session-restore-on-startup in your init:
(projectile-session-mode +1)
(setq projectile-session-restore-on-startup t)
This runs projectile-session-restore-all once from emacs-startup-hook,
after your init files have finished loading, giving you a desktop-style
restore of all your projects. Because the handler is installed when the
mode is enabled and emacs-startup-hook fires only once, enabling the mode
after Emacs has already started never triggers a restore; set it up in your
init for the startup behavior.
Registering buffer serializers
Which buffers a session records, and how, is controlled by
projectile-session-buffer-serializers, an alist mapping a key to a
(serialize . deserialize) pair. The key is a major-mode symbol, the
symbol t (any file-visiting buffer), or a predicate function of one
argument (the buffer). serialize runs with the buffer current and
returns a read-able record (or nil to decline it); deserialize takes
such a record and recreates the live buffer (or returns nil when it
can’t, e.g. the file is gone).
Out of the box, ordinary file-visiting buffers (recording the file and
point) and dired-mode buffers (recording the directory) round-trip.
Buffers no serializer handles are simply skipped. To persist other kinds
of buffers, e.g. Magit or eshell, add a handler keyed by their major mode:
(defun my-eshell-serialize (_buffer)
(list :dir default-directory))
(defun my-eshell-deserialize (record)
(let ((default-directory (plist-get record :dir)))
(save-window-excursion (eshell t))))
(add-to-list 'projectile-session-buffer-serializers
'(eshell-mode . (my-eshell-serialize . my-eshell-deserialize)))
Handlers keyed by a major-mode symbol or by t round-trip fully; restore
dispatches on that key.
On restore, a window whose buffer can’t be recreated (its file was
deleted, say) falls back to a placeholder buffer so the restore never
errors. This is handled explicitly to keep working on Projectile’s Emacs
28.1 floor, which predates Emacs 30’s window-restore-killed-buffer-windows.
|