I run Emacs fullscreen and have enough room for three buffers next to each other, the first two being 80-character wide and the last one being 74-character wide.
Setting this up correctly took me a few years. I did it incrementally.
First I used C-x 3
manually. Then I automated the process of splitting.
After a while I noticed how painful some tasks where. For instance,
the *compilation*
window would use any column in a seemingly random
fashion, or maybe even split an existing one in half, screwing the
whole layout. I also noticed that it was very convenient to be able
to swap or clone columns.
I finally have something I am confortable with. You can download it here. I will explain its features and how I use it. I will also go into the code in details, to serve as a tutorial.
File s3c.el
provides s3c
:
(provide 's3c)
So all you have to do is put s3c.el
next to other .el
files
and add the following to your .emacs
:
(require 's3c "s3c" 'noerror)
The 'noerror
option ensures that if s3c.el
is not installed,
Emacs will not complain. This makes it easier to copy your .emacs
to another computer.
If you are not interested in reading about how it works, here are the default important key bindings:
F1
: select left or center column;S-F1
: select right column;F11
: clone current buffer;F12
: swap left and center columns.The first function is s3c-initial-setup
, which actually splits Emacs.
(defun s3c-initial-setup ()
(interactive "")
(delete-other-windows)
(setq s3c-window-1 (selected-window))
(split-window-horizontally 82)
(switch-to-buffer "*scratch*")
(other-window 1)
(setq s3c-window-2 (selected-window))
(split-window-horizontally 82)
(other-window 1)
(switch-to-buffer "*Messages*")
(setq s3c-window-3 (selected-window))
(other-window 2)
(message "Setup 3 Columns - Done")
)
Note that there is only one Emacs window in the window manager sense. This Emacs window is split into three columns, and the Emacs term for each column is "window".
Let's see how it works:
first, it resets Emacs to having only one window;
then it remembers the current window as s3c-window-1
;
then it splits the window in two, with the left-hand side taking 82 characters (there are 2 empty columns, so the actual text area is 80-character wide);
then it switches the left window to buffer *scratch*
;
then it moves the cursor to the next window, i.e. the right-hand side;
then it remembers the current window as s3c-window-2
;
then it performs the same actions again to create the third column,
named s3c-window-3
which is set to contain the *Messages*
buffer;
it eventually puts the cursor in the center window, which still contains the original buffer.
So in the end there are three columns. The left one contains *scratch*
,
the center one contains the original buffer, and the right one contains
*Messages*
.
You don't actually have to call s3c-initial-setup
.
It is automatically called by another function
named s3c-check-setup
if needed. Function s3c-check-setup
is
itself called by most other functions. It checks
whether s3c-window-1
, s3c-window-2
and s3c-window-3
exist and
if they don't, it calls s3c-initial-setup
.
(defun s3c-check-setup ()
(interactive "")
(if
(not
(and
(boundp 's3c-window-1)
(window-live-p s3c-window-1)
(boundp 's3c-window-2)
(window-live-p s3c-window-2)
(boundp 's3c-window-3)
(window-live-p s3c-window-3)
)
)
(progn
(message "Setup 3 Columns - Automatic Initialization")
(s3c-initial-setup)
)
)
)
Once the windows are set up, we need some convenient functions to use
them efficiently. The first function is s3c-select-opposite-column
,
which moves the cursor to the left window if it was in the center one,
or in the center one if it was in the left one. The idea is that the
right window is not used for editing, but to show buffers like
*compilation*
, so selecting it is not a priority.
(defun s3c-opposite-column ()
(if
(eq (selected-window) s3c-window-1)
s3c-window-2
s3c-window-1
)
)
(defun s3c-select-opposite-column ()
(interactive "")
(s3c-check-setup)
(select-window (s3c-opposite-column))
)
We do want to select the right column sometimes, so there is a function which does exactly that.
(defun s3c-select-third ()
(interactive "")
(s3c-check-setup)
(select-window s3c-window-3)
)
The next function is s3c-clone
, which causes the current buffer to be
cloned into the opposite column. In other words, it will be present
in both the left and center columns.
There are two ways I use cloning. The first one is when I want to view one part of a file while editing another. The second one is when I want to go edit another part of the current file, and be able to easily come back where I was after that. Sometimes those two uses overlap.
(defun s3c-clone ()
(interactive "")
(s3c-check-setup)
(let
(
(opposite-column (s3c-opposite-column))
)
(set-window-buffer opposite-column (current-buffer))
(set-window-hscroll opposite-column (window-hscroll (selected-window)))
(set-window-point opposite-column (window-point (selected-window)))
(set-window-start opposite-column (window-start (selected-window)))
)
)
Notice that it does not just set the buffer, it also copies the position of the cursor and of the scroll bar so that the two columns are exactly the same.
I prefer to edit the center column, because it's the one which is directly
in front of me. If the buffer I want to edit is in the left column,
I like to swap the left and center columns. This also keeps the buffer which
was in the center column visible. This is the motivation for
s3c-exchange
.
(defun s3c-exchange ()
(interactive "")
(s3c-check-setup)
(let
(
(old-column1-buffer (buffer-name (window-buffer s3c-window-1)))
(old-column1-hscroll (window-hscroll s3c-window-1))
(old-column1-point (window-point s3c-window-1))
(old-column1-start (window-start s3c-window-1))
(old-column2-buffer (buffer-name (window-buffer s3c-window-2)))
(old-column2-hscroll (window-hscroll s3c-window-2))
(old-column2-point (window-point s3c-window-2))
(old-column2-start (window-start s3c-window-2))
)
(set-window-buffer s3c-window-1 old-column2-buffer)
(set-window-hscroll s3c-window-1 old-column2-hscroll)
(set-window-point s3c-window-1 old-column2-point)
(set-window-start s3c-window-1 old-column2-start)
(set-window-buffer s3c-window-2 old-column1-buffer)
(set-window-hscroll s3c-window-2 old-column1-hscroll)
(set-window-point s3c-window-2 old-column1-point)
(set-window-start s3c-window-2 old-column1-start)
)
)
Once again, we don't just swap the buffers, we also swap the cursor positions and the current scrolling.
I sometimes wonder if I should add a call to s3c-select-opposite-column
as well, so that the buffer being edited swaps as well. My initial idea
was that I would always edit the middle buffer, but I have not yet succeeded
in training me to do that. Feel free to change the behavior if you want to!
You could also bind a key to call s3c-exchange
followed by
s3c-select-opposite-column
instead of modifying s3c-exchange
directly.
The right-column window receives all special buffers like *compilation*
or *help*
. Sometimes one wants to come back to *compilation*
after
looking at *help*
. There are several ways to do that, which involve
selecting the right column and either killing the buffer or just switching
to the other buffer. I added two functions to switch without having
to select the right column.
(defun s3c-third-next ()
(interactive "")
(s3c-check-setup)
(let
(
(old-current-window (selected-window))
)
(select-window s3c-window-3)
(next-buffer)
(select-window old-current-window)
)
)
(defun s3c-third-previous ()
(interactive "")
(s3c-check-setup)
(let
(
(old-current-window (selected-window))
)
(select-window s3c-window-3)
(previous-buffer)
(select-window old-current-window)
)
)
To be honest, I don't use those much, I usually just select the third
buffer using s3c-select-third
and then switch normally.
Last but not least, here is the code which ensures that special buffers
like *compilation*
are always displayed in the third column.
(defun s3c-display-buffer (buffer options)
(if
(boundp 's3c-window-3)
(if
(window-live-p s3c-window-3)
(set-window-buffer s3c-window-3 buffer)
nil
)
nil
)
)
(setq display-buffer-overriding-action '(s3c-display-buffer . nil))
This overrides the default behavior of display-buffer
, which is called
to open buffers like *compilation*
. If s3c-window-3
exists, it is
used. Else, the default behavior is used instead. This ensures that
if you are not currently using three columns you can still see
special buffers normally.
This particular part of the code is very important to me, as I find buffers which appear in unexpected places very disturbing. They may hide information that you are currently using. If you are currently viewing the same buffer in the left and middle column, displaying a buffer there would make you lose the cursor position and the scrolling from the buffer, as switching back to it will bring you to the position of the version which is still displayed. Switching back is also rather annoying. All in all, the default behavior breaks the workflow too much. Instead I prefer to dedicate a column for buffers which are displayed automatically. The behavior is much more consistent and you can be sure that the left and middle columns will not be touched, which is very important when cloning buffers.
File s3c.el
comes with some default key bindings:
(global-set-key (kbd "<f1>") 's3c-select-opposite-column)
(global-set-key (kbd "<S-f1>") 's3c-select-third)
(global-set-key (kbd "<M-f11>") 's3c-initial-setup)
(global-set-key (kbd "<f11>") 's3c-clone)
(global-set-key (kbd "<f12>") 's3c-exchange)
(global-set-key (kbd "<M-f12>") 's3c-third-previous)
(global-set-key (kbd "<S-M-f12>") 's3c-third-next)
You don't actually need M-F11
as s3c-initial-setup
is called
automatically when needed, so just typing F11
or F12
for instance
would be enough. F11
has two ones in it, so it was chosen for cloning,
while F12
has one-two in it, so it was chosen for swapping.
I used to bind F1
to other-window
, now it does the same but
never selects the third column; I use S-F1
for that.
M-F12
and S-M-F12
can be used to switch the buffer of the third
column, but I don't actually use them much.