local F = vim.fn local G = vim.g local O = vim.o local K = vim.keymap local vimrc = require('vimrc') local my_env = require('vimrc.my_env') -- Note: |:noremap| defines mappings in |Normal|, |Visual|, |Operator-Pending| -- and |Select| mode. Because I don't use |Select| mode, I use |:noremap| -- instead of |:nnoremap|, |:xnoremap| and |:onoremap| for simplicity. -- Searching {{{1 -- Fix direction of n and N. K.set('', 'n', "v:searchforward ? 'n' : 'N'", { expr=true }) K.set('', 'N', "v:searchforward ? 'N' : 'n'", { expr=true }) K.set('', 'gn', "v:searchforward ? 'gn' : 'gN'", { expr=true }) K.set('', 'gN', "v:searchforward ? 'gN' : 'gn'", { expr=true }) K.set({'n', 'x'}, '&', '%&&') -- Registers and macros. {{{1 -- Access an resister in the same way in Insert and Commandline mode. K.set({'n', 'x'}, '', '"') F.setreg('j', 'j.') F.setreg('k', 'k.') F.setreg('n', 'n.') F.setreg('m', 'N.') K.set('n', '@N', '@m') -- Repeat the last executed macro as many times as possible. -- a => all K.set('n', '@a', '9999@@') -- Execute the last executed macro again. K.set('n', '`', '@@') -- Emacs like key mappings in Insert and CommandLine mode. {{{1 K.set('i', '', '') -- Go elsewhere without dividing the undo history. K.set('i', '', 'U') K.set('i', '', 'U') -- Delete something deviding the undo history. K.set('i', '', 'u') K.set('i', '', 'u') K.set('c', '', '') K.set('c', '', '') K.set('c', '', '') K.set('c', '', '') K.set('c', '', '') K.set('c', '', '') K.set('c', '', '') K.set({'c', 'i'}, '', '') K.set({'c', 'i'}, '', '') K.set('n', 'gA', function() local line = F.getline('.') if vim.endswith(line, ';;') then -- for OCaml return 'AUU' elseif vim.regex('[,;)]$'):match_str(line) then return 'AU' else return 'A' end end, { expr=true, replace_keycodes=true }) -- QuickFix or location list. {{{1 K.set('n', 'bb', 'cc') K.set('n', 'bn', ':=v:count1cnext', { silent = true }) K.set('n', 'bp', ':=v:count1cprevious', { silent = true }) K.set('n', 'bf', 'cfirst') K.set('n', 'bl', 'clast') K.set('n', 'bS', 'colder') K.set('n', 'bs', 'cnewer') -- Operators {{{1 -- Throw deleted text into the black hole register ("_). K.set({'n', 'x'}, 'c', '"_c') K.set({'n', 'x'}, 'C', '"_C') K.set('', 'g=', '=') K.set('', 'ml', 'gu') K.set('', 'mu', 'gU') K.set('', 'gu', '') K.set('', 'gU', '') K.set('x', 'u', '') K.set('x', 'U', '') K.set('x', 'x', '"_x') K.set('n', 'Y', 'y$') -- In Blockwise-Visual mode, select text linewise. -- By default, select text characterwise, neither blockwise nor linewise. K.set('x', 'Y', "mode() ==# 'V' ? 'y' : 'Vy'", { expr=true }) -- Swap the keys entering Replace mode and Visual-Replace mode. K.set('n', 'R', 'gR') K.set('n', 'gR', 'R') K.set('n', 'r', 'gr') K.set('n', 'gr', 'r') K.set('n', 'U', '') -- Motions {{{1 K.set('', 'H', '^') K.set('', 'L', '$') K.set('', 'M', '%') K.set('', 'gw', 'b') K.set('', 'gW', 'B') K.set('', 'k', 'gk') K.set('', 'j', 'gj') K.set('', 'gk', 'k') K.set('', 'gj', 'j') K.set('n', 'gff', 'gF') -- Completions {{{1 -- '/' works as '' does during file completion. -- https://zenn.dev/kawarimidoll/articles/54e38aa7f55aff K.set('i', '/', function() local complete_info = vim.fn.complete_info({'mode', 'selected'}) if complete_info.mode == 'files' and 0 <= complete_info.selected then return '' else return '/' end end, { expr = true }) -- Tabpages and windows. {{{1 local function move_current_window_to_tabpage() if F.winnr('$') == 1 then -- Leave the current window and open it in a new tabpage. -- Because :wincmd T fails when the current tabpage has only one window. vim.cmd('tab split') else -- Close the current window and re-open it in a new tabpage. vim.cmd('wincmd T') end end local function choose_window_interactively() local indicators = { 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ';', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', } -- List normal windows up to 20. local wins = {} for winnr = 1, F.winnr('$') do if winnr ~= F.winnr() and F.win_gettype(winnr) == '' then wins[#wins+1] = F.win_getid(winnr) end end if #indicators < #wins then for i = #indicators+1, #wins do wins[i] = nil end end -- Handle special cases. if #wins == 0 then return end if #wins == 1 then if wins[1] == F.win_getid() then F.win_gotoid(wins[2]) else F.win_gotoid(wins[1]) end return end -- Show popups. local popups = {} for i = 1, #wins do local winid = wins[i] local indicator = indicators[i] local buf_id = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_lines(buf_id, 0, -1, true, { ' ' .. indicator .. ' ' }) local popup = vim.api.nvim_open_win( buf_id, false, { relative = 'win', win = winid, row = (F.winheight(winid) - 5) / 2, col = (F.winwidth(winid) - 9) / 2, width = 5, height = 1, focusable = false, style = 'minimal', border = 'solid', noautocmd = true, }) popups[#popups+1] = { winid = popup, indicator = indicator, target_winid = winid, } end -- Prompt local result = vimrc.getchar_with_prompt('Select window: ') -- Jump local jump_target = -1 for i, popup in ipairs(popups) do if string.upper(result) == popup.indicator then jump_target = popup.target_winid end end if jump_target ~= -1 then F.win_gotoid(jump_target) end -- Close popups for i, popup in ipairs(popups) do vim.api.nvim_win_close(popup.winid, true) end end K.set('n', 'tt', 'tabnew') K.set('n', 'tT', move_current_window_to_tabpage) K.set('n', 'tn', ":=(tabpagenr() + v:count1 - 1) % tabpagenr('$') + 1tabnext", { silent=true }) K.set('n', 'tp', ":=(tabpagenr('$') * 10 + tabpagenr() - v:count1 - 1) % tabpagenr('$') + 1tabnext", { silent=true }) K.set('n', 'tN', 'tabmove +') K.set('n', 'tP', 'tabmove -') K.set('n', 'tsh', 'leftabove vsplit') K.set('n', 'tsj', 'rightbelow split') K.set('n', 'tsk', 'leftabove split') K.set('n', 'tsl', 'rightbelow vsplit') K.set('n', 'tsH', 'topleft vsplit') K.set('n', 'tsJ', 'botright split') K.set('n', 'tsK', 'topleft split') K.set('n', 'tsL', 'botright vsplit') K.set('n', 'twh', 'leftabove vnew') K.set('n', 'twj', 'rightbelow new') K.set('n', 'twk', 'leftabove new') K.set('n', 'twl', 'rightbelow vnew') K.set('n', 'twH', 'topleft vnew') K.set('n', 'twJ', 'botright new') K.set('n', 'twK', 'topleft new') K.set('n', 'twL', 'botright vnew') K.set('n', 'th', 'h') K.set('n', 'tj', 'j') K.set('n', 'tk', 'k') K.set('n', 'tl', 'l') K.set('n', 'tH', 'H') K.set('n', 'tJ', 'J') K.set('n', 'tK', 'K') K.set('n', 'tL', 'L') K.set('n', 'tx', 'x') -- r => manual resize. -- R => automatic resize. K.set('n', 'tRH', '_') K.set('n', 'tRW', '') K.set('n', 'tRR', '_') K.set('n', 't=', '=') K.set('n', 'tq', 'bdelete') K.set('n', 'tc', 'c') K.set('n', 'to', 'o') K.set('n', 'tO', 'tabonly') K.set('n', 'tg', choose_window_interactively) -- Toggle options {{{1 K.set('n', 'T', '') K.set('n', 'Ta', 'AutosaveToggle') K.set('n', 'Tb', 'if &background == "dark" set background=light else set background=dark endif') K.set('n', 'Tc', 'set cursorcolumn! set cursorline!') K.set('n', 'Td', 'if &diff diffoff else diffthis endif') K.set('n', 'Te', 'set expandtab!') K.set('n', 'Th', 'set hlsearch!') K.set('n', 'Tl', 'if &laststatus ==# 3 set laststatus=2 else set laststatus=3 endif') K.set('n', 'Tn', 'set number!') K.set('n', 'Ts', 'set spell!') K.set('n', 'T8', 'if &textwidth ==# 80 set textwidth=0 else set textwidth=80 endif') K.set('n', 'T0', 'if &textwidth ==# 100 set textwidth=0 else set textwidth=100 endif') K.set('n', 'T2', 'if &textwidth ==# 120 set textwidth=0 else set textwidth=120 endif') K.set('n', 'Tw', 'set wrap!') K.set('n', 'TA', 'Ta', { remap = true }) K.set('n', 'TB', 'Tb', { remap = true }) K.set('n', 'TC', 'Tc', { remap = true }) K.set('n', 'TD', 'Td', { remap = true }) K.set('n', 'TE', 'Te', { remap = true }) K.set('n', 'TH', 'Th', { remap = true }) K.set('n', 'TL', 'Tl', { remap = true }) K.set('n', 'TN', 'Tn', { remap = true }) K.set('n', 'TS', 'Ts', { remap = true }) K.set('n', 'TW', 'Tw', { remap = true }) -- Increment/decrement numbers {{{1 -- nnoremap + -- nnoremap - -- xnoremap + -- xnoremap - -- xnoremap g+ g -- xnoremap g- g -- Open *scratch* buffer {{{1 local EXTENSION_MAPPING = { bash = 'sh', haskell = 'hs', javascript = 'js', markdown = 'md', python = 'py', ruby = 'rb', rust = 'rs', typescript = 'ts', zsh = 'sh', } local function make_scratch_buffer_name(ft) local now = F.localtime() if ft == '' then ft = 'txt' end local ext = EXTENSION_MAPPING[ft] or ft return my_env.scratch_dir .. '/' .. F.strftime('%Y-%m', now), F.strftime('%Y-%m-%d-%H%M%S'), ext end local function open_scratch() local ok, ft = pcall(function() return vimrc.input('filetype: ') end) if not ok then vimrc.echo('Canceled', 'ErrorMsg') return end ft = vim.trim(ft) local dir, fname, ext = make_scratch_buffer_name(ft) if F.isdirectory(dir) == 0 then F.mkdir(dir, 'p') end vim.cmd(('SmartTabEdit %s/%s.%s'):format(dir, fname, ext)) if vim.bo.filetype ~= ft then vim.cmd('setlocal filetype=' .. ft) end vim.b._scratch_ = true if vim.fn.exists(':AutosaveEnable') == 2 then vim.cmd('AutosaveEnable') end end vim.api.nvim_create_user_command( 'Scratch', function() open_scratch() end, { desc = 'Open a *scratch* buffer', } ) K.set('n', 's', 'Scratch') -- Disable unuseful or dangerous mappings. {{{1 -- Disable Select mode. K.set('n', 'gh', '') K.set('n', 'gH', '') K.set('n', 'g', '') -- Disable Ex mode. K.set('n', 'Q', '') K.set('n', 'gQ', '') K.set('n', 'ZZ', '') K.set('n', 'ZQ', '') -- Help {{{1 -- Search help. K.set('n', '', ':SmartOpen help') -- For writing Vim script. {{{1 K.set('n', 'XV', 'SmartTabEdit $MYVIMRC') -- See |numbered-function|. K.set('n', 'XF', ':function {=v:count}', { silent=true }) K.set('n', 'XM', 'messages') -- Abbreviations {{{1 vimrc.iabbrev('TOOD', 'TODO') vimrc.iabbrev('retrun', 'return') vimrc.iabbrev('reutrn', 'return') vimrc.iabbrev('tihs', 'this') vimrc.cabbrev('S', '%s') -- Misc. {{{1 K.set('o', 'gv', ':normal! gv', { silent=true }) -- Swap : and ;. K.set('n', ';', ':') K.set('n', ':', ';') K.set('x', ';', ':') K.set('x', ':', ';') K.set('n', '@;', '@:') K.set('x', '@;', '@:') K.set('!', ';', ':') -- Since may be mapped to something else somewhere, it should be :map, not -- :noremap. K.set('!', 'jk', '', { remap=true }) K.set('n', '', ':nohlsearch', { silent=true }) -- "remap" flag is needed because [ and ] are implemented by Lua functions. K.set('n', 'go', ']', { remap = true }) K.set('n', 'gO', '[', { remap = true }) K.set('n', 'w', 'update') K.set('n', 'Z', 'wqall', { nowait = true }) -- `s` is used as a prefix key of plugin sandwich and hop. K.set('n', 's', '') K.set('x', 's', '')