/ nvim / lua / config / keymaps.lua
keymaps.lua
  1  -- Keymaps are automatically loaded on the VeryLazy event
  2  -- Default keymaps that are always set: https://github.com/LazyVim/LazyVim/blob/main/lua/lazyvim/config/keymaps.lua
  3  -- Add any additional keymaps here
  4  
  5  -- Wrapper for vim.keymap.set function
  6  local function map(mode, lhs, rhs, opts)
  7    vim.keymap.set(mode, lhs, rhs, opts)
  8  end
  9  
 10  -- Primary Leader keybindings
 11  map('n', '<leader><tab><tab>', '<cmd>enew<cr>', { desc = 'New empty buffer' })
 12  map('n', '<leader><tab>v', '<cmd>vnew<cr>', { desc = 'New vertically split pane' })
 13  map('n', '<leader>k', '<cmd>WhichKey<cr>', { desc = 'Whichkey' })
 14  map('n', '<leader>t', '', { desc = 'NeoTest' })
 15  
 16  -- LazyVim-style find/search (FzfLua) - space+space = find files, leader+ff/fw for files/grep
 17  map('n', '<leader><space>', '<cmd>FzfLua files<cr>', { desc = 'Find files (FzfLua)' })
 18  map('n', '<leader>ff', '<cmd>FzfLua files<cr>', { desc = 'Find files' })
 19  map('n', '<leader>fw', '<cmd>FzfLua live_grep<cr>', { desc = 'Find word (live grep)' })
 20  map('n', '<leader>fg', '<cmd>FzfLua live_grep<cr>', { desc = 'Live grep' })
 21  map('n', '<leader>fb', '<cmd>FzfLua buffers<cr>', { desc = 'Find buffers' })
 22  map('n', '<leader>fh', '<cmd>FzfLua helptags<cr>', { desc = 'Find help' })
 23  map('n', '<leader>fk', '<cmd>FzfLua keymaps<cr>', { desc = 'Find keymaps' })
 24  map('n', '<leader>fc', '<cmd>FzfLua lsp_code_actions<cr>', { desc = 'LSP code actions' })
 25  -- Search group: leader+s = search, leader+sk = which-key, leader+s/ = search (grep)
 26  map('n', '<leader>s', '<cmd>FzfLua live_grep<cr>', { desc = 'Search in project (grep)' })
 27  map('n', '<leader>sk', '<cmd>WhichKey<cr>', { desc = 'Which key (keymaps)' })
 28  map('n', '<leader>s/', '<cmd>FzfLua live_grep<cr>', { desc = 'Search (grep)' })
 29  map('n', '<leader>sw', '<cmd>FzfLua live_grep<cr>', { desc = 'Search word' })
 30  -- Find and replace in all files (Spectre)
 31  map('n', '<leader>sr', function()
 32    local ok, spectre = pcall(require, 'spectre')
 33    if ok and spectre and spectre.open then
 34      spectre.open()
 35    end
 36  end, { desc = 'Search and replace (Spectre)' })
 37  map('n', '<leader>sR', function()
 38    local ok, spectre = pcall(require, 'spectre')
 39    if ok and spectre and spectre.open_visual then
 40      spectre.open_visual({ select_word = true })
 41    end
 42  end, { desc = 'Search replace (word under cursor)' })
 43  map('v', '<leader>sR', function()
 44    local ok, spectre = pcall(require, 'spectre')
 45    if ok and spectre and spectre.open_visual then
 46      spectre.open_visual()
 47    end
 48  end, { desc = 'Search replace (selection)' })
 49  
 50  -- Buffers
 51  map('n', '<leader>bp', '<cmd>bprevious<cr>', { desc = 'Prev buffer' })
 52  map('n', '<leader>bn', '<cmd>bnext<cr>', { desc = 'Next buffer' })
 53  map('n', '<S-h>', '<cmd>bprevious<cr>', { desc = 'Prev buffer' })
 54  map('n', '<S-l>', '<cmd>bnext<cr>', { desc = 'Next buffer' })
 55  map('n', '[b', '<cmd>bprevious<cr>', { desc = 'Prev buffer' })
 56  map('n', ']b', '<cmd>bnext<cr>', { desc = 'Next buffer' })
 57  map('n', '<leader>bb', '<cmd>e #<cr>', { desc = 'Switch to other buffer' })
 58  map('n', '<leader>bd', '<cmd>bd<cr>', { desc = 'Delete buffer' })
 59  
 60  -- Quickfix / location list
 61  map('n', '<leader>xq', function()
 62    if vim.fn.getqflist({ winid = 0 }).winid ~= 0 then
 63      vim.cmd.cclose()
 64    else
 65      vim.cmd.copen()
 66    end
 67  end, { desc = 'Toggle quickfix list' })
 68  map('n', '<leader>xl', function()
 69    local winid = vim.fn.getloclist(0, { winid = 0 }).winid
 70    if winid and winid ~= 0 then
 71      vim.cmd.lclose()
 72    else
 73      pcall(vim.cmd.lopen) -- E776 when no location list; avoid error
 74    end
 75  end, { desc = 'Toggle location list' })
 76  map('n', '[q', '<cmd>cprev<cr>', { desc = 'Previous quickfix' })
 77  map('n', ']q', '<cmd>cnext<cr>', { desc = 'Next quickfix' })
 78  
 79  -- Diagnostics (vim.diagnostic.jump() + on_jump; goto_next/goto_prev and float= are deprecated in 0.13/0.14)
 80  local function diag_jump(count, severity)
 81    local opts = {
 82      count = count,
 83      on_jump = function(_, bufnr)
 84        vim.diagnostic.open_float({ bufnr = bufnr, scope = 'cursor', focus = false })
 85      end,
 86    }
 87    if severity then
 88      opts.severity = severity
 89    end
 90    vim.diagnostic.jump(opts)
 91  end
 92  map('n', '<leader>cd', vim.diagnostic.open_float, { desc = 'Line diagnostics' })
 93  map('n', ']d', function()
 94    diag_jump(1)
 95  end, { desc = 'Next diagnostic' })
 96  map('n', '[d', function()
 97    diag_jump(-1)
 98  end, { desc = 'Prev diagnostic' })
 99  map('n', ']e', function()
100    diag_jump(1, vim.diagnostic.severity.ERROR)
101  end, { desc = 'Next error' })
102  map('n', '[e', function()
103    diag_jump(-1, vim.diagnostic.severity.ERROR)
104  end, { desc = 'Prev error' })
105  map('n', ']w', function()
106    diag_jump(1, vim.diagnostic.severity.WARN)
107  end, { desc = 'Next warning' })
108  map('n', '[w', function()
109    diag_jump(-1, vim.diagnostic.severity.WARN)
110  end, { desc = 'Prev warning' })
111  
112  -- Trouble keymaps are registered in lua/plugins/trouble.lua after the plugin is loaded and set up.
113  
114  -- Format (conform)
115  map('n', '<leader>cf', function()
116    local ok, conform = pcall(require, 'conform')
117    if ok and conform and conform.format then
118      conform.format({ async = false })
119    end
120  end, { desc = 'Format' })
121  map('x', '<leader>cf', function()
122    local ok, conform = pcall(require, 'conform')
123    if ok and conform and conform.format then
124      conform.format({ async = false })
125    end
126  end, { desc = 'Format' })
127  
128  -- Windows
129  map('n', '<leader>-', '<cmd>split<cr>', { desc = 'Split below' })
130  map('n', '<leader>|', '<cmd>vsplit<cr>', { desc = 'Split right' })
131  map('n', '<leader>wd', '<cmd>close<cr>', { desc = 'Close window' })
132  -- Ctrl+W + Left/Right/Up/Down: move focus between windows
133  map('n', '<C-w><Left>', '<C-w>h', { desc = 'Focus left window' })
134  map('n', '<C-w><Right>', '<C-w>l', { desc = 'Focus right window' })
135  map('n', '<C-w><Up>', '<C-w>k', { desc = 'Focus upper window' })
136  map('n', '<C-w><Down>', '<C-w>j', { desc = 'Focus lower window' })
137  -- Ctrl+W + Ctrl+arrow: resize current window
138  map('n', '<C-w><C-Left>', '<cmd>vertical resize -5<cr>', { desc = 'Resize window left' })
139  map('n', '<C-w><C-Right>', '<cmd>vertical resize +5<cr>', { desc = 'Resize window right' })
140  map('n', '<C-w><C-Up>', '<cmd>resize +5<cr>', { desc = 'Resize window up' })
141  map('n', '<C-w><C-Down>', '<cmd>resize -5<cr>', { desc = 'Resize window down' })
142  
143  -- UI Toggles (leader+u): mirrors LazyVim <leader>u* - autoformat flag is checked in conform.lua BufWritePre
144  map('n', '<leader>uf', function()
145    vim.g.autoformat = vim.g.autoformat == false
146    vim.notify('Autoformat (global): ' .. (vim.g.autoformat and 'on' or 'off'))
147  end, { desc = 'Toggle autoformat (global)' })
148  map('n', '<leader>uF', function()
149    vim.b.autoformat = vim.b.autoformat == false
150    vim.notify('Autoformat (buffer): ' .. (vim.b.autoformat and 'on' or 'off'))
151  end, { desc = 'Toggle autoformat (buffer)' })
152  map('n', '<leader>us', function()
153    vim.wo.spell = not vim.wo.spell
154    vim.notify('Spell: ' .. (vim.wo.spell and 'on' or 'off'))
155  end, { desc = 'Toggle spell' })
156  map('n', '<leader>uw', function()
157    vim.wo.wrap = not vim.wo.wrap
158    vim.notify('Wrap: ' .. (vim.wo.wrap and 'on' or 'off'))
159  end, { desc = 'Toggle wrap' })
160  map('n', '<leader>ul', function()
161    vim.wo.number = not vim.wo.number
162    vim.notify('Line numbers: ' .. (vim.wo.number and 'on' or 'off'))
163  end, { desc = 'Toggle line numbers' })
164  map('n', '<leader>uL', function()
165    vim.wo.relativenumber = not vim.wo.relativenumber
166    vim.notify('Relative numbers: ' .. (vim.wo.relativenumber and 'on' or 'off'))
167  end, { desc = 'Toggle relative number' })
168  map('n', '<leader>ud', function()
169    local enabled = vim.diagnostic.is_enabled()
170    vim.diagnostic.enable(not enabled)
171    vim.notify('Diagnostics: ' .. (not enabled and 'on' or 'off'))
172  end, { desc = 'Toggle diagnostics' })
173  map('n', '<leader>uc', function()
174    vim.wo.conceallevel = vim.wo.conceallevel == 0 and 2 or 0
175    vim.notify('Conceallevel: ' .. vim.wo.conceallevel)
176  end, { desc = 'Toggle conceallevel' })
177  map('n', '<leader>uh', function()
178    local enabled = vim.lsp.inlay_hint.is_enabled({ bufnr = 0 })
179    vim.lsp.inlay_hint.enable(not enabled, { bufnr = 0 })
180    vim.notify('Inlay hints: ' .. (not enabled and 'on' or 'off'))
181  end, { desc = 'Toggle inlay hints' })
182  map('n', '<leader>uT', function()
183    if vim.b.ts_highlight then
184      vim.treesitter.stop()
185      vim.notify('Treesitter highlight: off')
186    else
187      vim.treesitter.start()
188      vim.notify('Treesitter highlight: on')
189    end
190  end, { desc = 'Toggle treesitter highlight' })
191  map('n', '<leader>ub', function()
192    vim.o.background = vim.o.background == 'dark' and 'light' or 'dark'
193    vim.notify('Background: ' .. vim.o.background)
194  end, { desc = 'Toggle background' })
195  map('n', '<leader>ug', function()
196    local ok, ibl = pcall(require, 'ibl')
197    if not ok then
198      return
199    end
200    vim.g.ibl_enabled = vim.g.ibl_enabled == false
201    ibl.update({ enabled = vim.g.ibl_enabled })
202    vim.notify('Indent guides: ' .. (vim.g.ibl_enabled and 'on' or 'off'))
203  end, { desc = 'Toggle indent guides' })
204  
205  -- Git (leader+g): diffview; gitsigns hunk keys are under \g (buffer-local in git buffers)
206  map('n', '<leader>gh', '<cmd>DiffviewOpen<cr>', { desc = 'Diffview open' })
207  map('n', '<leader>gc', '<cmd>DiffviewClose<cr>', { desc = 'Diffview close' })
208  map('n', '<leader>gH', '<cmd>DiffviewFileHistory %<cr>', { desc = 'File history (current)' })
209  
210  -- LSP bindings (K and gd are set buffer-local in LspAttach; these are global fallbacks/alternatives)
211  map('n', '@d', '<cmd>lua vim.lsp.buf.definition()<cr>', { desc = 'lsp buf definition' })
212  map('n', '@D', '<cmd>lua vim.lsp.buf.declaration()<cr>', { desc = 'lsp buf declaration' })
213  map('n', '@i', '<cmd>lua vim.lsp.buf.implementation()<cr>', { desc = 'lsp buf implementation' })
214  map('n', '@o', '<cmd>lua vim.lsp.buf.type_definition()<cr>', { desc = 'lsp type definition' })
215  map('n', '@r', '<cmd>lua vim.lsp.buf.references()<cr>', { desc = 'lsp buf references' })
216  map('n', '@s', '<cmd>lua vim.lsp.buf.signature_help()<cr>', { desc = 'lsp buf signature help' })
217  map('n', '@R', '<cmd>lua vim.lsp.buf.rename()<cr>', { desc = 'lsp buf rename' })
218  map('n', '@f', '<cmd>lua vim.lsp.buf.format({ async = false })<cr>', { desc = 'lsp buf format' })
219  map('n', '@a', '<cmd>lua vim.lsp.buf.code_action()<cr>', { desc = 'lsp buf code action' })
220  
221  -- Custom Leaders keybindings
222  map('n', '\\K', '<cmd>Kustomize<cr>', { desc = 'Kustomize' })
223  
224  -- FzfLua
225  map('n', '\\z', '<cmd>FzfLua<cr>', { desc = 'FuzzyLuaFinder' })
226  map('n', '\\zb', '<cmd>FzfLua buffers<cr>', { desc = 'FuzzyLuaFinder buffers' })
227  map('n', '\\zg', '<cmd>FzfLua live_grep<cr>', { desc = 'FuzzyLuaFinder livegrep' })
228  map('n', '\\zf', '<cmd>FzfLua files<cr>', { desc = 'FuzzyLuaFinder files' })
229  map('n', '\\zk', '<cmd>FzfLua builtin<cr>', { desc = 'Which Key' })
230  map('n', '\\zc', '<cmd>FzfLua lsp_code_actions<cr>', { desc = 'LSP Code Actions' })
231  map('n', '\\zt', '<cmd>FzfLua builtin<cr>', { desc = 'Tmux Buffers' })
232  
233  -- GitSigns
234  map('n', '\\g', '', { desc = 'GitSigns' })
235  map('v', '\\g', '', { desc = 'GitSigns' })
236  map('n', '\\gtD', '<cmd>diffthis<cr>', { desc = 'Toggle select for diff' })
237  
238  -- Lazygit floating terminal (from git root)
239  map('n', '\\gg', function()
240    local root = vim.fn.systemlist('git -C ' .. vim.fn.shellescape(vim.fn.getcwd()) .. ' rev-parse --show-toplevel')[1]
241    if vim.v.shell_error ~= 0 then
242      root = vim.fn.getcwd()
243    end
244    local buf = vim.api.nvim_create_buf(false, true)
245    local ui = vim.api.nvim_list_uis()[1]
246    local width = math.floor(ui.width * 0.9)
247    local height = math.floor(ui.height * 0.9)
248    local row = math.floor((ui.height - height) / 2)
249    local col = math.floor((ui.width - width) / 2)
250    vim.api.nvim_open_win(buf, true, {
251      relative = 'editor',
252      width = width,
253      height = height,
254      row = row,
255      col = col,
256      style = 'minimal',
257      border = 'rounded',
258      title = ' lazygit ',
259      title_pos = 'center',
260    })
261    vim.fn.jobstart('lazygit -p ' .. vim.fn.shellescape(root), {
262      term = true,
263      on_exit = function()
264        vim.api.nvim_buf_delete(buf, { force = true })
265      end,
266    })
267    vim.cmd('startinsert')
268  end, { desc = 'Lazygit (root)' })
269  
270  -- ModelMate
271  map('n', '\\m', '', { desc = 'ModelMate' })
272  map('n', '\\mo', '<cmd>ModelLlama<cr>', { desc = 'ModelLlama-chat' })
273  
274  -- CursorAgent
275  map('n', '\\c', '', { desc = 'CursorAgent' })
276  map('n', '\\ca', '<cmd>CursorOpen<cr>', { desc = 'Open (if needed), switch to Cursor Window' })
277  map('n', '\\cc', '<cmd>CursorClose<cr>', { desc = 'Close Cursor Window' })
278  map('n', '\\ct', '<cmd>CursorToggle<cr>', { desc = 'Toggle Cursor Window' })
279  -- EOF