Learning Neovim
I've been using Neovim as my primary editor for some time now and have grown quite comfortable with it. However, reaching this level of comfort required extensive research and learning. I thought it would be helpful to create a brief guide for those looking to transition to Neovim as their full-time editor. This guide assumes a basic understanding of Neovim. If you're entirely new to it, I recommend starting with the vimtutor command in the terminal to run through the introductory tutorial. Additionally, it's assumed you have Kickstart.nvm, LazyVim, NvChad, AstroNvim, or something similar set up. Alternatively, you've created your own config from scratch containing at least an LSP and Telescope.nvim.
If you're not sure which to use, I recommend using Kickstart, and make sure to read the comments in the main init.lua
file.
Navigating between files
I (almost always) start Neovim from the root of the repository I'm working with. To open a file, I'll use Telescope's find_files
. This one acts similarly to Ctrl/Cmd-P
in Neovim, allowing me to quickly find any file within the project by file name.
Often I will want to have two files side by side, which I can accomplish by Ctrl+v
when I have the file selected in Telescope (Ctrl+x
for a horizontal split).
With Telescope open, you still have access to many Neovim features. Opening it will put you in insert mode, allowing you to type the desired file name. If you hit escape, it will put you in normal mode while keeping Telescope open. To go up and down in the file list, you can either use Ctrl+p/n
(previous/next) in insert mode, or press escape to get to normal mode, then just use k/j
to go up and down.
Telescope also has many other helpful pickers available. Some I use frequently, with their relevant names:
- Fuzzily search project for text within a file:
live_grep
- Fuzzily search current file for text:
current_buffer_fuzzy_find
- Find where the variable/function/etc under the cursor is used:
lsp_references
- Search git commits that change the current file:
git_commits
- Search files that have uncommited changes:
git_status
- Repeat last search:
resume
- Search diagnostics in open buffers:
diagnostics
Make sure to check the Telescope documentation, as it has many different pickers you may find useful.
Sometimes you'll want to take a quick look at what a function does, dive deeper into a type declaration, or something similar. For this, I use the vim.lsp.buf.definition
function, which I have mapped to gd
([g]o to [d]efinition). When I want to inspect a function's details, I'll put my cursor on the function name and hit gd
which opens the file where this function is declared and puts the cursor directly on it. When I'm done looking, I use Vim's jump list functionality to return to my previous file with Ctrl+o
. Ctrl+i
goes forward in the jump list, so if you go back, move your cursor off the function name and decide you want to take another look, Ctrl+i
will bring you back to the function declaration file without having to move your cursor back to the function call and hitting gd
.
Navigating within a file
For navigating within a file, I typically use h, j, k, l
if my target is nearby, or H, M, L
(High, Middle, Low) for quick jumps to the top, middle, or bottom of the file. Additionally, I rely on the great plugin folke/flash.nvim
for more precise navigation. Activated by pressing s
or /
, you simply type the first letter or two of your destination. The plugin then displays a unique character key next to each match. Pressing the corresponding key transports your cursor directly to the desired location. This is somewhat akin to the ggandor/leap.nvim
plugin, but I find that flash.nvim
offers several additional features that make it my preferred choice.
Text block manipulation
There are several ways I tend to work with blocks of text or code. If there's a block of text you want to delete, you can use dap
(Delete Around Paragraph) with your cursor anywhere in the block. If you want to operate on something more granular, you can swap out the p
with whatever you're trying to target, for example {
or "
. Using a
(Around) will operate on the target as well, while using i
(In) will operate within the target.
// Original
function SomeFunc(arg) {
const foo = {
val1: "1",
val2: "2",
}
return foo;
}
// Using di{ with your cursor in between the foo object braces
function SomeFunc(arg) {
const foo = {
}
return foo;
}
// Using di{ with your cursor inside the function
function SomeFunc(arg) {
}
Of course, running the above commands with c
instead of d
will do the exact same, except put you in insert mode immediately. Or, if you use y
instead of d
, it will yank the target instead.
While it may seem like you need to remember a metric ton of different key combinations, these commands follow a language-type syntax, with verbs, motions, multipliers, and text objects (targets).
Common verbs are change (c
), delete (d
), visual select (v
), yank (copy) (y
), and paste (p
).
Common motions are the h/j/k/l
movements, around (a
), in (i
), find (f
, capitalized to find backwards), and till (t
, like find but not including the target letter).
Common text objects are paragraphs (p
), words (w
, W
to include punctuation), tags (t
), and specific brackets ({/[/(
or b
for any bracket).
Multipliers are simply numbers prefixed to repeat an action or motion that many times.
So, putting this all together, here are some examples of things you can do:
- Deleting a paragraph:
dap
(delete around paragraph) - Deleting the contents of a tag:
dit
(delete in tag) - Changing the contents from the cursor to a colon, not including the colon:
ct:
(change to:
) - Changing all the arguments of a function:
ci(
(change in(
) - Copying a word:
yiw
(yank in word)
Additional tips and tricks
- Take advantage of the jump list and it's back/forwards mappings (
Ctrl+o
andCtrl+i
) to quickly move around. Knowing when a new jump list entry gets added is pretty intuitive: any time you make the cursor jump, whether it's within the file, or opening a new file, it will probably make a jump list entry. Say you're working in a large file, editing something near the top, and realize you need to make a change in code that's way at the bottom of the file. You can use something like Telescope's current buffer fuzzy search to jump to where you need to be at the end of the file, do your thing, then immediately jump back to your original place withCtrl+o
. Realized you made a mistake at the end of the file and need to make a quick edit there?Ctrl+i
, edit,Ctrl+o
to return. - Similar to the above,
gi
will put you in insert mode wherever you last used insert mode. So if you're editing something in the middle of a long file and need to check some import, you can exit insert mode,gg
to jump to the start of the file, take a look at imports, thengi
to go back to insert mode where you left off.