Using display-buffer-alist
How to use display-buffer-alist to show special buffers in same window in GNU Emacs
GNU Emacs is editor of my choice for decade and a half. My usual work flow is to have 3 windows in one singe frame. Most of the time I use GNU Emacs in terminal, so multiple frames are not an option. Also, with limited space in terminal, 3 windows are just enough.
This is how I split frame into 3 windows:
-----------------------------
| Main editing area | shell |
| |-------|
| | rest |
-----------------------------
'Main editing area' is window where I actually edit files. 'Shell' is where I run eshell
with guard
, gulp watch
or other automated build/test process. Last window, labeled 'rest', is what this article is about. That window I use for displaying help, flycheck errors, GNU Emacs completions, ELDoc, etc…
Splitting windows
This is part of my .emacs
configuration (comments added for clarification):
(defun sasa/split-windows()
"Split windows my way."
(interactive)
;; Create new window right of the current one
;; Current window is 80 characters (columns) wide
(split-window-right 80)
;; Go to next window
(other-window 1)
;; Create new window below current one
(split-window-below)
;; Start eshell in current window
(eshell)
;; Go to previous window
(other-window -1)
;; never open any buffer in window with shell
(set-window-dedicated-p (nth 1 (window-list)) t))
Special buffers for display-buffer-alist
Before GNU Emacs 24.X i have used special-display-regexp
and special-display-function
, but those are deprecated now. New way to choose where to display specific buffer is to use display-buffer-alist
. Unfortunately, documentation is not clear and good examples are almost impossible to find. Here is what I manage to create after some research and testing.
(defun sasa/display-buffer (buffer &optional alist)
"Select window for BUFFER (need to use word ALIST on the first line).
Returns thirth visible window if there are three visible windows, nil otherwise.
Minibuffer is ignored."
(let ((wnr (if (active-minibuffer-window) 3 2)))
(when (= (+ wnr 1) (length (window-list)))
(let ((window (nth wnr (window-list))))
(set-window-buffer window buffer)
window)))
)
(defvar sasa/help-temp-buffers '("^\\*Flycheck errors\\*$"
"^\\*Completions\\*$"
"^\\*Help\\*$"
;; Other buffers names...
"^\\*Colors\\*$"
"^\\*Async Shell Command\\*$"))
(while sasa/help-temp-buffers
(add-to-list 'display-buffer-alist
`(,(car sasa/help-temp-buffers)
(display-buffer-reuse-window
sasa/display-buffer
display-buffer-in-side-window)
(reusable-frames . visible)
(side . bottom)
(window-height . 0.33)
))
(setq sasa/help-temp-buffers (cdr sasa/help-temp-buffers)))
Lets ignore sasa/display-buffer
function for a moment and concentrate on the rest of the code. sasa/help-temp-buffers
is a list of regular expressions for buffer names. In while loop I'm adding those regular expressions into display-buffer-alist
list. That list takes, except regular expression, list of display functions and display options.
display-buffer-reuse-window
will try to display buffer in the window that already displays buffer with a same name. if not, next display function will be called.
sasa/display-buffer
will display buffer in window that I previously called rest
, but only if there are 3 windows. Sometimes, there are actually 4 windows. In *Completion*
, for example, mini-buffer is also in window-list
, always in first place. In that case we need to open buffer on the fourth window, not third. If there are no three windows, function will return nil
and next display function is called.
display-buffer-in-side-window
will pop up new window, based on provided options: on the bottom of the frame and take about 33% of available height.