前回までのカスタマイズで、Vimのパターン検索機能を使って自分の求める場所へジャンプする機能を作成した。前回までに作成した設定は以下の通りだ。

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

これで「(」および「)」で動かしたい場所へカーソルがジャンプするようになった。動作例は次のようになる。

「)」でジャンプしていった場合のサンプル

「(」でジャンプしていった場合のサンプル

これらは、特にHTMLファイルやXMLファイルを編集するときに便利な設定だ。今回は、この設定をベースにしつつ、一文を設定するショートカットである「vis」をカスタマイズする例を取り上げる。

ジャンプ設定を応用して一文選択を更新

「(」と「)」をちょうど良い場所にジャンプするように設定したのだから、これを利用すれば、うまく「一文」を選択する機能を設定できることにも気が付いたはずだ。単純に考えると、次のような手順で設定すれば一文を設定する機能を設定できる。

  1. 「(」で1つ前の区切りへ移動
  2. ビジュアル選択を開始
  3. 「)」で1つ後ろの区切りへ移動


何も考えずに上記のまま設定を作成すると次のようになる。

nnoremap vis h?>[^\n]\\|.[^ \t>]<\\|[:。、?!]\\|[.,?!][ \t\n]<CR>lv/>[^\n]\\|.[^ \t>]<\\|[:。、?!]\\|[.,?!][ \t\n]<CR>l

「(」と「)」の設定をつなげただけだ。つなぎ部分には「v」を入れてあるので、前の区切りへ移動した後、ビジュアル選択モードに入ってから移動が行われ、結果として一文が選択されることになる。実行すると次のようになる。

visの動作を試すサンプルテキスト

visの実行結果

「(」と「)」の設定では、ジャンプ先に読点を含めているので、上記のように読点までで選択が終わっている。読点までで良いならこのままでも良いし、句点まで選択範囲を広げたいなら、次のように設定から読点を抜けばよい。

nnoremap vis h?>[^\n]\\|.[^ \t>]<\\|[:。?!]\\|[.?!][ \t\n]<CR>lv/>[^\n]\\|.[^ \t>]<\\|[:。?!]\\|[,?!][ \t\n]<CR>l

これで次のように句点まで選択されるようになる。

句点まで選択されている

同じことを今度は別の一文に対して実施してみる。

別の一文の途中へカーソルを移動させる

visで選択すると、句点の次まで選択範囲になっている

今度は選択範囲が句点までではなく、句点の1つ右まで伸びている。これは移動後に「l」で1つ右へ移動しているためだ。この設定を削除する。

nnoremap vis h?>[^\n]\\|.[^ \t>]<\\|[:。?!]\\|[.?!][ \t\n]<CR>lv/>[^\n]\\|.[^ \t>]<\\|[:。?!]\\|[,?!][ \t\n]<CR>

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

visの選択で句点までの選択となる

しかし、今度は次のように終了タグの前の句点が選択されなくなる。

今度は終了タグの前の句点が選択されなくなってしまった

些細な違いなのだが、これは致命的だ。こうした細かい部分が異なる挙動をすると、結局visでの選択が不安になり、結局ほかの方法で選択するようになってしまう。それでは時短を実現できない。しかし無理やり使うと、毎回ちゃんと選択されているか範囲を確認しなければならず、ストレスを感じる。この部分は解決しなければならない。

原因を調べてみよう。まず、ベースとしたジャンプ設定は次のようなものだ。

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

句点が一致するのは次の2つのパターンだ。

◆句点が一致するパターン(その1)

.[^ \t>]<

◆句点が一致するパターン(その2)

[:。、?!]

ここが問題だ。これまで繰り返し指摘してきたが、複数のパターンに一致する状態にしておくと問題が発生しやすくなる。まさに今回は、これが問題を引き起こしているのだ。パターン1のほうに終了タグ前の句点が一致し、パターン2のほうにそれ以外の句点が一致している。そのため、終了する位置がずれることになったのである。

これを回避するには、一致するパターンが1つになるようにパターンを変更すればよい。具体的には、次のように終了タグの前の句点に一致しないようにパターンを書き換える。

.[^ 。?!\t>]<

この設定をvisに反映させると次のようになる。

nnoremap vis h?>[^\n]\\|.[^ 。?!\t>]<\\|[:。?!]\\|[.?!][ \t\n]<CR>lv/>[^\n]\\|.[^ 。?!\t>]<\\|[:。?!]\\|[,?!][ \t\n]<CR>

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

visで一文が選択されていることを確認

visで一文が選択されていることを確認

先ほどの設定は日本語文のみを考慮したものだったので、それを英文にも適用できるように調整すると、最終的な設定は次のようになる。

nnoremap vis h?>[^\n]\\|.[^ 。?!.?!\t>]<\\|[:。?!]\\|[.?!][ \t\n]<CR>lv/>[^\n]\\|.[^ 。?!.?!\t>]<\\|[:。?!]\\|[,?!][ \t\n]<CR>

また、これまでと同じように、この選択機能はビジュアル選択モードに入っていても機能してほしい。すでにビジュアル選択モードに入っているのだから、いったんノーマルモードに戻ってから再度選択を行うような設定にすればよいので、次のような設定になる。最初に「ESC」キーを押すように変更しただけだ。

vnoremap vis <ESC>h?>[^\n]\\|.[^ 。?!.?!\t>]<\\|[:。?!]\\|[.?!][ \t\n]<CR>lv/>[^\n]\\|.[^ 。?!.?!\t>]<\\|[:。?!]\\|[,?!][ \t\n]<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

徐々に設定が長くなって呪文じみてきたが、丁寧に作業していけば、これくらいの設定は行える。そしてこれは、テキストファイルの編集時間の短縮に大きな力を発揮する。設定を模索している段階は面倒だが、クリアしてしまえば有力なツールとなるのは間違いない。ぜひとも、自分の状況に合わせてこういったカスタマイズに挑戦してもらえればと思う。建設的な設定の模索は、将来にわたって有益だ。

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

プラグインを使うために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>
vnoremap vis <ESC>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>