Complex Keymap Management In Neovim

Neovim’s vim.keymap.set and similar utilities are very useful for creating and managing keymaps, however they lack some ergonomics when building more complex keymaps that require setting and deleting the keymaps periodically. A simply utility function makes this really clean and easy to do.

I’ve called this function map_fn (because I honestly couldn’t come up with a better name), and when called it will return a class containing chainable set and del methods.

--- @class Map
--- @field set fun(): Map
--- @field del fun(): Map

--- Creates a keymap function
--- @param mode string|table
--- @param key string
--- @param cmd string|function
--- @param opts_or_desc table|string|nil
--- @return Map
local map_fn = function(mode, key, cmd, opts_or_desc)
	local Map = {}
	Map.__index = Map

	function Map:new()
		local instance = setmetatable({}, self)
		return instance
	end

	function Map:set()
        local opts

        if type(opts_or_desc) == "string" then
            opts = { desc = opts_or_desc, silent = true }
        else
            opts = vim.tbl_extend("keep", opts_or_desc or {}, { silent = true })
        end

		vim.keymap.set(mode, key, cmd, normalize_map_opts(opts_or_desc))
		return self
	end

	function Map:del()
		vim.keymap.del(mode, key)
		return self
	end

	return Map:new():set()
end

Combining this with the augroup function I created in a previous Byte, we can build a complex mapping and autocmd setup like this with very little code:

local command_line = map_fn("n", "q;", "q:", "Open command line window"):set()

augroup("command_line", function(autocmd)
	autocmd("RecordingEnter", { callback = command_line.del })
	autocmd("RecordingLeave", { callback = command_line.set })
end)

What this does is create a mapping q; which is equivalent to q: to open the command line window. However, because the macro recording stop keymap is q, we only want to enable this mapping when recording is not active, so our autocmd will handle deleting and re-creating the keymap for us when the autocmds fire.