I have a lot of installed vim plugins, and most of them have a setup configuration and keybindings for which-key. This configuration is spread into different spots:
packer.startup
hook in the plugins.lua
file.keybindings.lua
config
callback or in separate files if there are too many lines to keep plugins.lua
readable.SpaceVim is organizing vim plugins into layers, which can be enabled depending on user needs, so I decided to implement something alike to mitigate 2 things:
The implementation contains 2 parts:
The first one is quite simple — just make hooks for each specific step:
pre_install
— some code needs to be called before packer
handles installation.install
— a callback to control packer installation for package plugins.setup
— a callback to handle package configuration.post_setup
— this guy is called after all plugins are configured.If a package has an implementation of a specific hook, this hook gonna be called.
The second one is about handling the configuration, such as keybindings and specific package parameters:
keys
table, which is extended via package keybindings, if it has it.config.lua
file containing tables with specific settings. This config either can be read directly in a package or injected via lookup (if you prefer to avoid coupling). I'm personally fine with the first approach so far.A has_prop
function calls a callback against all packages
has a specific prop
:
-- lib/utils/has_prop.lua
local function has_prop(packages, prop, callback)
for _,p in pairs(packages) do
local package = require(p)
if package[prop]~=nil then
callback(package)
end
end
end
return {
has_prop = has_prop,
}
The list of packages looks like this:
-- config.lua
local enabled_packages = {
'packages/packer',
'packages/which-key',
'packages/theme',
'packages/indent-blankline',
'packages/git',
'packages/lualine',
'packages/fzf',
'packages/editorconfig',
'packages/comment',
'packages/cmp-completion',
'packages/lsp',
'packages/tmux-navigation',
'packages/nvim-session',
'packages/quickfix-to-bottom',
'packages/neo-tree',
'packages/treesitter',
'packages/null-ls',
'packages/trouble',
'packages/todo-comments'
}
Each package can be either a file packages/package-name.lua
or a directory with init.lua
if you need to split it into a few files (packages/package-name/init.lua
, etc) looks like below:
-- ./packages/some-plugin/init.lua
-- which-keys bindings
local keys = {}
local function pre_install(use)
-- pre-installation logic, if required
end
local function install(use)
use 'some/plugin.nvim'
end
local function setup()
-- Plugin setup
require('plugin').setup({})
end
local function post_setup()
-- Plugin setup
require('plugin').setup({})
end
return {
pre_install = pre_install,
install = install,
setup = setup,
post_setup = post_setup
keys = keys,
}
How packages and hooks are connected:
-- packages.lua
local packer = require('packer')
local has_prop = require('lib/utils/has_prop').has_prop
local enabled_packages = require('config').enabled_packages
-- Call pre_install hooks
has_prop(enabled_packages, 'pre_install', function(package)
package.pre_install()
end)
packer.startup(function (use)
-- Call install hooks
has_prop(enabled_packages, 'install', function(package)
package.install(use)
end)
-- Call setup hooks
has_prop(enabled_packages, 'setup', function(package)
package.setup()
end)
end)
-- Call post_setup hooks
has_prop(enabled_packages, 'post_setup', function(package)
package.post_setup()
end)
There is an example of how to handle which-key
keybindings. Let's create a global key
table in config.lua
:
-- config.lua
-- base which-key config
local keys = {}
return {
-- ...
keys = keys,
}
Then extend this table via package keybindings:
-- packages.lua
local table_merge = require('lib/utils/table_merge').table_merge
local keys = require('config').keys
-- ...
packer.startup(function (use)
-- ...
-- Get keybindings
has_prop(enabled_packages, 'keys', function(package)
table_merge(keys, package.keys)
end)
end
table_merge
implementation:
local function table_merge(t1, t2)
for k,v in pairs(t2) do
if type(v) == "table" then
if type(t1[k] or false) == "table" then
table_merge(t1[k] or {}, t2[k] or {})
else
t1[k] = v
end
else
t1[k] = v
end
end
return t1
end
return { table_merge = table_merge }
And let's handle configuring which-key
in its package:
-- packages/which-key.lua
-- use global `keys`:
local keys = require('config').keys
local function install(use)
use 'folke/which-key.nvim'
end
-- Register bindings after all packages have been initialized
-- and configure its keybindings.
local function post_setup()
local wk = require("which-key")
wk.register(keys, { prefix = "<leader>", mode = 'n' })
end
return {
install = install,
post_setup = post_setup
}
Now just enable package/which-keys
package:
-- config.lua
local enabled_packages = {
'packages/which-key',
-- ...
}
Here we go, now each package can have a keys
table, which is handled by which-keys
.
A package handling packer installation and configuration:
-- packages/packer.lua
local fn = vim.fn
local execute = vim.api.nvim_command
local function pre_install()
-- Auto-install packer if it hasn't been installed
local install_path = fn.stdpath('data')..'/site/pack/packer/start/packer.nvim'
if fn.empty(fn.glob(install_path)) > 0 then
fn.system({'git', 'clone', 'https://github.com/wbthomason/packer.nvim', install_path})
execute 'packadd packer.nvim'
end
end
local function install(use)
use 'wbthomason/packer.nvim'
end
return {
pre_install = pre_install,
install = install,
}
Pack to a package some specific behavior, like saving a session:
local api = vim.api
-- Save / Load session
local function setup()
api.nvim_command('au BufWinLeave,BufLeave,BufWritePost,BufHidden,QuitPre ?* nested silent! mkview!')
api.nvim_command('autocmd BufWinEnter ?* silent! loadview')
end
return {
setup = setup,
}
Trouble package:
-- packages/trouble.lua
local keys = {
T = {
name = "Trouble",
T = { '<cmd>TroubleToggle<cr>', 'Toggle' },
D = { '<cmd>TroubleToggle workspace_diagnostics<cr>', 'Workspace Diagnostics' },
d = { '<cmd>Trouble document_diagnostics<cr>', 'Document Diagnostics' },
q = { '<cmd>Trouble quickfix<cr>', 'Quickfix' },
l = { '<cmd>Trouble loclist<cr>', 'Loclist' },
r = { '<cmd>Trouble lsp_references<cr>', 'References' },
},
}
local function install(use)
use {
'folke/trouble.nvim',
requires = 'kyazdani42/nvim-web-devicons'
}
end
local function setup()
require('trouble').setup({})
end
return {
install = install,
setup = setup,
keys = keys,
}
Further Reading: