DEV Community

Cover image for Mastering Vim: Buffers, Windows, and Your Text Editing Arsenal
Boone Cabal
Boone Cabal

Posted on • Edited on

Mastering Vim: Buffers, Windows, and Your Text Editing Arsenal

Introduction

In this tutorial, we will cover the basics of working with buffers and windows (called panes) in Vim using an example-driven approach. To get the most out of it, I strongly recommend that you keep Vim open and follow each step. Do not skip any steps. Just be patient and do each step, one at a time. Actively doing it will reinforce the concepts, making it easier to learn.

Buffers: Your Textual Workspaces

A buffer is an area in memory that stores text. Every time you open a file, its contents are read into a buffer, allowing you to make changes to that buffer without affecting the original file.

Viewing Buffers

Let's begin by opening Vim. By default, you will have one unnamed buffer. You can view all your buffers using :ls or :buffers.

Use :ls.

:ls
  1 %a   "[No Name]"                    line 1
Enter fullscreen mode Exit fullscreen mode

The buffer list produced by :ls uses the following format:

Number Indicator    Name                          Line
Enter fullscreen mode Exit fullscreen mode
     1        %a   "[No Name]"                    line 1
Enter fullscreen mode Exit fullscreen mode

All of these fields are self-explanatory except for Indicator. Here is an excerpt from the Vim help for :ls:

u    an unlisted buffer (only displayed when [!] is used)
        |unlisted-buffer|
 %   the buffer in the current window
 #   the alternate buffer for ":e #" and CTRL-^
 a   an active buffer: it is loaded and visible
 h   a hidden buffer: It is loaded, but currently not
        displayed in a window |hidden-buffer|
  -  a buffer with 'modifiable' off
  =  a readonly buffer
  R  a terminal buffer with a running job
  F  a terminal buffer with a finished job
  ?  a terminal buffer without a job: `:terminal NONE`
  +  a modified buffer
  x  a buffer with read errors
Enter fullscreen mode Exit fullscreen mode

The Indicator in the above :ls output is %a, which specifies that the buffer is active (a) and in the current window or pane (%). + is useful; it tells you if a buffer is modified. We'll cover another useful one, #, later.

:ls Helper Function

Throughout this article, we will be issuing command-line commands followed by the :ls command, like :bd | ls. As a convenience, I recommend adding the following function to your .vimrc:

" You must specify 'normal' for all normal-mode commands to work.
function! ExeCmdLs(cmd)
  if a:cmd =~ '^normal'
    exe a:cmd
    ls
  elseif empty(a:cmd)
    ls
  else
    exe a:cmd .. ' | ls'
  endif
endfunction

nnoremap <space>c :call ExeCmdLs('')<left><left>
Enter fullscreen mode Exit fullscreen mode

The above code includes a useful normal-mode mapping, <space>c, which places you in command-line mode, types :call ExeCmdLs('_'), and places your cursor at the _.

For the rest of the article, wherever you see :[command] | ls, this translates to using the <space>c[command] macro.

Saving Buffers to Files

Before we can create a new buffer, we need to save the active buffer to a file, You can do this using :w. Add the text File a.txt to the buffer, and then use :w a.txt | ls.

"a.txt" [New] 1L, 11B written
  1 %a   "a.txt"                        line 1
Enter fullscreen mode Exit fullscreen mode

Now it has a name of a.txt, a number of 1, and an indicator of %a, telling you it is the active buffer in the current window.

In addition to :w, you can save all open buffers using :wa, which stands for "write all."

Creating Buffers

:enew creates a new, unnamed buffer. Use :enew | ls:

:call ExeCmdLs('enew')
  1 #    "a.txt"                        line 1
  3 %a   "[No Name]"                    line 1
Enter fullscreen mode Exit fullscreen mode

We have named buffer a.txt (1) and an unnamed buffer (3); buffer 3 is the active buffer. Enter the text File b.txt, then use :w b.txt | ls.

"b.txt" [New] 1L, 11B written
  1      "a.txt"                        line 1
  3 %a   "b.txt"                        line 1
Enter fullscreen mode Exit fullscreen mode

Now we have two files to work with.

Deleting Buffers

:bd deletes the active buffer. Delete buffer b.txt (3) using :bd | ls.

"a.txt" 1L, 11B
  1 %a   "a.txt"                        line 1
