Collecting my attempts to improve at tech, art, and life

My init.lua for Neovim

Tags: config

A snapshot of my Neovim config, literate programming style.

Right now, everything goes in init.lua. I may tidy that up later.

init.lua global prep

Most Neovim Lua functionality is contained in the vim module. Pull some of the frequently used ones into the current namespace, to save a little typing for our fingers.

vim commands (eg cmd('pwd'))
vim functions (eg fn.buffer())
a table for global variables
vim options
local cmd = vim.cmd
local fn = vim.fn
local g = vim.g
local opt = vim.opt

Helper functions

There’s just map for the moment. It creates mappings with noremap option enabled 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)

Bootstrap packer.nvim

Make sure the Packer plugin manager is instealled and ready.

local install_path = fn.stdpath('data')..'/site/pack/packer/start/packer.nvim'

if fn.empty(fn.glob(install_path)) > 0 then
  packer_bootstrap = fn.system({
    '--depth', '1',

  use "wbthomason/packer.nvim"

  // ==> Specify my packages.

  if packer_bootstrap then

The Packages

It’s mostly use "source/repo". I’ll pull the more interesting bits out into their own blocks.

//- Specify my packages

use "nvim-lua/popup.nvim"
use "nvim-lua/plenary.nvim"
use "kyazdani42/nvim-web-devicons"
use "neovim/nvim-lspconfig"

use "EdenEast/nightfox.nvim"
use "RRethy/nvim-base16"
use { "catppuccin/nvim", as = "catpuccin" }
use 'folke/tokyonight.nvim'
use {
  requires = {'rktjmp/lush.nvim'},
use 'pineapplegiant/spaceduck'

// ==> Load treesitter.
// ==> Load filetype.nvim.
// ==> Load telescope.nvim.
// ==> Load null-ls.nvim.
// ==> Load which-key.nvim.
// ==> Load lualine.nvim.
// ==> Load programming language support.
// ==> Load riv.vim.


nvim-treesitter is an experimental binding for Neovim. Something to do with syntax highlighting? Both it and the plugins that use it change frequently. So I better follow the instructions about keeping everything up to date when I sync.

//- Load treesitter
use {
  run = ":TSUpdate",
  config = function()
    require("nvim-treesitter.configs").setup {
      ensure_installed = "all",
      highlight = {
        enable = true,
      additional_vim_regex_highlighting = false,

    local parser_config = require("nvim-treesitter.parsers").get_parser_configs()
    parser_config.just = {
      install_info = {
        url = "", -- local path or git repo
        files = { "src/parser.c", "src/" },
        branch = "main",
      maintainers = { "@IndianBoy42" },


//- Load filetype.nvim

use {
  config = function()
    require("filetype").setup {
      overrides = {
        extensions = {
          tf = "terraform",
          tfvars = "terraform",
          tfstate = "json",


telescope.nvim is a ridiculously fancy fuzzy-finder.

Not sure if I must specify plenary.nvim as a requirement when I’m already loading it. Better safe than sorry.

//- Load telescope.nvim
use { "nvim-telescope/telescope.nvim",
  requires = { {"nvim-lua/plenary.nvim"} },

Global key bindings for telescope.nvim

Showing the global telescope.nvim key bindings here, though Yarner will be inserting them outside all this plugin definition stuff. I haven’t figured out how to do global keybindings in a plugin setup quite yet.

//- Add global bindings for telescope.nvim
map("n", "<leader>ff", "<cmd>lua require('telescope.builtin').find_files()<cr>")
map("n", "<leader>fg", "<cmd>lua require('telescope.builtin').live_grep()<cr>")
map("n", "<leader>fb", "<cmd>lua require('telescope.builtin').buffers()<cr>")
map("n", "<leader>fh", "<cmd>lua require('telescope.builtin').help_tags()<cr>")


//- Load null-ls.nvim
use {
  config = function()
      sources = {


I first bumped into the which-key help menu in Doom Emacs. Start a chained key binding like SPC, a menu pops up showing what chains are available. Indispensable there. Indispensable here. Thank goodness folks are porting so many Emacs packages to Neovim.

//- Load which-key.nvim
use {
  config = function()
    require("which-key").setup {}


//- Load lualine.nvim
use { 'nvim-lualine/lualine.nvim',
  requires = { 'kyazdani42/nvim-web-devicons', opt = true },
  config = function()
      -- options = { theme = "duskfox", }

Programming Languages

A couple of the tools I use regularly require some special handling.

//- Load programming language support
use "habamax/vim-asciidoctor"
use "vim-crystal/vim-crystal"
use "glench/vim-jinja2-syntax"
use {
  run = ":TSInstall nu",
  config = function()
use { "psf/black", cmd = {"Black"}}


I bounce way too much between systems. Right now I use Riv when in Neovim. What can I say? I like reStructuredText.

use Riv’s :doc: role instead of [[...]] for wiki links
//- Load riv.vim
use {
  config = function()
    riv_main = {
      path = "~/Dropbox/riv/main"
    vim.g.riv_projects = {riv_main}
    vim.g.riv_file_link_style = 2
    vim.g.riv_highlight_code = "lua,python,cpp,javascript,sh,terraform|hcl"

Global options

opt.autoread = true
opt.background = 'dark'
opt.completeopt = {'menuone', 'noinsert', 'noselect'}  -- completion options (for deoplete)
opt.cursorline = true               -- highlight current line
opt.encoding = "utf-8"
opt.expandtab = true                -- spaces instead of tabs
opt.hidden = true                   -- enable background buffers
opt.ignorecase = true               -- ignore case in search
opt.joinspaces = false              -- no double spaces with join
opt.list = true                     -- show some invisible characters
opt.maxmempattern = 1000            -- for Riv
opt.mouse = "nv"                    -- Enable mouse in normal and visual modes
opt.number = true                   -- show line numbers
opt.relativenumber = true           -- number relative to current line
opt.scrolloff = 4                   -- lines of context
opt.shiftround = true               -- round indent
opt.shiftwidth = 2                  -- size of 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 vertical splits to right
opt.termguicolors = true            -- truecolor support
opt.wildmode = {'list', 'longest'}  -- command-line completion mode
opt.wrap = false  -- disable line wrap

cmd[[filetype plugin on]]
cmd[[autocmd FileType * setlocal formatoptions-=cro]]
cmd[[autocmd FocusGained * checktime]]
cmd[[colorscheme nightfox]]

cmd[[autocmd BufWritePre *.py execute 'Black']]
cmd[[autocmd BufEnter *.astro set ft=astro]]

Global variables

g.mapleader = ' '
g.maplocalleader = ','
g.python3_host_prog = '~/.pyenv/versions/neovim/bin/python'
g.markdown_fenced_languages = {
g.rst_syntax_code_list = { "python" }


Because I dislike unexpected floating text in my terminal.

  virtual_text = false,


map("n", "<bs>", ":nohlsearch<cr>", { silent = true })

// ==> Add global bindings for telescope.nvim.

Language Server Protocol (LSP)

For a fancy IDE-like experience when editing code. And other structured text, if you’re so inclined.

I pretty much use the standard suggested config.

local lsp_opts = { noremap=true, silent=true }
map('n', '<space>e', '<cmd>lua vim.diagnostic.open_float()<CR>', lsp_opts)
map('n', '[d', '<cmd>lua vim.diagnostic.goto_prev()<CR>', lsp_opts)
map('n', ']d', '<cmd>lua vim.diagnostic.goto_next()<CR>', lsp_opts)
map('n', '<space>q', '<cmd>lua vim.diagnostic.setloclist()<CR>', lsp_opts)

-- Use an on_attach function to only map the following keys
-- after the language server attaches to the current buffer
local lspconfig_on_attach = function(client, bufnr)
  -- Enable completion triggered by <c-x><c-o>
  vim.api.nvim_buf_set_option(bufnr, 'omnifunc', 'v:lua.vim.lsp.omnifunc')

  -- Mappings.
  -- See `:help vim.lsp.*` for documentation on any of the below functions
  vim.api.nvim_buf_set_keymap(bufnr, 'n', 'gD', '<cmd>lua vim.lsp.buf.declaration()<CR>', lsp_opts)
  vim.api.nvim_buf_set_keymap(bufnr, 'n', 'gd', '<cmd>lua vim.lsp.buf.definition()<CR>', lsp_opts)
  vim.api.nvim_buf_set_keymap(bufnr, 'n', 'K', '<cmd>lua vim.lsp.buf.hover()<CR>', lsp_opts)
  vim.api.nvim_buf_set_keymap(bufnr, 'n', 'gi', '<cmd>lua vim.lsp.buf.implementation()<CR>', lsp_opts)
  vim.api.nvim_buf_set_keymap(bufnr, 'n', '<C-k>', '<cmd>lua vim.lsp.buf.signature_help()<CR>', lsp_opts)
  vim.api.nvim_buf_set_keymap(bufnr, 'n', '<space>wa', '<cmd>lua vim.lsp.buf.add_workspace_folder()<CR>', lsp_opts)
  vim.api.nvim_buf_set_keymap(bufnr, 'n', '<space>wr', '<cmd>lua vim.lsp.buf.remove_workspace_folder()<CR>', lsp_opts)
  vim.api.nvim_buf_set_keymap(bufnr, 'n', '<space>wl', '<cmd>lua print(vim.inspect(vim.lsp.buf.list_workspace_folders()))<CR>', lsp_opts)
  vim.api.nvim_buf_set_keymap(bufnr, 'n', '<space>D', '<cmd>lua vim.lsp.buf.type_definition()<CR>', lsp_opts)
  vim.api.nvim_buf_set_keymap(bufnr, 'n', '<space>rn', '<cmd>lua vim.lsp.buf.rename()<CR>', lsp_opts)
  vim.api.nvim_buf_set_keymap(bufnr, 'n', '<space>ca', '<cmd>lua vim.lsp.buf.code_action()<CR>', lsp_opts)
  vim.api.nvim_buf_set_keymap(bufnr, 'n', 'gr', '<cmd>lua vim.lsp.buf.references()<CR>', lsp_opts)
  vim.api.nvim_buf_set_keymap(bufnr, 'n', '<space>f', '<cmd>lua vim.lsp.buf.formatting()<CR>', lsp_opts)

require("lspconfig").pyright.setup { on_attach = lspconfig_on_attach, }

require("lspconfig").tsserver.setup {
  on_attach = lspconfig_on_attach,

And that’s it!

Added to vault 2024-01-15. Updated on 2024-02-05