Keymaps and Leader Key
Keymaps are the customizations that make Neovim your editor. A good keymap scheme is organized, memorable, and documented.
Core Idea
The leader key is a prefix key (default: \, recommended: <Space>) that namespaces your custom shortcuts to avoid conflicts with Neovim's built-in keys.
Setting the Leader Key
~/.config/nvim/init.lua (before any plugins!)
-- MUST be set before plugins load
vim.g.mapleader = " " -- Space as leader
vim.g.maplocalleader = "\\" -- Backslash as local leader
warning
The leader key MUST be set before lazy.nvim or any other plugin manager loads. Set it at the very top of init.lua or before require("plugins").
The keymap.set API
vim.keymap.set(mode, lhs, rhs, opts)
-- mode: "n" normal, "i" insert, "v" visual, "x" visual only,
-- "t" terminal, "c" command, "o" operator-pending
-- can be a table: {"n", "v"}
-- lhs: key combination string
-- rhs: action (string command or Lua function)
-- opts: table of options
Common Options
{
desc = "Description", -- shows in :map and which-key
noremap = true, -- don't allow remapping (default true in keymap.set)
silent = true, -- don't echo command
expr = true, -- rhs is an expression
buffer = 0, -- buffer-local mapping (0 = current buffer)
}
Keymap Organization by Leader Prefix
Organize keymaps into logical groups using leader prefixes:
~/.config/nvim/lua/config/keymaps.lua
local map = vim.keymap.set
-- ─── Files ──────────────────────────
map("n", "<leader>ff", "<cmd>Telescope find_files<cr>", { desc = "Find files" })
map("n", "<leader>fg", "<cmd>Telescope live_grep<cr>", { desc = "Find in files (grep)" })
map("n", "<leader>fr", "<cmd>Telescope oldfiles<cr>", { desc = "Recent files" })
map("n", "<leader>fw", "<cmd>Telescope grep_string<cr>", { desc = "Find word under cursor" })
-- ─── Buffers ────────────────────────
map("n", "<leader>bb", "<cmd>Telescope buffers<cr>", { desc = "Switch buffer" })
map("n", "<leader>bd", "<cmd>bdelete<cr>", { desc = "Delete buffer" })
map("n", "<leader>bn", "<cmd>bnext<cr>", { desc = "Next buffer" })
map("n", "<leader>bp", "<cmd>bprev<cr>", { desc = "Prev buffer" })
-- ─── Windows ────────────────────────
map("n", "<leader>wv", "<cmd>vsplit<cr>", { desc = "Vertical split" })
map("n", "<leader>ws", "<cmd>split<cr>", { desc = "Horizontal split" })
map("n", "<leader>wc", "<cmd>close<cr>", { desc = "Close window" })
map("n", "<leader>wo", "<cmd>only<cr>", { desc = "Close other windows" })
-- ─── LSP (filled in by lsp config) ─
-- <leader>ld → LSP diagnostics
-- <leader>lr → LSP references
-- <leader>lR → LSP rename
-- <leader>la → LSP code actions
-- <leader>lf → LSP format
-- ─── Git ────────────────────────────
map("n", "<leader>gs", "<cmd>Neogit<cr>", { desc = "Git status" })
map("n", "<leader>gc", "<cmd>Neogit commit<cr>", { desc = "Git commit" })
map("n", "<leader>gp", "<cmd>Neogit push<cr>", { desc = "Git push" })
map("n", "<leader>gb", "<cmd>Telescope git_branches<cr>", { desc = "Git branches" })
-- ─── Diagnostics ────────────────────
map("n", "<leader>dl", "<cmd>Telescope diagnostics<cr>", { desc = "List diagnostics" })
map("n", "]d", vim.diagnostic.goto_next, { desc = "Next diagnostic" })
map("n", "[d", vim.diagnostic.goto_prev, { desc = "Prev diagnostic" })
-- ─── Terminal ───────────────────────
map("n", "<leader>tt", "<cmd>terminal<cr>", { desc = "New terminal" })
map("t", "<Esc>", "<C-\\><C-n>", { desc = "Exit terminal mode" })
-- ─── Editor ─────────────────────────
map("n", "<leader>ec", "<cmd>e ~/.config/nvim/init.lua<cr>", { desc = "Edit config" })
map("n", "<leader>er", "<cmd>source ~/.config/nvim/init.lua<cr>", { desc = "Reload config" })
Leader Key Map Reference
| Prefix | Namespace |
|---|---|
<leader>f | Files (find, grep, recent) |
<leader>b | Buffers |
<leader>w | Windows |
<leader>l | LSP actions |
<leader>g | Git |
<leader>d | Diagnostics |
<leader>t | Terminal |
<leader>e | Editor (config, reload) |
<leader>s | Search/substitute |
<leader>u | UI (toggle options) |
Which-key Plugin
which-key.nvim shows available key bindings as you type the leader:
~/.config/nvim/lua/plugins/ui.lua
{
"folke/which-key.nvim",
event = "VeryLazy",
opts = {
plugins = { spelling = true },
},
config = function(_, opts)
local wk = require("which-key")
wk.setup(opts)
-- Register group names
wk.add({
{ "<leader>f", group = "Files" },
{ "<leader>b", group = "Buffers" },
{ "<leader>w", group = "Windows" },
{ "<leader>l", group = "LSP" },
{ "<leader>g", group = "Git" },
{ "<leader>d", group = "Diagnostics" },
{ "<leader>t", group = "Terminal" },
})
end,
}
Buffer-Local Keymaps
For keymaps that only apply in specific buffers (e.g., LSP keymaps):
-- In LSP on_attach callback:
local function on_attach(client, bufnr)
local map = function(lhs, rhs, desc)
vim.keymap.set("n", lhs, rhs, { buffer = bufnr, desc = desc })
end
map("gd", vim.lsp.buf.definition, "Go to definition")
map("gr", vim.lsp.buf.references, "References")
map("K", vim.lsp.buf.hover, "Hover docs")
map("<leader>lR", vim.lsp.buf.rename, "Rename")
map("<leader>la", vim.lsp.buf.code_action, "Code action")
map("<leader>lf", function()
vim.lsp.buf.format({ async = true })
end, "Format")
end