Buffers

Listing buffers

Every buffer created in Vim, at startup or later, is added to the buffer list where it is assigned a number that will never change until the buffer is explicitely wiped out from the list.

Beside that number, buffers are also named after the file they are associated with, if applicable. This gives us two ways to address a buffer:

  • by its number,

  • by its name.

A typical Vim session could consist of editing a single file—​and thus a single buffer—​or it could go on for weeks and consist of dozens and dozens of buffers. Keeping track of a potentially large number of buffers would be daunting if Vim didn’t gave us not one, not two, but three commands for listing buffers:

:buffers
:files
:ls

All those commands do the same thing so we will simply use the shortest: :ls. Here is what its output looks like:

1  h   "foo.txt"                 line 1
2 #h   "bar.txt"                 line 1
3 %a   "baz.txt"                 line 1
Press ENTER or type command to continue
  • The first column shows the buffer number.

  • The second column gives us information on the state of the buffer, h means “hidden”, a means “active”, % marks the current buffer and # marks the “alternate file”, which I usually think of as “previous buffer”.

  • The third column gives us the buffer name.

  • And the last column shows the current position of the cursor.

Switching buffers

But we have no cursor, no active line, no selection…​ Obviously, this list is not nicely interactive so what good is it? Well, the last line gives us a hint: pressing ENTER unsurprisingly makes the list go away but we can also press : to enter command-line mode and use an Ex command to do something with the info we get from the list.

Because of their central role, buffers come with a lot of navigation and management commands, let’s see a few things we can do with the list above:

command

description

:b1

jump to buffer number 1

:b#

jump back to previous buffer

:bd2

delete buffer number 2

Unlike “modern” editors, Vim can’t display a permanent list of loaded buffers by default. This may feel like a limitation if you are used to that feature but think about it for a moment…​ do you really need that list when you are not trying to jump to another buffer?

To sum it up, the most basic way to switch to another specific buffer is a two steps process:

command

description

:ls

jump to buffer number 1

:b24

jump back to previous buffer

Of course, it is not necessary to do :ls before doing :b24 but who wants to actively maintain a list of buffers in his head? Well…​ I certainly don’t.

One way to avoid thinking too much about your buffers would be to mash :bn[ext] and :bp[revious] until you get where you want. Because the buffer list is one-dimensional, you are sure to eventually get to the right buffer but cycling is not really a silver bullet when your destination is sixteen buffers away. There must be a more deterministic solution.

Let’s go back to that boring :ls, then <CR>, then :b24, then <CR> routine which looks like a good candidate for a nice, mnemonic, custom mapping. Flying is faster than cycling, right?

Following the pattern we established earlier, we could add something like this to our vimrc:

nnoremap <leader>b :ls<CR>:b<Space>

which seems to work perfectly:

(gifcast)

But there’s something fishy with that mapping: it certainly is easy to remember, easy to type, and easy to think about…​ but it doesn’t really behave like its friend <leader>f. Hmm…​ let’s try something different.

Vim is rather famous for the way its normal mode commands span the entire keyboard but there are a few gaps, here and there, that we can take to our advantage.

One of those gaps can be found in how operators work. Operators are normal mode commands that don’t do anything by themselves; they wait for us to tell them on what to operate. c, for example, is an operator, just like y or d, that expects a text-object or motion. If we don’t provide one, operators will keep waiting until we decide to give them a purpose. As an example, here is the command for “changing from the cursor to the end of the current word”:

ce

Now, Vim comes with a bunch of motions like e, but not all keys are associated with a motion. As it turns out, we can associate unused keys with c to create a many normal mode combos that don’t do anything:

ca  cd  cm  co  cp  cq  cr  cu  cv  cx  cy  cz
cA  cD  cO  cP  cQ  cR  cU  cV  cX  cY  cZ

And the same can be done with other operators:

da  dc  dm  dq  dr  du  dv  dx  dy  dz
dA  dC  dQ
va  vc  vd  vm  vo  vp  vq  vr  vs  vu  vv  vx  vy  vz
vA
ya  yc  yd  ym  yo  yp  yq  yr  ys  yu  yv  yx  yz
yA

There’s another—​slightly smaller—​gap: “commands starting with g” and “commands starting with z”. Even if they work differently, those two-characters commands look a lot like the operator-based commands above and the missing pairs can be used in the same way:

gb  gc  gl
gB
zp  zq  zu  zy

Hmm…​ gb sounds a lot like “goto buffer”, doesn’t it? Here we go:

nnoremap gb :ls<CR>:b<Space>

Now, pressing gb in normal mode opens the buffer list like :ls and populates the prompt with :b, ready for us to perform an operation on one or more listed buffers:

(gifcast)

Not bad at all, and that’s one more mapping to add to our vimrc.

But there’s one problem with numbers: the file-to-number relationship is not very intuitive. We Humans are naturally better at names than numbers so the second way to address buffers, names, may have a lot of potential here.

Well, it happens that the buffer commands we used before, :b and :bd, can take a buffer name (even partial) as argument so switching to a buffer, especially with tab-completion and the wildmenu, can be very close to friction-less:

(gifcast)

Again, we can easily create a convenient mapping to save a bunch of keystrokes:

nnoremap <leader>b :buffer *

The similarity with the <leader>f mapping we added to our vimrc in the previous chapter and the <leader>e mapping it replaced is not a happy coincidence. We have two commands with a similar purpose—​navigation—​that work exactly the same way and follow the same naming convention:

  • f for “file”,

  • b for “buffer”.

