Files
nvim/lua/keymap.lua

374 lines
16 KiB
Lua
Raw Permalink Normal View History

2025-08-26 18:23:14 +01:00
-- Basic Keymaps
-- See `:help vim.keymap.set()`
2025-04-12 23:49:02 +01:00
2025-11-07 22:10:53 +00:00
local helpers = require 'helpers'
2025-04-12 23:49:02 +01:00
-- Swap : and ; around
2025-04-13 22:53:08 +01:00
vim.keymap.set({ 'n', 'v' }, ':', ';')
vim.keymap.set({ 'n', 'v' }, ';', ':')
2025-04-12 23:49:02 +01:00
-- Clear highlights on search when pressing <Esc> in normal mode
-- See `:help hlsearch`
vim.keymap.set('n', '<Esc>', '<cmd>nohlsearch<CR>')
-- Diagnostic keymaps
vim.keymap.set('n', '<leader>q', vim.diagnostic.setloclist, { desc = 'Open diagnostic [Q]uickfix list' })
2025-08-26 18:23:14 +01:00
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' })
2025-04-12 23:49:02 +01:00
-- 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 <C-\><C-n>, 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 <C-\><C-n> to exit terminal mode
vim.keymap.set('t', '<Esc><Esc>', '<C-\\><C-n>', { desc = 'Exit terminal mode' })
2025-12-08 16:33:11 +00:00
vim.keymap.set('n', '<Leader>c', function()
2025-12-09 22:10:16 +00:00
vim.treesitter.inspect_tree()
end, { desc = 'Treesitter' })
vim.keymap.set('n', '<C-S-D>', 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', '<C-S-D>', function()
2025-12-09 22:49:24 +00:00
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
2025-12-08 16:33:11 +00:00
end
2025-12-09 22:49:24 +00:00
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) }
2025-12-09 22:10:16 +00:00
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' })
2025-12-09 22:49:24 +00:00
local swappable_nodes = { 'argument', 'array_element_initializer', 'simple_parameter', 'string', 'integer', 'member_call_expression', 'method_declaration' }
local function closest_swappable_node(node)
2025-12-09 23:07:24 +00:00
while node and not vim.list_contains(swappable_nodes, node:type()) and (not node:next_named_sibling() or not node:prev_named_sibling()) do
2025-12-09 22:49:24 +00:00
node = node:parent()
end
return node
end
2025-12-09 22:10:16 +00:00
vim.keymap.set({ 'n' }, '<C-S-h>', function()
2025-12-09 22:49:24 +00:00
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
2025-12-09 22:10:16 +00:00
if prev_node == nil then
return
end
2025-12-09 22:49:24 +00:00
require('nvim-treesitter.ts_utils').swap_nodes(node, prev_node, 0, true)
2025-12-09 22:10:16 +00:00
end, { desc = 'Swap with node to the left' })
vim.keymap.set({ 'n' }, '<C-S-l>', function()
2025-12-09 22:49:24 +00:00
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
2025-12-09 22:10:16 +00:00
if next_node == nil then
return
end
2025-12-09 22:49:24 +00:00
require('nvim-treesitter.ts_utils').swap_nodes(node, next_node, 0, true)
2025-12-09 22:10:16 +00:00
end, { desc = 'Swap with node to the right' })
2025-12-09 22:49:24 +00:00
vim.keymap.set('n', '<C-S-j>', 'ddp', { desc = 'Move line down' })
vim.keymap.set('n', '<C-S-k>', 'ddkP', { desc = 'Move line up' })
vim.keymap.set('v', '<C-S-j>', 'do<Esc>p`[v`]', { desc = 'Move selection down' })
vim.keymap.set('v', '<C-S-k>', 'dkO<Esc>p`[v`]', { desc = 'Move selection up' })
2025-12-08 16:33:11 +00:00
2025-04-12 23:49:02 +01:00
-- Keybinds to make split navigation easier.
-- Use CTRL+<hjkl> to switch between windows
--
-- See `:help wincmd` for a list of all window commands
2025-12-09 16:24:08 +00:00
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
2025-04-12 23:49:02 +01:00
vim.keymap.set('n', '<C-h>', '<C-w><C-h>', { desc = 'Move focus to the left window' })
vim.keymap.set('n', '<C-l>', '<C-w><C-l>', { desc = 'Move focus to the right window' })
2025-12-09 16:24:08 +00:00
vim.keymap.set('n', '<C-j>', (win_or_treesj)('j', 'Move focus to the lower window or treesj.toggle()'))
vim.keymap.set('n', '<C-k>', (win_or_treesj)('k', 'Move focus to the upper window or treesj.toggle()'))
2025-04-12 23:49:02 +01:00
2025-12-08 00:52:01 +00:00
vim.keymap.set({ 'i' }, '<C-J>', function()
local ls = require 'luasnip'
if ls.choice_active() then
ls.change_choice(1)
end
2025-12-09 16:24:08 +00:00
end, { desc = 'Toggle snippet choice', silent = true })
2025-12-08 00:52:01 +00:00
vim.keymap.set({ 'i' }, '<C-k>', function()
local ls = require 'luasnip'
if ls.choice_active() then
ls.change_choice(-1)
end
2025-12-09 16:24:08 +00:00
end, { desc = 'Toggle snippet choice', silent = true })
2025-12-08 00:52:01 +00:00
vim.keymap.set({ 'i', 's' }, '<C-L>', function()
local ls = require 'luasnip'
if ls.expandable() then
ls.expand()
elseif ls.in_snippet() then
ls.jump(1)
end
2025-12-09 16:24:08 +00:00
end, { desc = 'Expand snippet', silent = true })
2025-12-08 00:52:01 +00:00
vim.keymap.set({ 'i', 's' }, '<C-H>', function()
local ls = require 'luasnip'
if ls.in_snippet() then
ls.jump(-1)
end
2025-12-09 16:24:08 +00:00
end, { desc = 'Go back a snippet slot', silent = true })
2025-12-08 00:52:01 +00:00
2025-05-02 20:58:31 +01:00
vim.keymap.set('n', '<Leader>.', '<Cmd>tabnext<CR>', { desc = 'Next tab' })
vim.keymap.set('n', '<Leader>,', '<Cmd>tabprevious<CR>', { desc = 'Previous tab' })
2025-04-29 17:22:28 +01:00
2025-04-13 22:53:08 +01:00
-- Overriding CTRL mappings because some of them are stupid
2025-04-14 11:38:42 +01:00
vim.keymap.set({ 'n', 'v', 'c', 'i' }, '<C-S-B>', '<CMD>Git blame<CR>', { desc = 'Git blame' })
2025-04-13 22:53:08 +01:00
vim.keymap.set({ 'n', 'v', 'c', 'i' }, '<C-Q>', '<CMD>Telescope lsp_document_symbols<CR>', { desc = 'Show symbols in current document' })
vim.keymap.set({ 'n', 'v', 'c', 'i' }, '<C-S-Q>', '<CMD>Telescope lsp_workspace_symbols<CR>', { desc = 'Show symbols in workspace' })
vim.keymap.set({ 'n', 'v', 'c', 'i' }, '<C-E>', '<CMD>Telescope oldfiles<CR>', { desc = 'Show recently opened files' })
vim.keymap.set({ 'n', 'v', 'c', 'i' }, '<C-P>', function()
2025-04-15 20:48:35 +01:00
require('telescope.builtin').find_files {
show_untracked = true,
no_ignore = false,
hidden = true,
}
2025-04-14 15:20:54 +01:00
end, { desc = 'Find all files' })
2025-04-13 22:53:08 +01:00
vim.keymap.set({ 'n', 'v', 'c', 'i' }, '<C-S-P>', function()
require('telescope.builtin').find_files {
2025-04-14 15:20:54 +01:00
show_untracked = true,
2025-04-13 22:53:08 +01:00
no_ignore = true,
hidden = true,
}
end, { desc = 'Find all files' })
2025-11-09 23:03:51 +00:00
vim.keymap.set({ 'n', 'v', 'c', 'i' }, '<C-f>', function()
2025-04-13 22:53:08 +01:00
require('telescope.builtin').live_grep {
hidden = true,
}
end, { desc = 'Search within the whole project' })
vim.keymap.set({ 'n', 'v', 'c', 'i' }, '<C-Y>', '<CMD>Telescope quickfix<CR>', { desc = 'Show quickfix list' })
vim.keymap.set({ 'n', 'v', 'c', 'i' }, '<C-S-Y>', '<CMD>Telescope quickfixhistory<CR>', { desc = 'Show quickfix history' })
2025-04-14 11:38:42 +01:00
vim.keymap.set({ 'n', 'v', 'c', 'i' }, '<C-A>', '<CMD>CodeCompanionChat Toggle<CR>', { desc = 'Open AI Actions' })
vim.keymap.set({ 'n', 'v', 'c', 'i' }, '<C-S-A>', '<CMD>CodeCompanionActions<CR>', { desc = 'Open AI Actions' })
vim.keymap.set({ 'n', 'v', 'c', 'i' }, '<C-S>', function()
require('snacks').scratch()
end, { desc = 'Open scratchpad' })
vim.keymap.set({ 'n', 'v', 'c', 'i' }, '<C-S-S>', function()
require('snacks').scratch.select()
end, { desc = 'Open scratchpad buffers' })
vim.keymap.set({ 'n', 'v', 'c', 'i' }, '<C-T>', function()
require('snacks').terminal.toggle()
end, { desc = 'Open terminal' })
2025-04-13 22:53:08 +01:00
-- Editing helpers
vim.keymap.set('i', '<C-O>', '<Esc>o', { desc = 'Add line below' })
vim.keymap.set('i', '<C-S-O>', '<Esc>O', { desc = 'Add line above' })
2025-04-17 08:53:20 +01:00
local esc_keys = { 'jj', 'jk', 'kk' }
for _, key in ipairs(esc_keys) do
vim.keymap.set('i', key, '<Esc>', { desc = 'Exit insert mode' })
end
2025-04-13 22:53:08 +01:00
vim.keymap.set('i', '<C-D>', '<Esc>ddi', { desc = 'Delete line' })
vim.keymap.set('i', '<Leader>;', '<Esc>mzA;<Esc>`za', { desc = 'Append a semicolon' })
vim.keymap.set('n', '<Leader>;', 'mzA;<Esc>`za', { desc = 'Append a semicolon' })
vim.keymap.set('n', '<Leader>{', 'mzF[`a<CR><Esc>``%i<CR><Esc>`z', { desc = 'Indent an array' })
vim.keymap.set('n', '<Leader>}', 'mzF[`a<CR><Esc>``%i<CR><Esc>`zvi[:s/,\\s*/,\\r/g<CR>vi[=<Esc>`z:nohlsearch<CR>', { desc = 'Indent an array some other way?' })
-- Git mappings
vim.keymap.set('n', '<Leader>gb', '<CMD>Git blame<CR>', { desc = 'Git blame' })
vim.keymap.set('n', '<Leader>gd', '<CMD>Git diff<CR>', { desc = 'Git diff' })
2025-04-14 15:20:54 +01:00
vim.keymap.set('n', '<Leader>gdc', '<CMD>Git diff --cached<CR>', { desc = 'Git diff' })
2025-11-07 22:10:53 +00:00
vim.keymap.set('n', '<Leader>G', function()
helpers.open_term { cmd = 'lazygit' }
end, { desc = 'Git' })
2025-04-13 22:53:08 +01:00
2025-04-29 17:22:28 +01:00
-- 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', '<Tab>', ']c', { noremap = true, silent = true, desc = 'Next hunk in diff mode' })
vim.api.nvim_buf_set_keymap(0, 'n', '<S-Tab>', '[c', { noremap = true, silent = true, desc = 'Previous hunk in diff mode' })
-- Keymaps for resolving conflicts
vim.api.nvim_buf_set_keymap(0, 'n', '<leader>dl', ':diffget LOCAL<CR>', { noremap = true, silent = true, desc = 'Get LOCAL changes' })
vim.api.nvim_buf_set_keymap(0, 'n', '<leader>dr', ':diffget REMOTE<CR>', { noremap = true, silent = true, desc = 'Get REMOTE changes' })
end
end,
})
2025-04-13 22:53:08 +01:00
-- AI mappings
2025-05-11 18:22:45 +01:00
-- vim.keymap.set('v', '<Leader>ae', '<CMD>CodeCompanion<CR>', { desc = 'Edit selection with AI' })
-- vim.keymap.set('n', '<Leader>ac', '<CMD>CodeCompanionCmd<CR>', { desc = 'Run Neovim commands with AI' })
2025-04-29 17:22:28 +01:00
vim.api.nvim_create_autocmd('FileType', {
pattern = 'codecompanion',
callback = function()
2025-05-05 07:25:00 +01:00
vim.keymap.set('i', '<Enter>', function()
2025-05-02 20:58:31 +01:00
require('codecompanion').last_chat():submit()
2025-05-05 07:25:00 +01:00
end, { buffer = true, desc = 'Use enter to send the message' })
2025-05-02 20:58:31 +01:00
vim.keymap.set('i', '<S-Enter>', '<Enter>', { buffer = true, desc = 'Use shift enter to start a new line' })
vim.keymap.set('n', '<Enter>', 'G', { buffer = true, desc = 'Use enter in normal mode to go to the end of the chat' })
2025-04-29 17:22:28 +01:00
2025-11-10 09:19:30 +00:00
local models = { vim.env.DEFAULT_AI_MODEL, vim.env.REASONING_MODEL, vim.env.FAST_MODEL }
2025-04-29 17:22:28 +01:00
-- Loop through models and create keymaps for each
for i, model in ipairs(models) do
2025-05-07 07:58:00 +01:00
vim.keymap.set('n', '<C-' .. i .. '>', 'mzggj0W"_C' .. model .. '<Esc>`z', { desc = 'Switch to ' .. model })
vim.keymap.set('i', '<C-' .. i .. '>', '<Esc>mzggj0W"_C' .. model .. '<Esc>`za', { desc = 'Switch to ' .. model })
2025-04-29 17:22:28 +01:00
end
end,
})
2025-04-13 22:53:08 +01:00
-- Search mappings
vim.keymap.set('n', '<Leader>sGb', '<CMD>Telescope git_branches<CR>', { desc = 'Search git branches' })
vim.keymap.set('n', '<Leader>sGc', '<CMD>Telescope git_commits<CR>', { desc = 'Search git history' })
vim.keymap.set('n', '<Leader>sGh', '<CMD>Telescope git_bcommits<CR>', { desc = 'Search git history within current buffer/selection' })
vim.keymap.set('n', '<Leader>sGs', '<CMD>Telescope git_status<CR>', { desc = 'Search git status' })
vim.keymap.set('n', '<Leader>sGS', '<CMD>Telescope git_stash<CR>', { desc = 'Search git stash' })
-- Misc
vim.keymap.set('n', '<Leader>]', '<CMD>cnext<CR>', { desc = 'Next item in quickfix list' })
vim.keymap.set('n', '<Leader>[', '<CMD>cprevious<CR>', { desc = 'Previous item in quickfix list' })
vim.keymap.set('n', 'gd', '<CMD>Telescope lsp_definitions<CR>', { desc = 'Go to definition' })
2025-12-10 11:47:00 +00:00
2025-04-17 08:53:20 +01:00
local function open_test()
require('neotest').summary.open()
require('neotest').output_panel.open()
end
-- Testing
2025-12-09 22:10:16 +00:00
-- local test_maps = {
-- {
-- keys = { '<F12>', '<Leader>tn' },
-- action = function()
-- require('neotest').run.run()
-- open_test()
-- end,
-- desc = 'Run nearest test',
-- },
-- {
-- keys = { '<F9>', '<Leader>ta' },
-- action = function()
-- require('neotest').run.run { suite = true }
-- open_test()
-- end,
-- desc = 'Run all tests in the project',
-- },
-- {
-- keys = { '<F11>', '<Leader>tp' },
-- action = function()
-- require('neotest').run.run_last()
-- open_test()
-- end,
-- desc = 'Run previous test again',
-- },
-- {
-- keys = { '<F10>', '<Leader>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
--
-- 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
-- end,
-- desc = 'Run last test 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', '<Leader>tf', function()
-- require('neotest').run.run(vim.fn.expand '%')
-- open_test()
-- end, { desc = 'Run all tests in the current file' })
-- vim.keymap.set('n', '<Leader>tc', function()
-- require('neotest').summary.close()
-- require('neotest').output_panel.close()
-- end, { desc = 'Close test panels' })
2025-04-17 08:53:20 +01:00
2025-08-26 18:23:14 +01:00
-- vim.keymap.set('i', '<Tab>', function()
-- local copilot = require 'copilot.suggestion'
-- local ls = require 'luasnip'
-- if copilot.is_visible() then
-- vim.api.nvim_echo({{'Accepting copilot suggestion'}}, false, {})
-- copilot.accept()
-- copilot.dismiss()
-- elseif 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('<Tab>', true, true, true), 'n', true)
-- end
-- end, { desc = 'Luasnip accept copilot or jump forward' })
2025-04-13 22:53:08 +01:00
-- Leaving this commented out, I will try the format command instead
-- "A command to properly indent json code
-- command! FormatJSON %!python -m json.tool
2025-04-24 11:59:36 +01:00
-- Edit the snippet file for the current buffer filetype or the snippets
-- directory if the file does not exist
vim.keymap.set('n', '<Leader>es', function()
local ft = vim.bo.filetype
2025-08-26 18:23:14 +01:00
if ft == 'vue' then
ft = 'javascript'
end
2025-04-24 11:59:36 +01:00
local snippets_dir = vim.fn.stdpath 'config' .. '/lua/snippets'
2025-08-26 18:23:14 +01:00
local snippets_file = snippets_dir .. '/' .. ft .. '.lua'
2025-04-24 11:59:36 +01:00
vim.cmd('tabedit ' .. snippets_file)
end, { desc = 'Edit snippets file' })
2025-04-12 23:49:02 +01:00
require('helpers').edit_cf('k', '/lua/keymap.lua')