Enter fullscreen mode Exit fullscreen mode

Now we're back to buffer a.txt.

Opening Files Into Buffers

:e opens a file in the active buffer. Open b.txt using :e b.txt | ls.

"b.txt" 1L, 11B
  1 #    "a.txt"                        line 1
  3 %a   "b.txt"                        line 1
Enter fullscreen mode Exit fullscreen mode

The # Indicator

Notice that buffer a.txt (1) has an indicator of #. This is the
last edited buffer. You can use :e # to toggle between the current
and last edited buffers. Use :e # | ls a few times and notice how
the indicators change.

Navigating Between Buffers

The following commands allow you to navigate between buffers:

  • :[N]bnext [N] or :[N]bn [N]: move to next buffer, where N is the buffer number.
  • :[N]bprev [N] or :[N]bp [N]: move to the previous buffer, where N is the buffer number.
  • :[N]buffer [N] or :[N]b [N]: move to specified buffer N, where N is the buffer number.
  • :bf[irst]: move first buffer in buffer list.
  • :bl[ast]: move to last buffer in buffer list.
  • :buffer [N] or :b [N]: move to buffer [N].

From the previous :ls command, we have buffers a .txt (1) and b.txt (3) open, with b.txt (3) being the active buffer. Use :bp | ls to navigate to buffer a.txt (1).

"a.txt" 1L, 10B
  1 %a   "a.txt"                        line 1
  3 #    "b.txt"                        line 1
Enter fullscreen mode Exit fullscreen mode

The %a indicator tells you that buffer a.txt (1) is the active buffer. Now use :bp | ls.

"b.txt" 1L, 11B
  1 #    "a.txt"                        line 1
  3 %a   "b.txt"                        line 1
Enter fullscreen mode Exit fullscreen mode

Buffer b.txt (3) is now active.

You can navigate to a specific buffer using :buffer or :b. Use :b 1 | ls to navigate to buffer a.txt (1).

"a.txt" 1L, 10B
  1 %a   "a.txt"                        line 1
  3 #    "b.txt"                        line 1
Enter fullscreen mode Exit fullscreen mode

Iterating Over Buffers

:bufdo [command] allows you to perform a command over each buffer. Use :bufdo bd to delete all buffers.

:ls
  3 %a   "[No Name]"                    line 1
Enter fullscreen mode Exit fullscreen mode

One use of :bufdo s/File/&:/ | update is to perform search-and-replace across multiple buffers. Open a.txt and b.txt using :e a.txt | e b.txt | ls.

"b.txt" 1L, 11B
  1 #    "a.txt"                        line 1
  2 %a   "b.txt"                        line 1
Enter fullscreen mode Exit fullscreen mode

The contents of a.txt and b.txt are File a.txt and File b.txt, respectively. Let's search-and-replace File with File:. Use :bufdo! s/File/&:/ | update. Now, if you navigate between buffers using :bn and :bp, you will see that both files have been changed.

Using :bufdo! prevents the No write since last change error.

:update is a smarter version of :w in that it only saves the file if changes have been made.

Exiting Vim

You can exit Vim using the following commands:

  • :q: Quit the current window, which can be either the pane or the Vim window. Fails when changes have been made.
  • :wq: Write current file and close window. Quit after the last edit. Writing fails when the buffer is unnamed.
  • :x: Like :wq, except write only when changes have been made.

Let's look at a caveat of using quit commands. Save the active buffer and quit Vim using :wq. Now reopen a.txt and b.txt in Vim using the following command:

vim a.txt b.txt
Enter fullscreen mode Exit fullscreen mode

Using :ls shows both files are open.

:ls
  1 %a   "a.txt"                        line 1
  2      "b.txt"                        line 0
Enter fullscreen mode Exit fullscreen mode

Try to quit Vim using :q. It will say E173: 1 more file to edit. It turns out you have to activate every buffer before you can quit. Use :bn | q to navigate to the next buffer b.txt (2) and then quit. Now you shouldn't have this problem.

I noticed this problem when I tried to save all buffers and quit Vim using :wa | q, which failed.

Buffer States

Buffer states are complex. It took me a while to understand them. Let's cover each of the six buffer states.

Unlisted

An unlisted buffer does not appear in the buffer list. You can utilize an unlisted buffer whenever you want a temporary area in which to edit text. Once you're done, you can delete the buffer and discard the changes. The following code creates an unlisted buffer:

