Learning Neovim
I've been using Neovim as my primary editor for some time now and have grown quite adept at 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 a Language Server set up. If not, consider using Kickstart, or opt for an all-in-one configuration like LazyVim, NvChad, or AstroNvim.
I'll also recommend some plugins. While some users prefer to avoid motion-related plugins to maintain proficiency with Vim on remote machines, I find this concern less relevant for my needs. I rarely use Vim remotely, preferring to optimize my local Neovim experience instead. This preference extends to online coding platforms with Vim bindings, such as Leetcode or CodeSignal; however, I do not frequent these enough to justify limiting my use of motion plugins.
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.
Navigating between files
I highly recommend nvim-telescope/telescope.nvim
for this. This will enable features like VS Code's Ctrl/Cmd-P file search and the project-wide text search. These are my keybindings for it, inspired by the kickstart.nvim
bindings:
-- Allows you to fuzzy search the current file
vim.keymap.set("n", "<leader>/", function()
builtin.current_buffer_fuzzy_find(require("telescope.themes").get_dropdown({
winblend = 10,
previewer = false,
}))
end, { desc = "[/] Fuzzily search in current buffer" })
-- Search files in current project. Similar to VS Code's Ctrl/Cmd-P
vim.keymap.set("n", "<leader>sf", builtin.find_files, { desc = "[S]earch [F]iles" })
-- Search the project for occurrences of the word your cursor is on
vim.keymap.set("n", "<leader>sw", builtin.grep_string, { desc = "[S]earch current [W]ord" })
-- Similar to the project-wide text search in VS Code
vim.keymap.set("n", "<leader>sg", builtin.live_grep, { desc = "[S]earch by [G]rep" })
-- Lists all open buffers (files)
vim.keymap.set("n", "<leader>sb", builtin.buffers, { desc = "[S]earch [B]uffers" })
-- Searches through all available help docs
vim.keymap.set("n", "<leader>sh", builtin.help_tags, { desc = "[S]earch [H]elp" })
-- Searches through your key mappings
vim.keymap.set("n", "<leader>sk", builtin.keymaps, { desc = "[S]earch [K]eymaps" })
-- Search/list all symbls in the project (variables, functions, etc)
vim.keymap.set("n", "<leader>ss", builtin.lsp_document_symbols, { desc = "[S]ocument [S]ymbols" })
-- Search/list where the current variable/function/etc is used/declared
vim.keymap.set("n", "<leader>lr", builtin.lsp_references, { desc = "[L]ist LSP [R]eferences" })
-- Basically git log in Telescope
vim.keymap.set("n", "<leader>sc", builtin.git_commits, { desc = "[S]earch [C]ommits" })
-- Search Neovim config files
vim.keymap.set("n", "<leader>sn", function()
builtin.find_files({ cwd = vim.fn.stdpath("config") })
end, { desc = "[S]earch [N]eovim config" })
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
.
Test 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 this method works, it's not very precise. If you have a lot of nesting going on, it can be hard to target exactly what you want to. This is where the flash
plugin comes in handy again. When you trigger its treesitter selection mode, it will put a letter next to all valid treesitter targets, and as soon as you hit that key, it will select that:
In this screenshot, if I hit
d
the if
case gets selected, if I hit f
, the whole for
loop gets selected, if I hit g
the case
block gets selected, etc. This makes granular selection very easy.
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.