" vim:fdm=marker:fmr=§§,■■ " キーマッピング関連。 " そのキーマップが適切に動くようにするための関数や autocmd もここに載せる。 " §§1 changing display " Z を signcolumn/foldcolumn の toggle に使う nnoremap ZZ nnoremap ZQ nnoremap Z :call toggle_column() function! s:toggle_column() abort if &signcolumn ==# 'yes:2' && &foldcolumn == 0 setlocal foldcolumn=4 setlocal signcolumn=no else setlocal foldcolumn=0 setlocal signcolumn=yes:2 endif endfunction " temporal attention nnoremap call temporal_attention()call temporal_relnum() nnoremap zz zzcall temporal_attention() function! s:temporal_attention() abort setlocal cursorline setlocal cursorcolumn augroup temporal_attention autocmd! autocmd CursorMoved * ++once setlocal nocursorline autocmd CursorMoved * ++once setlocal nocursorcolumn augroup END endfunction function! s:temporal_relnum() abort setlocal relativenumber augroup temporal_relnum autocmd! autocmd CursorMoved * ++once setlocal norelativenumber augroup END endfunction " §§1 fold " nnoremap z zMzv " 自分のいない level1 の fold だけたたむ nnoremap z zMzA " §§1 search nnoremap g/ /\v nnoremap * *Ncall temporal_attention() nnoremap g* g*Ncall temporal_attention() nnoremap nohlsearch " 検索 with temporal attention nnoremap n 'Nn'[v:searchforward] .. 'call temporal_attention()' nnoremap N 'nN'[v:searchforward] .. 'call temporal_attention()' " VISUAL モードから簡単に検索 " http://vim.wikia.com/wiki/Search_for_visually_selected_text vnoremap * "my/\V=substitute( \escape(@m, '/\'), '\_s\+', '\\_s\\+', 'g')N vnoremap R "my:set hlsearch \:,$s//=escape(@m, '/\&~') \/gce1,''-&& " §§1 terminal tnoremap augroup vimrc autocmd TermOpen * call s:terminal_init() autocmd TermOpen * setlocal wrap autocmd TermOpen * setlocal nonumber autocmd TermOpen * setlocal signcolumn=no autocmd TermOpen * setlocal foldcolumn=0 augroup END function! s:terminal_init() " ここに :terminal のバッファ固有の設定を記述する nnoremap i nnoremap u "i" . repeat("", v:count1) . "" nnoremap "i" . repeat("", v:count1) . "" nnoremap sw :bd! nnoremap t :let g:current_terminal_job_id = b:terminal_job_id nnoremap dd i nnoremap A i nnoremap I "i\" . repeat("\", calc_cursor_right_num()) nnoremap p pi nnoremap endfunction function! s:calc_cursor_right_num() abort " ad hoc! let cpos = getcurpos() return cpos[2] - 5 endfunction nnoremap st :call openTermWindow() function! s:openTermWindow() abort if (bufname('term://') ==# '') call s:openTerminal() elseif (s:isWideWindow('.')) vsplit buffer term:// else split buffer term:// endif endfunction function! s:openTerminal() let ft = &filetype if (s:isWideWindow('.')) vsplit else split endif edit term://fish let g:current_terminal_job_id = b:terminal_job_id endfunction " §§2 send string to terminal buffer nnoremap t :set opfunc=op_send_terminalg@ nnoremap tp :set opfunc=op_send_terminalg@ap nnoremap tt :call send_terminal_line(v:count1) vnoremap t :call send_terminal_visual_range() function! s:op_send_terminal(type) let sel_save = &selection let &selection = "inclusive" let m_reg = @m if a:type == 'line' let visual_range = "'[V']" else let visual_range = '`[v`]' endif exe "normal! " . visual_range . '"my' call chansend(g:current_terminal_job_id, s:reformat_cmdstring(@m)) let &selection = sel_save let @m=m_reg endfunction function! s:send_terminal_line(n_line) let sel_save = &selection let &selection = "inclusive" let m_reg = @m exe "normal! " . '"m' . a:n_line . 'yy' call chansend(g:current_terminal_job_id, s:reformat_cmdstring(@m)) let &selection = sel_save let @m=m_reg endfunction function! s:send_terminal_visual_range() let sel_save = &selection let &selection = "inclusive" let m_reg = @m exe "normal! gv" . '"my' call chansend(g:current_terminal_job_id, s:reformat_cmdstring(@m)) let &selection = sel_save let @m=m_reg endfunction function! s:reformat_cmdstring(str) " 空行の削除 let s = substitute(a:str, '\n\n', '\n', 'g') " (なければ)最後に改行を付ける if s !~# '\n$' let s = s .. "\n" endif return s endfunction " §§1 input Japanese character " ちょっと j くんには悪いけど,fj は予約したほうが便利. " これで「fj.」 と押せば全角ピリオドを検索できる noremap fj fj noremap Fj Fj " normal/visual mode の t を潰したため omap のみ onoremap tj tj onoremap Tj Tj function s:register_digraph(key_pair, char) execute('digraphs ' .. a:key_pair .. ' ' .. char2nr(a:char) ) endfunction " これを設定することで, fjj を本来の fj と同じ効果にできる. call s:register_digraph('jj', 'j') " カッコ call s:register_digraph('j(', '(') call s:register_digraph('j)', ')') call s:register_digraph('j[', '「') call s:register_digraph('j]', '」') call s:register_digraph('j{', '『') call s:register_digraph('j}', '』') call s:register_digraph('j<', '【') call s:register_digraph('j>', '】') " 句読点 call s:register_digraph('j,', '、') call s:register_digraph('j.', '。') call s:register_digraph('j!', '!') call s:register_digraph('j?', '?') call s:register_digraph('j:', ':') " その他の記号 call s:register_digraph('j~', '〜') call s:register_digraph('j/', '・') call s:register_digraph('js', '␣') " §§1 window/buffer " https://qiita.com/tekkoc/items/98adcadfa4bdc8b5a6ca nnoremap s " バッファ作成と削除 nnoremap s_ :sp nnoremap s :vs nnoremap sv :vs nnoremap ss isWideWindow('.') ? ':vs' : ':sp' nnoremap sq :q " バッファ間移動 nnoremap sj j nnoremap sk k nnoremap sl l nnoremap sh h " バッファの移動(位置関係変更) nnoremap sJ J nnoremap sK K nnoremap sL L nnoremap sH H " 各ウィンドウの大きさ変更 " submode も参照 nnoremap s= = " タブページ nnoremap sT :tabnew % " nnoremap sN gt " nnoremap sP gT nnoremap sQ :tabc " Command-line window nnoremap s: q:G nnoremap s? q?G nnoremap s/ q/G " Sandwich.vim のデフォルトキーバインドを上書きする nnoremap srb function! s:isWideWindow(nr) let wd = winwidth(a:nr) let ht = winheight(a:nr) return wd > 2.2 * ht endfunction " §§1 operator / put " D や C との一貫性 nnoremap Y y$ " operator with temporal relnum nnoremap d call temporal_attention()call temporal_relnum()d nnoremap c call temporal_attention()call temporal_relnum()c nnoremap y call temporal_attention()call temporal_relnum()y nnoremap gu call temporal_attention()call temporal_relnum()gu nnoremap gU call temporal_attention()call temporal_relnum()gU nnoremap > call temporal_attention()call temporal_relnum()> nnoremap < call temporal_attention()call temporal_relnum()< nnoremap = call temporal_attention()call temporal_relnum()= " comment operator nmap , call temporal_attention()call temporal_relnum(), " よく使うレジスタは挿入モードでも挿入しやすく inoremap u" inoremap u0 inoremap u+ cnoremap " cnoremap 0 cnoremap + nnoremap p put + nnoremap P put! + vnoremap p call visual_replace('+') vnoremap p call visual_replace(v:register) " 選択箇所を指定レジスタの内容で置き換える。ただし、指定レジスタの中身はそのまま。 " デフォルトの p と同じ挙動を望む場合は `P` を用いる。 function! s:visual_replace(register) let reg_body = getreg(a:register) exe 'normal! "' .. a:register .. 'p' call setreg(a:register, reg_body) endfunction " §§2 operator-like in insert mode " a を最初に入れるのは,直後の 時に " インデントが消えてしまわないようにするため(もっといい方法がありそう) inoremap "a\k" . (getcurpos()[2] == 1 ? '' : 'l') . ":set opfunc=control_y\g@" inoremap "a\j" . (getcurpos()[2] == 1 ? '' : 'l') . ":set opfunc=control_e\g@" inoremap "a\k" . (getcurpos()[2] == 1 ? '' : 'l') . ":set opfunc=control_y\g@$" inoremap "a\j" . (getcurpos()[2] == 1 ? '' : 'l') . ":set opfunc=control_e\g@$" function! s:control_y(type) " 設定,レジスタの保存 let sel_save = &selection let m_reg = @m let &selection = "inclusive" normal! `[v`]"my if getpos(".")[2] > len(getline(line(".") + 1)) " 今いるところが次の行の末端よりも長いかどうか. " 行末だったら p(末尾に append) normal! j"mp startinsert! else " それ以外は P(途中から insert) normal! j"mPl startinsert endif " 設定,レジスタの復元 let &selection = sel_save let @m=m_reg endfunction function! s:control_e(type) let sel_save = &selection let m_reg = @m let &selection = "inclusive" normal! `[v`]"my if getpos(".")[2] > len(getline(line(".") - 1)) normal! k"mp startinsert! else normal! k"mPl startinsert endif let &selection = sel_save let @m=m_reg endfunction " §§1 motion/text object " smart j/k " thanks to tyru nnoremap j v:count == 0 ? 'gj' : 'j' xnoremap j (v:count == 0 && mode() ==# 'v') ? 'gj' : 'j' nnoremap k v:count == 0 ? 'gk' : 'k' xnoremap k (v:count == 0 && mode() ==# 'v') ? 'gk' : 'k' inoremap U inoremap U " 上記移動を行っていると が動作してしまうのが不便. " imap " としてもうまくいかないので,苦肉の策で を潰す inoremap " かしこい Home nnoremap h call smart_home() xnoremap h call smart_home() onoremap h ^ function! s:smart_home() let str_before_cursor = strpart(getline('.'), 0, col('.') - 1) " カーソル前がインデントしかないかどうかでコマンドを変える if str_before_cursor =~ '^\s*$' let move_cmd = '0' else let move_cmd = '^' endif let col_before = col(".") " まずは表示行の範囲で行頭移動 exe 'normal! g' .. move_cmd " 移動前後でカーソル位置が変わらなかったら再度やり直す let col_after = col(".") if col_before == col_after exe 'normal! ' .. move_cmd endif endfunction " かしこい End nnoremap l call smart_end() xnoremap l $ onoremap l $ function! s:smart_end() let col_before = col(".") " まずは表示行の範囲で行末移動 normal! g$ let col_after = col(".") " 移動前後でカーソル位置が変わらなかったら再度やり直す if col_before == col_after normal! $ endif endfunction " word-in-word motion onoremap u t_ onoremap U :call numSearchLine('[A-Z]', v:count1, '') function! s:numSearchLine(ptn, num, opt) for i in range(a:num) call search(a:ptn, a:opt, line('.')) endfor endfunction " 整合性のとれた括弧に移動するための motion noremap m) ]) noremap m} ]} vnoremap m] i]o`` vnoremap m( i)`` vnoremap m{ i}`` vnoremap m[ i]`` nnoremap dm] vi]o``d nnoremap dm( vi)``d nnoremap dm{ vi}``d nnoremap dm[ vi]``d nnoremap cm] vi]o``c nnoremap cm( vi)``c nnoremap cm{ vi}``c nnoremap cm[ vi]``c " クオート系テキストオブジェクトを自分好みに vnoremap a' 2i' vnoremap a" 2i" vnoremap a` 2i` onoremap a' 2i' onoremap a" 2i" onoremap a` 2i` vnoremap m' a' vnoremap m" a" vnoremap m` a` onoremap m' a' onoremap m" a" onoremap m` a` " Vertical WORD (vWORD) 単位での移動 let g:par_motion_continuous = v:false augroup vimrc autocmd CursorMoved * let g:par_motion_continuous = v:false augroup END function! s:smart_par_motion(direction, count) execute ((g:par_motion_continuous ? 'keepjumps ' : '') .. 'normal! ' .. a:count .. (a:direction ? '}' : '{')) endfunction noremap call smart_par_motion(v:true, v:count1)let g:par_motion_continuous = v:true noremap call smart_par_motion(v:false, v:count1)let g:par_motion_continuous = v:true " Command mode mapping cnoremap cnoremap cnoremap cnoremap cnoremap cnoremap cnoremap " §§1 macros " マクロの記録レジスタは "aq のような一般のレジスタを指定するのと同様の " インターフェースで変更するようにし、デフォルトの記録レジスタを q とする。 " マクロの自己再帰呼出しによるループや、マクロの中でマクロを呼び出すことは簡単にはできないようにしている。 " (もちろんレジスタを直接書き換えれば可能) " デフォルトのレジスタ @q は Vim の開始ごとに初期化される。 " マクロの記録を開始する。もし既に記録中であれば記録を停止する。 nnoremap q reg_recording() ==# '' ? keymap_start_macro(v:register) : 'q' " マクロを再生する。もし何らかの記録の途中であれば、その記録をキャンセル(今まで書いたものを破棄)する。 nnoremap Q reg_recording() ==# '' ? keymap_play_macro(v:register) : keymap_cancel_macro(reg_recording()) " マクロを再生する。もし何らかの記録の途中であれば、その記録をキャンセル(今まで書いたものを破棄)する。 nnoremap reg_recording() ==# '' ? keymap_play_macro(v:register) : keymap_cancel_macro(reg_recording()) " デフォルトの再生用キーマップは無効化(local なコマンドの prefix に使うため) nnoremap @ let g:last_played_macro_register = 'q' " マクロの記録を開始する。 function! s:keymap_start_macro(register) " 無名レジスタには格納できないようにする & デフォルトを q にする let _register = a:register if a:register ==# '"' let _register = 'q' endif return 'q' .. _register endfunction function! s:keymap_play_macro(register) " 無名レジスタには格納できないようにする " & デフォルトを前回再生したマクロにする let _register = a:register if a:register ==# '"' let _register = g:last_played_macro_register endif let g:last_played_macro_register = _register if getreg(_register) ==# '' echohl ErrorMsg echo 'Register @' .. _register .. ' is empty.' echohl None return '' endif echohl WarningMsg echo 'Playing macro: @' .. _register echohl None return '@' .. _register endfunction " 記録を中止 function! s:keymap_cancel_macro(register) " 現在のレジスタに入っているコマンド列を一旦 reg_content に退避 let cmd = ":\let reg_content = @" .. a:register .. "\" " マクロの記録を停止 let cmd .= 'q' " 対象としていたレジスタの中身を先程退避したものに入れ替える let cmd .= ":\let @" .. a:register .. " = reg_content\" " キャンセルした旨を表示 let cmd .= ":echo 'Recording Cancelled: @" .. a:register .. "'\" return cmd endfunction " §§1 特殊キー noremap noremap noremap noremap noremap noremap noremap noremap noremap noremap noremap noremap noremap! noremap! " prefix とするため noremap noremap " §§1 その他操作 " 改行だけを入力する " thanks to cohama nnoremap o call append_new_lines(line("."), v:count1) nnoremap O call append_new_lines(line(".") - 1, v:count1) function! s:append_new_lines(pos_line, n_lines) let lines = repeat([""], a:n_lines) call append(a:pos_line, lines) endfunction if !has('gui_running') " CUIで入力された,が拾えないので " iTerm2のキー設定を利用して特定の文字入力をmapする " Map ✠ (U+2720) to as is mapped to ✠ in iTerm2. map ✠ imap ✠ map ✢ imap ✢ map ➿ imap ➿ endif " 将来何かに割り当てようっと nnoremap nnoremap nnoremap " 長い文の改行をノーマルモードから楽に行う " try: f. or f, nnoremap a nnoremap a :call increment_char(v:count1) nnoremap x :call increment_char(-v:count1) " 文字コードのインクリメント function! s:increment_char(count) normal! v"my let char = @m let num = char2nr(char) let @m = nr2char(num + a:count) normal! gv"mp endfunction " 選択した数値を任意の関数で変換する. " たとえば 300pt の 300 を選択して s とし, " x -> x * 3/2 と指定すれば 450pt になる. " 計算式は g:monaqa_lambda_func に格納されるので r で使い回せる. " 小数のインクリメントや css での長さ調整等に便利?マクロと組み合わせてもいい. " 中で eval を用いているので悪用厳禁.基本的に数値にのみ用いるようにする vnoremap s :call applyLambdaToSelectedArea() vnoremap r :call repeatLambdaToSelectedArea() let g:monaqa_lambda_func = 'x' function s:applyLambdaToSelectedArea() abort let tmp = @@ silent normal gvy let visual_area = @@ let lambda_body = input('Lambda: x -> ', g:monaqa_lambda_func) let g:monaqa_lambda_func = lambda_body let lambda_expr = '{ x -> ' . lambda_body . ' }' let Lambda = eval(lambda_expr) let retval = Lambda(eval(visual_area)) let return_str = string(retval) let @@ = return_str silent normal gvp let @@ = tmp endfunction function s:repeatLambdaToSelectedArea() abort let tmp = @@ silent normal gvy let visual_area = @@ let lambda_body = g:monaqa_lambda_func let lambda_expr = '{ x -> ' . lambda_body . ' }' let Lambda = eval(lambda_expr) let retval = Lambda(eval(visual_area)) let return_str = string(retval) let @@ = return_str silent normal gvp let @@ = tmp endfunction " 便利なので連打しやすいマップにしてみる nnoremap g; nnoremap g, vnoremap gv vnoremap gv " 2020-11-12 に思いついた便利なアイデア。 " vnoremap "/\\%" .. virtcol(".") .. "v" " gF は gf の上位互換? nnoremap gf gF " x, u, U を統一して分かりやすくした。 " See |i_CTRL-V_digit| noremap! u =nr2char(0x) " https://github.com/ompugao/vim-bundle/blob/074e7b22320ad4bfba4da5516e53b498ace35a89/vimrc vnoremap I mode() ==# 'V' ? "\0o$I" : "I" vnoremap A mode() ==# 'V' ? "\0o$A" : "A" " アイデアはいいんだけど使う場面もないので保留 " cnoremap getcmdline()[:4] ==# "'<,'>" ? "gv" : ""