" Create a listed buffer
:enew 
" Make active buffer unlisted
:setlocal nobuflisted
Enter fullscreen mode Exit fullscreen mode

:ls! and :ls u display all buffers, both listed and unlisted. Using :ls! produces the following output:

:ls!
  1 #    "a.txt"                        line 1
  2      "b.txt"                        line 1
  3u%a   "[No Name]"                    line 1
Enter fullscreen mode Exit fullscreen mode

The u portion of buffer 3's indicator means unlisted.

Inactive

An inactive buffer is not displayed in any pane. For example, using :new creates a new pane and buffer, making the previously active buffer inactive.

Active

An active buffer is any buffer currently displayed in any pane. Therefore, you can have multiple active buffers opened in multiple panes.

Hidden

You can have multiple buffers loaded in memory, but you can only display one buffer per pane. In other words, any buffer not displayed in any pane is hidden. You can make a buffer hidden using :hide.

Loaded

A loaded buffer contains the contents of a file. For instance, if you open a file using :edit a.txt, that buffer is loaded.

Unloaded

:bunload N unloads a buffer and its contents from memory, but it still appears in the buffer list.

Windows (Panes): Viewing and Editing in Parallel

While buffers store your text in memory, windows (panes) are sections within the Vim window that display it. Splitting the Vim window into multiple panes lets you simultaneously view and edit different buffers, streamlining your workflow without constantly switching between files.

Splitting and Navigating

You can create panes using the following commands, each of which splits the Vim window into a new pane.

  • :new: create a new pane and start editing an unnamed buffer.
  • :split {file} sp: create a new pane and start editing file {file} in it. Works almost like :split | edit {file} when {file} is supplied.
  • :vsplit vs: works like :split but split vertically.
  • :vnew:

:q closes the active pane but leaves the buffer in memory. If you want to close both the pane and its buffer, use :bd.

Demonstrating :new

Close and reopen Vim, and then use :new | ls to create a bottom-docked pane. Observe how both panes contain the same buffer.

:call ExeCmdLs('new')
  1 #a   "[No Name]"                    line 1
  2 %a   "[No Name]"                    line 1
Enter fullscreen mode Exit fullscreen mode

Whichever pane contains the cursor is the active pane.

Demonstrating :vsplit

Use :vs to create a top-docked pane. This divides the bottom pane vertically into two sub-panes. Pretty cool, isn't it?

Demonstrating :split

Let's create a different layout. Reset the buffers using :bd | ls.

:ls
  1 %a   "[No Name]"                    line 1
Enter fullscreen mode Exit fullscreen mode

Use :vs to split Vim vertically into left- and right-docked panes. Next, use :sp to split the right-docked pane horizontally into two sub-panes.

Settings for :new, :sp, and :vs

The following options control the behavior of :split and :vsplit.

  • splitbelow or sb: When on, splitting a pane will put the new pane below the current one.
  • splitright or spr: When on, splitting a pane will put the new pane right the current one.

You can unset a Vim option by prepending the setting with no. For example, unset splitbelow using :set nosplitbelow.

Navigating Between Panes

You can navigate between panes using :wincmd {arg}, with {arg} being a normal-mode movement command like k (up), j (down), l (right), and h (left). Alternatively, you can use the hotkeys :Ctrl-w h/j/k/l.

It's important to understand that both buffers and windows have numbers that identify them, and you can supply them as arguments to various Vim functions. Furthermore, understanding the difference between navigating between panes versus buffers will help you avoid getting lost when you have lots of open panes.

Iterating Over Panes

Similar to :bufdo, :windo lets you invoke a command for each buffer. For instance,, use :windo echo winnr() to display the pane number of each pane.

Deleting Panes

:x, :bd, :q, and :wq close windows. Understand that you can delete a buffer or a buffer and a pane, but you can't delete a pane without deleting a buffer.

Close and then reopen Vim with a.txt. Next, use :vs b.txt | ls to open b.txt into a right-docked pane.

"b.txt" 1L, 11B
  1 #a   "a.txt"                        line 0
  2 %a   "b.txt"                        line 1
Enter fullscreen mode Exit fullscreen mode

Now the Vim window is vertically divided between left- and right-docked panes. Each pane now has an active buffer, with buffer b.txt in the current window (%).

Recall that the % indicator defines the active window.

