Getting Started with Neovim

Getting neovim configured can be a barrier to getting started. Let's break that wall.

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