![buffer line dark](/images/book/chapter-9/buffer-line-dark.png)
Chapter 9. Buffers and Layouts
No matter which programming language you are working with, it is inevitable that you will be working on multiple files at a time. And in multiple areas within the same file.
Like all coding editors (other than Notepad), Neovim has a robust system for working with multiple files. LazyVim is configured with a powerful buffer, file, and window management system that may feel familiar at first, but is actually far more powerful than your average editor.
9.1. Some Terminology
Sometimes it seems like every window management system uses the same words for different things. If you read the documents for e.g. tmux, emacs, kitty, vim, and i3, you’ll end up with multiple definitions for words like “window”, “pane”, “tab”, and “layout”.
I’ll stick with the Vim definitions of these words so that you can switch between this book and most Vim and Vim plugin help files, tutorials, and documentation, without getting confused. Unfortunately, this may mean you get confused when interacting with any other software!
This list goes roughly from least to most specific, though understand that the relationship between most of these elements is a graph rather than a tree; it’s not a strict hierarchy.
- Server
Neovim can run in a server mode and can have multiple clients attached. This means you can have multiple views into the same Neovim instance, and those views could be from different terminals or GUI software, web browsers or even a VS Code Extension. You will probably not need think about the Neovim server, and I won’t mention it again in this book. But if you want to do something interesting such as connect to an existing Neovim instance to open a commit message rather than opening Neovim in a new window, you now know that this is possible.
- Client
A Neovim application that you are actually running. Normally connected to its own independent server but can be configured to connect to an existing or remote one. A client is what starts up when you type
nvim
, but other clients include GUIs such as Neovide or VimR.- Tab
One client can have multiple tabs. Each tab is a full screen layout that is more or less independent from the other tabs. You can have different buffers visible and different configurations of window splits on each tab. Only one tab is visible at any one time. This is a much different paradigm from VS Code and many other environments where each split has its own set of tabs.
- Window
Also known as a “pane” or a “split”, a window is a section of the screen that is dedicated to viewing a buffer. Every tab has one or more windows on it. Every window is normally entirely visible; there is no overlap between windows. The excepton is floating windows such as the ones that pop up when you open a picker or Lazy Extras. If a buffer’s contents don’t fit in a window the window can be scrolled.
- Buffer
This is Vim’s word for a file that is currently open and available to be viewed/edited. One buffer can be displayed in multiple windows, which means you can have two side-by-side views into the same file at different scroll positions or you can view the same buffer in multiple tabs. If a buffer is visible in two places, they will have the exact same contents (other than scrolling position). There is only ever one underlying buffer open for each file, no matter how many views of the buffer are visible in different windows or tabs.
- Fold
Within any one view of a buffer, it is possible to “collapse” a section of that file (for example a function, class, or indentation level) into a single line, effectively hiding the contents. This allows you to view two disjointed sections of the same file at the same time while keeping the unrelated information between those two sections hidden.
- File
A file that exists on disk. Each buffer is linked to at most one file, though it is possible to have buffers with no file (sometimes called “scratch” buffers, a word borrowed from Emacs parlance). The contents of a buffer may not be the same as the contents of the file on disk if the buffer has not been saved.
So far in this book, all your interactions have happened in a single window in a single tab, with possibly multiple buffers open. Now, things are about to get much more complicated interesting.
9.2. Buffers
If you’ve used Telescope, Neo-tree, or mini.files to open multiple files, you may well think that a buffer is a tab. In this view, I have three buffers open, only one of which is currently visible:
![buffer line dark](/images/book/chapter-9/buffer-line-dark.png)
Figure 34. Bufferline With Three Buffers
Yes, I know they look like tabs in any other software, ever, but that is because LazyVim has configured the buffer line to look like tabs. With the buffer line visible, you may actually not need to use (real) Vim tabs very often, but in Vim, tabs are a completely different concept.
No matter how many windows you have open, there is only one buffer line. In the following screenshot, I have three buffers open, and two of them are visible in in separate windows, side by side. But there is still only one buffer line along the top of the editor.
![buffer line split dark](/images/book/chapter-9/buffer-line-split-dark.png)
Figure 35. Only One Buffer Line
This implies that buffers are a “global” concept. There is one collection of buffers for the entire Neovim client, and you can access any of those buffers from any window (or tab).
You can, of course, use the mouse to select different buffers from the buffer line with a single click. But why would you do that when there are so many ways to access buffers with the keyboard in LazyVim?
9.2.1. Navigating Between Open Buffers
The absolute easiest way to switch between buffers is using the H
and L
(i.e. Shift-h
and Shift-l
) keys. By this point you are hopefully intimately
familiar with the fact that h
means left and l
means right for cursor
movement. If you press the shift key with these, you will switch the buffer
visible in the currently active window to whichever buffer is to the left or
right of the current buffer in the buffer line.
Alternatively, you can use the [b
and ]b
commands which map to the same thing.
Annoyingly, you will find that these keybindings do not accept counts. So you
cannot, by default, use 2L
to jump two tabs to the right. This frustrated me
because I know the underlying :bnext
and :bprev
commands do accept a count.
It turns out that LazyVim maps these to a BufferLineCycleNext
command
provided by the underlying plugin, bufferline.nvim, and that plugin doesn’t,
as far as I can tell, support counts.
Upon investigation I learned that the BufferLineCycle*
commands exist because
the plugin can configure some kind of sorting mechanism on the buffer list. But
LazyVim isn’t configured to use that mechanism. So we can use the old-fashioned
commands instead. To do so, create a new file in your plugins
configuration
folder named (something like) extend-bufferline.lua
:
Listing 23. Simplify Buffer Navigation Keybindings
return {
"akinsho/bufferline.nvim",
keys = {
{
"L",
function()
vim.cmd("bnext " .. vim.v.count1)
end,
desc = "Next buffer",
},
{
"H",
function()
vim.cmd("bprev " .. vim.v.count1)
end,
desc = "Previous buffer",
},
{
"]b",
function()
vim.cmd("bnext " .. vim.v.count1)
end,
desc = "Next buffer",
},
{
"[b",
function()
vim.cmd("bprev " .. vim.v.count1)
end,
desc = "Previous buffer",
},
},
}
The vim.v.count1
variable is set whenever a keybinding is called with a
count, so it can be accessed inside the callback and passed to the Vim command
using string concatenation (the ..
operator). Restart Neovim and you can do
things like 3L
to jump three buffers to the right on the buffer line.
Another keybinding you will want to reach for when jumping between buffers is
<Space><Backtick>
. This one simply jumps between the current file and the
file that was most recently opened in the current window. In Vim parlance, this
is referred to as “the alternate file.”
If you have a large number of buffers open, the buffer line can get awfully crowded. At some point, it will show two arrows to the left and/or right of the buffer bar so you can tell that there are “hidden” buffers. When you navigate through buffers, it will always ensure the active buffer is visible. Here’s a very full buffer line with four buffers hidden to the left and two hidden to the right:
![buffer line full dark](/images/book/chapter-9/buffer-line-full-dark.png)
Figure 36. Full Buffer Line
If you have this many buffers open, you may find it easier to use Telescope or
Fzf.lua (depending which you have enabled) to search through the open buffers.
The keybinding to pop up a filterable, scrollable list of buffers is
<Space><comma>
. It has the exact same contents as the buffer line, but it’s a
different interaction effect.
This is useful if you are working on large projects that have so many files
that searching through them with <Space><Space>
is difficult. Open the
relatively low number of files that you actually need to access as active
buffers, so they are easy to filter in the <Space><comma>
buffer list.
Alternatively, you can use Neo-tree to navigate open buffers. If you show the
Neo-tree sidebar, you’ll see that it has some “accordion”-style widgets,
named Neo-Tree
, Neo-Tree Git
and Neo-Tree Buffers
. Neo-tree has a
variety of sources for navigating tree-like interfaces, though these three
are the only ones preconfigured by LazyVim.
To switch between the accordions (Neo-tree calls them “sources”), you can
click a different heading with the mouse, or use the <
and >
keys to cycle
through them. Alternatively, use the <Space>be
key sequence to directly show
the “Buffer Explorer”.
Once the Neo-Tree Buffers view is visible, it will look something like this:
![neotree buffers dark](/images/book/chapter-9/neotree-buffers-dark.png)
Figure 37. Neotree Buffers
If you have buffers open that are not rooted in the current working directory, they won’t show up in Neo-tree. You’ll have to use a picker for those. |
If you tend to have the Neo-tree sidebar open all the time, you might want to consider disabling the bufferline across the top of your screen. It shows the same information and there’s no reason to spend screen real estate (that most precious commodity) on having both of them visible all the time. Be aware that this will disable certain buffer management keybindings, however. |
9.2.2. Closing Buffers
You will often want to close the current buffer without closing the split(s)
it is currently open in. The keybinding for this is <Space>bd
, where
<Space>b
pops up a useful menu of other buffer related functions, and d
means “delete”. You aren’t actually deleting the underlying file when you do
this; you’re just deleting the buffer from Vim’s memory: i.e. closing it.
You can also just press bd
if you currently have Neo-tree’s buffer view focused.
I find that closing buffers is too common a task to deserve three keys,
so I have added the following to the keys
array I defined in
extend-bufferline.lua
:
Listing 24. Close Buffer Keymap
{
"<leader><delete>",
LazyVim.ui.bufremove,
desc = "Close current buffer"
},
Here are several other commands you can use to close buffers:
Keybinding | Description | Mnemonic |
---|---|---|
| Close buffer and the window split it is in. | Delete buffer, but “bigger” |
| Close all buffers to the right in the tab line | |
| Close all buffers to the left in the tab line | |
| Close all buffers other than the active one | “only” this buffer |
| Delete all non-pinned buffers | “P” is opposite of “p” |
The last one needs some clarification. You can toggle a “pin” on any active
buffer using <Space>bp
. You’ll see a pin icon show up to the left of the
buffer name. The only purpose of this pin is to keep it open if you want to
close all the “less important” (unpinned) files using <Space>bP
. I personally
don’t use buffer pinning, so for me, <Space>bP
is a shortcut for
“close all buffers”; useful when I complete one task and am ready to start
another.
You can also close buffers directly from the Telescope or FZF buffer picker
interface (from <Space>,
). If you use FZF, just hit Control-x
to close the
buffer that the cursor is on. If you use Telescope, the Alt-d
keybinding
should do the same.
Now that you know all about buffers, let’s discuss windows.
9.3. Windows
In most modern environments, “windows” refer to the OS-level windows such as the terminal you are running Neovim in. Since Vi predates such environments, they were able to use the word window to refer to what are nowadays more commonly described as “panes” or “splits” in other environments.
The window management commands are collected in the <Space>w
submode menu:
![window menu dark](/images/book/chapter-9/window-menu-dark.png)
Figure 38. Window Menu
We’ll cover many of these in the following sections.
This menu can also be accessed with |
9.3.1. Creating Window Splits
Windows in LazyVim can be created on the fly at any time. To split the current
window in half “vertically” with one window on the left and a new window on the
right, use the <Space>wv
keymap.
When you create a split, the new window will contain another view of the buffer you were already viewing, side by side. But once the split is opened, you can switch the buffer in that window using any of the buffer management commands or by opening a new file with any of the tools we’ve previously discussed for opening files.
To create a horizontal split between two windows (one above the other), use <Space>ws
.
The mnemonic for this is unfortunately just “split”. They weren’t able to
reuse <Space>wh
because that is already used for switching windows.
LazyVim also allows you to create a vertical split with <Space>| where
the | is the vertical bar when you Shift-Backslash , and <Space><Minus>
for a horizontal split. I find <Space>ws and <Space>wv easier to type. |
9.3.2. Creating Splits When Opening Files
You already know you can open a file in the current window from Neo-Tree by
moving your cursor to the file and pressing <Enter>
. You can also use the s
key in Neo-Tree to open it in a vertical split (irritatingly asymmetric with
the <Space>ws
key to create a horizontal split in a normal buffer). The
shifted form, S
in Neo-Tree is used to create a horizontal split.
If you are using Telescope or Fzf.lua to open files, you’ll use yet another set
of keybindings! To open a file in a vertical split, use the Control-v
keybinding. This works in both Insert and Normal mode in the Telescope prompt
area, but only in Insert mode with Fzf.lua. To open it in a horizontal split
you use Control-x
(I know: WTF, right?)
Finally, if you use mini.files, you can open a file in a split sensibly using
the same keybindings as in a normal window (<Space>wv
and <Space>ws
).
9.3.3. Navigating Between Windows
You can move your cursor between window splits by holding the control key along
with any of the h
, j
, k
, or l
home row "arrow-key" directions. They can
also be prefixed with numerical counts if you want to skip over a window to get
to the next one.
Alternatively, you can use the same keys with <Space>w
. So <Space>wh
will
move to the window to the left of the current one.
Smart Splits
I suggest the mrjones2014/smart-splits.nvim plugin, which can be configured to navigate between Vim windows and Kitty, Wezterm, or Tmux panes with the same keybindings. Consider this screenshot:
![vim kitty splits dark](/images/book/chapter-9/vim-kitty-splits-dark.png)
Figure 39. Kitty and Vim Splits
I have three Kitty Terminal panes open. The left one is running Neovim with two windows in it, one above the other. The right is split into two normal terminal panes. By default, if I want to navigate between the three Kitty panes, I have to use one set of keybindings, and if I want to navigate between the two Neovim windows, I have to use another set of keybindings. With the smart-splits.nvim plugin, I can navigate between all the windows with the same keybindings, no matter where my cursor is.
Setting up the terminal integration for smart splits is beyond the scope of
this book (documentation on the README in the GitHub repository should be
sufficient), but to configure the smart-splits plugin in Neovim, create a file
in the plugins directory called e.g. smart-splits.lua
:
Listing 25. Smart-splits configuration
return {
"mrjones2014/smart-splits.nvim",
build = "./kitty/install-kittens.bash",
keys = {
{
"<A-h>",
function()
require("smart-splits").move_cursor_left()
end,
desc = "Move to left window",
},
{
"<A-l>",
function()
require("smart-splits").move_cursor_right()
end,
desc = "Move to right window",
},
{
"<A-j>",
function()
require("smart-splits").move_cursor_down()
end,
desc = "Move to below window",
},
{
"<A-k>",
function()
require("smart-splits").move_cursor_up()
end,
desc = "Move to above window",
},
},
}
If you are using WezTerm or Tmux you won’t need the build =
line, but for all
three environments, you’ll also need to add some configuration to your Kitty,
WezTerm, or Tmux configuration as described in the plugin’s README.
9.3.4. Closing a Window Split
You can close a window at any time using one of three keybindings:
<Space>wq
closes the window, and if it is the only window open, exits (quits) Neovim.<Space>wc
closes the window unless it is the only window open, in which case it displays an error and refuses to close.<Space>wd
deletes the window. It is actually doing exactly the same operation as<Space>wc
, but it is helpful for muscle memory for its symmetry with<Space>bd
to “delete” an open buffer.
In all three cases, the buffer remains open in the bufferline. Only the window split is closed.
If, instead, you want to close all splits except the active one, use <Space>wo
for
“only this window” or “close other” (whichever is easier to
remember).
9.3.5. Resizing Windows
In my unconventional opinion, the easiest way to resize Vim splits is to use… the mouse. There is a vertical bar between vertical splits that you can click and drag on. The mouse cursor doesn’t change to give you any feedback that you can click and drag on it, but it works.
For horizontal splits (when two windows are one above the other), there is no obvious bar to click. But you can actually just drag on the status bar of the “upper” window to move it up or down.
If you insist on using the keyboard, the keybindings are in the <Space>w
menu: <Space>w+
and <Space>w-
to increase or decrease the height of the
active window in a horizontal split, and <Space>w>
or <Space>w<
to increase
or decrease the width of a vertical split. They only move by one row or column
at a time, so you will almost certainly want to prefix these commands with a
count greater than 10, or use “Hydra” mode, discussed next.
To change everything to a “default” size, use <Space>w=
which will make
all the windows “equally high and wide.”
9.3.6. Hydra Mode
Occasionally, you’ll want to run several window commands in a row, for example
when resizing a window or creating a layout containing several splits. Having
to type <Space>w
between each command would be rather tedious in these situations,
so the which-key
plugin gives us Hydra mode.
To enter Hydra window mode, press <Space>w<Space>
. All this does is “pin”
the window menu open, so you can issue multiple keypresses from it without
automatically leaving the menu. For example, <Space>w<Space>vvvh
will create
four splits (three vertical and one horizontal).
To leave Hydra mode just hit <Escape>
and return to your normal editing.
9.4. Tabs
Tabs in Vim are rather unusual. Some other paradigms might describe them as “Layouts”. All tabs are connected to the same list of currently open buffers, which is different from most tab models, but each tab can be split into different window layouts. So you might have one tab with three vertical splits and a second tab with four windows open in a grid, for example. In each of the seven splits you can have any buffer you like open, possibly in multiple locations.
LazyVim has a dedicated tab menu that is accessed by pressing <Space><Tab>
:
![tab menu dark](/images/book/chapter-9/tab-menu-dark.png)
Figure 40. Space Tab Menu
To create a new tab, just press <Space><Tab><Tab>
. If you want to “move”
the currently open window split into a new tab, use <Space>wT
(that’s a
capital T). This will effectively close the current window split and create a
new tab with the same buffer in that tab.
After you create a tab, you’ll likely have trouble finding it again! The tabs are grouped at the right end of the buffer line:
![tabs in buffer line dark](/images/book/chapter-9/tabs-in-buffer-line-dark.png)
Figure 41. Tabs in Buffer Line
This screenshot has two tabs, numbered 1 and 2 at the right with an X
beside them. The two buffers at the left are not tabs. Have I emphasized
that too many times?
Unfortunately, other than numbers, the tabs don’t do anything to make themselves look unique; it is not possible to preview which buffers are active in each tab or what layout they have.
To navigate between tabs, you can click the numbers, or you can use the default
vim keybindings of gt
and gT
to go to the next or previous tab.
Alternatively, the <Space><Tab>[
and <Space><Tab>]
keybindings provided by
LazyVim can also switch tabs. To go to a specific tab by number, use that
number as the count when calling gt
. For example 3gt
will show the tab
number 3 rather than jumping three tabs to the right.
There are several ways to close a tab:
Just close the last window in the tab (i.e. with
<Space>wq
) and the tab will disappear.The
<Space><tab>d
keybinding will close all the windows in a tab as well as the tab itself. The buffers will stay open.Click the
X
icon to the right of the tabs in the tab bar.
9.5. Code Folding
Vim’s code folding system is almost too robust, probably because it has had many iterations of “best practices” over the years. LazyVim is configured with the current best practices, so you generally only need a small subset of the complete list of folding commands.
If you are unfamiliar with the concept, code folding allows you to hide entire sections of code by collapsing them into a single line. Visually, this has a similar effect to splitting a window horizontally and then reading two sections of the same file above and below the split, but when you use folding only one view of the buffer is visible and it scrolls as a single entity.
Consider this section of code:
![no folds dark](/images/book/chapter-9/no-folds-dark.png)
Figure 42. Code With No Folds
While I’m editing, imagine that I am interested in the clearExistingTimeout
function at the top of the screenshot and the addTodo
function at the bottom,
but not currently interested in the contents of the two save
callbacks. I can
collapse those sections and my screen looks like this:
![folds dark](/images/book/chapter-9/folds-dark.png)
Figure 43. Collapsed Folds
Most fold operations are accessible from the z
mode menu accessible by typing
z
in Normal mode (we discussed some of the z
mode operations in chapter 3
when we were dealing with scrolling). To collapse a section of code into a
fold, use whatever navigation operations you like to get to that section and
type zc
for “collapse fold”.
To open it again, use zo
for “open fold”.
Alternatively, if you only want to remember a single keybinding, za
will
toggle a fold, collapsing if you are not on a folded line and expanding if you
are.
If you have collapsed some folds and want to quickly get back to a point where
there are no folds collapsed, use zR
to open all folds. I had no idea what
mnemonic is supposed to work with R
, but an early reader helpfully pointed
out that zr
is “reduce folding”, so zR
is “Reduce folding BUT BIGGER”.
You can even nest folds by folding already folded code. If you want to open
folds recursively, use zO
, which will open a fold and any folds that are
nested under that fold.
The way LazyVim is configured, you don’t have much control over what gets folded, but it will usually do something close to what you expect based on where your cursor is in the document. “What you expect” depends on both the LSP and the TreeSitter grammar for the language you are working on, but I find it best to just let it do its thing and not disagree with it.
If you find that you want way more control over code folding, I recommend
reading :help folding
in its entirety. More than likely, you’ll decide that
actually, you don’t want more control over folding and just want LazyVim to
handle it for you!
9.6. Sessions
After a long hard day of coding, you probably have several buffers open and your splits and tabs configured with all the files in just the right places. Wouldn’t it be nice to put the code away for the evening and come back to all those buffers, tabs, and splits just as they were?
LazyVim has built-in session management enabled by default. Simply close
LazyVim with <Space>qq
and you’re done. Tomorrow morning, open it to the
dashboard with the nvim
command and hit s
to be on your way.
If you forgot to open it right away and the dashboard is long gone, you can use
<Space>qs
to restore Neovim to wherever it was when you last closed it
(though any files you modified and saved in the meantime will still have their
new contents).
If you have opened Neovim temporarily and want to close it without wiping out
the session that was saved the last time you closed it, hit <Space>qd
at any
time after you open Neovim and before you close it. In some contexts (notably,
git commit messages), this happens automatically so you don’t have to worry
about making a commit after you close the editor and then losing your session.
9.7. Summary
In this chapter, we learned about Vim’s buffers, windows and tabs, and how they are different not only from each other, but also from many other window management paradigms. Vim has the same concepts as other editors, but they are sometimes mixed or named in different ways.
We also covered code folding to make it easier to wrangle large files, and session management to return to your window configuration and come back to it later. This is particularly useful when combined with LazyVim’s lightning fast startup time. There’s no reason to keep your code editor open consuming memory when you aren’t actually, you know, editing code.
In the next chapter, we’ll dig into some of the terrific programming language support that LazyVim provides. This is arguably the one thing that made VS Code amazing, but the Vim community has learned from its competitors and eventually outmatched them.