:close closes the active pane without deleting its buffer. Use :close | ls and notice how closing a pane doesn't delete a buffer.

As a final example, use :vs b.txt to open b.txt into a right-docked pane and then :bd | ls to delete it.

:call ExeCmdLs('bd')
  1 %a   "a.txt"                        line 1
Enter fullscreen mode Exit fullscreen mode

Notice how this deletes buffer b.txt and its pane. Again, it is important the distinguish between deleting panes and buffers.

Example Using Buffers and Panes

Let's walk through a comprehensive example using everything we've learned thus far. Begin by opening a.txt, b.txt, and c.txt in Vim using the bash command vim a.txt b.txt c.txt, and then use :ls. Observe the indicators as you use the following commands.

:ls
  1 %a   "a.txt"                        line 1
  2      "b.txt"                        line 0
  3      "c.txt"                        line 0
Enter fullscreen mode Exit fullscreen mode

Use :vnew | new | ls to vertically split the Vim window into left- and right-docked panes, with the right-docked pane split horizontally between bottom- and top-docked sub-panes.

:vnew
:new
:ls
  1  a   "a.txt"                        line 0
  2      "b.txt"                        line 0
  3      "c.txt"                        line 0
  4 #a   "[No Name]"                    line 0
  5 %a   "[No Name]"                    line 1
Enter fullscreen mode Exit fullscreen mode

Use :b c.txt | ls to navigate to buffer c.txt and notice how this deletes buffer 5.

:b c.txt
:ls
  1  a   "a.txt"                        line 0
  2      "b.txt"                        line 0
  3 %a   "c.txt"                        line 1
  4  a   "[No Name]"                    line 0
Enter fullscreen mode Exit fullscreen mode

Use :winc k | ls to move the cursor to the top-right pane.

:winc k
:ls
  1 #a   "a.txt"                        line 0
  2      "b.txt"                        line 0
  3  a   "c.txt"                        line 1
  4 %a   "[No Name]"                    line 1
Enter fullscreen mode Exit fullscreen mode

Use b b.txt | ls to navigate to buffer b.txt.

:b b.txt
:ls
"b.txt" 1L, 11B
  1  a   "a.txt"                        line 0
  2 %a   "b.txt"                        line 1
  3  a   "c.txt"                        line 1
Enter fullscreen mode Exit fullscreen mode

Simplifying Commands

Let's consolidate some commands. Using :vs b.txt | sp c.txt | ls achieves the same result as the previous example.

:ls
  1  a   "a.txt"                        line 0
  2 #a   "b.txt"                        line 1 <== Alternate buffer
  3 %a   "c.txt"                        line 1 <== Active buffer
Enter fullscreen mode Exit fullscreen mode

Notice that buffer b.txt (2) has the # indicator, which represents the alternate toggle buffer. Use :e # | ls to toggle to it.

"b.txt" 1 line --100%-- ((1) of 3)
  1  a   "a.txt"                        line 0
  2 %a   "b.txt"                        line 1 <== Active buffer
  3 #    "c.txt"                        line 1 <== Alternate buffer
Enter fullscreen mode Exit fullscreen mode

Now buffer c.txt (3) is the alternate buffer.

Use :e # | ls to toggle back to buffer b.txt (2), and then use :bd | bd | ls to delete buffers b.txt (2) and c.txt (3). Notice how deleting each buffer closes its associated panes.

:bd
:bd
:ls
  1 %a   "a.txt"                        line 1
Enter fullscreen mode Exit fullscreen mode

Customizing and Optimizing

Beyond the fundamental commands, Vim offers many options to tailor your editing environment. By mastering buffer states (active, inactive, hidden, and unloaded), you gain a deeper understanding of Vim's text management and unlock further customization possibilities. Additionally, delve into creating and saving personalized window layouts, enabling you to craft a workspace perfectly suited to your specific tasks and preferences.

The vibrant Vim community also offers a wealth of plugins that can further enhance your buffer and window management experience. Explore these plugins to discover tools that streamline navigation, improve organization, and add more powerful features to your arsenal.

Embrace the Journey of Mastery

You made it! Hopefully, this tutorial has demystified using buffers and panes in Vim. This barely scratches the surface of Vim's capabilities. I strongly recommend that you experiment and descend rabbit holes using :help.

Well done, friend. Now leave, please.

Top comments (0)