-- Set as the leader key -- See `:help mapleader` -- NOTE: Must happen before plugins are loaded (otherwise wrong leader will be used) vim.g.mapleader = ' ' vim.g.maplocalleader = ' ' -- Core Neovim keymaps (non-plugin) -- These are basic Neovim operations like: -- - Window navigation (Ctrl + hjkl) -- - Window resizing (Ctrl + Arrow keys) -- - Line/selection movement (Alt + jk) -- - Quick save and quit (w, W, Q) local core_keymaps = { -- Clear highlights on search when pressing in normal mode { mode = 'n', lhs = '', rhs = 'nohlsearch', opts = { desc = 'Clear search highlights' } }, -- Exit terminal mode with a more discoverable shortcut { mode = 't', lhs = '', rhs = '', opts = { desc = 'Exit terminal mode' } }, -- Window navigation (Ctrl + hjkl) { mode = 'n', lhs = '', rhs = '', opts = { desc = 'Focus: Window left' } }, { mode = 'n', lhs = '', rhs = '', opts = { desc = 'Focus: Window right' } }, { mode = 'n', lhs = '', rhs = '', opts = { desc = 'Focus: Window down' } }, { mode = 'n', lhs = '', rhs = '', opts = { desc = 'Focus: Window up' } }, -- Window resizing (Ctrl + Arrow keys) { mode = 'n', lhs = '', rhs = 'resize +2', opts = { desc = 'Window: Increase height' } }, { mode = 'n', lhs = '', rhs = 'resize -2', opts = { desc = 'Window: Decrease height' } }, { mode = 'n', lhs = '', rhs = 'vertical resize -2', opts = { desc = 'Window: Decrease width' } }, { mode = 'n', lhs = '', rhs = 'vertical resize +2', opts = { desc = 'Window: Increase width' } }, -- Move lines up and down (Alt + jk) { mode = 'n', lhs = '', rhs = ':m .+1==', opts = { desc = 'Move line down', silent = true } }, { mode = 'n', lhs = '', rhs = ':m .-2==', opts = { desc = 'Move line up', silent = true } }, { mode = 'v', lhs = '', rhs = ":m '>+1gv=gv", opts = { desc = 'Move selection down', silent = true } }, { mode = 'v', lhs = '', rhs = ":m '<-2gv=gv", opts = { desc = 'Move selection up', silent = true } }, -- Quick save and quit { mode = 'n', lhs = 'w', rhs = 'w', opts = { desc = 'Save file' } }, { mode = 'n', lhs = 'W', rhs = 'wa', opts = { desc = 'Save all files' } }, { mode = 'n', lhs = 'Q', rhs = 'qa', opts = { desc = 'Quit all' } }, } -- LSP keymaps - these will be set up when LSP attaches to a buffer -- Navigation: -- - gd: Go to definition -- - gr: Find references -- - gI: Go to implementation -- - gy: Go to type definition -- -- Workspace: -- - ls: Document symbols -- - lS: Workspace symbols -- -- Code Actions: -- - lr: Rename symbol -- - la: Code action -- - lf: Format code -- -- Documentation: -- - K: Show documentation local M = {} function M.setup_lsp_keymaps(bufnr) local keymaps = { -- Go to definitions { mode = 'n', lhs = 'gd', rhs = function() require('telescope.builtin').lsp_definitions() end, opts = { desc = 'LSP: Go to definition' } }, { mode = 'n', lhs = 'gr', rhs = function() require('telescope.builtin').lsp_references() end, opts = { desc = 'LSP: Find references' } }, { mode = 'n', lhs = 'gI', rhs = function() require('telescope.builtin').lsp_implementations() end, opts = { desc = 'LSP: Go to implementation' } }, { mode = 'n', lhs = 'gy', rhs = function() require('telescope.builtin').lsp_type_definitions() end, opts = { desc = 'LSP: Go to type definition' } }, -- Symbol navigation { mode = 'n', lhs = 'ls', rhs = function() require('telescope.builtin').lsp_document_symbols() end, opts = { desc = 'LSP: Document symbols' } }, { mode = 'n', lhs = 'lS', rhs = function() require('telescope.builtin').lsp_dynamic_workspace_symbols() end, opts = { desc = 'LSP: Workspace symbols' } }, -- Code actions { mode = 'n', lhs = 'lr', rhs = vim.lsp.buf.rename, opts = { desc = 'LSP: Rename symbol' } }, { mode = 'n', lhs = 'la', rhs = vim.lsp.buf.code_action, opts = { desc = 'LSP: Code action' } }, { mode = 'n', lhs = 'lf', rhs = function() vim.lsp.buf.format({ async = true }) end, opts = { desc = 'LSP: Format code' } }, -- Documentation { mode = 'n', lhs = 'K', rhs = vim.lsp.buf.hover, opts = { desc = 'LSP: Show documentation' } }, } -- First clear any existing LSP keymaps for this buffer local lsp_maps = { 'gd', 'gr', 'gI', 'gy', 'K', 'ls', 'lS', 'lr', 'la', 'lf' } for _, lhs in ipairs(lsp_maps) do pcall(vim.keymap.del, 'n', lhs, { buffer = bufnr }) end -- Then set our keymaps with buffer local for _, mapping in ipairs(keymaps) do vim.keymap.set(mapping.mode, mapping.lhs, mapping.rhs, vim.tbl_extend('force', mapping.opts, { buffer = bufnr, replace_keycodes = false, nowait = true, silent = true, })) end end -- Telescope keymaps (all under s for Search) -- Help: -- - sh: Search help tags -- - sk: Search keymaps -- -- Files: -- - sf: Find files -- - sr: Recent files -- -- Text Search: -- - sg: Live grep in workspace -- - sw: Search word under cursor -- - /: Fuzzy find in current buffer -- - s/: Live grep in open files -- -- Workspace: -- - sd: Search diagnostics -- - sb: Search buffers M.telescope_keymaps = { -- Help { mode = 'n', lhs = 'sh', rhs = function() require('telescope.builtin').help_tags() end, opts = { desc = 'Search: Help' } }, { mode = 'n', lhs = 'sk', rhs = function() require('telescope.builtin').keymaps() end, opts = { desc = 'Search: Keymaps' } }, -- Files { mode = 'n', lhs = 'sf', rhs = function() require('telescope.builtin').find_files() end, opts = { desc = 'Search: Files' } }, { mode = 'n', lhs = 'sr', rhs = function() require('telescope.builtin').oldfiles() end, opts = { desc = 'Search: Recent files' } }, -- Text { mode = 'n', lhs = 'sg', rhs = function() require('telescope.builtin').live_grep() end, opts = { desc = 'Search: Text in workspace' } }, { mode = 'n', lhs = 'sw', rhs = function() require('telescope.builtin').grep_string() end, opts = { desc = 'Search: Word under cursor' } }, { mode = 'n', lhs = '/', rhs = function() require('telescope.builtin').current_buffer_fuzzy_find(require('telescope.themes').get_dropdown { winblend = 10, previewer = false, }) end, opts = { desc = 'Search: Text in buffer' } }, { mode = 'n', lhs = 's/', rhs = function() require('telescope.builtin').live_grep { grep_open_files = true, prompt_title = 'Live Grep in Open Files', } end, opts = { desc = 'Search: Text in open files' } }, -- Workspace { mode = 'n', lhs = 'sd', rhs = function() require('telescope.builtin').diagnostics() end, opts = { desc = 'Search: Diagnostics' } }, { mode = 'n', lhs = 'sb', rhs = function() require('telescope.builtin').buffers() end, opts = { desc = 'Search: Buffers' } }, } -- Diagnostic keymaps -- Navigation: -- - [d: Previous diagnostic -- - ]d: Next diagnostic -- -- Viewing: -- - tt: Show diagnostic details in float -- - tl: Show diagnostics in location list M.diagnostic_keymaps = { -- Navigation { mode = 'n', lhs = '[d', rhs = vim.diagnostic.goto_prev, opts = { desc = 'Diagnostic: Previous' } }, { mode = 'n', lhs = ']d', rhs = vim.diagnostic.goto_next, opts = { desc = 'Diagnostic: Next' } }, -- Viewing diagnostics { mode = 'n', lhs = 'tt', rhs = vim.diagnostic.open_float, opts = { desc = 'Diagnostic: Show details' } }, { mode = 'n', lhs = 'tl', rhs = vim.diagnostic.setloclist, opts = { desc = 'Diagnostic: Show list' } }, } -- Database keymaps (all under D for Database) -- UI: -- - Dt: Toggle database UI -- - Df: Find database buffer -- - Dr: Rename database buffer -- - Dl: Show last query info M.dadbod_keymaps = { { mode = 'n', lhs = 'Dt', rhs = 'DBUIToggle', opts = { desc = 'Database: Toggle UI' } }, { mode = 'n', lhs = 'Df', rhs = 'DBUIFindBuffer', opts = { desc = 'Database: Find buffer' } }, { mode = 'n', lhs = 'Dr', rhs = 'DBUIRenameBuffer', opts = { desc = 'Database: Rename buffer' } }, { mode = 'n', lhs = 'Dl', rhs = 'DBUILastQueryInfo', opts = { desc = 'Database: Last query' } }, } -- Session management keymaps (all under m for Memory) -- Session Operations: -- - mw: Write/save current session -- - mr: Read/restore saved session -- - md: Delete saved session M.session_keymaps = { { mode = 'n', lhs = 'mw', rhs = function() local name = vim.fn.fnamemodify(vim.fn.getcwd(), ':t') require('mini.sessions').write(name, { force = true }) end, opts = { desc = 'Memory: Write session' } }, { mode = 'n', lhs = 'mr', rhs = function() local name = vim.fn.fnamemodify(vim.fn.getcwd(), ':t') require('mini.sessions').read(name) end, opts = { desc = 'Memory: Read session' } }, { mode = 'n', lhs = 'md', rhs = function() local name = vim.fn.fnamemodify(vim.fn.getcwd(), ':t') require('mini.sessions').delete(name) end, opts = { desc = 'Memory: Delete session' } }, } -- Scratch buffer keymaps -- Buffer Operations: -- - .: Toggle scratch buffer -- - S: Select scratch buffer -- - nh: Show notification history M.scratch_keymaps = { { mode = 'n', lhs = '.', rhs = function() require("snacks").scratch() end, opts = { desc = 'Toggle scratch buffer' } }, { mode = 'n', lhs = 'S', rhs = function() require("snacks").scratch.select() end, opts = { desc = 'Select scratch buffer' } }, { mode = 'n', lhs = 'nh', rhs = function() require("snacks.notifier").show_history() end, opts = { desc = 'Show notification history' } }, } -- Snacks keymaps (explorer and other features) -- File Explorer: -- - e: Toggle explorer -- - E: Focus current file in explorer -- - o: Alternative key to focus current file -- -- Terminal: -- - : Toggle terminal in float window M.snacks_keymaps = { -- Explorer { mode = 'n', lhs = 'e', rhs = function() require('snacks.picker').explorer() end, opts = { desc = 'Explorer: Toggle' } }, { mode = 'n', lhs = 'E', rhs = function() require('snacks.picker').explorer({ reveal = true }) end, opts = { desc = 'Explorer: Focus current file' } }, { mode = 'n', lhs = 'o', rhs = function() require('snacks.picker').explorer({ reveal = true }) end, opts = { desc = 'Explorer: Focus current file' } }, -- Terminal { mode = 'n', lhs = '', rhs = function() require('snacks').terminal.toggle() end, opts = { desc = 'Terminal: Toggle float window' } }, } -- Git signs keymaps (all under g for Git) -- Navigation: -- - ]c: Next hunk -- - [c: Previous hunk -- -- Actions: -- - gh: Preview hunk -- - gs: Stage hunk -- - gu: Undo stage hunk -- - gr: Reset hunk -- - gS: Stage buffer -- - gR: Reset buffer -- - gb: Blame line -- - gd: Diff this M.gitsigns_keymaps = { -- Navigation { mode = 'n', lhs = ']c', rhs = function() if vim.wo.diff then return ']c' end vim.schedule(function() require('gitsigns').next_hunk() end) return '' end, opts = { desc = 'Git: Next hunk', expr = true } }, { mode = 'n', lhs = '[c', rhs = function() if vim.wo.diff then return '[c' end vim.schedule(function() require('gitsigns').prev_hunk() end) return '' end, opts = { desc = 'Git: Previous hunk', expr = true } }, -- Actions { mode = 'n', lhs = 'gh', rhs = function() require('gitsigns').preview_hunk() end, opts = { desc = 'Git: Preview hunk' } }, { mode = 'n', lhs = 'gb', rhs = function() require('gitsigns').blame_line() end, opts = { desc = 'Git: Blame line' } }, { mode = 'n', lhs = 'gd', rhs = function() require('gitsigns').diffthis() end, opts = { desc = 'Git: Show diff' } }, { mode = { 'n', 'v' }, lhs = 'gs', rhs = function() require('gitsigns').stage_hunk() end, opts = { desc = 'Git: Stage hunk' } }, { mode = { 'n', 'v' }, lhs = 'gr', rhs = function() require('gitsigns').reset_hunk() end, opts = { desc = 'Git: Reset hunk' } }, { mode = 'n', lhs = 'gS', rhs = function() require('gitsigns').stage_buffer() end, opts = { desc = 'Git: Stage buffer' } }, { mode = 'n', lhs = 'gu', rhs = function() require('gitsigns').undo_stage_hunk() end, opts = { desc = 'Git: Undo stage' } }, { mode = 'n', lhs = 'gR', rhs = function() require('gitsigns').reset_buffer() end, opts = { desc = 'Git: Reset buffer' } }, } -- Setup function for git signs keymaps function M.setup_gitsigns_keymaps() for _, mapping in ipairs(M.gitsigns_keymaps or {}) do vim.keymap.set(mapping.mode, mapping.lhs, mapping.rhs, mapping.opts) end end -- Leap keymaps -- 's' for bidirectional search (both forward and backward) -- 'S' for searching in all windows M.leap_keymaps = { -- 's' for bidirectional search (both forward and backward) { mode = { 'n', 'x', 'o' }, lhs = 's', rhs = function() require('leap').leap {} end, opts = { desc = 'Leap: Search bidirectional' } }, -- 'S' for searching in all windows { mode = { 'n', 'x', 'o' }, lhs = 'S', rhs = function() require('leap').leap { target_windows = vim.tbl_filter( function (win) return vim.api.nvim_win_get_config(win).focusable end, vim.api.nvim_tabpage_list_wins(0) )} end, opts = { desc = 'Leap: Search across windows' } }, } -- Setup function for leap keymaps function M.setup_leap_keymaps() for _, mapping in ipairs(M.leap_keymaps) do vim.keymap.set(mapping.mode, mapping.lhs, mapping.rhs, mapping.opts) end end -- Setup functions for keymaps function M.setup_dadbod_keymaps() local keymaps = M.dadbod_keymaps or {} for _, mapping in ipairs(keymaps) do vim.keymap.set(mapping.mode, mapping.lhs, mapping.rhs, mapping.opts) end end function M.setup_session_keymaps() local keymaps = M.session_keymaps or {} for _, mapping in ipairs(keymaps) do vim.keymap.set(mapping.mode, mapping.lhs, mapping.rhs, mapping.opts) end end -- Initialize keymaps local function init_keymaps() -- Create an autocmd group for our keymaps local keymap_group = vim.api.nvim_create_augroup('custom_keymaps', { clear = true }) -- Handle snacks explorer during buffer writes vim.api.nvim_create_autocmd({ 'BufWritePre' }, { group = keymap_group, callback = function() -- Pause snacks explorer updates during write pcall(function() local picker = require('snacks.picker') if picker.is_open() then picker.pause_updates() vim.schedule(function() picker.resume_updates() end) end end) end, }) -- Function to set up snacks explorer keymaps local function setup_explorer_keymaps() -- First remove any existing mappings for _, key in ipairs({ 'e', 'E' }) do pcall(vim.keymap.del, 'n', key) pcall(vim.keymap.del, 'n', key, { buffer = true }) end -- Set our mappings with high priority for _, mapping in ipairs(M.snacks_keymaps or {}) do if mapping.lhs == 'e' or mapping.lhs == 'E' then vim.keymap.set(mapping.mode, mapping.lhs, mapping.rhs, vim.tbl_extend('force', mapping.opts, { replace_keycodes = false, nowait = true, silent = true, })) end end end -- Set up autocmds to maintain our keymaps vim.api.nvim_create_autocmd({ 'VimEnter', 'BufEnter' }, { group = keymap_group, callback = setup_explorer_keymaps, }) -- Set up LSP keymaps on attach vim.api.nvim_create_autocmd('LspAttach', { group = keymap_group, callback = function(args) M.setup_lsp_keymaps(args.buf) end, }) -- Set up core keymaps for _, mapping in ipairs(core_keymaps) do vim.keymap.set(mapping.mode, mapping.lhs, mapping.rhs, mapping.opts) end -- Set up plugin keymaps for _, mapping in ipairs(M.telescope_keymaps or {}) do vim.keymap.set(mapping.mode, mapping.lhs, mapping.rhs, mapping.opts) end for _, mapping in ipairs(M.diagnostic_keymaps or {}) do vim.keymap.set(mapping.mode, mapping.lhs, mapping.rhs, mapping.opts) end for _, mapping in ipairs(M.buffer_keymaps or {}) do vim.keymap.set(mapping.mode, mapping.lhs, mapping.rhs, mapping.opts) end -- Set up snacks keymaps for _, mapping in ipairs(M.snacks_keymaps or {}) do -- Skip explorer keymaps as they're handled by setup_explorer_keymaps if mapping.lhs ~= 'e' and mapping.lhs ~= 'E' then vim.keymap.set(mapping.mode, mapping.lhs, mapping.rhs, mapping.opts) end end -- Set up scratch buffer keymaps for _, mapping in ipairs(M.scratch_keymaps or {}) do vim.keymap.set(mapping.mode, mapping.lhs, mapping.rhs, mapping.opts) end -- Set up database keymaps M.setup_dadbod_keymaps() -- Set up session keymaps M.setup_session_keymaps() -- Set up git signs keymaps M.setup_gitsigns_keymaps() -- Set up leap keymaps M.setup_leap_keymaps() end -- Initialize all keymaps init_keymaps() -- Export `init_keymaps` as `M.setup` so `require('core.keymaps').setup()` works M.setup = init_keymaps -- Return the module return M