-- Basic Keymaps -- See `:help vim.keymap.set()` local helpers = require("helpers") -- Swap : and ; around vim.keymap.set({ "n", "v" }, ":", ";") vim.keymap.set({ "n", "v" }, ";", ":") -- Clear highlights on search when pressing in normal mode -- See `:help hlsearch` vim.keymap.set("n", "", "nohlsearch") -- Diagnostic keymaps vim.keymap.set("n", "q", vim.diagnostic.setloclist, { desc = "Open diagnostic [Q]uickfix list" }) vim.keymap.set({ "n", "v" }, "c", '"zc', { desc = "Change without copying to clipboard" }) vim.keymap.set({ "n", "v" }, "x", '"zx', { desc = "Cut without copying to clipboard" }) vim.keymap.set("v", "p", '"zdP', { desc = "Paste over selection without yanking replaced text" }) -- Exit terminal mode in the builtin terminal with a shortcut that is a bit easier -- for people to discover. Otherwise, you normally need to press , which -- is not what someone will guess without a bit more experience. -- -- NOTE: This won't work in all terminal emulators/tmux/etc. Try your own mapping -- or just use to exit terminal mode vim.keymap.set("t", "", "", { desc = "Exit terminal mode" }) vim.keymap.set("n", "z", function() vim.treesitter.inspect_tree() end, { desc = "Treesitter" }) vim.keymap.set("n", "", function() local node = vim.treesitter.get_node({}) local range = { vim.treesitter.get_node_range(node) } vim.api.nvim_win_set_cursor(0, { range[3] + 1, range[4] - 1 }) vim.fn.setpos("'x", { 0, range[1] + 1, range[2] + 1, 0 }) vim.cmd.normal("v`x") end, { desc = "Select surrounding treesitter node" }) vim.keymap.set("v", "", function() local start = vim.api.nvim_win_get_cursor(0) local start_row, start_column if start[2] == 0 then start_row = start[1] - 1 start_column = 0 else start_row = start[1] start_column = start[2] - 1 end vim.api.nvim_win_set_cursor(0, { start_row, start_column }) local node = vim.treesitter.get_node({}) local range = { vim.treesitter.get_node_range(node) } vim.api.nvim_win_set_cursor(0, { range[3] + 1, range[4] - 1 }) vim.fn.setpos("'x", { 0, range[1] + 1, range[2] + 1, 0 }) vim.cmd.normal("i") vim.cmd.normal("v`x") end, { desc = "Select surrounding treesitter node" }) local swappable_nodes = { "argument", "array_element_initializer", "simple_parameter", "string", "integer", "member_call_expression", "method_declaration", } local function closest_swappable_node(node) while node and not vim.list_contains(swappable_nodes, node:type()) and (not node:next_named_sibling() or not node:prev_named_sibling()) do node = node:parent() end return node end vim.keymap.set({ "n" }, "", function() local node = closest_swappable_node(vim.treesitter.get_node()) if not node then return end local prev_node = node:prev_named_sibling() while prev_node and not vim.list_contains(swappable_nodes, prev_node:type()) do prev_node = prev_node:prev_named_sibling() end if prev_node == nil then return end require("nvim-treesitter.ts_utils").swap_nodes(node, prev_node, 0, true) end, { desc = "Swap with node to the left" }) vim.keymap.set({ "n" }, "", function() local node = closest_swappable_node(vim.treesitter.get_node()) if not node then return end local next_node = node:next_named_sibling() while next_node and not vim.list_contains(swappable_nodes, next_node:type()) do next_node = next_node:next_named_sibling() end if next_node == nil then return end require("nvim-treesitter.ts_utils").swap_nodes(node, next_node, 0, true) end, { desc = "Swap with node to the right" }) vim.keymap.set("n", "", "ddp", { desc = "Move line down" }) vim.keymap.set("n", "", "ddkP", { desc = "Move line up" }) vim.keymap.set("v", "", "dop`[v`]", { desc = "Move selection down" }) vim.keymap.set("v", "", "dkOp`[v`]", { desc = "Move selection up" }) -- Keybinds to make split navigation easier. -- Use CTRL+ to switch between windows -- -- See `:help wincmd` for a list of all window commands local function win_or_treesj(dir_cmd, desc) return function() local cur = vim.api.nvim_get_current_win() vim.cmd("wincmd " .. dir_cmd) if vim.api.nvim_get_current_win() == cur then local ok, treesj = pcall(require, "treesj") if ok and type(treesj.toggle) == "function" then treesj.toggle() end end end, { desc = desc } end vim.keymap.set("n", "", "", { desc = "Move focus to the left window" }) vim.keymap.set("n", "", "", { desc = "Move focus to the right window" }) vim.keymap.set("n", "", (win_or_treesj)("j", "Move focus to the lower window or treesj.toggle()")) vim.keymap.set("n", "", (win_or_treesj)("k", "Move focus to the upper window or treesj.toggle()")) vim.keymap.set("t", "", "h", { desc = "Move focus to the left window" }) vim.keymap.set("t", "", "l", { desc = "Move focus to the right window" }) vim.keymap.set("t", "", "j", { desc = "Move focus to the lower window" }) vim.keymap.set("t", "", "k", { desc = "Move focus to the upper window" }) vim.keymap.set({ "i" }, "", function() local ls = require("luasnip") if ls.choice_active() then ls.change_choice(1) end end, { desc = "Toggle snippet choice", silent = true }) vim.keymap.set({ "i" }, "", function() local ls = require("luasnip") if ls.choice_active() then ls.change_choice(-1) end end, { desc = "Toggle snippet choice", silent = true }) vim.keymap.set({ "i", "s" }, "", function() local ls = require("luasnip") if ls.expandable() then ls.expand() elseif ls.in_snippet() then ls.jump(1) end end, { desc = "Expand snippet", silent = true }) vim.keymap.set({ "i", "s" }, "", function() local ls = require("luasnip") if ls.in_snippet() then ls.jump(-1) end end, { desc = "Go back a snippet slot", silent = true }) vim.keymap.set("n", ".", "tabnext", { desc = "Next tab" }) vim.keymap.set("n", ",", "tabprevious", { desc = "Previous tab" }) -- Overriding CTRL mappings because some of them are stupid vim.keymap.set({ "n", "v", "c", "i" }, "", "Git blame", { desc = "Git blame" }) vim.keymap.set( { "n", "v", "c", "i" }, "", "Telescope lsp_document_symbols", { desc = "Show symbols in current document" } ) vim.keymap.set( { "n", "v", "c", "i" }, "", "Telescope lsp_workspace_symbols", { desc = "Show symbols in workspace" } ) vim.keymap.set({ "n", "v", "c", "i" }, "", "Telescope oldfiles", { desc = "Show recently opened files" }) vim.keymap.set({ "n", "v", "c", "i" }, "", function() require("telescope.builtin").find_files({ show_untracked = true, no_ignore = false, hidden = true, }) end, { desc = "Find all files" }) vim.keymap.set({ "n", "v", "c", "i" }, "", function() require("telescope.builtin").find_files({ show_untracked = true, no_ignore = true, hidden = true, }) end, { desc = "Find all files" }) vim.keymap.set({ "n", "v", "c", "i" }, "", function() require("telescope.builtin").live_grep({ hidden = true, }) end, { desc = "Search within the whole project" }) vim.keymap.set({ "n", "v", "c", "i" }, "", "Telescope quickfix", { desc = "Show quickfix list" }) vim.keymap.set( { "n", "v", "c", "i" }, "", "Telescope quickfixhistory", { desc = "Show quickfix history" } ) vim.keymap.set({ "n", "v", "c", "i", "t" }, "", function() local ai_tools = { "codex", "agy", "opencode" } local ai_win_opts = { width = math.floor(vim.o.columns / 3), vertical = true, split = "right", } local term_bufnr = -1 for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do if vim.bo[bufnr].buftype == "terminal" then local name = vim.api.nvim_buf_get_name(bufnr) for _, tool in ipairs(ai_tools) do if name:match(":" .. tool .. "$") or name:match("/" .. tool .. "$") then term_bufnr = bufnr end end end end if term_bufnr ~= -1 then local term_winid = vim.fn.bufwinid(term_bufnr) if term_winid ~= -1 then vim.api.nvim_win_hide(term_winid) return end vim.api.nvim_open_win(term_bufnr, true, ai_win_opts) vim.cmd.startinsert() return end for _, tool in ipairs(ai_tools) do if vim.fn.executable(tool) == 1 then helpers.open_term({ cmd = tool, win_opts = ai_win_opts, buf_opts = { bufhidden = "hide", modifiable = false, }, }) return end end vim.api.nvim_echo( { { "No AI tool found. Please install one of the following: codex, agy, opencode", "WarningMsg" } }, false, {} ) end, { desc = "Toggle AI tool" }) -- vim.keymap.set({ 'n', 'v', 'c', 'i' }, '', 'vsplit term://codexi', { desc = 'Open AI tool' }) vim.keymap.set({ "n", "v", "c", "i" }, "", function() require("snacks").scratch() end, { desc = "Open scratchpad" }) vim.keymap.set({ "n", "v", "c", "i" }, "", function() require("snacks").scratch.select() end, { desc = "Open scratchpad buffers" }) vim.keymap.set({ "n", "v", "c", "i", "t" }, "", function() require("snacks").terminal.toggle() end, { desc = "Toggle terminal" }) vim.keymap.set({ "n", "v", "c", "i" }, "", "", { desc = "Enter normal mode" }) vim.keymap.set({ "t" }, "", "", { desc = "Enter normal mode" }) vim.keymap.set({ "t" }, "", "", { desc = "Scroll up in terminal" }) vim.keymap.set({ "t" }, "", "", { desc = "Scroll down in terminal" }) -- Editing helpers vim.keymap.set("i", "", "o", { desc = "Add line below" }) vim.keymap.set("i", "", "O", { desc = "Add line above" }) local esc_keys = { "jj", "jk", "kk" } for _, key in ipairs(esc_keys) do vim.keymap.set("i", key, "", { desc = "Exit insert mode" }) end vim.keymap.set("i", "", "ddi", { desc = "Delete line" }) vim.keymap.set("i", ";", "mzA;`za", { desc = "Append a semicolon" }) vim.keymap.set("n", ";", "mzA;`za", { desc = "Append a semicolon" }) vim.keymap.set("n", "{", "mzF[`a``%i`z", { desc = "Indent an array" }) vim.keymap.set( "n", "}", "mzF[`a``%i`zvi[:s/,\\s*/,\\r/gvi[=`z:nohlsearch", { desc = "Indent an array some other way?" } ) -- Git mappings vim.keymap.set("n", "gb", "Git blame", { desc = "Git blame" }) vim.keymap.set("n", "gd", "Git diff", { desc = "Git diff" }) vim.keymap.set("n", "gdc", "Git diff --cached", { desc = "Git diff" }) vim.keymap.set("n", "G", function() helpers.open_term({ cmd = "lazygit" }) end, { desc = "Git" }) -- Add keymaps for diff mode vim.api.nvim_create_autocmd("BufEnter", { callback = function() if vim.wo.diff then -- Keymaps for navigating hunks vim.api.nvim_buf_set_keymap( 0, "n", "", "]c", { noremap = true, silent = true, desc = "Next hunk in diff mode" } ) vim.api.nvim_buf_set_keymap( 0, "n", "", "[c", { noremap = true, silent = true, desc = "Previous hunk in diff mode" } ) -- Keymaps for resolving conflicts vim.api.nvim_buf_set_keymap( 0, "n", "dl", ":diffget LOCAL", { noremap = true, silent = true, desc = "Get LOCAL changes" } ) vim.api.nvim_buf_set_keymap( 0, "n", "dr", ":diffget REMOTE", { noremap = true, silent = true, desc = "Get REMOTE changes" } ) end end, }) -- AI mappings -- vim.keymap.set('v', 'ae', 'CodeCompanion', { desc = 'Edit selection with AI' }) -- vim.keymap.set('n', 'ac', 'CodeCompanionCmd', { desc = 'Run Neovim commands with AI' }) vim.api.nvim_create_autocmd("FileType", { pattern = "codecompanion", callback = function() vim.keymap.set("i", "", function() require("codecompanion").last_chat():submit() end, { buffer = true, desc = "Use enter to send the message" }) vim.keymap.set("i", "", "", { buffer = true, desc = "Use shift enter to start a new line" }) vim.keymap.set( "n", "", "G", { buffer = true, desc = "Use enter in normal mode to go to the end of the chat" } ) local models = { vim.env.DEFAULT_AI_MODEL, vim.env.REASONING_MODEL, vim.env.FAST_MODEL } -- Loop through models and create keymaps for each for i, model in ipairs(models) do vim.keymap.set("n", "", 'mzggj0W"_C' .. model .. "`z", { desc = "Switch to " .. model }) vim.keymap.set( "i", "", 'mzggj0W"_C' .. model .. "`za", { desc = "Switch to " .. model } ) end end, }) -- Search mappings vim.keymap.set("n", "sGb", "Telescope git_branches", { desc = "Search git branches" }) vim.keymap.set("n", "sGc", "Telescope git_commits", { desc = "Search git history" }) vim.keymap.set( "n", "sGh", "Telescope git_bcommits", { desc = "Search git history within current buffer/selection" } ) vim.keymap.set("n", "sGs", "Telescope git_status", { desc = "Search git status" }) vim.keymap.set("n", "sGS", "Telescope git_stash", { desc = "Search git stash" }) -- Misc vim.keymap.set("n", "]", "cnext", { desc = "Next item in quickfix list" }) vim.keymap.set("n", "[", "cprevious", { desc = "Previous item in quickfix list" }) vim.keymap.set("n", "gd", "Telescope lsp_definitions", { desc = "Go to definition" }) vim.keymap.set("n", "r", function() for _, c in ipairs(vim.lsp.get_clients({ bufnr = 0 })) do if c.name == "phpantom_lsp" then c:stop(true) end end vim.cmd("edit") end, { desc = "Reload buffer" }) local function open_test() require("neotest").summary.open() require("neotest").output_panel.open() end -- Testing local test_maps = { { keys = { "tc" }, action = function() require("neotest").summary.close() require("neotest").output_panel.close() end, desc = "Close [T]est panels", }, { keys = { "tn" }, action = function() require("neotest").run.run() open_test() end, desc = "Run nearest [T]est", }, { keys = { "ta" }, action = function() require("neotest").run.run({ suite = true }) open_test() end, desc = "Run all [T]ests in the project", }, { keys = { "tp" }, action = function() require("neotest").run.run_last() open_test() end, desc = "Run previous [T]est again", }, { keys = { "td" }, action = function() local dap = require("dap") if dap.session() == nil then dap.continue() end require("dapui").open() local neotest = require("neotest") local bufnr = vim.api.nvim_get_current_buf() local row = vim.api.nvim_win_get_cursor(0)[1] - 1 local adapters = neotest.state.adapter_ids() local found = false vim.g.neotest_debug = true for _, adapter_id in ipairs(adapters) do local tree = neotest.state.positions(adapter_id, { buffer = bufnr }) if tree then local nearest = require("neotest.lib.positions").nearest(tree, row) if nearest and nearest:data().type ~= "file" then neotest.run.run() found = true break end end end if not found then neotest.run.run_last() end vim.g.neotest_debug = nil end, desc = "Run last [T]est with debugger", }, } for _, map_info in ipairs(test_maps) do for _, key in ipairs(map_info.keys) do vim.keymap.set("n", key, map_info.action, { desc = map_info.desc }) end end vim.keymap.set("n", "tf", function() require("neotest").run.run(vim.fn.expand("%")) open_test() end, { desc = "Run all tests in the current file" }) vim.keymap.set("n", "tc", function() require("neotest").summary.close() require("neotest").output_panel.close() end, { desc = "Close test panels" }) vim.keymap.set("i", "", function() local ls = require("luasnip") if ls.jumpable(1) then vim.api.nvim_echo({ { "Jumping in snippet" } }, false, {}) ls.jump() else vim.api.nvim_echo({ { "Inserting tab" } }, false, {}) vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("", true, true, true), "n", true) end end, { desc = "Luasnip accept copilot or jump forward" }) -- Leaving this commented out, I will try the format command instead -- "A command to properly indent json code -- command! FormatJSON %!python -m json.tool -- Edit the snippet file for the current buffer filetype or the snippets -- directory if the file does not exist vim.keymap.set("n", "es", function() local ft = vim.bo.filetype if ft == "vue" or ft == "typescript" then ft = "javascript" end local snippets_dir = vim.fn.stdpath("config") .. "/lua/snippets" local snippets_file = snippets_dir .. "/" .. ft .. ".lua" vim.cmd("tabedit " .. snippets_file) end, { desc = "Edit snippets file" }) vim.keymap.set("n", "cz", function() require("utils/cheatsheets").open("zsh") end, { desc = "Open zsh cheat sheet" }) vim.keymap.set("n", "cb", function() require("utils/cheatsheets").open("bash") end, { desc = "Open bash cheat sheet" }) vim.keymap.set("n", "cr", function() require("utils/cheatsheets").open("regex") end, { desc = "Open RegExp cheat sheet" }) require("helpers").edit_cf("k", "/lua/keymap.lua")