Demystifying Emacs’s Window Manager
Emacs is a fantastic tiling window manager, and not enough people know that. There, it’s out now; I said it; now everybody knows. And thanks to a number of consolidations and improvements, it’s no longer the indomitable black box it once was.
At some point in the distant past, Emacs got into the business of handling frames. I’m sure you, like me, eschew frames in favor of windows. Frames are just that much harder to deal with — unless you’re using an actual tiling window manager, of course. But few do, and the baleful support for frames in regular window managers nix any temptation to make it work.
Later on, tabs also joined the family business.
M-x tab-bar-mode adds tab-style window configurations to Emacs, and
M-x tab-line-mode a way of browsing visible buffers.
Back to windows. They’re one of Emacs’s greatest assets, and they’re highly configurable. Historically it was a rather difficult affair to control window and buffer placement as you’d have to rein in a cavalcade of variables and functions and hope they did what you wanted. But not any more.
Modern Emacs is now able to mirror the paneling so beloved of IDE users, and without the tears! And you can easily control how or where buffers and windows must go, giving you even more control over your Emacs experience. That means you may not need a third-party window management library – if you ever tried to use one, that is – for most things.
So here’s a snapshot of what is possible today:
- Grouping Windows
You can now group windows so certain commands that affect one now affect all of them. They’re given the cryptic name atomic.
When you call
C-x 0and delete an atomic (ugh) window, all other windows that belong to that cohort also disappear. It does come with a couple of limitations, but it’s still a great feature.
- Dedicating Windows
You can dedicate a window to a buffer. Dedicated windows are locked to the buffer it was dedicated to, and so any attempt to switch its buffer will merely display that buffer in another window. And if you switch to a buffer that belongs to a dedicated window, Emacs selects the dedicated window instead.
This feature solves another common complaint: that it’s easy to screw up a perfectly manicured window layout – like
gdb’s debug view,
M-x calc, your fancy Org agenda setup, and so on – because you accidentally switched buffers in one of them.
You can now attach a window to one of the four sides of a frame: left, right, top or bottom. Sidebars are like their kin in other IDEs: they are a full-length (or full-width) windows that resist switching and splitting.
With a sidebar you can finally dedicate sides of a frame to specialized buffers, like the Calendar, Org Agenda, Compilation Output, GDB, you name it. And thanks to a handy command, you can toggle the sidebars to hide or show them all at once.
- Override window and buffer placement
Almost all window customization now involve one – albeit elephantine – variable that controls all of it.
Much like Emacs’s reformed minibuffer completion system you’re able to precisely control how Emacs creates and shapes windows using a tiered system of overrides. And package authors can still suggest a preferred layout that you can override if you want to.
Using a regular expression you can command all python buffers to appear only in open windows that already have python mode buffers; you can order
M-x eshellto always pop up a new window, no matter what; or insist that
M-x compilebuffers appear as a side bar on the right-hand side of your screen, and that it must never change your selected window’s buffer.
You can also dictate if you can switch to windows with
C-x o, or even prevent Emacs from deleting a window, and thus preserve your layout, even if you use
These four concepts give you near-complete control of all aspects of Emacs’s window management, and that’s not even everything. Yet they’re enough to put you firmly in the driver’s seat.
And the best thing about it all? Experimenting with it is easy and interactive. If you use my snippets of templates and examples you can use them as a starting point for your own window excursions.
Important Terminology and Concepts
There is a subtle but important distinction between displaying a buffer and switching to it. Switching is done with
C-x b (or its sibling commands, like
C-x 4 b) and it is the default user-facing key binding for switching a window’s buffer.
By default Emacs distinguishes between automatic and manual window switching. If you effect a window switch yourself with
C-x b, it’s manual — and exempt from any display action rules you create yourself.
You probably don’t want that. I recommend you set this:
;; Requires Emacs 27+ (setq switch-to-buffer-obey-display-actions t)
Now Emacs treats manual buffer switching the same as programmatic switching.
However, it also guards against (some) misbehaving commands you may encounter: those that call out to
switch-to-buffer programmatically. That is “against the rules”, as
switch-to-buffer is a user-facing command.
Picking the Right Window
Thanks to a concerted cleanup,
display-buffer is now the ultimate arbiter of where your buffer and/or window goes. And that’s a good thing indeed, as the previous system required a mish-mash of
let-bound variables and mystery functions to do what you wanted.
M-x display-buffer is both a command and an important internal function. It’s called in one of several ways. Here are two common scenarios:
- A function somewhere wants to compel Emacs to display a buffer in a frame, like
- You have manually switched buffers with
C-x 4 b,
M-x ibufferor some other means.
Eventually they – if they’re programmed right – call, either directly or indirectly,
display-buffer to do this. By centralizing the display of buffers you can more easily control how it happens.
display-buffer takes an optional
ACTION argument with a suggested display action. At this point in time, the
ACTION is a guideline. Emacs may, or may not, follow it; that’s important, because
display-buffer decides where things go, and it does so by consulting this list:
- First it checks
display-buffer-overriding-action, but that is for Lisp use only, so we won’t talk about it again.
- Then it checks
display-buffer-alist, which we very much will talk about, as it’s the most important variable in Emacs’s window management system.
- Then it checks the
Then it checks
display-buffer-base-action, which is a catch-all default.
It’s a user-facing variable you can configure, and unlikeAlthough we won’t talk about it much, it’s mostly there so you can build a catch-all rule for windows that don’t fall under rules 1, 2 or 3.
display-buffer-alistit can only hold one
At last it checks
display-buffer-fallback-action, but that is also for Lisp only.
The variable is only consulted last, and it’s the variable of last resort if there are no other actions that match.
Although the term
ACTION is thrown around a lot, it’s a bit of a misnomer, I think. It’s really a constraint. It is a mixture of “do that” along with “but only if these facts hold true”.
You see, when you ask Emacs to display a buffer it checks the aforementioned list. Each variable in that list may hold – particularly in the case of
display-buffer-alist – rules that Emacs must check before it decides on the next course of action.
The first set of constraints – actions – that satisfy Emacs, wins. It’s perfectly fine to have more than one set of constraints that match a given buffer; just know that the first one Emacs encounters is the one it picks.
So as you can see, the
ACTION argument in #3 is really more of a guideline, even though the package author had a particular layout in mind when they wrote it. That means elisp hackers can’t know for sure if Emacs will actually display a buffer the way they intended — good!
Look no further than
M-x speedbar for a disastrously bad example of curtailing choice. Speedbar only works in a standalone frame, and it’s impossible, without a third-party hack, to get it to display in a window.
display-buffer function checks a list of variables (and its own
ACTION argument) to determine what it needs to do. They’re all, in one way or another, made up of
Here’s the name of a function, and one part of, an action:
display-buffer-same-window. As its name alludes to, it’ll display a buffer in the same window (same refers to the selected window.) Its definition looks like this:
(defun display-buffer-same-window (buffer alist) (unless (or (cdr (assq 'inhibit-same-window alist)) (window-minibuffer-p) (window-dedicated-p)) (window--display-buffer buffer (selected-window) 'reuse alist)))
Asking Emacs to put something in the same window is only possible if:
This flag – more on it later – instructs Emacs to never put something in the same window. Which, of course, in the case of
display-buffer-same-windowdefeats the point of the display function!
The window is not the minibuffer.We can’t use that, for obvious reasons.
The window is not dedicated.
Dedicated windows resist attempts to change their current buffer.
If it’s none of those things, then the predicate succeeds and the buffer is displayed in the selected window. The search is over, and
display-buffer has done its job.
Note that there’s a mixture of both predicates and logic to carry out the work and that’s because – back to my argument about constraints – that in order for Emacs to put a buffer somewhere it must first ascertain whether it’s even possible.
In the unlikely event that Emacs is not able to place a buffer, it’ll eventually consult
display-buffer-fallback-action and pick an option from that list.
Consider this. If you split your frame into a kaleidoscope of windows, each doing their own thing, and you then add a bunch of custom rules, like:
*Help*windows go in a sidebar on the left;
- All Python buffers recycle existing Python windows;
*Compilation*mode buffers always pop up in a new window, split along the top, with a height of 10 lines.
*EShell*buffers, once shown, are marked dedicated.
You’re giving Emacs a large number of rules it must abide whenever you display a buffer. Not only must Emacs respect the rules you’ve defined in
display-buffer-alist, but it must also contend with the existing windows and their buffers in your frame.
Now where should the next buffer you want to display go?
That is the dilemma Emacs faces when it must place both a buffer and maybe create a window. For every rule you add, you are constraining Emacs’s ability to pick (what it thinks) is the best place for a window or a buffer.
Dedicated windows, sidebars, forcing Emacs to re-use a window, or enforcing a minimum height or width for a window — they all add up to to a laundry list of constraints.
Emacs is not always able to do what you tell it to. Given a sufficiently complex web of rules, it’s possible those rules conflict with one another (and your current window layout) and so Emacs is unable to follow those rules.
That is why Emacs has a “safety valve”, of sorts. It’ll do its darnedest to respect the rules you set, and if it can’t, it’ll turn maverick and just display it somewhere. That’s why, despite Emacs’s best intentions, it may switch a buffer in a window you told it not to touch, or even – as the last resort – create a brand-new frame.
It’s rare, but it can happen.
Trees, Windows and Internal Windows
Emacs tracks your current window layout in a tree structure called the window tree. The window tree represents the dimensions and location of every window in your current frame. A tree structure is ideally suited to this task, as subdivisions are either horizontal or vertical, which Emacs represents as nodes in the tree. The tree also knows where your minibuffer is, but that is not very interesting to us, although it has a window also.
You can browse your current window tree by evaluating
When you split a window, Emacs creates a new node in the tree that holds the live windows (the ones with buffers in them.) But as you successively split windows, you’ll end up with nodes in the tree that aren’t actual windows in your frame. Those nodes are called internal windows.
Internal windows aren’t terribly interesting to us, so I won’t belabor their role in Emacs’s window tree much. The only thing you need to know is that whenever you split a window, Emacs needs to keep track of each distinct, live, window you see on the screen. It does so with ‘invisible’ windows called internal windows.
+----------+-----+-----+ | | | | | R1 | R2 | R3 | | | | | +----------+-----+-----+
R1 is the selected window and you want to split below, then all you need to do is call
C-x 2. But if you want to split across
R3 you can’t — not with
C-x 2 anyway.
But it is possible: but you must split from the shared parent of
R3 — an internal window.
Without an internal window, or some other structure that can represent the relationship between windows, you wouldn’t be able to split across those three windows.
Although it’s not super-duper important to know about, internal windows play an important role when you want to carry out actions that involve the parent, children or siblings of windows.
The default split commands –
C-x 2 and
C-x 3 – split below or to the right of the selected window. Sometimes, though, you may want to split from the root of the tree. So if you want a full-length window below or to the right of all your other windows, you can, with a little bit of elisp:
(defun mp-split-below (arg) "Split window below from the parent or from root with ARG." (interactive "P") (split-window (if arg (frame-root-window) (window-parent (selected-window))) nil 'below nil))
You’re free to replace
right if you want it to split to the right instead, or swap the order of the
if statement if you’d rather invert the operation.
If you want to bind it to a key, check out my article on binding keys in Emacs.
This command works well when you want to split against the parent of the selected window, or from the root with
split-window is the function that all the other ones call when they want to split a window. The distinguishing factor is the
WINDOW argument, as it controls the window you want to split from.
M-x mp-split-below yields:
+----------+-----+-----+ | | RT1 | RT2 | | +-----+-----+ | | RB2 | | L1 +-----+-----+ | | | | | RB1 | | | | +----------+-----------+
C-u M-x mp-split-below:
+----------+-----+-----+ | | | | | | RT1 | RT2 | | L1 | | | | +-----+-----+ | | RB1 | +----------+-----------+ | B1 | +----------+-----------+
This concept plays a role in a few of the display
ACTION functions we’ll look at later.
When you dedicate a window, you’re effectively saying that you would prefer that Emacs does not switch the buffer in that window for another. It’s not an ironclad guarantee; there are instances where Emacs is up against the wall and has to put something somewhere.
Having said that, window dedication works well, and you should definitely use it.
A dedicated window confers the following benefits:
Switching buffers manually with
C-x bfails with an error.
You can customize
switch-to-buffer-in-dedicated-windowand control how it behaves.
I set it to
pop, because I want it to open the buffer somewhere else instead. But there’s also
ignore, and it’ll ignore your switch;
nil, which raises an error and stops the switch; and
t, that undedicates the window and switches.
Note that if you use custom completion frameworks that circumvent
switch-to-buffer) like Ido Mode it may not behave exactly this way.
- Splitting a dedicated window with
C-x 3fails with an error.
- Emacs will not switch buffers in dedicated windows.
However, dedicated windows are not immovable objects. You can still delete the windows with
C-x 0 or
C-x 1; kill the buffers they belong to; or resize them.
set-window-dedicated-p handles the dedicating. Unfortunately it’s not exposed to users by default, so let’s fix that by making a little elisp helper that toggles dedication in the selected window:
(defun mp-toggle-window-dedication () "Toggles window dedication in the selected window." (interactive) (set-window-dedicated-p (selected-window) (not (window-dedicated-p (selected-window)))))
Note that the documentation is quite diffuse about the flag you can set. Here I toggle between
nil, but it accepts a range of non-
nil values. Values like
side is not as strong a dedication as setting it to
But yeah. Just toggle between
nil and it’ll generally behave the way you want.
Another approach is setting
dedicated flag directly in
display-buffers-alist, but more on that later.
The command is still useful, though: you can set and unset it as part of your window layout workflow, by using a keyboard macro.
Side windows are full-length windows attached to either the
right-hand side of a frame. You can have more than one side window attached to the same side of the frame, and you can have sidebars on more than one side at a time.
A side window and a dedicated window go hand-in-hand, and they share many similarities. Side windows are also dedicated, but they set
side, which is not as strong a promise as setting it to
Every side in a frame has a number of slots, controlled by the
window-sides-slots variable. If you want to use side windows, then I strongly recommend that you customize it and set limits on how many you’ll allow on each side. Once you exceed the number of slots, Emacs will instead cycle the buffers in the existing side bar windows.
Side windows are special because you can order them relative to one another using their
slot, an integer position.
They also benefit from a really neat command that toggles side windows visible or invisible:
M-x window-toggle-side-window. Really useful command to know about if you’re flanked on all sides and want to briefly toggle them off.
One thing to be aware of is that, if you’re experimenting with windows, that the frame’s window state can get out of whack. The fix is to evaluate this form:
(set-frame-parameter (selected-frame) 'window-state nil)
Controlling Buffer and Window Display
If you want to control where a buffer or window must appear, you must customize
It’s a little… complex. Well, actually, once I explain how it all fits together, you’ll see that it is not.
The first thing you should do is customize it with
M-x customize-option. Insert an entry and click around a bit to get a feel for what it’s about. Don’t worry too much about making sense of the component parts of it yet. I’ll explain that in a moment.
First things first though. I want you to consider customizing these, and at least consider why I want you to change them:
Controls what happens if you, as a user, attempt to switch buffers in a dedicated window. (Remember, sidebars are also a form of dedicated window.)
popto the default, as I’d rather have it pop up the buffer somewhere else than simply error out. That then goes hand-in-hand with the next variable.
nil, the default, user-switched buffers are exempt from display buffer actions set in
display-buffer-alist& friends. Requires Emacs 27.
You probably want those rules to affect your interactive buffer switching, as it makes for a consistent buffer switching experience. I set it to
I find that some packages and functions disregard all rhyme and reason and use
display-bufferwhen they want to show a buffer to the user.
This is wrong. And the net result is that your display rules won’t apply if this variable is not set to
Testing and Experimenting
If you’re not an elisp expert I recommend you use the Customize interface.
You can use it to browse and tweak
display-buffer-alist, even though my examples below use elisp. So my suggested workflow is:
- Evaluate the elisp you want to test.
You’ll see the newly-added entry, and you can modify it in-situ.(If it’s already open,
M-x revert-bufferreloads the Customize interface.)
- Apply changes with
C-c C-cto test them.
display-buffer-alist entry consists of:
CONDITION, which is either a string or a function
ACTION, which is either a function or a list of functions
- An optional
ALISTof flags and settings
Every entry must have a
CONDITION, which is either a regular expression matched against the buffer name, or a function that returns
t if a buffer name is a valid match or
nil if it is not.
Practically speaking you are limited to searches by buffer name with regular expressions. That’s almost always enough. If it is not, you have to use a function (and I’ll show you how to make one later on.)
Common reasons for needing a function include wanting a condition that triggers if a buffer belongs to a derived mode, for instance.
If you need help writing your regular expressions, then I recommend you use Emacs’s interactive regexp builder.
Choosing the Predicated
When you have defined your
CONDITION, you can give it either a singular
ACTION or a
(ACTION_1 ... ACTION_N) list of actions.
But what’s an
ACTION? Well, the description for
display-buffer does proffer a list of suggested
ACTION functions, but this is not the complete list.
To get the complete list you… well, you have to hope they’re sensibly named.
This is one way to find the builtin ones:
C-u C-h a ^display-buffer-[^-]
Note that you may find a few stray functions that have nothing to do with windows. But as you scan the list, you’ll see a pattern:
display-buffer-below-selected display-buffer-at-bottom ...
ACTION entries. Or as I prefer to think of them: predicated actions.
Recall that they not only determine where something goes, but also check if they’re able to carry out the action in the first place!
Whether you specify a single
ACTION or a list of
ACTIONs, the outcome is the same:
Emacs cycles through one of more
ACTIONS and it stops when it receives the window the buffer was displayed in.
That means you can order your preferred display methods in the order you want Emacs to try them. It also means that a sneaky display action can change stuff and not return a window — necessitating more work by Emacs. That is, as far as I know, only used in one place (so far.)
For instance, you can declare that
*Help* windows must:
display-buffer-reuse-window, which tells Emacs to find an existing
*Help*window and re-use that one.
- Next, try
display-buffer-pop-up-windowand pop up a new window with the buffer.
- And if that fails, the entire display entry is skipped and Emacs continues with the next one in
That example works like this:
(add-to-list 'display-buffer-alist '("\\*Help\\*" (display-buffer-reuse-window display-buffer-pop-up-window)))
Here’s a list of some of the more useful display buffer functions you can use. As always I encourage you to use apropos to find the complete list yourself.
NOTE: If you use the customize interface, know that not all the functions below appear in the dropdown. You may have to select
Other Functions and type in the name below for this to work.
Some of the
ACTION functions require additional setttings to work. More on how to set them below. I recommend you
C-h f the functions you want to use.
Attempts to display the buffer immediately below the selected window.
This is useful for contextual popups that you want near the selected window that triggered the buffer display.
You can optionally specify a setting of
window-min-heightthat governs the required minimum size of the new window.
Marks the window as atomic when it is displayed somewhere. It will ‘stick’ to nearby atomic windows, if such windows exist.
You can control where it is displayed relative to an existing window by setting
You must also specify a
sidewhere the new window is created, relative to
Useful for “shared” windows that must only exist as long as the other, related, windows exist.
One example is
M-x calcand its calculation window and history window.
Really intriguing option that pops up an internal child frame, as opposed to a top-level frame. So it’s more like an in-frame popup window than a standalone window.
You can control its frame parameters with
Displays a buffer in a window in an absolute direction from a window.
windowis the selected window, and so it’s a generalized version of
display-buffer-below-selected, but capable of displaying the buffer in any
If you set
windowto root you can split across the height or width of the entire frame.
NOTE: These display actions only return a valid window – thus ending the search – after initializing the tab, and only if the buffer is already present in a window. That means you can use these actions to initialize the tab and another action to refine what happens after that has taken place.
Places a buffer in a (new) tab. Note that this refers to
M-x tab-bar-modeand not the tab line mode.
Tab bars are effectively tabbed window configurations, and a great addition to Emacs.
display-buffer-in-tabpops a buffer in a tab named
tab-name(and optionally part of a
If the tab does not exist, it is first created. If you use
display-buffer-in-new-tabthen a new tab is always created, and existing ones are never reused.
Tab groups are collections of tabs; they are not displayed by default, so customize
M-x customize-option tab-bar-formatfirst.
Do not display the buffer in a window at all.
Useful if you want to suppress a buffer from showing. Great for throw-away buffer windows that insist on appearing even if you do not want them to.
Pops a buffer in a new window. If you want Emacs to always requisition a new window for a buffer, use this.
Force Emacs to reuse a window that has a buffer with
modesetting also works with a list of major modes. It also uses
derived-mode-pto check if a buffer’s major mode qualifies: that means you can type
prog-modeto catch all the many programming modes.
Reuse a window if it is already displaying that buffer.
So if you want Emacs to reuse the window that is already displaying the buffer, then use this. Note that you can force Emacs to disregard the window if it is the same window you’re in. Set
Much like their window cousins, you can instruct Emacs to pop up a buffer in another frame if is already displaying the buffer.
You can also tell it to always pop up a new frame, or simply use any frame it likes.
Forces Emacs to display a buffer in the currently selected window.
Note that it respects window dedication (including sidebars), so it will not work there.
Instructs Emacs to prefer the LRU (least recently used) window.
How it determines that is how often you (or Emacs) has interacted with a window.
Displays the buffer in some window in your frame. If you don’t care where a buffer is put, you can use this.
It also supports
inhibit-same-window, so if you want it to display anywhere but your current window, set that flag.
In addition to one or more
ACTION entries, you can – sometimes must – specify settings that fine tune how Emacs places a buffer or window.
I call them settings but they don’t have, as far as I can tell, a formal name beyond
ALIST in the description for
ALIST is just a generic word for association list, so I’ll refer to them as settings from now on.
You can find a list of settings in
C-h f display-buffer but it is, yet again, not a complete list. (But you should read the list of settings, nonetheless.)
ACTION functions have their own requirements. They are usually listed in that function’s docstring. An example is
The following two symbols, when used in ALIST, have a special meaning: ‘side’ denotes the side of the frame where the new window shall be located. Valid values are ‘bottom’, ‘right’, ‘top’ and ‘left’. The default is ‘bottom’. ‘slot’ if non-nil, specifies the window slot where to display BUFFER. A value of zero or nil means use the middle slot on the specified side. A negative value means use a slot preceding (that is, above or on the left of) the middle slot. A positive value means use a slot following (that is, below or on the right of) the middle slot. The default is zero.
The manual is also a good choice if you want further information. (Press
i in the Help buffer.)
I’ve included some of the more interesting
ALIST settings here that I think most people would find useful:
It defaults to
nil, but if you set it to
tthen Emacs will ignore any desire to place a buffer in your same (selected) window.
inhibit-same-windowto force Emacs to place a window in a different window from your current one, even if the function would prefer to use your current one.
This setting is used by
Controls the ultimate window size. Great if you know roughly how big you want the window to be, in height or width.
If you specify an integer then it’s a constant-sized window.
If you use a floating point number you can instead make it scale to the size of the frame.
0.25is 1/4th the frame size, for instance.
You can also give it a function and Emacs will attempt to use that function to size the window. Try
fit-window-to-buffer. Note that it may not be able to fit the window to the buffer!
The width and size settings only apply to some functions.
window-min-heightin particular only works with
Orders Emacs to preserve (or not) the height and/or width of a window. It’s a cons form with the following format:
(WIDTH . HEIGHT).
nilto preserve or not preserve the size in that direction.
Marks the window dedicated if
t. You can also set it to
side, but I don’t recommend that. Stick to
right) to determine the direction you want the window to be.
Controls the integer
slotwhere the window must go. Used by
display-buffer-in-directionand it places the window in
directionof the selected window.
directionmust be one of
Great if you prefer your
M-x calcwindows to always appear at the top or bottom.
Reuses an existing window provided it has one of
This is used by
display-buffer-reuse-mode-windowand it’s great if you want to reuse windows based on a major mode.
You can combine ‘similar’ major modes, like
help-modeand Emacs will dutifully put a buffer in any one of those.
Very useful, but requires that you know the names of useful window parameters.
There are many, and I recommend you explore a window of your choosing by evaluating this:
M-: (window-parameters (selected-window))
Of particular interest, I think, are the following:
t, the window is not deleted when you call
Great for side bars and other important windows.
t, the window is unselectable with
Great for windows where you want to look, but not interact with, the contents of the buffer in that window.
You can still click-select the window or
C-x bto it. It’s only removed from the
C-x o) cycle.
window-parameterssetting is itself an alist.
Putting it all together
Now that you know how each component part of a display entry works, let’s look at a few different ways of constructing them.
I still recommend you use the Customize interface if you’re not interested in hacking around with elisp.
The most basic of display entries looks like this:
(add-to-list 'display-buffer-alist '("\\*Compilation\\*" display-buffer-reuse-window))
We match against one buffer name,
*Compilation*; with one
display-buffer-reuse-window; and no
ALIST settings, so it’s not listed.
A more complicated example might look like this:
(add-to-list 'display-buffer-alist '("\\*info\\*" (display-buffer-in-side-window) (side . right) (slot . 0) (window-width . 80) (window-parameters (no-delete-other-windows . t))))
There’s a bit more going on here. I want
*info* windows in a side bar window; it must be on the right-hand
side and in
slot 0; the
window-width must be 80; and Emacs must set the window
no-delete-other-windows window parameter to
(add-to-list 'display-buffer-alist '("\\*Help\\*" (display-buffer-reuse-window display-buffer-pop-up-window) (inhibit-same-window . t)))
Here I insist that
*Help* buffers reuse any existing
*Help* window if such a window exists. And if that is not possible, it must pop up a new window. Furthermore, Emacs cannot use the same (selected) window, and it must use another.
Let’s talk limitations. There’s a large corpus of third-party packages – and a number of internal ones in Emacs – that don’t know or care about cooperating with you or your window configuration. More often than not it’s because they believe their settings are the best; they may well be right, but it’s still wrong. And older packages are at least partially excused, as they predate the modernization efforts. But there’s a large body of up-to-date packages that don’t create or manage windows correctly even when it’s most likely easier to do so.
If you encounter such a package – they are more common than you think – you have little in the way of recourse beyond working around the code, or raising an issue with the maintainer(s).
In particular, package authors must use
pop-to-buffer) and avoid the temptation to write long tracts of elisp that manicure a window immediately after displaying a buffer. Calling out to
display-buffer-xxxx actions is also not allowed.
Moving Around & Undoing Window Changes
If you’re not already aware,
M-x winner-mode is a builtin minor mode that lets you undo (and redo) window configuration changes. They’re bound to
C-c <left> and
C-c <right>, respectively, by default.
There’s also a near-equivalent for tab bar window configurations called
M-x tab-bar-history-mode. You can now run
M-x tab-bar-history-forward and
M-x tab-bar-history-backward to cycle through changes made in the active tab’s window layout.
I also want to quickly mention
windmove. It’s a package that makes it possible to move (switch to a buffer) in all four cardinal directions using – by default –
S-<left/right/up/down>. It notably got an upgrade in Emacs 28 to let you delete or create windows in a direction also.
You may prefer
C-x o. I prefer
M-o though as the default is awful. More about my personalized key bindings here.
Templates & Examples
Here’s a selection of templates that demonstrate all the concepts you’ve read so far. They’re designed to selectively override how Emacs displays buffers and windows.
I recommend, as always, that you experiment thoroughly. That is easiest if you don’t
add-to-list (it’s all too easy to end up with near-duplicated entries) but instead
setq or the Customize interface.
One common theme that I see a lot of newer users ask for is a way of repeating their favorite layouts, particularly between restarts or if the layout is changed. So I recommend you record a keyboard macro. Keyboard macros are more than capable of not only opening files, splitting windows or switching buffers, they can do nearly everything you can do. So give them a try. No elisp required.
Another suggestion is to use
tab-bar-mode. A tab is merely a window configuration — nothing more, nothing less.
Handy Helper Functions
Here’s a selection of helper functions you may find useful.
Match buffers against their major modes
(defun make-display-buffer-matcher-function (major-modes) (lambda (buffer-name action) (with-current-buffer buffer-name (apply #'derived-mode-p major-modes))))
NOTE: This requires
lexical-binding set to
This function returns a lambda function that matches against a list of
major-modes. You can use it in a backquoted form to quickly create a matcher that checks if a buffer’s major mode is
derived-mode-p from your list of
major-modes. (By all means create individually, named functions instead!)
Great when you can’t rely on the name of the buffer but must check the major mode.
Only match buffers that have a project
(defun mp-buffer-has-project-p (buffer action) (with-current-buffer buffer (project-current nil)))
nil if a buffer belongs to a project – i.e.,
C-x p p – and it is another way to match buffers.
Frustration-Free Window Recycling
This setup tames all those annoying windows that keep popping up when you run a command, even if there’s already a window with a similar buffer. VTerm is one such culprit.
NOTE: As I explained earlier, you can only modify well-behaving windows. Some packages – even some of the ones in Emacs – are not, and they may ignore your attempts to tame them.
Example 1: Preventing Popups & Reusing a Window
This snippet instructs any buffer named
*vterm* to reuse a window with a major mode of
t prevents Emacs from reusing the selected window if it’s a
vterm window (you’ll instead get a new window.)
(add-to-list 'display-buffer-alist '("\\*vterm\\*" display-buffer-reuse-mode-window ;; change to `t' to not reuse same window (inhibit-same-window . nil) (mode vterm-mode vterm-copy-mode)))
Example 2: Reusing Windows
Here I collect a bunch of buffer names and ask that they share their respective buffers’ windows. That is,
*xref* always reuses an
*xref* window; a
*grep* buffer a
*grep* window, etc.
(add-to-list 'display-buffer-alist `(,(rx (| "*xref*" "*grep*" "*Occur*")) display-buffer-reuse-window (inhibit-same-window . nil)))
Here I default to
If you have a random selection of buffers that pop up here and there, you can collect them all in one rule that forces them to reuse an existing window if such a window exists.
Example 3: Matching by major mode
One common problem is matching against one or more major modes. Sometimes, it’s simply not possible to match against the buffer name.
Consider Magit that has a large range of buffer names and major modes.
;; Required. But note that this _does_ change Magit's default buffer display behavior. (setq magit-display-buffer-function #'display-buffer) (add-to-list 'display-buffer-alist `(,(make-display-buffer-matcher-function '(magit-mode)) (display-buffer-reuse-mode-window display-buffer-in-direction) (mode magit-mode) (window . root) (window-width . 0.15) (direction . left)))
This snippet forces magit to reuse any
magit-mode-derived buffer window already present.
If there is no such buffer, Emacs will instead create a window in the
left-most direction with a width of 15% of the frame for all
You can also instruct Emacs to not show a window at all if it matches a buffer pattern.
*compilation* buffers. You can still switch to them, but their display is inhibited when created.
(add-to-list 'display-buffer-alist '("\\*compilation\\*" display-buffer-no-window (allow-no-window . t)))
Great for all those annoying popups where you only care about the contents when something goes wrong.
This setup emulates the paneling you often find in commercial IDEs. They are more often than not attached to the sides.
- The bottom of the screen is used for ephemeral Eshell or
Shellmode buffers, for command line shell access.
The right-hand side is a side bar for compiling and running scripts with
M-x compileor the output from
The sidebar is sacrosanct, so it prevents deletion with
C-x 1. It is also marked dedicated, so you cannot
C-x binside it by accident. This also stops Emacs from reusing it for other things.
- Any buffer named
test_is opened to the immediate right of the selected window.
This first part controls the shells. It will force Emacs to place them at the bottom, with a window height of no more than 30% of the size of the frame.
(add-to-list 'display-buffer-alist '("\\*e?shell\\*" display-buffer-in-direction (direction . bottom) (window . root) (window-height . 0.3)))
Controlling side windows is equally straight forward. I recommend you limit the number of side window slots –
nil means infinite, and a positive number the maximum you’ll allow – as that means Emacs swaps out the buffer in that
slot instead of creating a new entry. Great for situations where you have a handful of ephemeral things – compilation, test output, shell command output, etc. – that you want to share windows.
I also set a window parameter,
no-delete-other-windows, that prevents Emacs from destroying the side window when you type
C-x 1. It also has a fixed size of 80.
;; left, top, right, bottom (setq window-sides-slots '(0 0 1 0)) (add-to-list 'display-buffer-alist `(,(rx (| "*compilation*" "*grep*")) display-buffer-in-side-window (side . right) (slot . 0) (window-parameters . ((no-delete-other-windows . t))) (window-width . 80)))
And don’t forget: you can type
M-x window-toggle-side-windows to toggle them visible or hidden.
This snippet places a buffer to the immediate right of the current window if the buffer name starts with
(add-to-list 'display-buffer-alist `("^test[-_]" display-buffer-in-direction (direction . right)))
Tweak it to use something else, of course. Maybe header files if you’re hacking on C or C++.
NOTE: This makes use of Emacs’s
Also, I recommend you customize
tab-bar-format and change it so that you show formatted groups.
Grouping Buffers into a Named Tab
Let’s say you frequently work with Org mode files, and you want them to appear in a separate tab. That tab is your dedicated Org mode tab; only org-related stuff should appear in it.
(add-to-list 'display-buffer-alist `(,(make-display-buffer-matcher-function '(org-mode org-agenda-mode)) (display-buffer-in-tab display-buffer-in-direction) (ignore-current-tab . t) (direction . bottom) (window-height . .2) (tab-name . "🚀 My ORG Mode Files") ;; Optional (tab-group . "Org")))
This creates a display entry that routes
org-mode major modes to:
A tab calledIf you leave out
🚀 My Org Mode Files. If the tab does not exist, it is first created.
tab-nameEmacs will instead auto-generate it. That’ll generate lots of new tabs. So beware.
- The tab-group
Orgis also set up if it does not exist. The group is optional and is not a requirement.
If the buffer Emacs is displaying is already visible in a window in that tab bar, then Emacs stops the search.
However, if it is not, Emacs will continue with the search and apply
display-buffer-in-directionin that tab.
The newly-minted window is given a height of 20%.
As you can see,
display-buffer-in-tab is special: it’s capable of creating a tab and handing over the window display to a subordinate display routine.
Tabs for Elfeed
Here I force Elfeed to use unique buffers for each elfeed article. I then match both the generic search buffer and the entry pages and show them in a dedicated tab group called
📻 Elfeed. The tab name is auto-generated and sourced from the feed title.
(setq elfeed-show-entry-switch #'pop-to-buffer elfeed-show-unique-buffers t) (add-to-list 'display-buffer-alist `("^\\*elfeed-entry-" (display-buffer-in-tab) (dedicated . t) (tab-name . (lambda (buffer alist) (with-current-buffer buffer (concat "🚀 " (elfeed-feed-title (elfeed-entry-feed elfeed-show-entry)))))) (tab-group . "📻 Elfeed"))) (add-to-list 'display-buffer-alist `("\\*elfeed-search\\*" (display-buffer-in-tab) (dedicated . t) (tab-name . "📣 Entries") (tab-group . "📻 Elfeed")))
This creates a tab group for each project – as defined by
C-x p p and Emacs’s project management implementation – and with each buffer getting its own tab. Buffers without projects are ignored.
NOTE: This is more of a demonstration; you’d probably want to tweak this quite heavily to match your workflow which does, unfortunately, require some elisp skills. It’s also not 100% free of bugs or quirks either!
(defun mp-buffer-has-project-p (buffer action) (with-current-buffer buffer (project-current nil))) (defun mp-tab-group-name (buffer alist) (with-current-buffer buffer (concat "🗃 " (or (cdr (project-current nil)) "🛡 Ungrouped")))) (defun mp-tab-tab-name (buffer alist) (with-current-buffer buffer (buffer-name))) (add-to-list 'display-buffer-alist '(mp-buffer-has-project-p (display-buffer-in-tab display-buffer-reuse-window) (tab-name . mp-tab-tab-name) (tab-group . mp-tab-group-name))) ;;; OPTIONAL, but probably required for everything to work 100% (defun tab-bar-tabs-set (tabs &optional frame) "Set a list of TABS on the FRAME." (set-frame-parameter frame 'tabs (seq-sort-by (lambda (el) (alist-get 'group el nil)) #'string-lessp tabs))) (defun mp-reload-tab-bars (&optional dummy) "Reload the tab bars... because they're buggy." (interactive) (tab-bar-tabs-set (frame-parameter nil 'tabs))) (add-hook 'kill-buffer-hook #'mp-reload-tab-bars) (add-hook 'window-selection-change-functions #'mp-reload-tab-bars)
The optional code at the end is there to, ah… fix a few ‘quirks’ in tab bar’s implementation. Basically the tab groups (as you seem them in the tab bar) don’t sort by the tab group, so you end up with duplicates, which is weird. The hooks are there to try and rein in consistency bugs where the tab bar’s not updated as frequently as it should be.
With a little bit of work you can (probably!) coax Emacs to do what you want it to do. The window manager is not perfect by any means, but I think it’s good enough to accommodate a large range of workflows.
If you build something nifty, by all means let me know.