Getting Started with Neovim
Installation
We're going to be using nvim v0.5 as it's slotted for release in the near future and offers some extra features we'll make use of. If you don't have it installed you can find installation instructions here.
If you'd like to check your config into version control (you should) you'll most
likely want that on your filesystem somewhere other than the default location.
Neovim loads it's config from $XDG_CONFIG_HOME/nvim/init.lua
(or init.vim
if
that's your style). $XDG_CONFIG_HOME
is often exported as $HOME
so to
symlink your version-controlled config run:
ln -sf /absolute/path/to/config $HOME/nvim/init.lua
:help is Helpful
Before we get started on anything, a short PSA.
If your homepage is StackOverflow I've got great news for you: Vim comes
packaged with absoulutely fantastic documentation! If you find yourself using
manpages you'll be right at home with vim's :help
.
Try to build the habit of :h question-i-have
into your workflow as having
answers to your question built into you editor will save you a lot of time.
Unfortunately :h me
is about menus, not fixing my life.
For example, if you struggled with the last step or are still marvelling at my
ability to retain such esoteric knowledge type :h config
Lua
Configuring neovim in lua really doesn't require knowing much lua but makes the
experience much more enjoyable. If you come from the JS ecosystem, the
difference is similar to configuring your project in .json
files vs.
.config.js
files. Having the ease and flexibility of functions and variables
makes writing your config a delight.
I suggest learning just enough lua during the process of configuring your editor instead of spending another weekend learning a language. However, if you're one who feels like you need to know more nvim-lua-guide has a lot of good resources.
Options
Nvim comes out-of-the-box looking a bit plain and certainly not optimized for your specific preferences. The first first step it making it feel like home is done through setting options.
Each option is set as either a global, buffer-scoped, or window-scoped (see
:h lua-vim-options
). These are set with vim.o
, vim.bo
, and vim.wo
respectively. If you're unsure what scope to use for a specific option
:h option-name
is your friend.
Below are some sensible defaults. I'm choosing to not describe what each of the options does to give you a chance to utilize the help pages (no, I'm not just lazy).
o.termguicolors = true
o.syntax = 'on'
o.errorbells = false
o.smartcase = true
o.showmode = false
bo.swapfile = false
o.backup = false
o.undodir = vim.fn.stdpath('config') .. '/undodir'
o.undofile = true
o.incsearch = true
o.hidden = true
o.completeopt='menuone,noinsert,noselect'
bo.autoindent = true
bo.smartindent = true
o.tabstop = 2
o.softtabstop = 2
o.shiftwidth = 2
o.expandtab = true
wo.number = true
wo.relativenumber = true
wo.signcolumn = 'yes'
wo.wrap = false
Mappings
Let's get started with mapping your leader (a special key that will prefix the
majority of your custom mappings). We'll map it to space
here but feel free to
use anything that seems convenient to you. This will allow us to map things like
<leader>l
to quick actions without polluting global mappings.
vim.g.mapleader = ' '
To learn the basic of mapping let's start with disabling arrow keys (just bite
the bullet now and you'll thank me later) and mapping jk
(and all my various
typos) to <ESC>
to make leaving insert mode easier.
We'll write a basic lua function that wraps vim.api.nvim_set_keymap
(remember
:h nvim_set_keymap
makes your life easier).
local key_mapper = function(mode, key, result)
vim.api.nvim_set_keymap(
mode,
key,
result,
{noremap = true, silent = true}
)
end
And then add our mappings:
key_mapper('', '<up>', '<nop>')
key_mapper('', '<down>', '<nop>')
key_mapper('', '<left>', '<nop>')
key_mapper('', '<right>', '<nop>')
key_mapper('i', 'jk', '<ESC>')
key_mapper('i', 'JK', '<ESC>')
key_mapper('i', 'jK', '<ESC>')
key_mapper('v', 'jk', '<ESC>')
key_mapper('v', 'JK', '<ESC>')
key_mapper('v', 'jK', '<ESC>')
Now try to use your arrow keys and you'll see they're gloriously disabled! Vim allows no crutches (not really and you can remove those if you want. But don't.) We'll leave our mappings there for a bit and revisit once we set up our plugins.
Plugins
Plugins are additional configurations that you can apply to your editor to add features and improve your quality of life. Much like you can find a VSCode extension for just about everything you'd want to do (and probably not want to do) you can find a vim plugin as well. There are many good plugin managers but we'll be good nvim'ers and use packer.nvim. The quickstart is a quick way to get started but if you're allergic to links you can install it in the proper location with:
git clone https://github.com/wbthomason/packer.nvim\
~/.local/share/nvim/site/pack/packer/opt/packer.nvim
local vim = vim
local execute = vim.api.nvim_command
local fn = vim.fn
-- ensure that packer is installed
local install_path = fn.stdpath('data')..'/site/pack/packer/opt/packer.nvim'
if fn.empty(fn.glob(install_path)) > 0 then
execute('!git clone https://github.com/wbthomason/packer.nvim '..install_path)
execute 'packadd packer.nvim'
end
vim.cmd('packadd packer.nvim')
local packer = require'packer'
local util = require'packer.util'
packer.init({
package_root = util.join_paths(vim.fn.stdpath('data'), 'site', 'pack')
})
--- startup and add configure plugins
packer.startup(function()
local use = use
-- add you plugins here like:
-- use 'neovim/nvim-lspconfig'
end
)
To install plugins run :PackerCompile
and :PackerInstall
. However, we
haven't added any plugins yet. Onward!
Highlighting With Treesitter
0.5 ships with built-in tree-sitter support for improved syntax highlighting. Typically, syntax highlighting is done through regexes that match on language-specific strings. Tree-sitter works differently by incrementally parsing your file into a tree identified by a language grammar. This allows tree-sitter to efficiently highlight even as your file changes as updates are only applied ot the diff.
In short, it's fast, precise, and has extra goodies.
We'll add tree-sitter, some fallback highlighting support, and a theme to our plugins.
packer.startup(function()
local use = use
use 'nvim-treesitter/nvim-treesitter'
use 'sheerun/vim-polyglot'
-- these are optional themes but I hear good things about gloombuddy ;)
-- colorbuddy allows us to run the gloombuddy theme
use 'tjdevries/colorbuddy.nvim'
use 'bkegley/gloombuddy'
-- sneaking some formatting in here too
use {'prettier/vim-prettier', run = 'yarn install' }
end
)
To set the default theme use:
vim.g.colors_name = 'gloombuddy'
And finally configure tree-sitter with:
local configs = require'nvim-treesitter.configs'
configs.setup {
ensure_installed = "maintained",
highlight = {
enable = true,
}
}
Then to install the parser for a specific language use :TSInstall {language}
(languages are tab-completed for ease) and :TSInstallInfo
to see what parsers
you have installed.
LSP
v0.5 introduces support for built-in LSP for code completion, go to definition,
semantic renaming, and more! Language Server Protocol is a specific set of
methods that a server/client implement in order to accomplish common editor
actions. You can see a list at :h lsp-method
.
While there are other plugins to add similar functionality, built-in LSP is highly customizable and easily satisfies all our needs. We'll get started by adding a couple plugins to help configure lsp.
use 'neovim/nvim-lspconfig'
use 'nvim-lua/completion-nvim'
use 'anott03/nvim-lspinstall'
After we've installed our plugins (:PackerCompile
, :PackerInstall
) we're
ready to configure lsp:
local lspconfig = require'lspconfig'
local completion = require'completion'
local function custom_on_attach(client)
print('Attaching to ' .. client.name)
completion.on_attach(client)
end
local default_config = {
on_attach = custom_on_attach,
}
-- setup language servers here
lspconfig.tsserver.setup(default_config)
You'll notice that out of the box diagnostic errors can be a little obtrusive.
This is where the flexibility of built-in lsp really shines. Each method handler
can be customized. We'll customize the textDocument/publishDiagnostics
handler.
vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with(
vim.lsp.diagnostic.on_publish_diagnostics, {
underline = true,
virtual_text = false,
signs = true,
update_in_insert = true,
}
)
Now that our errors aren't dominating our editor let's set up some mappings to
make accessing functionality easier. As always :h
is your ally here
(specifically :h lsp-buf
).
key_mapper('n', 'gd', ':lua vim.lsp.buf.definition()<CR>')
key_mapper('n', 'gD', ':lua vim.lsp.buf.declaration()<CR>')
key_mapper('n', 'gi', ':lua vim.lsp.buf.implementation()<CR>')
key_mapper('n', 'gw', ':lua vim.lsp.buf.document_symbol()<CR>')
key_mapper('n', 'gW', ':lua vim.lsp.buf.workspace_symbol()<CR>')
key_mapper('n', 'gr', ':lua vim.lsp.buf.references()<CR>')
key_mapper('n', 'gt', ':lua vim.lsp.buf.type_definition()<CR>')
key_mapper('n', 'K', ':lua vim.lsp.buf.hover()<CR>')
key_mapper('n', '<c-k>', ':lua vim.lsp.buf.signature_help()<CR>')
key_mapper('n', '<leader>af', ':lua vim.lsp.buf.code_action()<CR>')
key_mapper('n', '<leader>rn', ':lua vim.lsp.buf.rename()<CR>')
Fuzzy Finding
Making the switch to vim changes your workflow pretty significantly. While some things can be painful at first, you'll quickly adapt and forget your pre-vim self. One of the biggest changes is the loss of your mouse (a moment of silence for the mice). While navigating your file tree via mouse is pretty natural it's not the most efficient with your keyboard. Enter, fuzzy finding.
Fuzzy finding is a powerful way to navigate your project by not only file name, but also file text, open files, and so much more.
There are a few options for fuzzy finding but telescope.nvim is built to be tailored to suit your preferences. Let's add it to our plugins:
use 'nvim-lua/popup.nvim'
use 'nvim-lua/plenary.nvim'
use 'nvim-lua/telescope.nvim'
use 'jremmen/vim-ripgrep'
And then some some simple mappings:
key_mapper('n', '<C-p>', ':lua require"telescope.builtin".find_files()<CR>')
key_mapper('n', '<leader>fs', ':lua require"telescope.builtin".live_grep()<CR>')
key_mapper('n', '<leader>fh', ':lua require"telescope.builtin".help_tags()<CR>')
key_mapper('n', '<leader>fb', ':lua require"telescope.builtin".buffers()<CR>')
Congrats! Now you're ready to join the pros!
Let's stay in touch!
Follow me ontwitter