by Olivier Roques
Neovim 0.5 ships with some major additions. The main features this new version introduces are:
init.lua
as an
user config.This post will help you write a very basic init.lua
which include all these
new features. There are definitely some personal choices thrown in there so feel
free to ignore them or use the suggested alternatives.
We’re going to avoid Vimscript entirely, and instead use Lua and the Neovim Lua API extensively. The best reference on this subject is the nvim-lua-guide. Also check Learn Lua in Y minutes for a quick overview of the Lua language.
We’re going to use these aliases for the rest of this post:
local cmd = vim.cmd -- to execute Vim commands e.g. cmd('pwd')
local fn = vim.fn -- to call Vim functions e.g. fn.bufnr()
local g = vim.g -- a table to access global variables
local opt = vim.opt -- to set options
You have several options to manage your plugins:
$ git clone https://github.com/savq/paq-nvim.git \
"${XDG_DATA_HOME:-$HOME/.local/share}"/nvim/site/pack/paqs/opt/paq-nvim
Our init.lua
starts like this:
cmd 'packadd paq-nvim' -- load the package manager
local paq = require('paq-nvim').paq -- a convenient alias
paq {'savq/paq-nvim', opt = true} -- paq-nvim manages itself
paq {'shougo/deoplete-lsp'}
paq {'shougo/deoplete.nvim', run = fn['remote#host#UpdateRemotePlugins']}
paq {'nvim-treesitter/nvim-treesitter'}
paq {'neovim/nvim-lspconfig'}
paq {'junegunn/fzf', run = fn['fzf#install']}
paq {'junegunn/fzf.vim'}
paq {'ojroques/nvim-lspfuzzy'}
g['deoplete#enable_at_startup'] = 1 -- enable deoplete at startup
Now you can run :PaqInstall
to install all plugins, :PaqUpdate
to update
them and :PaqClean
to remove unused ones.
About the plugins:
Here is how LSP and FZF interact together when looking for symbol references:
You can also find here a full list of plugins specifically developed for Neovim: awesome-neovim.
To set options in Lua, use the vim.opt
table which behaves exactly like the
set
function in Vimscript. This table should cover most usages.
Otherwise the Neovim Lua API provides 3 tables if you need to specifically set an option locally (only a buffer or in a window) or globally:
vim.o
to set global options: vim.o.hidden = true
vim.bo
to set buffer-scoped options: vim.bo.expandtab = true
vim.wo
to set window-scoped options: vim.wo.number = true
To know on which scope a particular option acts on, check Vim help pages.
For further documentation on vim.opt
, check the help :h lua-vim-options
.
Here is a list of useful settings to illustrate the use of vim.opt
(aliased to
opt
here):
cmd 'colorscheme desert' -- Put your favorite colorscheme here
opt.completeopt = {'menuone', 'noinsert', 'noselect'} -- Completion options (for deoplete)
opt.expandtab = true -- Use spaces instead of tabs
opt.hidden = true -- Enable background buffers
opt.ignorecase = true -- Ignore case
opt.joinspaces = false -- No double spaces with join
opt.list = true -- Show some invisible characters
opt.number = true -- Show line numbers
opt.relativenumber = true -- Relative line numbers
opt.scrolloff = 4 -- Lines of context
opt.shiftround = true -- Round indent
opt.shiftwidth = 2 -- Size of an indent
opt.sidescrolloff = 8 -- Columns of context
opt.smartcase = true -- Do not ignore case with capitals
opt.smartindent = true -- Insert indents automatically
opt.splitbelow = true -- Put new windows below current
opt.splitright = true -- Put new windows right of current
opt.tabstop = 2 -- Number of spaces tabs count for
opt.termguicolors = true -- True color support
opt.wildmode = {'list', 'longest'} -- Command-line completion mode
opt.wrap = false -- Disable line wrap
The vim.api.nvim_set_keymap()
function allows you to define a new mapping.
Specific behaviors such as noremap
must be passed as a table to that function.
Here is a helper to create mappings with the noremap
option set to true
by
default:
local function map(mode, lhs, rhs, opts)
local options = {noremap = true}
if opts then options = vim.tbl_extend('force', options, opts) end
vim.api.nvim_set_keymap(mode, lhs, rhs, options)
end
And here are mapping suggestions to illustrate the use of above helper:
map('', '<leader>c', '"+y') -- Copy to clipboard in normal, visual, select and operator modes
map('i', '<C-u>', '<C-g>u<C-u>') -- Make <C-u> undo-friendly
map('i', '<C-w>', '<C-g>u<C-w>') -- Make <C-w> undo-friendly
-- <Tab> to navigate the completion menu
map('i', '<S-Tab>', 'pumvisible() ? "\\<C-p>" : "\\<Tab>"', {expr = true})
map('i', '<Tab>', 'pumvisible() ? "\\<C-n>" : "\\<Tab>"', {expr = true})
map('n', '<C-l>', '<cmd>noh<CR>') -- Clear highlights
map('n', '<leader>o', 'm`o<Esc>``') -- Insert a newline in normal mode
The tree-sitter is very easy to configure:
local ts = require 'nvim-treesitter.configs'
ts.setup {ensure_installed = 'maintained', highlight = {enable = true}}
Here the maintained
value indicates that we wish to use all maintained
languages modules. You also need to set highlight to true
otherwise the plugin
will be disabled. The full documentation can be found on the Github
page.
Highlighting is not the only module available: you can also enable the indent
module which provides indentation based on the filetype and context. More
details on the available modules
here.
Thanks to the lspconfig
plugin, configuring the LSP client is relatively easy:
setup()
to enable the server. Check the
nvim-lspconfig documentation for
advanced configuration.Here is an example of configuration which sets up the servers for Python and C/C++ (respectively pylsp and ccls, assuming they’re already installed). We also create mappings for the most useful LSP commands.
local lsp = require 'lspconfig'
local lspfuzzy = require 'lspfuzzy'
-- We use the default settings for ccls and pylsp: the option table can stay empty
lsp.ccls.setup {}
lsp.pylsp.setup {}
lspfuzzy.setup {} -- Make the LSP client use FZF instead of the quickfix list
map('n', '<space>,', '<cmd>lua vim.lsp.diagnostic.goto_prev()<CR>')
map('n', '<space>;', '<cmd>lua vim.lsp.diagnostic.goto_next()<CR>')
map('n', '<space>a', '<cmd>lua vim.lsp.buf.code_action()<CR>')
map('n', '<space>d', '<cmd>lua vim.lsp.buf.definition()<CR>')
map('n', '<space>f', '<cmd>lua vim.lsp.buf.formatting()<CR>')
map('n', '<space>h', '<cmd>lua vim.lsp.buf.hover()<CR>')
map('n', '<space>m', '<cmd>lua vim.lsp.buf.rename()<CR>')
map('n', '<space>r', '<cmd>lua vim.lsp.buf.references()<CR>')
map('n', '<space>s', '<cmd>lua vim.lsp.buf.document_symbol()<CR>')
Unfortunately Neovim doesn’t have an interface to create commands and autocommands yet. Work is in progress to implement such an interface, see PR#11613 for commands and PR#12378 for autocommands.
You can still define commands or autocommands using Vimscript via vim.cmd
. For
instance Neovim 0.5 introduces the highlight on
yank feature which briefly
highlights yanked text. You can enable it as an autocommand like so:
cmd 'au TextYankPost * lua vim.highlight.on_yank {on_visual = false}' -- disabled in visual mode
I’ve included in the config the plugins I think are the most essential ones for a basic usage of Neovim. Here are some other plugins that leverage the new features of Neovim and that could greatly improve your workflow:
Here is the complete init.lua:
-------------------- HELPERS -------------------------------
local cmd = vim.cmd -- to execute Vim commands e.g. cmd('pwd')
local fn = vim.fn -- to call Vim functions e.g. fn.bufnr()
local g = vim.g -- a table to access global variables
local opt = vim.opt -- to set options
local function map(mode, lhs, rhs, opts)
local options = {noremap = true}
if opts then options = vim.tbl_extend('force', options, opts) end
vim.api.nvim_set_keymap(mode, lhs, rhs, options)
end
-------------------- PLUGINS -------------------------------
cmd 'packadd paq-nvim' -- load the package manager
local paq = require('paq-nvim').paq -- a convenient alias
paq {'savq/paq-nvim', opt = true} -- paq-nvim manages itself
paq {'shougo/deoplete-lsp'}
paq {'shougo/deoplete.nvim', run = fn['remote#host#UpdateRemotePlugins']}
paq {'nvim-treesitter/nvim-treesitter'}
paq {'neovim/nvim-lspconfig'}
paq {'junegunn/fzf', run = fn['fzf#install']}
paq {'junegunn/fzf.vim'}
paq {'ojroques/nvim-lspfuzzy'}
g['deoplete#enable_at_startup'] = 1 -- enable deoplete at startup
-------------------- OPTIONS -------------------------------
cmd 'colorscheme desert' -- Put your favorite colorscheme here
opt.completeopt = {'menuone', 'noinsert', 'noselect'} -- Completion options (for deoplete)
opt.expandtab = true -- Use spaces instead of tabs
opt.hidden = true -- Enable background buffers
opt.ignorecase = true -- Ignore case
opt.joinspaces = false -- No double spaces with join
opt.list = true -- Show some invisible characters
opt.number = true -- Show line numbers
opt.relativenumber = true -- Relative line numbers
opt.scrolloff = 4 -- Lines of context
opt.shiftround = true -- Round indent
opt.shiftwidth = 2 -- Size of an indent
opt.sidescrolloff = 8 -- Columns of context
opt.smartcase = true -- Do not ignore case with capitals
opt.smartindent = true -- Insert indents automatically
opt.splitbelow = true -- Put new windows below current
opt.splitright = true -- Put new windows right of current
opt.tabstop = 2 -- Number of spaces tabs count for
opt.termguicolors = true -- True color support
opt.wildmode = {'list', 'longest'} -- Command-line completion mode
opt.wrap = false -- Disable line wrap
-------------------- MAPPINGS ------------------------------
map('', '<leader>c', '"+y') -- Copy to clipboard in normal, visual, select and operator modes
map('i', '<C-u>', '<C-g>u<C-u>') -- Make <C-u> undo-friendly
map('i', '<C-w>', '<C-g>u<C-w>') -- Make <C-w> undo-friendly
-- <Tab> to navigate the completion menu
map('i', '<S-Tab>', 'pumvisible() ? "\\<C-p>" : "\\<Tab>"', {expr = true})
map('i', '<Tab>', 'pumvisible() ? "\\<C-n>" : "\\<Tab>"', {expr = true})
map('n', '<C-l>', '<cmd>noh<CR>') -- Clear highlights
map('n', '<leader>o', 'm`o<Esc>``') -- Insert a newline in normal mode
-------------------- TREE-SITTER ---------------------------
local ts = require 'nvim-treesitter.configs'
ts.setup {ensure_installed = 'maintained', highlight = {enable = true}}
-------------------- LSP -----------------------------------
local lsp = require 'lspconfig'
local lspfuzzy = require 'lspfuzzy'
-- We use the default settings for ccls and pylsp: the option table can stay empty
lsp.ccls.setup {}
lsp.pylsp.setup {}
lspfuzzy.setup {} -- Make the LSP client use FZF instead of the quickfix list
map('n', '<space>,', '<cmd>lua vim.lsp.diagnostic.goto_prev()<CR>')
map('n', '<space>;', '<cmd>lua vim.lsp.diagnostic.goto_next()<CR>')
map('n', '<space>a', '<cmd>lua vim.lsp.buf.code_action()<CR>')
map('n', '<space>d', '<cmd>lua vim.lsp.buf.definition()<CR>')
map('n', '<space>f', '<cmd>lua vim.lsp.buf.formatting()<CR>')
map('n', '<space>h', '<cmd>lua vim.lsp.buf.hover()<CR>')
map('n', '<space>m', '<cmd>lua vim.lsp.buf.rename()<CR>')
map('n', '<space>r', '<cmd>lua vim.lsp.buf.references()<CR>')
map('n', '<space>s', '<cmd>lua vim.lsp.buf.document_symbol()<CR>')
-------------------- COMMANDS ------------------------------
cmd 'au TextYankPost * lua vim.highlight.on_yank {on_visual = false}' -- disabled in visual mode
I hope you’ve found this guide useful. You can find my own init.lua from which most of the above code has been taken from here: init.lua.
Also you might be interested in the Vim/Neovim plugins I’ve developed: