March 06, 2022
Professionally I started using Vi in 1997 on AIX. Personally I started using Vim around 2008 when I began using Octopress as my static site generator. In November 2011 I started keeping the configuration in a Git repository. In December 2014 I started using Neovim in addition to Vim. Eventually my use of Vim tapered off, and for the past several years I haven’t bothered to keep my Vim configuration up-to-date. It has now been deprecated in my dotfiles repository in favor of Neovim.
The size and complexity of my configuration has ebbed and flowed over time; generally trending
toward more complexity and greater size. When I set out to migrate to a Lua based configuration
file, my init.vim
file was just over 1000 lines long, excluding comments and white space.
To paraphrase Gregory Mallory, “Because I can.”
All kidding aside I converted for two reasons. First I don’t know anything about Lua , and this was a chance to learn a little about this language. Second, it would force me to examine my entire configuration, allowing for some house cleaning.
The support for Lua is still evolving, so having your configuration written in Lua is pretty close to bleeding edge. For me, tinkering with my Neovim setup is part of the enjoyment of using Neovim, so I’m willing to endure some pain caused by being closer to the leading edge of development.
Rather than have a single, monolithic file, containing my entire configuration, I wanted to modularize my setup. Fortunately many of the examples I found on GitHub and the r/vimporn and r/neovim Reddits are broken out into directories and files.
Only install the plugins I have a use for. My previous Neovim configuration had gotten crufty, bloated even, with plugins I no longer used, and with odd mappings I no longer remembered the purpose for.
I have several computers of my own, and a couple provided by my employer, I need this configuration to be portable and relatively easy to install in a variety of environments. My preferred OS is MacOS, but I work on Ubuntu and AmazonLinux servers professionally, and I have a Linux laptop for personal experimentation.
Not wanting to corrupt my current Neovim configuration, I chose to start experimenting with a Lua configuration on a Raspberry Pi. I simply didn’t setup my configuration when I installed Neovim on the Pi. Since this meant I was using the configuration I was making to edit the configuration I was making (eating my own dog food), I was painfully aware any time I managed to break something.
When, in the course of exploring other people’s configurations, I discovered some new plugin that I
wanted right away, I’d add it to the init.vim
Neovim
configuration on my other machines, by embedding the Lua code in the Vimscript base.
Eventually I felt I understood enough about how to structure a Lua configuration, that I started in earnest with a new branch of my dotfiles repository. That branch continued to improved and I started using it for everyday use about a month ago.
This past weekend I watched nearly all of the videos in the “Neovim from Scratch” series on YouTube, which resulted in a major refactoring of my setup. The result is cleaner and better organized. It is also more robust.
My Neovim configuration is organized into several directories and about 40 files. While this may seem like a lot, the structure is straight forward and easy to understand.
nvim
├── lua
│ ├── config
│ │ ├── lsp
│ │ │ ├── settings
│ │ │ │ ├── jsonls.lua
│ │ │ │ └── sumneko_lua.lua
│ │ │ ├── handlers.lua
│ │ │ ├── init.lua
│ │ │ └── lsp-installer.lua
│ │ ├── cmp.lua
│ │ ├── gitsigns.lua
│ │ ├── gundo.lua
│ │ ├── lualine.lua
│ │ ├── nvim-comment.lua
│ │ ├── nvim-tree.lua
│ │ ├── tabline.lua
│ │ ├── telescope.lua
│ │ ├── toggleterm.lua
│ │ ├── treesitter.lua
│ │ └── which-key.lua
│ └── usr
│ ├── autocmds.lua
│ ├── colors.lua
│ ├── helpers.lua
│ ├── mappings.lua
│ ├── options.lua
│ └── plugins.lua
├── plugin
│ └── packer_compiled.lua
├── spell
│ ├── en.utf-8.add
│ └── en.utf-8.add.spl
├── .gitignore
├── README.md
└── init.lua
The nvim
directory is located in ~/.config
. It contains the init.lua
file, a README, the Git
repository and ignore file, and a lua
directory. The init.lua
file has a list of requires
, one
for each of these categories.
autocmds
, colors
, mappings
, and options
all contain what you would expect: auto commands, my
color scheme, all my key mappings, and all my options.
helpers
has several functions that are useful for creating mappings or setting options.
plugins
sets up my plugin manager of choice, and all the plugins I use.
The actual files referenced by these requires
are kept in ~/.config/nvim/lua/usr
. Putting them in a folder
under the lua
directory creates a namespace, which is useful in avoiding collisions with files that
might be included in plugins added later. The namespace directory can be called anything, I chose
usr
since the contents are for me, and since user
might show up in a plugin. Many people use
their GitHub account name for this namespace directory.
The lua
directory has two sub-directories: config
and usr
.
config
is where the configuration files for plugins are kept. For any plugin where there is a
configuration file, that file is kept here. Since setting up and maintaining language servers is
slightly different than most plugins, there is a separate directory under config
for LSP specific
configurations.
As described above, usr
contains the files that describe my mappings, options, auto commands,
color scheme, and plugins.
The entire configuration can be viewed and cloned from my dotfiles repository. However there are some specific examples I think are important enough to draw attention to.
Including a plugin via the Lua require
statement will result in an error if the plugin can’t be
found or isn’t available. When this happens the Neovim configuration won’t load properly. Using the Lua
pcall
function to wrap the require allows the status of the call to be captured and tested, thus
protecting the rest of the configuration process.
Each of my plugin configuration files has this code block at the start of the file.
local status_ok, plugin_handle = pcall(require, "plugin_name")
if not status_ok then
return
end
The actual name of the plugin is substituted in for plugin_name
. plugin-handle
is a
local variable that is used by the rest of the file as it points to the instance of the plugin
returned by the require statement. A print statement or vim.notify
statement could be added just
ahead of the return
, if you wanted to provide some visible feedback in the event of a failed
require.
I’m using Packer to manage my plugins. The following code block will automatically install Packer if it isn’t already installed.
local fn = vim.fn
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 {
'git',
'clone',
'--depth',
'1',
'https://github.com/wbthomason/packer.nvim',
install_path,
}
print "Installing Packer, close and reopen Neovim."
vim.cmd [[packadd packer.nvim]]
end
In essence this clones the GitHub repository for Packer into the proper location in the file system.
This block of code will trigger a :PackerSync
command any time the plugins.lua
buffer is
written. Very useful for updating the current list of plugins.
vim.cmd [[
augroup packer_user_config
autocmd!
autocmd BufWritePost plugins.lua source <afile> | PackerSync
augroup end
]]
use
and get_config
All of the plugins are managed inside this code block.
return packer.startup(function(use)
-- plugins go here
if PACKER_BOOTSTRAP then
require('packer').sync()
end
end)
Each plugin is identified by a use
statement.
use { "plugin_name" }
It is possible to specify dependencies on other plugins as a part of the use
statement.
use {
"plugin_name",
requires { "dependency" },
}
It is also possible to specify the configuration file for the plugin here.
use {
"plugin_name",
config = get_config("plugin"),
}
get_config
is a small helper function I included in the plugins.lua
file.
local function get_config(name)
return string.format("require(\"config/%s\")", name)
end
Currently I am using the following plugins.
There are others (dependencies and ancillary plugins) I haven’t listed. See the my GitHub repository for the complete set.
I’ve been using my “new and improved” Neovim configuration for several days now. Other than a couple of minor tweaks to plugin settings and mappings, it has worked flawlessly. I’ve been able to install it on all my computers, with very little effort. The only part that isn’t complete is the tracking of words I’ve added to the spelling dictionary.
Neovim continues to be my favorite tool, and tinkering with its configuration is a very satisfying activity.
These are some of the sources I used while converting from a vimscript based configuration to a Lua based configuration.