-- Rafi's Neovim keymaps -- github.com/rafi/vim-config -- === -- This file is automatically loaded by rafi.config.init local Util = require('rafi.lib.utils') local map = vim.keymap.set local function augroup(name) return vim.api.nvim_create_augroup('rafi_' .. name, {}) end -- Elite-mode: Arrow-keys resize window if vim.g.rafi_elite_mode then map('n', '', 'resize +1', { desc = 'Resize Window' }) map('n', '', 'resize -1', { desc = 'Resize Window' }) map('n', '', 'vertical resize +1', { desc = 'Resize Window' }) map('n', '', 'vertical resize -1', { desc = 'Resize Window' }) end -- Package-manager map('n', 'l', 'Lazy', { desc = 'Open Lazy UI' }) -- stylua: ignore start -- Navigation -- === -- Moves through display-lines, unless count is provided map({ 'n', 'x' }, 'j', "v:count == 0 ? 'gj' : 'j'", { expr = true, silent = true }) map({ 'n', 'x' }, 'k', "v:count == 0 ? 'gk' : 'k'", { expr = true, silent = true }) -- Easier line-wise movement map('n', 'gh', 'g^') map('n', 'gl', 'g$') map('n', '', 'V', { desc = 'Visual Mode' }) map('x', '', '', { desc = 'Exit Visual Mode' }) -- Toggle fold or select option from popup menu ---@return string map('n', '', function() return vim.fn.pumvisible() == 1 and '' or 'za' end, { expr = true, desc = 'Toggle Fold' }) -- Focus the current fold by closing all others map('n', '', 'zMzv', { remap = true, desc = 'Focus Fold' }) -- Location/quickfix list movement if not Util.has('mini.bracketed') and not Util.has('trouble.nvim') then map('n', ']q', 'cnext', { desc = 'Next Quickfix Item' }) map('n', '[q', 'cprev', { desc = 'Previous Quickfix Item' }) end map('n', ']a', 'lnext', { desc = 'Next Loclist Item' }) map('n', '[a', 'lprev', { desc = 'Previous Loclist Item' }) -- Whitespace jump (see plugin/whitespace.vim) map('n', ']s', function() require('rafi.lib.edit').whitespace_jump(1) end, { desc = 'Next Whitespace' }) map('n', '[s', function() require('rafi.lib.edit').whitespace_jump(-1) end, { desc = 'Previous Whitespace' }) -- Navigation in command line map('c', '', '') map('c', '', '') map('c', '', '') map('c', '', '') -- Scroll step sideways map('n', 'zl', 'z4l') map('n', 'zh', 'z4h') -- Clipboard -- === -- Yank buffer's relative path to clipboard map('n', 'y', function() local path = vim.fn.fnamemodify(vim.api.nvim_buf_get_name(0), ':~:.') vim.fn.setreg('+', path) vim.notify(path, vim.log.levels.INFO, { title = 'Yanked relative path' }) end, { silent = true, desc = 'Yank relative path' }) -- Yank absolute path map('n', 'Y', function() local path = vim.fn.fnamemodify(vim.api.nvim_buf_get_name(0), ':p') vim.fn.setreg('+', path) vim.notify(path, vim.log.levels.INFO, { title = 'Yanked absolute path' }) end, { silent = true, desc = 'Yank absolute path' }) -- Paste in visual-mode without pushing to register map('x', 'p', 'p:let @+=@0:let @"=@0', { silent = true, desc = 'Paste' }) map('x', 'P', 'P:let @+=@0:let @"=@0', { silent = true, desc = 'Paste In-place' }) -- Edit -- === -- Macros map('n', '', 'q', { desc = 'Macro Prefix' }) -- Start new line from any cursor position in insert-mode map('i', '', 'o', { desc = 'Start Newline' }) -- Re-select blocks after indenting in visual/select mode map('x', '<', '', '>gv|', { desc = 'Indent Left and Re-select' }) -- Use tab for indenting in visual/select mode map('x', '', '>gv|', { desc = 'Indent Left' }) map('x', '', 'k', 'move-2==', { desc = 'Move line up' }) map('n', 'j', 'move+==', { desc = 'Move line down' }) map('x', 'k', ":move'<-2gv=gv", { desc = 'Move selection up' }) map('x', 'j', ":move'>+gv=gv", { desc = 'Move selection down' }) -- Duplicate lines without affecting PRIMARY and CLIPBOARD selections. map('n', 'd', 'm`""Y""P``', { desc = 'Duplicate line' }) map('x', 'd', '""Y""Pgv', { desc = 'Duplicate selection' }) -- Duplicate paragraph map('n', 'cp', 'yapp', { desc = 'Duplicate Paragraph' }) -- Remove spaces at the end of lines map('n', 'cw', 'lua MiniTrailspace.trim()', { desc = 'Erase Whitespace' }) -- Search & Replace -- === -- Switch */g* and #/g# map('n', '*', 'g*') map('n', 'g*', '*') map('n', '#', 'g#') map('n', 'g#', '#') -- Clear search with map('n', '', 'noh', { desc = 'Clear Search Highlight' }) -- Clear search, diff update and redraw taken from runtime/lua/_editor.lua map( 'n', 'ur', 'nohlsearchdiffupdatenormal! ', { desc = 'Redraw / clear hlsearch / diff update' } ) -- Use backspace key for matching parens map({ 'n', 'x' }, '', '%', { remap = true, desc = 'Jump to Paren' }) -- Select last paste map('n', 'gpp', "'`['.strpart(getregtype(), 0, 1).'`]'", { expr = true, desc = 'Select Paste' }) -- Quick substitute within selected area map('x', 'sg', ':s//gc', { desc = 'Substitute Within Selection' }) -- C-r: Easier search and replace visual/select mode map( 'x', '', ":%s/\\V=v:lua.require'rafi.lib.edit'.get_visual_selection()" .. '//gc', { desc = 'Replace Selection' } ) -- Command & History -- === -- Start an external command with a single bang map('n', '!', ':!', { desc = 'Execute Shell Command' }) -- Put vim command output into buffer map('n', 'g!', ":put=execute('')", { desc = 'Paste Command' }) -- Switch history search pairs, matching my bash shell ---@return string map('c', '', function() return vim.fn.pumvisible() == 1 and '' or '' end, { expr = true }) map('c', '', function() return vim.fn.pumvisible() == 1 and '' or '' end, { expr = true }) map('c', '', '') map('c', '', '') -- Allow misspellings vim.cmd.cnoreabbrev('qw', 'wq') vim.cmd.cnoreabbrev('Wq', 'wq') vim.cmd.cnoreabbrev('WQ', 'wq') vim.cmd.cnoreabbrev('Qa', 'qa') vim.cmd.cnoreabbrev('Bd', 'bd') vim.cmd.cnoreabbrev('bD', 'bd') -- File operations -- === -- Switch (window) to the directory of the current opened buffer map('n', 'cd', function() local bufdir = vim.fn.fnamemodify(vim.api.nvim_buf_get_name(0), ':p:h') if bufdir ~= nil and vim.loop.fs_stat(bufdir) then vim.cmd.tcd(bufdir) vim.notify(bufdir) end end, { desc = 'Change Local Directory' }) -- Fast saving from all modes map('n', 'w', 'write', { desc = 'Save' }) map({ 'n', 'i', 'v' }, '', 'write', { desc = 'Save' }) -- Editor UI -- === -- Toggle editor's visual effects map('n', 'uf', require('rafi.plugins.lsp.format').toggle, { desc = 'Toggle format on Save' }) map('n', 'us', 'setlocal spell!', { desc = 'Toggle Spellcheck' }) map('n', 'ul', 'setlocal nonumber!', { desc = 'Toggle Line Numbers' }) map('n', 'uo', 'setlocal nolist!', { desc = 'Toggle Whitespace Symbols' }) map('n', 'uu', 'nohlsearch', { desc = 'Hide Search Highlight' }) if vim.lsp.inlay_hint then map('n', 'uh', function() vim.lsp.inlay_hint(0, nil) end, { desc = 'Toggle Inlay Hints' }) end -- Smart wrap toggle (breakindent and colorcolumn toggle as-well) map('n', 'uw', function() vim.opt_local.wrap = not vim.wo.wrap vim.opt_local.breakindent = not vim.wo.breakindent if vim.wo.colorcolumn == '' then vim.opt_local.colorcolumn = tostring(vim.bo.textwidth) else vim.opt_local.colorcolumn = '' end end, { desc = 'Toggle Wrap' }) -- Tabs: Many ways to navigate them map('n', '', 'tabnext', { desc = 'Next Tab' }) map('n', '', 'tabprevious', { desc = 'Previous Tab' }) map('n', '', 'tabprevious', { desc = 'Previous Tab' }) map('n', '', 'tabnext', { desc = 'Next Tab' }) map('n', '', 'tabnext', { desc = 'Next Tab' }) map('n', '', 'tabprevious', { desc = 'Previous Tab' }) -- Moving tabs map('n', '', '-tabmove', { desc = 'Tab Move Backwards' }) map('n', '', '+tabmove', { desc = 'Tab Move Forwards' }) -- Show treesitter nodes under cursor -- highlights under cursor if vim.fn.has('nvim-0.9') == 1 then map('n', 'ui', vim.show_pos, { desc = 'Show Treesitter Node' }) end -- Custom Tools -- === -- Append mode-line to current buffer map('n', 'ml', function() require('rafi.lib.edit').append_modeline() end, { desc = 'Append Modeline' }) -- Jump entire buffers throughout jumplist map('n', 'g', function() require('rafi.lib.edit').jump_buffer(1) end, { desc = 'Jump to newer buffer' }) map('n', 'g', function() require('rafi.lib.edit').jump_buffer(-1) end, { desc = 'Jump to older buffer' }) -- Context aware menu. See lua/lib/contextmenu.lua map('n', 'c', function() require('rafi.lib.contextmenu').show() end, { desc = 'Content-aware menu' }) -- Lazygit map('n', 'tg', function() Util.float_term({ 'lazygit' }, { cwd = Util.get_root(), esc_esc = false }) end, { desc = 'Lazygit (root dir)' }) map('n', 'tG', function() Util.float_term({ 'lazygit' }, { esc_esc = false }) end, { desc = 'Lazygit (cwd)' }) -- Floating terminal map('t', '', '', { desc = 'Enter Normal Mode' }) map('n', 'tt', function() Util.float_term(nil, { cwd = Util.get_root() }) end, { desc = 'Terminal (root dir)' }) map('n', 'tT', function() Util.float_term() end, { desc = 'Terminal (cwd)' }) if vim.fn.has('mac') then -- Open the macOS dictionary on current word map('n', '?', 'silent !open dict://', { desc = 'Dictionary' }) -- Use Marked for real-time Markdown preview -- See: https://marked2app.com/ if vim.fn.executable('/Applications/Marked 2.app') then vim.api.nvim_create_autocmd('FileType', { group = augroup('marked_preview'), pattern = 'markdown', callback = function() local cmd = "silent !open -a Marked\\ 2.app '%:p'" map('n', 'P', cmd, { desc = 'Markdown Preview' }) end, }) end end -- Windows, buffers and tabs -- === -- Ultimatus Quitos if vim.F.if_nil(vim.g.rafi_window_q_mapping, true) then vim.api.nvim_create_autocmd({ 'BufWinEnter', 'VimEnter' }, { group = augroup('quit_mapping'), callback = function(event) if vim.bo.buftype == '' and vim.fn.maparg('q', 'n') == '' then local args = { buffer = event.buf, desc = 'Quit' } map('n', 'q', 'quit', args) end end, }) end -- Toggle quickfix window map('n', 'q', function() require('rafi.lib.edit').toggle_list('quickfix') end, { desc = 'Open Quickfix' }) -- Set locations with diagnostics and open the list. map('n', 'a', function() if vim.bo.filetype ~= 'qf' then vim.diagnostic.setloclist({ open = false }) end require('rafi.lib.edit').toggle_list('loclist') end, { desc = 'Open Location List' }) -- Switch with adjacent window map('n', '', 'x', { remap = true, desc = 'Swap windows' }) map('n', 'sb', 'buffer#', { desc = 'Alternate buffer' }) map('n', 'sc', 'close', { desc = 'Close window' }) map('n', 'sd', 'bdelete', { desc = 'Buffer delete' }) map('n', 'sv', 'split', { desc = 'Split window horizontally' }) map('n', 'sg', 'vsplit', { desc = 'Split window vertically' }) map('n', 'st', 'tabnew', { desc = 'New tab' }) map('n', 'so', 'only', { desc = 'Close other windows' }) map('n', 'sq', 'quit', { desc = 'Quit' }) map('n', 'sz', 'vertical resize | resize | normal! ze', { desc = 'Maximize' }) map('n', 'sx', function() require('mini.bufremove').delete(0, false) vim.cmd.enew() end, { desc = 'Delete buffer and open new' }) -- Background dark/light toggle map('n', 'sh', function() if vim.o.background == 'dark' then vim.o.background = 'light' else vim.o.background = 'dark' end end, { desc = 'Toggle background dark/light' })