romain.bardou.fr - blog RSS

Setting And Using Emacs In Three Columns

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.

Quick Start

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:

Initial Setup

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:

  1. first, it resets Emacs to having only one window;

  2. then it remembers the current window as s3c-window-1;

  3. 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);

  4. then it switches the left window to buffer *scratch*;

  5. then it moves the cursor to the next window, i.e. the right-hand side;

  6. then it remembers the current window as s3c-window-2;

  7. then it performs the same actions again to create the third column, named s3c-window-3 which is set to contain the *Messages* buffer;

  8. 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*.

Automatic Setup

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)
    )
  )
)

Selecting Columns

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)
)

Clone Buffer

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.

Exchange Windows

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.

Right-Column Buffer Switching

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.

Display Buffer

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.

Key Bindings

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.