前回は、「)」を押したときに、自分の求める場所へカーソルをジャンプさせる設定方法について説明した。最終的に、以下のような設定にしたことで開始タブの次の場所へカーソルがジャンプするようになり、不必要な場所へはジャンプしないようになった。
nnoremap ) />[^\n]<CR>l
今回は、終了タブの前へジャンプするようにするほか、句読点へもジャンプするように設定する。これで「)」によるジャンプの設定は完成だ。
次は終了タグの前へジャンプさせる
まず、何も考えずに終了タグの先頭である「<」をパターン検索の対象へ加える。次のように設定すればよい。
nnoremap ) h/>[^\n]\\|<<CR>l
上記のように、「)」でパターン検索が行われるようにする設定の場合、OR検索を実施するために「\|」で条件を分ける必要がある。パターン検索で直接パターンを記入する場合は「\|」が区切り文字になるのだが、上記のように「map」で設定を行う場合には「\|」のように記載することになる。「\|」の後に「<」を書いてあるので、パターン検索として「<」も一致するようになるわけだ。
上記設定で「)」を押していくと次のようにカーソルがジャンプしていく。
当然だが、全ての<へジャンプしていくので、このままでは使い物にならない。終了タグの前にだけジャンプしてくれればよいので、それ以外の場所に一致しないように条件を加えていく。
ここでまずポイントとして、前回指定した「>[^\n]」と条件が被らないように「<」のほうを書き換える。パターンが重複するような状態になっているとどちらのパターンに一致したかがわからなくなってしまうので、それを避けるためだ。「>[^\n]」と「<」では「><」のときにどちらに一致するのか一見してわかりにくい。そこで次のように書き換える。
nnoremap ) />[^\n]\\|[^>]<<CR>l
「>[^\n]」と「[^>]<」の指定に変えたので、「><」は「>[^\n]」に一致することになる。
次に、一致してほしくない条件を追加していく。まず、ここでは「<」の前が空白の場所にジャンプしても意味がないので、次のようにその条件に一致しないように設定を変更する。
nnoremap ) />[^\n]\\|[^ >]<<CR>l
この設定で「)」を押していくとジャンプする場所が次のようになる。
ジャンプする場所はかなり良くなった。
ここで、終了タグ側のジャンプ先が1文字左へ移動した点に注目してほしい。「<」だけのときは<の1つ右へジャンプしていたが、「[^ >]<」にするとジャンプする先がそこよりも1つ左へ移動している。これはパターンを「[^ >]<」に変更したことで、一致先が<よりも1文字左へ移動したことが原因だ。意図せずに望む方向へ移動したことになる。
なお、エディタによってはインデントに空白ではなくタブが使われるので、設定に空白だけではなくタブに関しても一致しないように次のように設定を変更する。
nnoremap ) />[^\n]\\|[^ \t>]<<CR>l
最後に終了タグへジャンプするときの場所の調整だ。前回、開始タグにジャンプしたときにいい具合にカーソルが着地するように、ジャンプした後に「l」で1つ右へ移動するように設定した。このため、先ほど「[^>]<」で一致させたにもかかわらず、カーソルが「<」の上へジャンプしてしまっていた。
ここで先の現象と同じ理屈を使ってジャンプ先を調整する。「<」を「[^ >]<」に変更したことでジャンプ先が1つ左へ移動したのだから、パターンを「.[^ >]<」のように任意の1文字を加えることで、さらにジャンプ先を1つ左へ移動させることができるはずだ。設定をまとめると次のようになる。
nnoremap ) />[^\n]\\|.[^ \t>]<<CR>l
この設定で動作させると次のようになる。
これで、狙い通りに終了タグの前にジャンプするようになった。
パターン検索の機能で任意の場所に飛ばそうとしているので、上記パターンのように「任意の文字をパターンに加えてジャンプ先を調整する」というちょっとした工夫が役立つ。右方向へずらすには検索が終わった後でlを、左方向へずらすには.で一致パターンの範囲を左方向へ広げる、といったテクニックだ。
句読点をジャンプ先へ追加
「)」はもともと一文ごとにジャンプする機能だ。日本語に対応させたときは一文だと範囲が広くて使いにくかったので、句読点ごとにジャンプさせた。ここでもこの機能を取り込んでおきたいので、次のように句読点へジャンプするためのパターンを追加する。
nnoremap ) h/>[^\n]\\|.[^ \t>]<\\|[:。、?!]<CR>l
追加したのは「\|[:。、?!]」だ。\|がORの区切り指定で、[:。、?!]に句読点およびそれに類する文字を加えてある。動作させると次のようになる。
これで大体、思い描いていたようなジャンプをしてくれるようになった。
「)」はもともと英文の一文ごとにジャンプしていく機能だ。これに合わせて英文だった場合にもジャンプするように「\|[.,?!][ \t\n]」の設定を追加して、次のような設定にしておく。
nnoremap ) h/>[^\n]\\|.[^ \t>]<\\|[:。、?!]\\|[.,?!][ \t\n]<CR>l
これで「)」のカスタマイズは完了だ。
OR条件を追加するごとに条件は厳しくなっていく
こうなってくると、例えば「ジャンプの対象に丸括弧を加えたいと」か、「波括弧を加えたい」、「セミコロンを加えたい」といった細かい希望が出てくることもあると思う。上記パターンのどこかに当てはまるなら、そこに文字を追加するとか、パターンをコピーしてOR条件で追加していけば機能するだろう。
しかし、OR条件を追加するごとにどんどん指定できるパターンが厳しくなっていくことを覚えておいたほうがよい。後から追加した条件のほうが優先的に動作するように従来のパターンを変更すると、今度はこれまでジャンプできていた場所にジャンプできなくなるなど、困った状態になるはずだ。
あくまでもVimの提供するパターン検索の機能を使うことでジャンプを実現しているので、そこで実現できない飛び方をさせることはできない。これ以上のことをさせたいなら別の方法で実装する必要がある。Vimのカスタマイズは、この程度までにしておくのが良いかなと思う。
万能ではないが、ジャンプがかなり強力な機能であることは間違いない。設定したことがなければ、ぜひ一度試してみていただきたい。
付録: 使っている設定ファイルとセットアップ方法
プラグインを使うためにDeinをセットアップする方法
mkdir -p ~/.cache/dein
cd ~/.cache/dein/
curl https://raw.githubusercontent.com/Shougo/dein.vim/master/bin/installer.sh > installer.sh
sh ./installer.sh .
rm ./installer.sh
本連載で使っている~/.vimrcファイル
"dein Scripts=============================
if &compatible
set nocompatible " Be iMproved
endif
" Required:
set runtimepath+=~/.cache/dein/./repos/github.com/Shougo/dein.vim
" Required:
if dein#load_state('~/.cache/dein/.')
call dein#begin('~/.cache/dein/.')
" Let dein manage dein
" Required:
call dein#add('~/.cache/dein/./repos/github.com/Shougo/dein.vim')
" Add or remove your plugins here
call dein#add('junegunn/seoul256.vim')
call dein#add('vim-airline/vim-airline')
call dein#add('vim-airline/vim-airline-themes')
call dein#add('preservim/nerdtree')
call dein#add('tpope/vim-commentary')
call dein#add('tpope/vim-fugitive')
call dein#add('fholgado/minibufexpl.vim')
call dein#add('dense-analysis/ale')
call dein#add('junegunn/fzf', {'build': './install --all'})
call dein#add('junegunn/fzf.vim')
call dein#add('sheerun/vim-polyglot')
call dein#add('junegunn/vim-easy-align')
" Required:
call dein#end()
call dein#save_state()
endif
" Required:
filetype plugin indent on
syntax enable
" If you want to install not installed plugins on startup.
if dein#check_install()
call dein#install()
endif
" seoul256
let g:seoul256_background = 233
colo seoul256
" vim-airline
let g:airline_powerline_fonts = 1
let g:airline_theme = 'molokai'
" NERDTree
" <C-o> open NERDTree
nnoremap <silent> <C-o> :NERDTreeToggle<CR>
" minibufexpl
nnoremap <silent> bn :<C-u>:bnext<CR>
nnoremap <silent> b1 :<C-u>:b1<CR>
nnoremap <silent> b2 :<C-u>:b2<CR>
nnoremap <silent> b3 :<C-u>:b3<CR>
nnoremap <silent> b4 :<C-u>:b4<CR>
nnoremap <silent> b5 :<C-u>:b5<CR>
nnoremap <silent> b6 :<C-u>:b6<CR>
nnoremap <silent> b7 :<C-u>:b7<CR>
nnoremap <silent> b8 :<C-u>:b8<CR>
nnoremap <silent> b9 :<C-u>:b9<CR>
" fzf
nnoremap <silent> fzf :Files<CR>
nnoremap <silent> ls :Buffers<CR>
" vim-easy-align
xmap ga <Plug>(EasyAlign)
nmap ga <Plug>(EasyAlign)
"End dein Scripts=========================
set number
syntax on
set whichwrap=b,s,[,],<,>,~,h,l
set cursorline
set incsearch
set hlsearch
set ignorecase
nnoremap k gk
nnoremap gk k
nnoremap j gj
nnoremap gj j
nnoremap q b
vnoremap q b
nnoremap <Tab> 15<Right>
nnoremap <S-Tab> 15<Left>
nnoremap vis h?>[^\n]\\|.[^ \t>]<\\|[。?!]\\|[.?!][ \t\n]<CR>lv/>[^\n]\\|.[^ \t>。?!]<\\|[。?!]\\|[.?!][ \t\n]<CR>
nnoremap >< /<\([^>/]\+\)><\/\1><CR>/<<CR>
nnoremap <> ?<<CR>h?<\([^>/]\+\)><\/\1><CR>/<<CR>
nnoremap ( h?>[^\n]\\|.[^ \t>]<\\|[:。、?!]\\|[.,?!][ \t\n]<CR>l
vnoremap ( h?>[^\n]\\|.[^ \t>]<\\|[:。、?!]\\|[.,?!][ \t\n]<CR>l
nnoremap ) />[^\n]\\|.[^ \t>]<\\|[:。、?!]\\|[.,?!][ \t\n]<CR>l
vnoremap ) />[^\n]\\|.[^ \t>]<\\|[:。、?!]\\|[.,?!][ \t\n]<CR>l
nnoremap "" /""<CR>l
nnoremap '' /''<CR>l
nnoremap :: hh?""<CR>l
nnoremap ;; hh?''<CR>l
nnoremap <C-a> <Home>
inoremap <C-a> <Home>
cnoremap <C-a> <Home>
vnoremap <C-a> <Home>
nnoremap <C-e> <End>
inoremap <C-e> <End>
cnoremap <C-e> <End>
vnoremap <C-e> <End>