これまでに取り上げたカスタマイズの中に、「(」と「)」によるカーソル移動を日本語の文章に拡張するというものがあった。一文ごとにカーソルを移動させていく機能を日本語の文章に対して機能するようにするというものだ。この機能は、移動する対象を一文だけではなく、自分の必要とする場所にすることでもっと便利に使うことができるようになる。

実現する方法はいくつかあるが、ここでは簡単にできる方法としてパターン検索を利用する方法を取り上げる。題材としては次のようなXML文書を考える。

題材として利用するXML文書

「)」を押したらまず要素内のテキストへカーソルがジャンプするようにしたい。空要素なら空の部分にカーソルが飛ぶ感じだ。これでいちいち要素にカーソルを合わせることなく、「)」を押すことでポンポンと必要な場所へ飛んでいける。飛ぶ場所は要素内の始まりと終わり、それに句読点や区切りに使われる記号などを設定しよう。

Vimのパターン検索はOR検索をさせることができるので、この機能を駆使して上記ポイントへジャンプさせる。パターン検索は強力な機能であり、これで用が済むならそれに越したことはない。ただし、この方法を使う際には注意が必要だ。次の点を頭に入れて取り組んでもらえればと思う。

  • この方法はORで指定する対象が増えるほど、期待する位置へパーフェクトに移動させることが不可能なパターンが出てくる。完璧にはできない、ということを前提にしておくこと。
  • 「[](角括弧)」をパターン検索の対象に取り入れることがかなり難しいため、角括弧を対象とすることはできないと考えた方がよいこと。


指定できないパターンも、パズル的にうまく指定すれば動くのでは……と設定を工夫しても、できないものはできない。設定の模索にかける数時間はVimの知識を増やすという点では無駄ではないかもしれないが、結局あまり生産的な時間にはならないだろう。あらかじめできないことを把握した上で取り組んでいただきたい。

ファーストステップ:要素の開始部分へ飛ぶ

まず今回は「XMLの要素内へジャンプしたい」ということで、タグの「>」を検索対象として移動することを考える。とりあえず飛びたいグループのうち、最も外側から攻めてみよう。XMLの要素内を1つのグループだと考えると、「>テキスト<」となるので、>を最初のとっかかりとした。題材のXML文書だと、次のスクリーンショットのように「>」がある。

題材XML文書で「>」がある場所

何も考えずに次のような設定をしてみる。これで「)」を押すと「>」で検索が行われるようになる。

nnoremap ) /><CR>

設定を有効にして「)」を押していくと、次のように上記スクリーンショットで示した「>」の場所へカーソルが移動していくことを確認できる。

「>」へカーソルが移動していく

ここから徐々に求めるかたちへ育ててみよう。

無駄なジャンプ対象を削減する

先ほどのジャンプの仕方だと、飛んでも意味のない無駄な場所が多くなる。特に改行前の「>」には何も書き込まないので、ここにカーソルがジャンプしても意味がないだろう。ということで、次のように「>」の後に改行がある場所には飛ばないように設定を変更する。

nnoremap ) />[^\n]<CR>

この設定で「)」を押していくと次のようになる。

実行サンプル

まずまず、少しいい感じの動作になった。

ジャストな場所へ飛ぶように調整する

スクリーンショットではわかりにくいかもしれないが、この設定だとカーソルは「>」の上へ移動している。文字を書き込むには「a」で次の文字から書き出すか、「l」で右へ移動してから書き込みを行っていくことになる。これは手間だ。そこで、移動した後は自動的に1つ右へカーソルを移動するように変更する。

nnoremap ) />[^\n]<CR>l

実行すると次のようになる。

実行サンプル

これで、移動した先ですぐに入力へ移ることができる。

次からの作戦:2つ目以降のパターン追加には注意が必要

以降は次の順序で機能を追加していく。

  1. 「<」を検索対象としてカーソルを移動させる。
  2. 「。、?!」などを検索対象としてカーソルを移動させる。


ここで次の点に注意する必要がある。

  • OR検索で指定する対象を増やしていくと、どの条件で一致したのかがわかりにくくなってくる。そのため、ほかの条件を追加するときは既存の条件と被ることがないように指定するパターンを書く
  • 検索したあとに「l」で1つ右へ移動するという処理を加えたため、ほかの条件で一致した場合も移動後に1つ右へ移動した状態で求める場所へ移動するようにパターンを指定する必要がある


少なくともこれら2つの条件を満たせないと、若干ずれた場所にカーソルが移動したり、求めている場所へカーソルが移動してくれなかったりといったことになる。本稿ではスムーズにできたかのようにまとめているが、実際にはいろいろな設定を試しており、それなりに時間もかかった。そこで得た教訓だ上記2つということだ。今回、付録として稿末に完成版の設定を記載しているので、そちらをご覧いただきたい。

なお、ジャンプしたい対象に角括弧が含まれている場合の設定はパターン検索を使う方法ではうまくいかなかった。この辺りは設定方法がわかったら都度紹介したい。

付録: 使っている設定ファイルとセットアップ方法

プラグインを使うために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>l
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 ) h/>[^\n]\\|.[^ \t>]<\\|[:。、?!]\\|[.,?!][ \t\n]<CR>l
vnoremap ) h/>[^\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>