-- nvim/.config/nvim/lua/keymaps.lua
-- Leader-key maps I actually remember. I try to keep this under 150 lines
-- because beyond that I forget what I bound. Plugin-specific maps live
-- with the plugin config, not here.
local map = vim.keymap.set
local silent = { silent = true }
local desc = function(d) return { silent = true, desc = d } end
vim.g.mapleader = " "
vim.g.maplocalleader = ","
-- Save / quit
map("n", "<leader>w", "<cmd>write<cr>", desc("write buffer"))
map("n", "<leader>W", "<cmd>wall<cr>", desc("write all buffers"))
map("n", "<leader>q", "<cmd>quit<cr>", desc("quit window"))
map("n", "<leader>Q", "<cmd>quitall!<cr>", desc("force quit everything"))
-- Clear search highlight fast; ctrl-l is the default redraw I never use.
map("n", "<C-l>", "<cmd>nohlsearch<cr><C-l>", silent)
-- Centre the cursor after common jumps so the surrounding context stays.
map("n", "<C-d>", "<C-d>zz", silent)
map("n", "<C-u>", "<C-u>zz", silent)
map("n", "n", "nzzzv", silent)
map("n", "N", "Nzzzv", silent)
-- Yank to system clipboard without changing the default register on
-- everything else. Keeps :reg readable.
map({ "n", "v" }, "<leader>y", [["+y]], desc("yank to system clipboard"))
map("n", "<leader>Y", [["+Y]], desc("yank line to system clipboard"))
map({ "n", "v" }, "<leader>p", [["+p]], desc("paste from system clipboard"))
map({ "n", "v" }, "<leader>P", [["+P]], desc("paste from system clipboard (before)"))
-- When pasting over a selection, keep the original yank register.
map("x", "<leader>d", [["_d]], desc("delete without yanking"))
map("x", "p", [["_dP]], desc("paste without yanking the selection"))
-- Move visual selections up/down with shift+arrow. Feels natural after IDEs.
map("v", "<S-Up>", ":m '<-2<cr>gv=gv", silent)
map("v", "<S-Down>", ":m '>+1<cr>gv=gv", silent)
-- Keep indent when moving a block.
map("v", "<", "<gv", silent)
map("v", ">", ">gv", silent)
-- Split / window management
map("n", "<leader>-", "<cmd>split<cr>", desc("split horizontal"))
map("n", "<leader>\\", "<cmd>vsplit<cr>", desc("split vertical"))
map("n", "<C-h>", "<C-w>h", silent)
map("n", "<C-j>", "<C-w>j", silent)
map("n", "<C-k>", "<C-w>k", silent)
map("n", "<C-l>", "<C-w>l", silent) -- overrides redraw above; intentional.
map("n", "<M-Left>", "<cmd>vertical resize -4<cr>", silent)
map("n", "<M-Right>", "<cmd>vertical resize +4<cr>", silent)
map("n", "<M-Up>", "<cmd>resize +2<cr>", silent)
map("n", "<M-Down>", "<cmd>resize -2<cr>", silent)
-- Buffers
map("n", "<Tab>", "<cmd>bnext<cr>", desc("next buffer"))
map("n", "<S-Tab>", "<cmd>bprevious<cr>", desc("previous buffer"))
map("n", "<leader>bd", "<cmd>bdelete<cr>", desc("delete buffer"))
map("n", "<leader>bo",
function()
local cur = vim.api.nvim_get_current_buf()
for _, b in ipairs(vim.api.nvim_list_bufs()) do
if b ~= cur and vim.api.nvim_buf_is_loaded(b) then
pcall(vim.api.nvim_buf_delete, b, {})
end
end
end,
desc("close other buffers"))
-- Quickfix / diagnostics navigation
map("n", "]q", "<cmd>cnext<cr>zz", silent)
map("n", "[q", "<cmd>cprev<cr>zz", silent)
map("n", "]d", function() vim.diagnostic.goto_next({ float = false }) end, desc("next diagnostic"))
map("n", "[d", function() vim.diagnostic.goto_prev({ float = false }) end, desc("prev diagnostic"))
map("n", "<leader>xd", vim.diagnostic.open_float, desc("show diagnostic"))
-- Terminal
map("n", "<leader>t", "<cmd>terminal<cr>", desc("open terminal"))
map("t", "<Esc><Esc>", [[<C-\><C-n>]], desc("exit terminal mode"))
-- Files
map("n", "<leader>e", "<cmd>edit %:p:h<cr>", desc("explore cwd of file"))
map("n", "<leader>.",
function()
-- Reopen the previously edited file; skips the alternate being unlisted.
local alt = vim.fn.bufname("#")
if alt ~= "" then vim.cmd.edit(alt) end
end,
desc("reopen previous file"))
-- Search visual selection with * and #.
map("x", "*", [[y/\V<C-r>=escape(@",'/\')<cr><cr>N]], desc("search selection forward"))
map("x", "#", [[y?\V<C-r>=escape(@",'?\')<cr><cr>N]], desc("search selection backward"))
-- Toggle options quickly
map("n", "<leader>oh", function() vim.o.hlsearch = not vim.o.hlsearch end, desc("toggle hlsearch"))
map("n", "<leader>on", function() vim.wo.number = not vim.wo.number end, desc("toggle line numbers"))
map("n", "<leader>or", function() vim.wo.relativenumber = not vim.wo.relativenumber end, desc("toggle relative numbers"))
map("n", "<leader>ow", function() vim.wo.wrap = not vim.wo.wrap end, desc("toggle wrap"))
-- Reload config without restarting. Handy after editing this file.
map("n", "<leader>rR",
function()
for name, _ in pairs(package.loaded) do
if name:match("^user%.") then package.loaded[name] = nil end
end
dofile(vim.env.MYVIMRC)
vim.notify("config reloaded", vim.log.levels.INFO)
end,
desc("reload nvim config"))