Neovim Integration
Neovim exposes a rich Lua API. Master it to build powerful configurations and plugins.
vim Namespace
vim.opt (Options)
-- Set options
vim.opt.number = true
vim.opt.relativenumber = true
vim.opt.tabstop = 4
vim.opt.shiftwidth = 4
vim.opt.expandtab = true
vim.opt.smartindent = true
vim.opt.wrap = false
vim.opt.ignorecase = true
vim.opt.smartcase = true
vim.opt.termguicolors = true
vim.opt.signcolumn = "yes"
vim.opt.updatetime = 50
vim.opt.colorcolumn = "80"
-- Get option value
local tabstop = vim.opt.tabstop:get()
-- List options (append/remove)
vim.opt.wildignore:append({ "*.pyc", "__pycache__" })
vim.opt.wildignore:remove("*.pyc")
-- Map options
vim.opt.listchars = { tab = "» ", trail = "·", nbsp = "␣" }
-- Check if option is set
if vim.opt.number:get() then
print("Numbers enabled")
end
vim.g (Global Variables)
-- Set global variables (like let g:var in Vimscript)
vim.g.mapleader = " "
vim.g.maplocalleader = " "
-- Plugin variables
vim.g.loaded_netrw = 1
vim.g.loaded_netrwPlugin = 1
-- Get variable
local leader = vim.g.mapleader
-- Unset
vim.g.some_variable = nil
vim.bo / vim.wo (Buffer/Window Options)
-- Buffer-local options
vim.bo.filetype = "lua"
vim.bo.buftype = "nofile"
-- Window-local options
vim.wo.wrap = false
vim.wo.number = true
-- For specific buffer/window
vim.bo[bufnr].modifiable = false
vim.wo[winnr].cursorline = true
vim.fn (Vimscript Functions)
-- Call Vimscript functions
local home = vim.fn.expand("~")
local exists = vim.fn.filereadable("/etc/hosts")
local cwd = vim.fn.getcwd()
local lines = vim.fn.readfile("/etc/hosts")
-- System commands
local result = vim.fn.system("ls -la")
local lines = vim.fn.systemlist("ls -la")
-- Input
local name = vim.fn.input("Enter name: ")
local confirmed = vim.fn.confirm("Delete?", "&Yes\n&No") == 1
-- Clipboard
vim.fn.setreg("+", "copied text")
local clipboard = vim.fn.getreg("+")
Keymaps
vim.keymap.set
local map = vim.keymap.set
-- Basic mapping
map("n", "<leader>w", "<cmd>w<CR>", { desc = "Save file" })
map("n", "<leader>q", "<cmd>q<CR>", { desc = "Quit" })
-- With callback
map("n", "<leader>e", function()
vim.cmd("Explore")
end, { desc = "Open explorer" })
-- Multiple modes
map({ "n", "v" }, "<leader>y", '"+y', { desc = "Yank to clipboard" })
-- Buffer-local
map("n", "<leader>f", function()
vim.lsp.buf.format()
end, { buffer = true, desc = "Format buffer" })
-- Options
map("n", "j", "gj", {
noremap = true, -- Don't remap
silent = true, -- Don't echo
expr = false, -- Not an expression
desc = "Move down visual line"
})
-- Delete mapping
vim.keymap.del("n", "<leader>w")
Keymap Patterns
-- Leader mappings
local function setup_keymaps()
local map = vim.keymap.set
-- File operations
map("n", "<leader>ff", "<cmd>Telescope find_files<CR>", { desc = "Find files" })
map("n", "<leader>fg", "<cmd>Telescope live_grep<CR>", { desc = "Live grep" })
map("n", "<leader>fb", "<cmd>Telescope buffers<CR>", { desc = "Buffers" })
-- LSP
map("n", "gd", vim.lsp.buf.definition, { desc = "Go to definition" })
map("n", "gr", vim.lsp.buf.references, { desc = "References" })
map("n", "K", vim.lsp.buf.hover, { desc = "Hover" })
map("n", "<leader>ca", vim.lsp.buf.code_action, { desc = "Code action" })
map("n", "<leader>rn", vim.lsp.buf.rename, { desc = "Rename" })
-- Diagnostics
map("n", "[d", vim.diagnostic.goto_prev, { desc = "Previous diagnostic" })
map("n", "]d", vim.diagnostic.goto_next, { desc = "Next diagnostic" })
map("n", "<leader>d", vim.diagnostic.open_float, { desc = "Show diagnostic" })
end
Autocommands
vim.api.nvim_create_autocmd
-- Single autocommand
vim.api.nvim_create_autocmd("BufWritePre", {
pattern = "*.lua",
callback = function()
vim.lsp.buf.format()
end,
desc = "Format Lua on save"
})
-- Multiple events
vim.api.nvim_create_autocmd({ "BufEnter", "BufWinEnter" }, {
pattern = "*.md",
callback = function()
vim.opt_local.wrap = true
vim.opt_local.spell = true
end,
})
-- With group (for clearing)
local group = vim.api.nvim_create_augroup("MyGroup", { clear = true })
vim.api.nvim_create_autocmd("FileType", {
group = group,
pattern = "python",
callback = function(args)
vim.bo[args.buf].tabstop = 4
end,
})
-- Clear group
vim.api.nvim_clear_autocmds({ group = "MyGroup" })
Common Autocommands
local augroup = vim.api.nvim_create_augroup
local autocmd = vim.api.nvim_create_autocmd
-- Highlight on yank
autocmd("TextYankPost", {
group = augroup("HighlightYank", { clear = true }),
callback = function()
vim.highlight.on_yank({ timeout = 200 })
end,
})
-- Remove trailing whitespace
autocmd("BufWritePre", {
group = augroup("TrimWhitespace", { clear = true }),
pattern = "*",
command = [[%s/\s\+$//e]],
})
-- Restore cursor position
autocmd("BufReadPost", {
group = augroup("RestoreCursor", { clear = true }),
callback = function()
local mark = vim.api.nvim_buf_get_mark(0, '"')
local line_count = vim.api.nvim_buf_line_count(0)
if mark[1] > 0 and mark[1] <= line_count then
vim.api.nvim_win_set_cursor(0, mark)
end
end,
})
-- Auto-resize splits
autocmd("VimResized", {
group = augroup("AutoResize", { clear = true }),
command = "wincmd =",
})
User Commands
vim.api.nvim_create_user_command
-- Simple command
vim.api.nvim_create_user_command("Hello", function()
print("Hello, World!")
end, { desc = "Say hello" })
-- With arguments
vim.api.nvim_create_user_command("Greet", function(opts)
print("Hello, " .. opts.args)
end, {
nargs = 1, -- Exactly one argument
desc = "Greet someone"
})
-- With completion
vim.api.nvim_create_user_command("Config", function(opts)
vim.cmd("edit ~/.config/nvim/" .. opts.args)
end, {
nargs = 1,
complete = function()
return { "init.lua", "lua/plugins/init.lua", "lua/config/keymaps.lua" }
end,
})
-- Range command
vim.api.nvim_create_user_command("FormatRange", function(opts)
vim.lsp.buf.format({
range = {
["start"] = { opts.line1, 0 },
["end"] = { opts.line2, 0 },
}
})
end, { range = true })
-- Buffer-local command
vim.api.nvim_buf_create_user_command(0, "LocalCmd", function()
print("Buffer-local command")
end, {})
vim.api (Low-Level API)
Buffer Operations
-- Get current buffer
local bufnr = vim.api.nvim_get_current_buf()
-- Get buffer lines
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
-- Set buffer lines
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, {"line1", "line2"})
-- Get buffer name
local name = vim.api.nvim_buf_get_name(bufnr)
-- Create new buffer
local new_buf = vim.api.nvim_create_buf(true, false) -- listed, scratch
-- Delete buffer
vim.api.nvim_buf_delete(bufnr, { force = true })
Window Operations
-- Get current window
local winnr = vim.api.nvim_get_current_win()
-- Get window buffer
local bufnr = vim.api.nvim_win_get_buf(winnr)
-- Set window buffer
vim.api.nvim_win_set_buf(winnr, bufnr)
-- Get cursor position
local pos = vim.api.nvim_win_get_cursor(winnr) -- {row, col}
-- Set cursor
vim.api.nvim_win_set_cursor(winnr, { 10, 0 })
-- Create split
vim.api.nvim_command("vsplit")
local new_win = vim.api.nvim_get_current_win()
Floating Windows
-- Create floating window
local buf = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_lines(buf, 0, -1, false, {
"Floating window content",
"Line 2",
"Line 3"
})
local width = 40
local height = 10
local win = vim.api.nvim_open_win(buf, true, {
relative = "editor",
width = width,
height = height,
col = (vim.o.columns - width) / 2,
row = (vim.o.lines - height) / 2,
style = "minimal",
border = "rounded",
})
-- Close floating window
vim.api.nvim_win_close(win, true)
vim.notify (Notifications)
-- Basic notification
vim.notify("File saved")
-- With level
vim.notify("Warning!", vim.log.levels.WARN)
vim.notify("Error occurred", vim.log.levels.ERROR)
vim.notify("Info message", vim.log.levels.INFO)
-- Levels: TRACE, DEBUG, INFO, WARN, ERROR, OFF
-- With title (requires nvim-notify or similar)
vim.notify("Message", vim.log.levels.INFO, { title = "My Plugin" })
vim.schedule
-- Schedule function to run in main loop
-- Required when calling API from callbacks
vim.schedule(function()
vim.api.nvim_buf_set_lines(0, 0, -1, false, {"Updated"})
end)
-- Deferred execution
vim.defer_fn(function()
print("After 1 second")
end, 1000)
-- vim.schedule_wrap for callbacks
local safe_callback = vim.schedule_wrap(function()
vim.notify("Safe to call API here")
end)
Plugin Development
Plugin Structure
-- lua/myplugin/init.lua
local M = {}
M.config = {
enabled = true,
option = "default"
}
function M.setup(opts)
M.config = vim.tbl_deep_extend("force", M.config, opts or {})
if not M.config.enabled then
return
end
M._setup_keymaps()
M._setup_commands()
M._setup_autocmds()
end
function M._setup_keymaps()
vim.keymap.set("n", "<leader>mp", M.action, { desc = "My plugin action" })
end
function M._setup_commands()
vim.api.nvim_create_user_command("MyPlugin", M.action, {})
end
function M._setup_autocmds()
local group = vim.api.nvim_create_augroup("MyPlugin", { clear = true })
vim.api.nvim_create_autocmd("BufEnter", {
group = group,
callback = M._on_buf_enter,
})
end
function M.action()
vim.notify("Plugin action!", vim.log.levels.INFO, { title = "MyPlugin" })
end
function M._on_buf_enter()
-- Handle buffer enter
end
return M
Usage
-- In user config
require("myplugin").setup({
enabled = true,
option = "custom"
})
Next Module
Practical Patterns - Real patterns from domus-nvim.