nvim/.config/nvim/lua/keymaps.lua

-- 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"))