vim/gvim挿入モードのキーを若干Emacs風味にカスタマイズ

若干Emacs風味なvimのキー設定

本来vimの挿入モードは長居をするところではありません。

とはいえ挿入モードだけでほとんどの入力編集を可能にすることも出来ます。

特に日本語を使用する場合などはノーマルモードでの利点が無くなってくるので、ある程度編集可能にしておいた方が便利な時もあります。

若干Emacs風味なvimの挿入モードのキーカスタマイズ設定を書いてみたので参考にしてください。

  • ほとんどのキーに<C-g>uが付いているのはアンドゥを設定するためです。
  • また単純に<C-o>からノーマルモードのコマンドを実行していないのは、<C-o>ではIMEの状態がうまく制御されていないのと、virtualedit=onemoreを使用しないと行末で動作的に問題があるコマンドのためです。
  • 挿入モードでの単語単位移動では行末で一度は止まらないと困るので専用の関数を用意しました。
  • ノーマルモードの j や k に gj や gk を割り当てている場合は<C-p>や<C-n>も gj や gk になります。
    • 常に j や k 互換にしたいなら、定義部分の normal を normal! に変更して下さい。
  • 行頭、行末移動の MyJumptoEol(''), MyJumptoBol('')では、 MyJumptoEol(' 。、.,/!?「」') などを指定してやると行頭、行末へ移動する前に指定したセパレータで止まります。
    • 日本語の時だけ " 。、.,/!?「」" などで止まって欲しい時には便利です。
    • 不要なら普通に^や0や$を MyExecExCommandで実行して ください。
  • その他の実行したいノーマルコマンドはMyExecExCommandを使用している部分を参考に適当に書き換えてください。
    • 行末が絡むコマンドは二つ目のパラメータに onemoreを一緒に指定すると良いかもしれません。

当然マップによっては使えなくなるキーが出てきますので、好みで変更してください。

例えばこの場合補完は<C-p>,<C-n>が行移動になっていて使えないので<C-x>から実行するか、自動補完のスクリプトを使用するのが前提になってくると思います。

自動補完については<C-p>,<C-n>が他のキーにマップされていても使用できるスクリプトを書きました。

自動補完

これぐらいあると、特にノーマルモードに移行しなくても入力作業は普通に行えると思いますが、3回以上コマンドを使用するような場合はノーマルモードを使用して移動するというようなルールを決めて使用する方がいいと思います。

<M-f>のようにAltキーもマップ出来ますが、ポリシー的に2ストロークキーを採用しています。

一応Altキーを使う設定もコメントとして書いてありますが、Altキーを使用する場合は winaltkeys を使用しなければいけない場合もあるかと思います。

winaltkeysについてはヘルプを参照して下さい。

また一部のキー(<C-s>など)はgvimのみ使用可能で、コンソール版では使用できないこともあります。

"カーソル一文字単位移動

inoremap <silent> <C-s> <Left>

inoremap <silent> <C-d> <Right>

"単語単位移動(行末で止めたい場合)

inoremap <silent> <C-f> <C-r>=MyMoveWord_i('w')<CR>

inoremap <silent> <C-b> <C-r>=MyMoveWord_i('b')<CR>

"単語単位移動(行末で止まる必要がない場合)

inoremap <silent> <C-f> <S-Left>

inoremap <silent> <C-b> <S-Right>

"非補完時は行移動をj,kと同じ動作にして補完中は候補選択

inoremap <silent> <expr> <C-p> pumvisible() ? "\<C-p>" : "<C-r>=MyExecExCommand('normal k')<CR>"

inoremap <silent> <expr> <C-n> pumvisible() ? "\<C-n>" : "<C-r>=MyExecExCommand('normal j')<CR>"

inoremap <silent> <expr> <Up> pumvisible() ? "\<C-p>" : "<C-r>=MyExecExCommand('normal k')<CR>"

inoremap <silent> <expr> <Down> pumvisible() ? "\<C-n>" : "<C-r>=MyExecExCommand('normal j')<CR>"

"行頭へ

inoremap <silent> <C-a> <C-r>=MyJumptoBol(' 。、.,/!?「」')<CR>

"行末へ

inoremap <silent> <C-e> <C-r>=MyJumptoEol(' 。、.,/!?「」')<CR>

"カーソル前の文字削除

inoremap <silent> <BS> <C-g>u<BS>

inoremap <silent> <C-h> <C-g>u<C-h>

"カーソル後の文字削除

inoremap <silent> <Del> <C-g>u<Del>

inoremap <silent> <C-g> <C-g>u<Del>

"カーソル位置から前の単語を削除

inoremap <silent> <C-w> <C-g>u<C-r>=MyExecExCommand('normal! db')<CR>

"カーソル位置から後の単語を削除

inoremap <silent> <C-t> <C-g>u<C-r>=MyDeleteWord()<CR>

"最後に挿入した文字列を挿入

