diff options
| author | nsfisis <nsfisis@gmail.com> | 2022-12-19 01:39:56 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2022-12-19 02:02:41 +0900 |
| commit | b67f8c76c20c8cb07b5f0aa52f53aaaf9f9f7576 (patch) | |
| tree | eb6e8f3786e791ffd71a33f4ee75079470dc9630 /.config/nvim/lua/init/04-mappings.lua | |
| parent | ae9846c9563f398b51927d499f0bfe45c9f53185 (diff) | |
| download | dotfiles-b67f8c76c20c8cb07b5f0aa52f53aaaf9f9f7576.tar.gz dotfiles-b67f8c76c20c8cb07b5f0aa52f53aaaf9f9f7576.tar.zst dotfiles-b67f8c76c20c8cb07b5f0aa52f53aaaf9f9f7576.zip | |
neovim: refactor: split init.lua
Diffstat (limited to '.config/nvim/lua/init/04-mappings.lua')
| -rw-r--r-- | .config/nvim/lua/init/04-mappings.lua | 563 |
1 files changed, 563 insertions, 0 deletions
diff --git a/.config/nvim/lua/init/04-mappings.lua b/.config/nvim/lua/init/04-mappings.lua new file mode 100644 index 0000000..f7f8a41 --- /dev/null +++ b/.config/nvim/lua/init/04-mappings.lua @@ -0,0 +1,563 @@ +local F = vim.fn +local G = vim.g +local O = vim.o +local K = vim.keymap + +local vimrc = require('vimrc') + + +-- 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'}, '&', '<Cmd>%&&<CR>') + + +-- Registers and macros. {{{1 + +-- Access an resister in the same way in Insert and Commandline mode. +K.set({'n', 'x'}, '<C-r>', '"') + +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', '<C-d>', '<Del>') + +-- Go elsewhere without dividing the undo history. +K.set('i', '<C-b>', '<C-g>U<Left>') +K.set('i', '<C-f>', '<C-g>U<Right>') + +-- Delete something deviding the undo history. +K.set('i', '<C-u>', '<C-g>u<C-u>') +K.set('i', '<C-w>', '<C-g>u<C-w>') + +K.set('c', '<C-a>', '<Home>') +K.set('c', '<C-e>', '<End>') +K.set('c', '<C-f>', '<Right>') +K.set('c', '<C-b>', '<Left>') +K.set('c', '<C-n>', '<Down>') +K.set('c', '<C-p>', '<Up>') +K.set('c', '<C-d>', '<Del>') + +K.set({'c', 'i'}, '<Left>', '<Nop>') +K.set({'c', 'i'}, '<Right>', '<Nop>') + + + +K.set('n', 'gA', function() + local line = F.getline('.') + if vim.endswith(line, ';;') then -- for OCaml + return 'A<C-g>U<Left><C-g>U<Left>' + elseif vim.regex('[,;)]$'):match_str(line) then + return 'A<C-g>U<Left>' + else + return 'A' + end +end, { expr=true, replace_keycodes=true }) + + + +-- QuickFix or location list. {{{1 + +K.set('n', 'bb', '<Cmd>cc<CR>') + +K.set('n', 'bn', ':<C-u><C-r>=v:count1<CR>cnext<CR>', { silent = true }) +K.set('n', 'bp', ':<C-u><C-r>=v:count1<CR>cprevious<CR>', { silent = true }) + +K.set('n', 'bf', '<Cmd>cfirst<CR>') +K.set('n', 'bl', '<Cmd>clast<CR>') + +K.set('n', 'bS', '<Cmd>colder<CR>') +K.set('n', 'bs', '<Cmd>cnewer<CR>') + + + +-- 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', '<Nop>') +K.set('', 'gU', '<Nop>') +K.set('x', 'u', '<Nop>') +K.set('x', 'U', '<Nop>') + + +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', '<C-r>') + + + + +-- 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') + + + +-- 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', '<Cmd>tabnew<CR>') +K.set('n', 'tT', move_current_window_to_tabpage) + +K.set('n', 'tn', ":<C-u><C-r>=(tabpagenr() + v:count1 - 1) % tabpagenr('$') + 1<CR>tabnext<CR>", { silent=true }) +K.set('n', 'tp', ":<C-u><C-r>=(tabpagenr('$') * 10 + tabpagenr() - v:count1 - 1) % tabpagenr('$') + 1<CR>tabnext<CR>", { silent=true }) + +K.set('n', 'tN', '<Cmd>tabmove +<CR>') +K.set('n', 'tP', '<Cmd>tabmove -<CR>') + +K.set('n', 'tsh', '<Cmd>leftabove vsplit<CR>') +K.set('n', 'tsj', '<Cmd>rightbelow split<CR>') +K.set('n', 'tsk', '<Cmd>leftabove split<CR>') +K.set('n', 'tsl', '<Cmd>rightbelow vsplit<CR>') + +K.set('n', 'tsH', '<Cmd>topleft vsplit<CR>') +K.set('n', 'tsJ', '<Cmd>botright split<CR>') +K.set('n', 'tsK', '<Cmd>topleft split<CR>') +K.set('n', 'tsL', '<Cmd>botright vsplit<CR>') + +K.set('n', 'twh', '<Cmd>leftabove vnew<CR>') +K.set('n', 'twj', '<Cmd>rightbelow new<CR>') +K.set('n', 'twk', '<Cmd>leftabove new<CR>') +K.set('n', 'twl', '<Cmd>rightbelow vnew<CR>') + +K.set('n', 'twH', '<Cmd>topleft vnew<CR>') +K.set('n', 'twJ', '<Cmd>botright new<CR>') +K.set('n', 'twK', '<Cmd>topleft new<CR>') +K.set('n', 'twL', '<Cmd>botright vnew<CR>') + +K.set('n', 'th', '<C-w>h') +K.set('n', 'tj', '<C-w>j') +K.set('n', 'tk', '<C-w>k') +K.set('n', 'tl', '<C-w>l') + +K.set('n', 'tH', '<C-w>H') +K.set('n', 'tJ', '<C-w>J') +K.set('n', 'tK', '<C-w>K') +K.set('n', 'tL', '<C-w>L') + +K.set('n', 'tx', '<C-w>x') + +-- r => manual resize. +-- R => automatic resize. +K.set('n', 'tRH', '<C-w>_') +K.set('n', 'tRW', '<C-w><Bar>') +K.set('n', 'tRR', '<C-w>_<C-w><Bar>') + +K.set('n', 't=', '<C-w>=') + +K.set('n', 'tq', '<Cmd>bdelete<CR>') + +K.set('n', 'tc', '<C-w>c') + +K.set('n', 'to', '<C-w>o') +K.set('n', 'tO', '<Cmd>tabonly<CR>') + +K.set('n', 'tg', choose_window_interactively) + + + +local function smart_open(command) + local modifiers + if F.winwidth(F.winnr()) < 150 then + modifiers = 'topleft' + else + modifiers = 'vertical botright' + end + + vim.cmd(([[ + try + %s %s + let g:__ok = v:true + catch + echohl Error + echo v:exception + echohl None + let g:__ok = v:false + endtry + ]]):format(modifiers, command)) + if not G.__ok then + return + end + + if O.filetype == 'help' then + if vim.bo.textwidth > 0 then + vim.cmd(('vertical resize %d'):format(vim.bo.textwidth)) + end + -- Move the cursor to the beginning of the line as help tags are often + -- right-justfied. + F.cursor( + 0 --[[ stay in the current line ]], + 1 --[[ move to the beginning of the line ]]) + end +end + +vim.api.nvim_create_user_command( + 'SmartOpen', + function(opts) smart_open(opts.args) end, + { + desc = 'Smartly open a new buffer', + nargs = '+', + complete = 'command', + } +) + + + + +-- Toggle options {{{1 + +K.set('n', 'T', '<Nop>') + +K.set('n', 'Ta', '<Cmd>AutosaveToggle<CR>') +K.set('n', 'Tb', ':<C-u>if &background == "dark" <Bar>set background=light <Bar>else <Bar>set background=dark <Bar>endif<CR>', { silent=true }) +K.set('n', 'Tc', ':<C-u>set cursorcolumn! <Bar>set cursorline!<CR>', { silent=true }) +K.set('n', 'Td', ':<C-u>if &diff <Bar>diffoff <Bar>else <Bar>diffthis <Bar>endif<CR>', { silent=true }) +K.set('n', 'Te', '<Cmd>set expandtab!<CR>') +K.set('n', 'Th', '<Cmd>set hlsearch!<CR>') +K.set('n', 'Tn', '<Cmd>set number!<CR>') +K.set('n', 'Ts', '<Cmd>set spell!<CR>') +K.set('n', 'T8', ':<C-u>if &textwidth ==# 80 <Bar>set textwidth=0 <Bar>else <Bar>set textwidth=80 <Bar>endif<CR>', { silent=true }) +K.set('n', 'T0', ':<C-u>if &textwidth ==# 100 <Bar>set textwidth=0 <Bar>else <Bar>set textwidth=100 <Bar>endif<CR>', { silent=true }) +K.set('n', 'T2', ':<C-u>if &textwidth ==# 120 <Bar>set textwidth=0 <Bar>else <Bar>set textwidth=120 <Bar>endif<CR>', { silent=true }) +K.set('n', 'Tw', '<Cmd>set wrap!<CR>') + +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', 'TN', 'Tn', { remap=true }) +K.set('n', 'TS', 'Ts', { remap=true }) +K.set('n', 'TW', 'Tw', { remap=true }) + + + +-- Increment/decrement numbers {{{1 + +-- nnoremap + <C-a> +-- nnoremap - <C-x> +-- xnoremap + <C-a> +-- xnoremap - <C-x> +-- xnoremap g+ g<C-a> +-- xnoremap g- g<C-x> + + + +-- 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', '<Space>s', '<Cmd>Scratch<CR>') + + + +-- Disable unuseful or dangerous mappings. {{{1 + +-- Disable Select mode. +K.set('n', 'gh', '<Nop>') +K.set('n', 'gH', '<Nop>') +K.set('n', 'g<C-h>', '<Nop>') + +-- Disable Ex mode. +K.set('n', 'Q', '<Nop>') +K.set('n', 'gQ', '<Nop>') + +K.set('n', 'ZZ', '<Nop>') +K.set('n', 'ZQ', '<Nop>') + + +-- Help {{{1 + +-- Search help. +K.set('n', '<C-h>', ':<C-u>SmartOpen help<Space>') + + + +-- For writing Vim script. {{{1 + +K.set('n', 'XV', '<Cmd>SmartTabEdit $MYVIMRC<CR>') + +-- See |numbered-function|. +K.set('n', 'XF', ':<C-u>function {<C-r>=v:count<CR>}<CR>', { silent=true }) + +K.set('n', 'XM', '<Cmd>messages<CR>') + + + + + + + +-- 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', ':<C-u>normal! gv<CR>', { silent=true }) + +-- Swap : and ;. +K.set('n', ';', ':') +K.set('n', ':', ';') +K.set('x', ';', ':') +K.set('x', ':', ';') +K.set('n', '@;', '@:') +K.set('x', '@;', '@:') +K.set('!', '<C-r>;', '<C-r>:') + + +-- Since <ESC> may be mapped to something else somewhere, it should be :map, not +-- :noremap. +K.set('!', 'jk', '<ESC>', { remap=true }) + + + +K.set('n', '<C-c>', ':<C-u>nohlsearch<CR>', { silent=true }) + + +-- Lua function cannot be set to 'operatorfunc' for now. +vim.cmd([[ + function! Vimrc_insert_black_line_below(type = '') abort + if a:type ==# '' + set operatorfunc=Vimrc_insert_black_line_below + return 'g@ ' + else + for i in range(v:count1) + call append(line('.'), '') + endfor + endif + endfunction + function! Vimrc_insert_black_line_above(type = '') abort + if a:type ==# '' + set operatorfunc=Vimrc_insert_black_line_above + return 'g@ ' + else + for i in range(v:count1) + call append(line('.') - 1, '') + endfor + endif + endfunction +]]) +K.set('n', 'go', F.Vimrc_insert_black_line_below, { expr = true }) +K.set('n', 'gO', F.Vimrc_insert_black_line_above, { expr = true }) + + +K.set('n', '<Space>w', '<Cmd>update<CR>') + +K.set('n', 'Z', '<Cmd>wqall<CR>', { nowait = true }) + + +-- `s` is used as a prefix key of plugin sandwich and hop. +K.set('n', 's', '<Nop>') +K.set('x', 's', '<Nop>') |