With <leader>f and now <leader>b we have the beginning of a collection of easy-to-remember and consistant navigation mappings—​exactly the kind of mapping we need—​as well as another one, gb, which simply demonstrates another way to approach the same problem.

Working across buffers

We shouldn’t be concerned with buffers only for navigation, of course: Vim allows us to perform actions on each buffer in the buffer list with the :bufdo command:

:bufdo %s/foo/bar/g
:bufdo set number

:bufdo can be used to perform an identical edit on every listed buffer, set a local option or do whatever complex thing we need.

By the way, did you notice that Vim doesn’t ask us to write each buffer before changing to another one? That’s because of the hidden option we enabled at the start of our journey. With nohidden, the default value, juggling with buffers would be a lot less fun.

Reference

:help windows
:help :buffer
:help :bnext
:help :bprevious
:help :ls
:help :bufdo

Windows

Creating windows

Splitting the current window in two side-by-side views is done with either <C-w>v--all window-related commands start with <C-w>--or :vsplit (shortened to :vs).

Splitting the current window in two windows one above the other is done with either <C-w>s or :split (shortened to :sp).

By default, the splitting is done above and to the left of the current window. This can be very counter-intuitive but that behavior is easily changed by prepending :vs or :sp with :bel[owright]:

:bel vs
:bel sp

or—​more constructively—​by adding those two options to your vimrc:

set splitright
set splitbelow

The funny thing with window-splitting, though, is that the vocabulary is a bit counter-intuitive. :vsplit, for example, splits the current window along the horizontal axis to obtain vertical windows and :split does its splitting along the vertical axis to obtain horizontal windows.

vertical       horizontal
splitting      splitting
┌────┬────┐    ┌─────────┐
│    │    │    │         │
│    ←    │    ├─── ↑ ───┤
│    │    │    │         │
└────┴────┘    └─────────┘

OK. Maybe I’m the only one finding that “funny”.

:vs and :sp can be used with or without argument. When given an argument, they do their usual splitting and edit the given file in the new window. Basically, :vs file2 is the same as :vs followed by :e file2.

Moving the cursor between windows

If buffers and windows had a one-to-one relationship, listing windows would make just as much sense as listing buffers for navigation purpose but there is no such relationship and no built-in method to address a window by its name.

Moving the cursor to another window usually involves the <C-w> prefix, followed by a letter indicating the direction or destination:

command

description

<C-w>h

moves the cursor to the window on the left of the current window

<C-w>j

moves the cursor to the window below the current window

<C-w>k

moves the cursor to the window above the current window

<C-w>l

moves the cursor to the window on the right of the current window

<C-w>t

moves the cursor to the topmost/leftmost window

<C-w>b

moves the cursor to the bottommost/rightmost window

<C-w>w

moves the cursor to the window directly below/right of the current window

<C-w>W

moves the cursor to the window directly above/left of the current window

Note
Prepending <C-w> with a count moves the cursor by count windows.
(gifcast)

Then comes my favorite window-related command:

command

description

<C-w>p

moves the cursor to the previous window

which makes alterning between two windows a breeze.

(gifcast)

Moving and resizing windows

Sometimes, building the perfect layout means moving windows around:

command

description

<C-w>H

moves the current window to the far left of the screen

<C-w>J

moves the current window to the bottom of the screen

<C-w>K

moves the current window to the top of the screen

<C-w>L

moves the current window to the far right of the screen

or resizing them:

command

description

<C-w>>

increases the width of the current window

<C-w><

decreases the width of the current window

<C-w>+

increases the height of teh current window

<C-w>-

decreases the height of the current window

<C-w>_

makes the current window as tall as possible

<C-w>|

makes the current window as wide as possible

<C-w>=

makes all windows more or less equally tall and equally wide

But, to be perfectly honest, I rarely have multiple windows and, when I do, I prefer to use my mouse/trackpad to resize them. YMMV.

Here is a last command to wrap up the subject:

command

description

<C-w>T

moves the current window to a new tab page

Closing windows

Reference

Tab pages

So…​ how do we use them?

Working with tab pages

Creating a tab page is easy:

:tabnew            " creates a new tab page containing one window
                   " displaying a new, unnamed, empty buffer.
:tabe[dit] file    " creates a new tab page containing one window
                   " displaying a buffer associated with 'file'.
:tabf[ind] file    " works similarly as the previous one but uses
                   " ':find' instead of ':edit'.

The commands above can’t be easily shortened so one could very well create custom mappings along the same line as the ones we created earlier:

nnoremap <leader>tn :tabnew<CR>
nnoremap <leader>te :tabedit **/*
nnoremap <leader>tf :tabfind *

Closing a tab page is just as easy:

:tabc[lose]      " closes the current tab page,
:tabc[lose] 3    " closes tab page number 3,
:tabo[nly]       " closes all tab pages except the current one.

Note that closing a tab page also closes its windows but it doesn’t delete the buffers currently displayed in those windows. Remember : windows are views and tab pages are assemblages of windows.

Switching to another tab page is also well covered and pretty intuitive:

:tabfir[st]
:tabn[ext]
:tabp[revious]
:tabl[ast]

In normal mode, gt is the equivalent of :tabnext and gT is the equivalent of :tabprevious.

Here are a few example usages:

(pic)
(pic)
(pic)
(gifcast)

Like with buffers and windows, it is also possible to execute a command in each tab page: :tabdo command:

(gifcast)

Reference

:help tab-page