From b67f8c76c20c8cb07b5f0aa52f53aaaf9f9f7576 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Mon, 19 Dec 2022 01:39:56 +0900 Subject: neovim: refactor: split init.lua --- .config/nvim/lua/init/04-mappings.lua | 563 ++++++++++++++++++++++++++++++++++ 1 file changed, 563 insertions(+) create mode 100644 .config/nvim/lua/init/04-mappings.lua (limited to '.config/nvim/lua/init/04-mappings.lua') 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'}, '&', '%&&') + + +-- 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') + + + +-- 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) + + + +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', '') + +K.set('n', 'Ta', 'AutosaveToggle') +K.set('n', 'Tb', ':if &background == "dark" set background=light else set background=dark endif', { silent=true }) +K.set('n', 'Tc', ':set cursorcolumn! set cursorline!', { silent=true }) +K.set('n', 'Td', ':if &diff diffoff else diffthis endif', { silent=true }) +K.set('n', 'Te', 'set expandtab!') +K.set('n', 'Th', 'set hlsearch!') +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', { silent=true }) +K.set('n', 'T0', ':if &textwidth ==# 100 set textwidth=0 else set textwidth=100 endif', { silent=true }) +K.set('n', 'T2', ':if &textwidth ==# 120 set textwidth=0 else set textwidth=120 endif', { silent=true }) +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', '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 }) + + +-- 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', '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', '') -- cgit v1.2.3-70-g09d2