inoremap <silent> <C-z> <C-g>u<C-a>

"現在行をインデント

inoremap <silent> <Tab> <C-g>u<C-t>

inoremap <silent> <S-Tab> <C-g>u<C-d>

"undo

inoremap <silent> <C-u> <C-g>u<C-r>=MyExecExCommand('undo', 'onemore')<CR>

"2ストロークキー使用

if 1

"カーソル以降削除

inoremap <silent> <C-k><C-k> <C-g>u<C-r>=MyExecExCommand('normal! D', 'onemore')<CR>

"redo

inoremap <silent> <C-k><C-r> <C-r>=MyExecExCommand('redo', 'onemore')<CR>

"行連結

inoremap <silent> <C-k><C-j> <C-g>u<C-r>=MyExecExCommand('normal! J')<CR>

"新行挿入

inoremap <silent> <C-k><C-n> <C-g>u<C-r>=MyExecExCommand("call cursor(line('.'), col('$'))")<CR><CR>

endif

"メタ(alt)キー使用

if 0

"カーソル以降削除

inoremap <silent> <C-k> <C-g>u<C-r>=MyExecExCommand('normal! D', 'onemore')<CR>

"リドゥ

inoremap <silent> <M-r> <C-r>=MyExecExCommand('redo', 'onemore')<CR>

"行連結

inoremap <silent> <M-j> <C-g>u<C-r>=MyExecExCommand('normal! J')<CR>

"新行挿入

inoremap <silent> <M-n> <C-g>u<C-r>=MyExecExCommand("call cursor(line('.'), col('$'))")<CR><CR>

endif

""""""""""""""""""""""""""""""

"sepが空でなければ、sepをセパレータとしてジャンプ。

"見つからなければ見かけの行頭へ。

"カーソル位置が見かけの行頭の場合は真の行頭へ。

""""""""""""""""""""""""""""""

function! MyJumptoBol(sep)

if col('.') == 1

call cursor(line('.')-1, col('$'))

call cursor(line('.'), col('$'))

return ''

endif

if matchend(strpart(getline('.'), 0, col('.')), '[[:blank:]]\+') >= col('.')-1

silent exec 'normal! 0'

return ''

endif

if a:sep != ''

call search('[^'.a:sep.']\+', 'bW', line("."))

if col('.') == 1

silent exec 'normal! ^'

endif

return ''

endif

exec 'normal! ^'

return ''

endfunction

""""""""""""""""""""""""""""""

"sepが空でなければ、sepをセパレータとしてジャンプ。

"見つからなければ行末へ。

""""""""""""""""""""""""""""""

function! MyJumptoEol(sep)

if col('.') == col('$')

silent exec 'normal! w'

return ''

endif

if a:sep != ''

let prevcol = col('.')

call search('['.a:sep.']\+[^'.a:sep.']', 'eW', line("."))

if col('.') != prevcol

return ''

endif

endif

call cursor(line('.'), col('$'))

return ''

endfunction

""""""""""""""""""""""""""""""

"行末でも停止する単語単位移動コマンド

""""""""""""""""""""""""""""""

function! MyMoveWord_i(cmd)

let isEol = 0

if col('$') == col('.')

let isEol = 1

endif

let prevline = line('.')

silent exec 'normal! '.a:cmd

if line('.') == prevline

return ''

endif

if a:cmd == 'w'

if isEol == 0

call cursor(prevline, 0)

call cursor(line('.'), col('$'))

endif

if line('.') - prevline > 1

call cursor(prevline+1, 0)

call cursor(line('.'), col('$'))

endif

elseif a:cmd == 'b'

call cursor(line('.'), col('$'))

if prevline - line('.') > 1

call cursor(prevline-1, 0)

call cursor(line('.'), col('$'))

endif

endif

return ''

endfunction

""""""""""""""""""""""""""""""

"カーソル以降の単語削除

""""""""""""""""""""""""""""""

function! MyDeleteWord()

if col('.') == col('$')

return ''

endif

let save_cursor = getpos('.')

silent exec 'normal! wge'

if save_cursor[1] != line('.') || (save_cursor[2] > col('.'))

call setpos('.', save_cursor)

return MyExecExCommand('normal! dw', 'onemore')

endif

silent exec 'normal! v'

call setpos('.', save_cursor)

return MyExecExCommand('normal! d')

endfunction

""""""""""""""""""""""""""""""

"IMEの状態とカーソル位置保存のため<C-r>を使用してコマンドを実行。

""""""""""""""""""""""""""""""

function! MyExecExCommand(cmd, ...)

let saved_ve = &virtualedit

let index = 1

while index <= a:0

if a:{index} == 'onemore'

silent setlocal virtualedit+=onemore

endif

let index = index + 1

endwhile

silent exec a:cmd

if a:0 > 0

silent exec 'setlocal virtualedit='.saved_ve

endif

return ''

endfunction