Skip to content
AgileLogics
5 min read

Configuring and Extending Neovim

Prerequisites: Modules and require

Neovim embeds LuaJIT and exposes its whole API to Lua. Configuration lives in ~/.config/nvim/init.lua (on Windows, ~/AppData/Local/nvim/init.lua).

Options, key mappings, and autocommands

-- illustrative
-- init.lua
vim.g.mapleader = " "

vim.opt.number = true
vim.opt.relativenumber = true
vim.opt.tabstop = 2
vim.opt.shiftwidth = 2
vim.opt.expandtab = true

vim.keymap.set("n", "<leader>w", "<cmd>write<cr>", {desc = "Save file"})
vim.keymap.set("n", "<leader>q", function()
  vim.notify("Goodbye!")
  vim.cmd("quit")
end, {desc = "Quit"})

vim.api.nvim_create_autocmd("TextYankPost", {
  callback = function() vim.highlight.on_yank({timeout = 200}) end,
})

require("config.lazy")   -- loads lua/config/lazy.lua
  • vim.opt sets options, vim.keymap.set defines mappings (the right-hand side can be a Lua function), and vim.api.nvim_create_autocmd runs code on events.
  • require looks in the lua/ folder of your config, so lua/config/lazy.lua is require("config.lazy").

A tiny plugin

-- illustrative
-- lua/todo/init.lua
local M = {}

function M.find()
  local buf = vim.api.nvim_get_current_buf()
  local lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
  local items = {}
  for i, line in ipairs(lines) do
    if line:find("TODO") then
      table.insert(items, {bufnr = buf, lnum = i, text = line})
    end
  end
  vim.fn.setqflist(items)
  vim.cmd("copen")
end

function M.setup(opts)
  opts = opts or {}
  vim.api.nvim_create_user_command("Todos", M.find, {})
  vim.keymap.set("n", opts.key or "<leader>t", M.find, {desc = "List TODOs"})
end

return M

The setup(opts) convention lets users configure a plugin with require("todo").setup({key = "<leader>T"}).

The ecosystem

  • lazy.nvim — the most popular plugin manager.
  • vim.lsp — built-in Language Server Protocol client.
  • vim.treesitter — syntax-aware highlighting and text objects.
  • :help lua-guide — the official guide to Lua in Neovim.

Remember that Neovim runs LuaJIT: no integer division, <close>, or other 5.3+ features.