Vim (neovim) configuration¶
See Why? for why nvim (short for Neovim) instead of vim.
The nvim community moves quickly, and this config and these accompanying docs change more often than any other configs in this repo.
You can take these new changes for a test drive by running
./setup.sh --nvim-test-drive
This will move existing nvim config and plugins to backup directories. This does not affect any other config (like bash). You can always roll back to what you had before. The commands to roll back are printed at the end of the command.
Config file location¶
The nvim configuration is split over multiple files.
This makes it easier to see what changed, and decide if you want those changes or not. It also enables us to import the config files into this documentation to easily view them (look for the “Config:” dropdowns).
This is just a starting point, you should adjust the config to your liking!
Here is a description of the different pieces of the nvim config. Paths are
relative to ~/.config/nvim
:
init.lua
The file .config/nvim/init.lua
is the entry point of the nvim config.
This in turn loads files in the lua
subdirectory. For example, the
syntax require("config.lazy")
will load
.config/nvim/lua/config/lazy.lua
.
-- Lua config for neovim. Coming from Vim lanuage? See
-- https://neovim.io/doc/user/lua.html for the basics.
-- leader must be set before plugins are set up.
vim.cmd("let mapleader=','") -- Re-map leader from default \ to , (comma)
vim.cmd("let maplocalleader = '\\'") -- Local leader becomes \.
-- This allows nvim-tree to be used when opening a directory in nvim.
vim.g.loaded_netrw = 1
vim.g.loaded_netrwPlugin = 1
vim.cmd("set termguicolors") -- use full color in colorschemes
require("config.lazy")
require("config.options")
-- Colorscheme.
-- Add your favorite colorscheme to lua/plugins/colorscheme.lua (which will be
-- loaded with `config.lazy` above), and then use it here.
vim.cmd("colorscheme zenburn")
-- Uncomment these lines if you use a terminal that does not support true color:
-- vim.cmd("colorscheme onedark")
-- vim.cmd("set notermguicolors")
require("config.keymaps")
require("config.autocmds")
-- vim: nowrap
lua/config/lazy.lua
.config/nvim/lua/config/lazy.lua
loads the lazy.nvim plugin manager. The plugins in the
.config/nvim/lua/plugins
directory are loaded here.
-- Bootstrap lazy.nvim
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not (vim.uv or vim.loop).fs_stat(lazypath) then
local lazyrepo = "https://github.com/folke/lazy.nvim.git"
local out = vim.fn.system({ "git", "clone", "--filter=blob:none", "--branch=stable", lazyrepo, lazypath })
if vim.v.shell_error ~= 0 then
vim.api.nvim_echo({
{ "Failed to clone lazy.nvim:\n", "ErrorMsg" },
{ out, "WarningMsg" },
{ "\nPress any key to exit..." },
}, true, {})
vim.fn.getchar()
os.exit(1)
end
end
vim.opt.rtp:prepend(lazypath)
require("lazy").setup({
spec = {
-- imports all *.lua files in the plugins/ directory
{ import = "plugins" },
},
checker = { enabled = false },
})
lua/config/options.lua
.config/nvim/lua/config/options.lua
sets global options.
-- Global options.
vim.opt.syntax = "on" -- Syntax highlighting; also does an implicit filetype on
vim.opt.foldenable = false -- Files will open with everything unfolded
vim.opt.backspace = "indent,eol,start" -- Make backspace work as expected in some situations
vim.opt.smarttab = true -- Insert spaces according to shiftwidth at the beginning of each line
vim.opt.expandtab = true -- <Tab> inserts spaces, not "\t"
vim.opt.scrolloff = 3 -- Keep some lines above and below cursor to keep it visible
vim.opt.list = true -- Show non-printing characters
vim.opt.showmatch = true -- Show matching parentheses
vim.opt.nu = true -- Show line numbers
vim.opt.showmode = false -- The lualine plugin provides modeline for us
-- In addition to allowing clicking and scrolling, vim.opt.mouse = "a" also:
-- * Supports mouse-enabled motions. To try this, left-click to place the
-- cursor. Type y then left-click to yank from current cursor to where you
-- next clicked.
-- * Drag the status-line or vertical separator to resize
-- * Double-click to select word; triple-click for line
vim.opt.mouse = "a"
-- Change the behavior of various formatting.
-- Explanation of these options; see :h formatoptions for more info.
-- q: gq also formats comments
-- r: insert comment leader after <Enter> in insert mode
-- n: recognize numbered lists
-- 1: don’t break a line after a 1-letter word
-- c: autoformat comments
-- o: automatically insert comment leader afer ‘o’ or ‘O’ in Normal mode.
-- j: where it makes sense, remove a comment leader when joining lines
vim.opt.formatoptions="qrn1coj"
vim.opt.hidden = true -- Open a new buffer without having to save first
vim.opt.swapfile = false -- Disable swap file creation. Keep enabled for huge files (:set swapfile)
vim.opt.ignorecase = true -- Ignore case when searching...
vim.opt.smartcase = true -- ...unless at least one character is uppercase
vim.opt.hlsearch = false -- Don't highlight search items by default
vim.opt.wildmenu = true -- Make tab completion for files/buffers act like bash
vim.opt.wildmode="list:full" -- Show a list when pressing tab; complete first full match
vim.opt.wildignore:append("*.swp,*.bak,*.pyc,*.class") -- Ignore these when autocompleting
vim.opt.cursorline = true -- Highlight line where the cursor is
vim.opt.fillchars:append { diff = "·" } -- in diffs, show deleted lines with dots rather than dashes
vim.opt.signcolumn = "yes" -- always show the signcolumn to minimize distraction of appearing and disappearing
-- vim.cmd(":autocmd InsertEnter * set nocul") -- Remove color when entering insert mode
-- vim.cmd(":autocmd InsertLeave * set cul") -- Turn color back on when exiting insert mode
-- vim.cmd("set guicursor=i:block") -- Always use block cursor. In some terminals and fonts (like iTerm), it can be hard to see the cursor when it changes to a line.
-- Copy all yanked/deleted lines to the "+" buffer. Useful when you want to use
-- the OS clipboard.
vim.cmd("set clipboard=unnamedplus")
lua/config/autocmds.lua
.config/nvim/lua/config/autocmds.lua
configures autocommands –
settings that are specific to a filetype or that should be triggered on
certain events.
-- Autocommands.
-- Display nonprinting characters (tab characters and trailing spaces).
vim.cmd(":autocmd InsertEnter * set listchars=tab:>•")
-- Also show trailing spaces after exiting insert mode
vim.cmd(":autocmd InsertLeave * set listchars=tab:>•,trail:∙,nbsp:•,extends:⟩,precedes:⟨")
-- Set the working directory to that of the opened file
vim.cmd("autocmd BufEnter * silent! lcd %:p:h")
-- <leader>d inserts a header for today's date. Different commands depending on
-- the format of the filetype (ReStructured Text or Markdown)
vim.api.nvim_create_autocmd("Filetype", {
pattern = "rst",
callback = function()
vim.keymap.set(
{ "n", "i" },
"<leader>d",
'<Esc>:r! date "+\\%Y-\\%m-\\%d"<CR>A<CR>----------<CR>',
{ desc = "Insert date as section title" }
)
vim.keymap.set(
"n",
"<leader>p",
'i` <>`__<Esc>F<"+pF`a',
{ desc = "Paste a ReST-formatted link from system clipboard" }
)
end,
})
-- (R)Markdown-specific mappings
vim.api.nvim_create_autocmd("Filetype", {
pattern = { "markdown", "rmd" },
callback = function()
vim.keymap.set(
{ "n", "i" },
"<leader>d",
'<Esc>:r! date "+\\# \\%Y-\\%m-\\%d"<CR>A',
{ desc = "Insert date as section title" }
)
vim.keymap.set(
"n",
"<leader>p",
'i[]()<Esc>h"+pF]i',
{ desc = "Paste a Markdown-formatted link from system clipboard" }
)
end,
})
-- Tell nvim about the snakemake filetype
vim.filetype.add({
filename = {
["Snakefile"] = "snakemake",
},
pattern = {
["*.smk"] = "snakemake",
["*.snakefile"] = "snakemake",
["*.snakemake"] = "snakemake",
["Snakefile*"] = "snakemake",
},
})
-- Set commentstring for snakemake, which is needed for vim-commentary
vim.api.nvim_create_autocmd("FileType", {
pattern = "snakemake",
callback = function() vim.cmd("set commentstring=#\\ %s") end,
})
-- Render RMarkdown in R running in terminal with <leader>k
vim.api.nvim_create_autocmd("FileType", {
pattern = { "rmarkdown", "rmd" },
callback = function()
vim.keymap.set(
"n",
"<leader>k",
":TermExec cmd='rmarkdown::render(\"%:p\")'<CR>",
{ desc = "Render RMar[k]down to HTML" }
)
vim.keymap.set(
"n",
"<leader>rm",
function ()
ft = vim.opt.ft:get()
if ft == "rmarkdown" or ft == "rmd" then
vim.cmd("set ft=markdown")
vim.cmd("RenderMarkdown enable")
end
if ft == "markdown" then
vim.cmd("set ft=rmarkdown")
vim.cmd("RenderMarkdown disable")
end
end,
{ desc = "Toggle render-markdown on an RMarkdown file" }
)
vim.keymap.set({
"n",
"i",
}, "<leader>`", "<Esc>i```{r}<CR>```<Esc>O", { desc = "New fenced RMarkdown code block" })
end,
})
-- Run Python code in IPython running in terminal
vim.api.nvim_create_autocmd("FileType", {
pattern = "python",
callback = function()
vim.keymap.set("n", "<leader>k", ":TermExec cmd='run %:p'<CR>", { desc = "Run Python file in IPython" })
end,
})
-- Briefly highlight yanked text
vim.api.nvim_create_autocmd("TextYankPost", {
callback = function()
vim.highlight.on_yank{higroup = "IncSearch", timeout=100}
end,
pattern = "*",
})
-- Modified from https://github.com/nvim-tree/nvim-tree.lua/wiki/Auto-Close.
-- If the last buffer(s) open are nvim-tree or trouble.nvim or aerial, then close them all and quit.
vim.api.nvim_create_autocmd("QuitPre", {
callback = function()
local close_wins = {}
local floating_wins = {}
local wins = vim.api.nvim_list_wins()
for _, w in ipairs(wins) do
local bufname = vim.api.nvim_buf_get_name(vim.api.nvim_win_get_buf(w))
if bufname:match("NvimTree_") ~= nil then -- nvim-tree buffer
table.insert(close_wins, w)
end
if bufname:match("Trouble") ~= nil then -- trouble.nvim buffer
table.insert(close_wins, w)
end
if bufname:match("Scratch") ~= nil then -- aerial buffer
table.insert(close_wins, w)
end
if vim.api.nvim_win_get_config(w).relative ~= "" then -- floating windows
table.insert(floating_wins, w)
end
end
-- If the buffer we are closing during this QuitPre action is the only one
-- that does not match the above patterns, then consider it the last text buffer,
-- and close all other buffers.
if 1 == #wins - #floating_wins - #close_wins then
for _, w in ipairs(close_wins) do
vim.api.nvim_win_close(w, true)
end
end
end,
})
lua/config/keymaps.lua
.config/nvim/lua/config/keymaps.lua
configures keymappings that are
not otherwise configured in the individual plugin configs.
-- Keymappings.
-- These are general keymappings. For keymappings related to plugins, see
-- lua/plugins/*.lua. Many keymappings will have descriptions which will show
-- up in which-key.
--
-- In general, see the `desc` fields for the keymap description.
-- Set up labeled groupings in which-key
local wk = require('which-key')
wk.register( { ["<leader>c"] = { name = "+code" } } )
wk.register( { ["<leader>f"] = { name = "+file or +find" } } )
wk.register( { ["<leader>o"] = { name = "+obsidian" } } )
vim.keymap.set("t", "<Esc>", "<C-\\><C-n>") -- Fix <Esc> in terminal buffer
vim.keymap.set("n", "<Leader>H", ":set hlsearch!<CR>", { desc = "Toggle search highlight" })
vim.keymap.set("n", "<leader>W", ":%s/\\s\\+$//<cr>:let @/=''<CR>", { desc = "Clean trailing whitespace" })
vim.keymap.set({ "n", "i" }, "<leader>R", "<Esc>:syntax sync fromstart<CR>", { desc = "Refresh syntax highlighting" })
vim.keymap.set(
{ "n", "i" },
"<leader>ts",
'<Esc>o<Esc>:r! date "+\\%Y-\\%m-\\%d \\%H:\\%M "<CR>A',
{ desc = "Insert timestamp" }
)
vim.keymap.set("n", "<leader>-", "80A-<Esc>d80<bar>", { desc = "Fill rest of line with -" })
vim.keymap.set("n", "<leader><tab>", ":set nowrap tabstop=", { desc = "Prepare for viewing TSV" })
-- Buffer navigation keymappings
vim.keymap.set("n", "<leader>1", ":bfirst<CR>", { desc = "First buffer" })
vim.keymap.set("n", "<leader>2", ":blast<CR>", { desc = "Last buffer" })
vim.keymap.set("n", "[b", ":bprevious<CR>", { desc = "Previous buffer" })
vim.keymap.set("n", "]b", ":bnext<CR>", { desc = "Next buffer" })
vim.keymap.set("n", "H", ":bprevious<CR>", { desc = "Previous buffer" })
vim.keymap.set("n", "L", ":bnext<CR>", { desc = "Next buffer" })
-- In preparation for copying, turn off various symbols and text that shouldn't
-- be copied, such as indent-blankline vertical lines, indicators of git
-- changes, and various virtual text and symbols. Also toggle line numbers.
--
-- In all cases, only disable things if the plugin is loaded in the first
-- place.
vim.keymap.set("n", "<leader>cp",
function()
if package.loaded["ibl"] ~= nil then
vim.cmd("IBLToggle")
end
if package.loaded["gitsigns"] ~= nil then
vim.cmd("Gitsigns toggle_signs")
end
if package.loaded["render-markdown"] ~= nil then
vim.cmd(":RenderMarkdown toggle")
end
vim.cmd("set nu!")
end,
{ desc = "Prepare for copying text to another program"}
)
-- Keymappings for navigating terminals.
-- <leader>q and <leader>w move to left and right windows respectively. Useful
-- when working with a terminal, or to aerial panels or nvim-tree panels. Works
-- even in insert mode; to enter a literal ',w' type more slowly after the
-- leader.
vim.keymap.set({ "n", "i" }, "<leader>w", "<Esc>:wincmd l<CR>", { desc = "Move to right window" })
vim.keymap.set({ "n", "i" }, "<leader>q", "<Esc>:wincmd h<CR>", { desc = "Move to left window" })
vim.keymap.set("t", "<leader>q", "<C-\\><C-n>:wincmd h<CR>", { desc = "Move to left window" })
-- Registers
vim.fn.setreg("l", "I'A',j") -- "listify": wrap with quotes and add trailing comma
lua/plugins/
Each plugin is described in more detail below in its own section. Each has
its own file in the .config/nvim/lua/plugins
directory.
Leader key¶
The leader key is remapped to ,. This is configured in .config/nvim/init.lua
.
command |
description |
---|---|
, |
Remapped leader. Below, when you see <leader> it means ,. |
Opening multiple files¶
command |
description |
---|---|
:e <filename> |
Open filename in new buffer |
<leader>ff |
Search for file in directory to open in new buffer (Telescope) |
<leader>fb |
Toggle file browser, hit Enter on file (see neotree for more) |
Switching between open files¶
command |
description |
configured in |
---|---|---|
[b, ]b |
Prev and next buffers |
|
H, L |
Prev buffer, next buffer |
|
<leader>1, <leader>2 |
First buffer, last buffer |
|
<leader>b then type highlighted letter in tab |
Switch buffer |
|
Don't like this?
See the config for the bufferline plugin to change; the bufferline is additionally styled using the zenburn plugin/colorscheme.
Screencast of switching buffers
This example uses an older version of the bufferline plugin and so the styling is a little different, but the shortcuts demonstrated are the same.

Shortcuts¶
Here are the shortcuts configured in the main config. See Nvim plugins for all the plugin-specific shortcuts.
You can browse around to see if there’s anything you think might be useful, but there’s no need for you to know all these! I happen to use them, and since I’ve included them in this config you get them for free.
The whichkey plugin will pop up a window showing what keys you can press, so you can use that for exploration as well.
command |
description |
configured in |
---|---|---|
<leader>H |
Toggle highlighted search. Sometimes it’s distracting to have all the highlights stick around. |
|
<leader>W |
Remove all trailing spaces in the file. Useful when cleaning up code to commit. |
|
<leader>R |
Refresh syntax highlighting. Useful when syntax highlighting gets wonky. |
|
@l |
Macro to surround the line with quotes and add a trailing comma. Useful for making Python or R lists out of pasted text |
|
<leader>- |
Fills in the rest of the line with “-”, out to column 80. Useful for making section separators. |
|
<leader><TAB> |
Useful for working with TSVs. Starts the command |
|
<leader>` |
(that’s a backtick) Adds a new RMarkdown chunk and places the cursor inside it |
|
<leader>d |
Insert the current date as a ReST or Markdown-formatted title, depending on the file type. Useful when writing logs. |
|
<leader>p |
Paste the contents of the OS clipboard into a formatted link as appropriate for the file type (ReST and Markdown currently supported) and puts the cursor in the link description. Note that this will not work to paste to vim on a remote server, unless you do tricky things with X forwarding, so consider it local-only. |
|
<leader>cp |
Toggle a sort of “copy mode”. Turns off line numbers, the vertical indentation lines from indent-blankline, any sign columns, and render-markdown (if enabled) so you can more easily copy text into another app. |
|
<leader>ts |
Insert a timestamp of the form |
|
Changed in version 2024-01-21: Added <leader>p for pasting formatted Markdown/ReST links
Changed in version 2024-03-31: Added <leader>cp for a convenient “copy mode”
Changed in version 2024-09-01: <leader>cp is more complete (toggles render-markdown and sign columns, too)
Other behaviors¶
Here are some other behaviors that are configured in .config/nvim/lua/config/options.lua
; expand for details.
Non-printing characters
<TAB> characters look like >••••
. Trailing spaces show up as dots like
∙∙∙∙∙
. Differentiating between tabs and spaces is extremely helpful in
tricky debugging situations.
~/.config/nvim/lua/config/autocmds.lua
has these lines:
vim.cmd(":autocmd InsertEnter * set listchars=tab:>•")
vim.cmd(":autocmd InsertLeave * set listchars=tab:>•,trail:∙,nbsp:•,extends:⟩,precedes:⟨")
The autocmds here mean that we only show the trailing spaces when we’re outside of insert mode, so that every space typed doesn’t show up as trailing. When wrap is off, the characters for “extends” and “precedes” indicate that there’s text offscreen.
Formatting text
The following options change the behavior of various formatting; see :h formatoptions
:
vim.opt.formatoptions = "qrn1coj"
Explanation of these options:
q: gq also formats comments
r: insert comment leader after <Enter> in insert mode
n: recognize numbered lists
1: don’t break a line after a 1-letter word
c: autoformat comments
o: automatically insert comment leader afer ‘o’ or ‘O’ in Normal mode.
Use Ctrl-u to quickly delete it if you didn’t want it.
j: where it makes sense, remove a comment leader when joining lines
Spell check
In case you’re not aware, vim has built-in spellcheck.
command |
description |
---|---|
|
Enable spell check |
]s |
Next spelling error |
[s |
Previous spelling error |
z= |
Show spelling suggestions |
Line coloring and cursor
See .config/nvim/lua/config/options.lua
for the set cul
and set nocul
options and set to your liking
Plugin documentation¶
See the separate section Nvim plugins for documentation.