1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
|
# Perspective for Emacs
## Description
The Perspective package provides multiple named workspaces (or "perspectives")
in Emacs, similar to multiple desktops in window managers like Awesome and
XMonad, and Spaces on the Mac.
Each perspective has its own buffer list and its own window layout. This makes
it easy to work on many separate projects without getting lost in all the
buffers. Switching to a perspective activates its window configuration, and when
in a perspective, only its buffers are available (by default).
Each Emacs frame has a distinct list of perspectives.
Perspective supports saving its state to a file, so long-lived work sessions may
be saved and recovered as needed.
## Sample Use Cases
### Multiple Projects
Working on multiple projects can become difficult to organize as their buffer
lists mix together during a long-running Emacs session. Searching for a buffer
by name works well if you know what to search for, but sometimes picking from a
list is easier — in which case, keeping the list well-pruned for relevant
buffers becomes an important source of efficiency in file and buffer management.
Perspective helps out by letting you keep separate named buffer lists and window
layouts.
This use case works really well in conjunction with
[Projectile](https://github.com/bbatsov/projectile). Projectile helps with
buffer navigation (and other project-specific tasks) in cases when a project has
a well-defined root directory. Perspective then steps in to help manage
unrelated buffers: shells, REPLs, `dired` buffers visiting directories outside
the project, or files relevant to the project not under the same root as the
rest of the source. It also helps deal with the situation of one project with
multiple source repositories where having a shared window layout or buffer list
makes sense.
### [Yak Shaving](http://catb.org/jargon/html/Y/yak-shaving.html)
Suppose you're developing feature X in perspective `feature-X`. This keeps you
working with one set of files and windows. You then realize that this feature
requires you to fix a bug in an unrelated set of files. You don't want to lose
all the context you have built up for feature X, so you open a new perspective,
`bugfix-Y`, letting you open new files and buffers without disturbing your work
on `feature-X`. Then you are asked to urgently look into something related to
development of feature Z, but again: you don't want to lose context. So you open
a new perspective `feature-Z`, and fill it with a whole bunch of new files and
windows — all without losing any of the context for your work on bug Y or
feature X.
When you finish looking at Z, you close perspective `feature-Z`, and return to
`bugfix-Y`, and restore its window layout and buffer list. When you finish with
Y, you close perspective `bugfix-Y` and return to `feature-X`.
(Hint: this workflow works best with the `persp-sort` variable set to `'created`
— see documentation below.)
## Similar Packages
The following Emacs packages implement comparable functionality:
- [persp-mode](https://github.com/Bad-ptr/persp-mode.el): A Perspective
fork, which implements perspective sharing between Emacs frames. It also has a
different approach to saving state and different configuration options. There
has been some
[interest](https://github.com/nex3/perspective-el/issues/88#issuecomment-513996542)
[expressed](https://github.com/nex3/perspective-el/issues/111) in merging the
two projects. _Due to conflicting function names, `persp-mode.el` and
Perspective cannot be installed simultaneously._
- [Workgroups 2](https://github.com/pashinin/workgroups2): Similar to
Perspective in terms of features. Its [original
codebase](https://github.com/tlh/workgroups.el) seems to predate Emacs
acquiring a native ability to serialize window layouts, so it has custom
serialization code.
- [eyebrowse](https://github.com/wasamasa/eyebrowse): Supports window layouts
but not buffer lists.
- [wconf](https://github.com/ilohmar/wconf): Supports window layouts but not
buffer lists.
- [ElScreen](https://github.com/knu/elscreen): Supports window layouts but not
buffer lists; seems unmaintained.
Emacs 27 includes two new buffer and window organizing features: Tab Line
(`global-tab-line-mode`) and Tab Bar (`tab-bar-mode`).
- Tab Line maintains a list of buffers which had been opened in a given window,
and anchors it to that window. It is analogous to tabs as used in web browsers
and other text editors, and therefore orthogonal to Perspective.
- Tab Bar maintains window layouts (with optional names). In this, it is similar
to Perspective. Unlike Perspective, it does not support buffer lists. Using
Perspective and Tab Bar at the same time is not recommended at this time,
since the tab list is global (i.e., will show up in all perspectives) and is
likely to cause confusion. It would be an interesting future feature for
Perspective to adopt the tab bar and allow keeping a distinct set of tabs
per-perspective.
## Compatibility
Perspective does not work with [Emacs
`desktop.el`](https://www.gnu.org/software/emacs/manual/html_node/emacs/Saving-Emacs-Sessions.html).
This is because Perspective state stores buffer and window information in frame
parameters, and `desktop-save-mode` does not support saving those types of data.
Instead, Perspective provides its own [disk save and
load](#saving-sessions-to-disk) feature, which cleanly saves perspectives.
## Installation
You should install Perspective from [MELPA](https://melpa.org/) or [MELPA Stable](https://stable.melpa.org/).
Users of [`use-package`](https://github.com/jwiegley/use-package) can install Perspective as follows:
```
(use-package perspective
:config
(persp-mode))
```
Alternately, put `perspective.el` from this source repository in your load path
and run `(require 'perspective)`.
Users of Debian 9 or later or Ubuntu 16.04 or later may simply `apt-get install
elpa-perspective`, though be aware that the stable version provided in these
repositories is likely to be (extremely) outdated.
## Usage
To activate perspective use `(persp-mode)`. This creates a single default `main`
perpsective.
Commands are all prefixed by `C-x x` by default. To change the prefix key,
customize `persp-mode-prefix-key`. Additionally, creating a key binding for
`persp-mode-map` will also activate the prefix.
Here are the main commands defined in `persp-mode-map`:
- `s` — `persp-switch`: Query a perspective to switch to, or create
- `` ` `` — `persp-switch-by-number`: Switch to perspective by number, or switch
quickly using numbers `1, 2, 3.. 0` as prefix args; note this will probably be
most useful with `persp-sort` set to `'created`
- `k` — `persp-remove-buffer`: Query a buffer to remove from current perspective
- `c` — `persp-kill` : Query a perspective to kill
- `r` — `persp-rename`: Rename current perspective
- `a` — `persp-add-buffer`: Query an open buffer to add to current perspective
- `A` — `persp-set-buffer`: Add buffer to current perspective and remove it from all others
- `b` - `persp-switch-to-buffer`: Like `switch-to-buffer`; includes all buffers
from all perspectives; changes perspective if necessary
- `i` — `persp-import`: Import a given perspective from another frame.
- `n`, `<right>` — `persp-next`: Switch to next perspective
- `p`, `<left>` — `persp-prev`: Switch to previous perspective
- `C-s` — `persp-state-save`: Save all perspectives in all frames to a file
- `C-l` — `persp-state-load`: Load all perspectives from a file
Since Perspective maintains distinct buffer lists for each perspective, it helps
to use a Perspective-aware buffer switcher.
**Ido**: [Interactive Do (Ido,
`ido-mode`)](https://www.gnu.org/software/emacs/manual/html_node/ido/index.html),
in particular its `ido-switch-buffer` command, is automatically
Perspective-aware when `persp-mode` is enabled.
**`bs.el`**: Perspective provides a wrapper for
[`bs-show`](https://www.gnu.org/software/emacs/manual/html_node/emacs/Buffer-Menus.html):
`persp-bs-show`. When this function is called normally, it shows a list of
buffers filtered by the current perspective. With a prefix argument, it shows a
list of buffers in all perspectives.
**IBuffer**: Perspective provides a wrapper for
[`ibuffer`](https://www.gnu.org/software/emacs/manual/html_node/emacs/Buffer-Menus.html):
`persp-ibuffer`. When this function is called normally, it shows a list of
buffers filtered by the current perspective. With a prefix argument, it shows a
list of buffers in all perspectives.
**Helm**: Perspective ships with buffer-listing advice for Helm, so Helm's
buffer listing code should be automatically Perspective-aware when `persp-mode`
is enabled. (Older versions of Helm relied on the machinery of `ido-mode` for
listing buffers, so they did not require this advice; see [`this Helm
commit`](https://github.com/emacs-helm/helm/commit/f7fa3a9e0ef1f69c42e0c513d02c9f76ea9a4344)
and [`this Perspective
commit`](https://github.com/nex3/perspective-el/commit/c2d3542418967b55f05d5b5ba71c9fbfe4cd3d4f)
for details.)
**Ivy / Counsel**: Perspective provides two commands for listing buffers using
Ivy and Counsel: `persp-ivy-switch-buffer` and `persp-counsel-switch-buffer`.
When these functions are called normally, they show a list of buffers filtered
by the current perspective. With a prefix argument, they shows a list of buffers
in all perspectives. The distinction between the `ivy` and `counsel` versions is
the same as between `ivy-switch-buffer` and `counsel-switch-buffer`: the latter
shows a preview of the buffer to switch to, and the former does not.
Globally binding one of these helper functions to a buffer-switching key is a
good idea, e.g.:
```emacs-lisp
(global-set-key (kbd "C-x C-b") (lambda (arg)
(interactive "P")
(if (fboundp 'persp-bs-show)
(persp-bs-show arg)
(bs-show "all"))))
```
### Notes on `completing-read` Enhancements
Users of a `completing-read` enhancement framework (such as Ivy or
[Selectrum](https://github.com/raxod502/selectrum)) may wish to use the
following two functions:
- `persp-switch-to-buffer*` replaces `switch-to-buffer`
- `persp-kill-buffer*` replaces `kill-buffer`
Both these functions behave like the built-ins, but use `completing-read`
directly. When called normally, they list buffers filtered by the current
perspective. With a prefix argument, they list buffers in all perspectives.
The following sample `use-package` invocation changes Emacs default key bindings
to use the replacements:
```
(use-package perspective
:bind (("C-x b" . persp-switch-to-buffer*)
("C-x k" . persp-kill-buffer*))
:config
(persp-mode))
```
## Saving Sessions to Disk
A pair of functions, `persp-state-save` and `persp-state-load`, implement
perspective durability on disk. When called interactively, they prompt for files
to save sessions to and restore from.
A custom variable, `persp-state-default-file`, sets a default file to use for
saving and restoring perspectives. When it is set, `persp-state-save` may be
called non-interactively without an argument and it will save to the file
referenced by that variable. This makes it easy to automatically save
perspective sessions when Emacs exists:
```
(add-hook 'kill-emacs-hook #'persp-state-save)
```
A limitation of `persp-state-save` and `persp-state-load` is that they do not
attempt to deal with non-file-visiting buffers with non-trivial state. Saving
shell, REPL, and `compilation-mode` buffers is not supported. When saved to a
file, any windows pointing to them are changed to point to the perspective's
`*scratch*` buffer. (Live windows are, of course, left alone.)
## Customization
Perspective supports several custom variables (see its section in `M-x
customize`). The following are likely to be of most interest:
- `persp-sort`: Select the order in which to sort perspectives when calling
`persp-switch`. Defaults to `'name` (alphabetical), but `'access` (by most
recently accessed) and `'created` (by order created) are available. Note that
`persp-switch-by-number` is likely to be confusing when this is set to
`'access`, as the numbers associated with a perspective will change all the time.
- `persp-interactive-completion-function`: Used for prompting for a perspective
name. `completing-read` is the default, with `ido-completing-read` enabled
with `ido-mode`. `ivy-completing-read` is broadly compatible, but
unfortunately sorts alphabetically and therefore breaks the `persp-sort`
setting. Helm, unfortunately, does not have a `completing-read` compatible
implementation out of the box (`helm-completing-read-default-1` purports to be
this but does not have the same `&optional` defaults). _`ido-completing-read`
is the recommended setting here unless a `completing-read` enhancement
framework is used._
- `persp-mode-prefix-key`: Changes the default key prefix for Perspective
commands.
- `persp-state-default-file`: Changes the default file to use for saving and
loading Perspective state.
- `persp-show-modestring`: Determines if Perspective should show its status in
the modeline. It defaults to `t`, but can also be `nil` (turning off the
modeline status display) or `'header` (which uses the header line instead of
the modeline).
- `persp-modestring-short`: When set to `t`, show a shortened modeline string
with only the current perspective instead of the full list. Defaults to `nil`.
To change keys used after the prefix key, with `use-package` you can do:
;; remap n to N to switch to next perspective
(use-package perspective
:bind (
:map perspective-map
("n" . nil)
("N" . persp-next)))
Or without `use-package`:
(define-key perspective-map (kbd "n") nil)
(define-key perspective-map (kbd "N") 'persp-next)
## Some Musings on Emacs Window Layouts
The following discussion exceeds the needs of documenting Perspective, but it
falls in the category of helping users learn to manage Emacs sessions, and
therefore will likely help potential users of Perspective make the experience
smoother.
Emacs has bad default behavior when it comes to window handling: many commands
and modes have a habit of splitting existing windows and changing the user's
carefully thought-out window layout. This tends to be a more serious problem for
people who run Emacs on large displays (possibly in full-screen mode): the
greater amount of screen real estate makes it easy to split the frame into many
smaller windows, making any unexpected alterations more disruptive.
As a result of indiscriminate-seeming window splits and buffer switching in
existing windows, new Emacs users can get into the habit of expecting Emacs and
its packages to lack basic respect for their layouts. Hence the popularity of
things like `winner-mode`, and packages like
[shackle](https://github.com/wasamasa/shackle).
This may make the value of Perspective seem questionable: why bother with
carefully preserving window layouts if Emacs just throws them away on a `M-x
compile`? The answer is to fix the broken defaults. This is fairly easy:
```emacs-lisp
(setq display-buffer-alist
'((".*"
(display-buffer-reuse-window display-buffer-same-window)
(reusable-frames . t))))
(setq even-window-sizes nil) ; display-buffer hint: avoid resizing
```
The Emacs framework responsible for "pop-up" windows is `display-buffer`. The
relevant [section of the Emacs
manual](https://www.gnu.org/software/emacs/manual/html_node/elisp/Displaying-Buffers.html#Displaying-Buffers)
is dense and difficult to read, so there have been attempts to summarize the
most important bits:
- https://ess.r-project.org/Manual/ess.html#Controlling-buffer-display
- https://old.reddit.com/r/emacs/comments/cpdr6m/any_additional_docstutorials_on_displaybuffer_and/ews94n1/
The suggested settings above do the following:
1. Tell `display-buffer` to reuse existing windows as much as possible,
including in other frames. For example, if there is already a `*compilation*`
buffer in a visible window, switch to that window. This means that Emacs will
usually switch windows in a "do what I mean" manner for a warmed-up workflow
(one with, say, a couple of source windows, a compilation output window, and
a Magit window).
2. Prevent splits by telling `display-buffer` to switch to the target buffer in
the _current_ window. For example, if there is no `*compilation*` buffer
visible, then the buffer in whichever window was current when `compile` was
run will be replaced with `*compilation*`. This may seem intrusive, since it
changes out the current buffer, but keep in mind that most buffers popped up
in this manner are easy to dismiss, either with a dedicated keybinding (often
`q`) or the universally-applicable `kill-buffer`. This is easier than
restoring window arrangements. It is also easier to handle for pre-arranged
window layouts, since the appropriate command can simply be run in a window
prepared for it in advance. (If this is a step too far, then replace
`display-buffer-same-window` with `display-buffer-pop-up-window`.)
|