Vim-Galore : Vimについて知っておくべき全てのこと (4/5)

(訳注: 2016/2/26、記事タイトルを変更いたしました。)

vim-galore


特定のトピックについての記述をご希望ですか? Issueを立てるか、Twitterで私までお知らせください!ありがとう!


はじめに

基礎

使用方法

ヒント

コマンド

デバッグ

その他

  • その他の資料
  • Vimのディストリビューション
  • 標準プラグイン
  • CapsLockをCtrlにマップする
  • イースターエッグ
  • なぜナビゲーションはhjklなのか?

Quirks

  • 小さいファイルの編集が遅い
  • 大きいファイルの編集が遅い
  • NULに使われる改行
  • 括弧付きペースト(なぜいつも”paste”を設定しなくてはならないのか)
  • ターミナルでEscを使った時の遅延

カラースキームのリスト

プラグインのリスト

Neovim


ヒント

適切なnとNの振る舞い

nNの方向は、前方検索の/または後方検索の?のどちらが使われるかによって決まります。これにはとても混乱させられます。
nを前方へ、Nを後方へと固定したい場合には以下のようにします。

nnoremap <expr> n  'Nn'[v:searchforward]
nnoremap <expr> N  'nN'[v:searchforward]

適切なコマンドライン履歴

私みたいな人間は、「次項へ」は<c-n>、「前項へ」は<c-p>をよく使います。デフォルトでは、同じ機能がコマンドラインでも働き、過去や最近のコマンドラインを履歴から呼び出します。
これで及第点でしょう。しかし、<up><down>がもっと気が利いています。この2つは、先頭が現在のコマンドラインと一致するコマンドラインを呼び出しします。例えば:echo <up>:echo "Vim rocks!に変化します。
もちろん、矢印キーは使って欲しくないので、代わりにこんなふうにマッピングしてください。

cnoremap <c-n>  <down>
cnoremap <c-p>  <up>

私は日に何度もこの動作に頼っています。

適切なCTRL-L

デフォルトの<c-l>は画面をクリアにして再び表示します(:redraw!と同じです)。以下のマッピングも同じ動きをします。その上、/?などで見つかった一致のハイライトを消し、さらに、構文のハイライト箇所を修正し(複雑なハイライトのルールのせいで、Vimは時々ハイライトしそこないます)、そして、差分モードにおける構文のハイライトをアップデートさせます。

nnoremap <leader>l :nohlsearch<cr>:diffupdate<cr>:syntax sync fromstart<cr><c-l>

ビープ音とビジュアルベルを無効にする

set noerrorbells
set novisualbell
set t_vb=

Vim Wiki: ビープ音の無効化を見てください。

現在の行を動かす

時には、現在の行を上か下へ、てっとり早く動かす方法が必要になります。

nnoremap [e  :<c-u>execute 'move -1-'. v:count1<cr>
nnoremap ]e  :<c-u>execute 'move +'. v:count1<cr>

このマッピングでは行も指定できます。2]eは現在の行を2行下へ移します。

空行を追加する

nnoremap [<space>  :<c-u>put! =repeat(nr2char(10), v:count1)<cr>'[
nnoremap ]<space>  :<c-u>put =repeat(nr2char(10), v:count1)<cr>

5[<space>は現在の行の上に空白行5行を挿入します。

マクロを編集する

これぞ本当の宝ですよ! マッピングはレジスタ(あるいは、デフォルトでは*)を使い、コマンドラインのウィンドウ内でレジスタをオープンします。レジスタの編集や設定が終わったら<cr>と打ち込んでください。
私は、マクロの記録中にやってしまったタイポを修正するのに下記を使っています。

nnoremap <leader>m  :<c-u><c-r><c-r>='let @'. v:register .' = '. string(getreg(v:register))<cr><c-f><left>

上のマクロを<leader>mまたは"q<leader>mのように使ってください。
を使って<c-r>が文字通り確実に挿入されていることに注意してください。:h c_^R^Rを見てください。

ヘッダやソースファイルにジャンプする

このテクニックは多くのファイルタイプに使えるかもしれません。ソースファイルやヘッダファイルから離れるときに、これはファイルマーク:h marksを見てください)をセットします。おかげで、'C'Hを使って最後にアクセスしたファイルへ一足飛びで戻れます(:h 'Aを参照)。

autocmd BufLeave *.{c,cpp} mark C
autocmd BufLeave *.h       mark H

注: この情報はviminfoのファイルにありました。:set viminfo?:h viminfo-'があるかどうかを確かめてください。

GUIのフォントサイズを変更する

以下はtpopeのコンフィグから持ってきたと思います。

command! Bigger  :let &guifont = substitute(&guifont, '\d\+$', '\=submatch(0)+1', '')
command! Smaller :let &guifont = substitute(&guifont, '\d\+$', '\=submatch(0)-1', '')

モードに応じてカーソルスタイルを変更する

ノーマルモードではブロックカーソル、挿入モードではI型のカーソル、上書きモードではアンダーライン型のカーソルを使っています。中間でtmuxを使っている時も同じです。

if empty($TMUX)
  let &t_SI = "\<Esc>]50;CursorShape=1\x7"
  let &t_EI = "\<Esc>]50;CursorShape=0\x7"
  let &t_SR = "\<Esc>]50;CursorShape=2\x7"
else
  let &t_SI = "\<Esc>Ptmux;\<Esc>\<Esc>]50;CursorShape=1\x7\<Esc>\\"
  let &t_EI = "\<Esc>Ptmux;\<Esc>\<Esc>]50;CursorShape=0\x7\<Esc>\\"
  let &t_SR = "\<Esc>Ptmux;\<Esc>\<Esc>]50;CursorShape=2\x7\<Esc>\\"
endif

上記は、挿入モードに入る前と出た後にVimがある種の文字のシーケンス(エスケープシーケンス)を画面に表示することを単純に指示しています。端末側が処理し評価します。

しかし、短所がひとつあります。多数の端末エミュレータが実装されていますが、全てのエミュレータが同じ処理をするのに必ず同じシーケンスを使っているというわけではありません。上記のシーケンスも実装された端末が違えばと同様には動きません。異なる形のカーソルのサポートすら実装されていないかもしれません。ドキュメントを確認して下さい。

上記の例はiTerm2で使えます。

セレクションを横に移動させた時に見失わない

1つ以上の行を選択した場合に、<>で横へ移動させることができます。不運にも、そのあと、あっという間にセレクションを見失います。

gvを使って直近のセレクションを再び選択できます(:h gvを見てください)。そうすれば下記のような感じで回避することができます。

xnoremap <  <gv
xnoremap >  >gv

これで、問題なく画面上のセレクションで>>>>>が使えるようになります。

注: .を使うと同じことができ、直近の変更を繰り返し使えます。

保存時にファイルをリロードする

autocmdを使うと保存動作の最中に何でもできます。例えば、dotfileの中を確かめたり、ソースコードの構文エラーをチェックするためにLinterを走らせたりできます。

autocmd BufWritePost $MYVIMRC source $MYVIMRC
autocmd BufWritePost ~/.Xdefaults call system('xrdb ~/.Xdefaults')

スマートなカーソルライン

カーソルラインは大好きですが、使いたいのは現在のウィンドウの中で、挿入モードではない時だけです。

autocmd WinEnter    * set cursorline
autocmd WinLeave    * set nocursorline
autocmd InsertEnter * set nocursorline
autocmd InsertLeave * set cursorline

より速いキーワード補完

キーワードの補完(<c-n>/<c-p>)は、'complete'オプション中にリストされたものすべてを補完しようとします。デフォルトでは、(煩わしくもなる)タグや(とても時間がかかる)全てのインクルードファイルのスキャンが含まれています。もし、これらがなくとも大丈夫なら無効にしてください。

set complete-=i   " disable scanning included files
set complete-=t   " disable searching tags

コマンド

知っておくと良い便利なコマンドです。もっと詳しく知りたい方は、例えば、:h :globalのように、:h :<command name>を利用してください。

:global

条件にマッチする全ての行にコマンドを実行します。例えば、:global /regexp/ printは、”regexp”が含まれている全ての行で、:printを実行します。

面白い事実: おそらく皆さんは、Ken Thompsonによって書かれたフィルタプログラムの古き良きgrepをご存知でしょう。grepは何を行うものでしょう。grepは、特定の正規表現にマッチする全ての行を出力します! では、:global /regexp/ printの短縮形でしょうか。そのとおりです! これは、:g/re/pです。Ken Thompsonは、viの:globalに触発されてgrepを書いたのです。

:globalは、デフォルトでは、その名の通り全ての行が対象ですが、範囲を指定することもできます。現在の行から次の空白行(正規表現^ $と一致する行)までの範囲で、”foo”が含まれる全ての行を:deleteしたい場合は、次のように記述します。

:,/^$/g/foo/d

:normalと:execute

この2つのコマンドは、一般的にVimスクリプトで使用されます。

:normalコマンドを使うと、コマンドラインからノーマルモードへのマッピングを行うことができます。例えば、:normal! 4jはカーソルを4行下に移動させます(”!”を付けると”j”へのカスタムマッピングは行われません)。

:normalは範囲も指定できるので、:%norm! Iabcとすると、全ての行の先頭に “abc”が追加されることを忘れないでください。

:executeコマンドを使うと、式と一緒にコマンドを実行できます。C言語のソースファイルを編集していて、そのヘッダファイルに切り替えたい場合は、次のように記述します。

:execute 'edit' fnamemodify(expand('%'), ':r') . '.h'

2つのコマンドは、よく一緒に使われます。カーソルを”n”行下に移動させたい場合は、次のように記述します。

:let n = 4
:execute 'normal!' n . 'j'

:redir

メッセージを出力するコマンドはいろいろありますが、:redirは、その出力をリダイレクトすることができます。ファイル、レジスタまたは変数にリダイレクトすることができるのです。

:redir => neatvar
:reg
:redir END
:echo neatvar
:" For fun let's also put it onto the current buffer.
:put =nicevar

関連するヘルプ: :h :redir

デバッグ

一般的なヒント

おかしな振る舞いが発生した場合は、次のとおり実行しても再現するかどうか確認してください。

vim -u NONE –N

これを実行すると、vimrcなし(つまりデフォルトの設定)で、かつ非互換モード(viのデフォルトではなく、Vimのデフォルトを使うようになります)でVimが起動されます(起動時にロードするものの組み合わせに関しては、:h --noplugin を参照してください)。

それでもおかしな振る舞いが再現する場合、Vim自体のバグの可能性が最も高いです! vim_devのメーリングリストに報告してください。大抵の場合、問題がその時点ですぐに解決されることはありません。さらに調査する必要があるでしょう。

プラグインはよく、新しい/変更された/障害のある振る舞いを見せることがあります。例えば、保存中に発生した場合は、考えられる原因をリストアップするために:verb au BufWritePostをチェックして下さい。

プラグインマネージャを使用している場合は、原因が分かるまで、それらをコメントアウトしてください。

問題はまだ解決されていませんか? 原因がプラグインでない場合は、オプションやautocmdなど、他の設定に違いありません。

そこで、二分探索の出番です。原因の行を見つけるまで繰り返し探索空間を2分割します。2分割するので、ステップ数が少なくて済みます。

実際に行うのは以下のようなことです。::finishコマンドをvimrcの真ん中に入れます。 Vimはそれ以降の全てをスキップします。まだ問題が発生する場合は、問題はアクティブな上半分の中にあるので、:finishその上半分の真ん中に移動させます。そうでなければ問題は非アクティブな下半分の中にあるので、:finishその下半分の真ん中に移動させます。それを繰り返します。

起動時間のプロファイル

Vimの起動が遅いと感じますか? では、いくつか計算をしてみましょう。

vim --startuptime /tmp/startup.log +q && vim /tmp/startup.log

最初の列が一番重要で、絶対的な経過時間を表示するためのものです。2つの行の間の時間に大きな開きがある場合、2行目は非常に大きなファイルか、または調査に値する障害のあるVimLコードを持つファイルのいずれかです。

ランタイムでのプロファイル

必要な機能: +profile

Vimは、実行中にプロファイルするための組み込み機能を提供しています。この素晴らしい方法を使えば、自分の環境で遅いコードを見つけられます。

:profileコマンドには、プロファイルするものを指定するためのサブコマンドがたくさんあります。

全てをプロファイルする場合は、次の操作を行います。

:profile start /tmp/profile.log
:profile file *
:profile func *
<do something in Vim>
<quit Vim>

Vimはメモリ内にプロファイル情報を保持し、終了する時にのみログファイルに書き出します(Neovimは、:profile dumpを使えるようにしたことで、この点を修正しました)。

/tmp/profile.logを見てください。プロファイル中に実行された全てのコードが書かれています。各行ごとに実行頻度と実行時間を見ることができます。

ほとんどはユーザの知らないプラグインコー​​ドですが、特定の問題を調査している場合は、ログの一番下に飛んでください。そこには、FUNCTIONS SORTED ON TOTAL TIMEFUNCTIONS SORTED ON SELF TIMEという、とても役に立つ2つの異なるセクションがあります。特定の関数にかなり時間がかかっていれば、一目で分かります。

verbose

Vimが現在何をしているかを監視するための別の便利な方法は、verboseのレベルを上げることです。Vimは現状、9つの異なるレベルをサポートしています。
全リストは:h 'verbose'を実行して確認してください。

:e /tmp/foo
:set verbose=2
:w
:set verbose=0

これは、情報として得られる全てのファイルを表示します。例えばアンドゥファイルや保存時に動作するいろいろなプラグインなどです。

もし、単一コマンドでverboseの値を上げたいのであれば、:verboseがあり、どのコマンドの前にでも置くことができます。verboseのレベルは数値で表され、そのデフォルト値は1です。

:verb set verbose
"  verbose=1
:10verb set verbose
"  verbose=10

オプションが最後にセットされた箇所を示すために、verboseのデフォルト、レベル1は頻繁に使われます。

:verb set ai?
"      Last set from ~/.vim/vimrc

もちろん、verboseレベルが高ければ高いほど、アウトプットはかなり大きなものになります。しかし心配する必要はありません。アウトプットを簡単にファイルにリダイレクトすることができます。

:set verbosefile=/tmp/foo | 15verbose echo "foo" | vsplit /tmp/foo

Vimスクリプトのデバッグ

これまで、コマンドラインデバッガーを使っていたなら、:debugにはすぐに親しみがわくでしょう。

他のコマンドの先頭に:debugと入力するだけで、デバッグモードに入れます。つまり、実行しようとしている最初の行で実行が止まり、その行が表示されるのです。

:h >contと以下の6つのデバッガーコマンドを実行して確認してください。gbdや類似のデバッガーと同様に、6つの短い書式、cqnsifが使用できます。

この6つ以外にも、Vimコマンドはどれでも自由に使うことができます。例えば、:echo myvarならコードの現在の位置のコンテキストにおいて実行されます。

:debug 1を使えば、基本的にはREPL になります。

最後の行までシングルステップで実行しなければならないのは、辛いでしょう。なので、もちろんブレイクポイントを設定することもできます。(ブレイクポイントは、ヒットした時点で実行が止まるのでブレイクポイントと呼ばれています。関係ないコードを簡単にスキップできます)。詳細については、:h :breakadd:h :breakdel:h :breaklistを実行して確認して下さい。

例えば、ファイルを保存するたびに、どんなコードを実行しているのかを知りたいとしましょう。

:au BufWritePost
" signify  BufWritePost
"     *         call sy#start()
:breakadd func *start
:w
" Breakpoint in "sy#start" line 1
" Entering Debug mode.  Type "cont" to continue.
" function sy#start
" line 1: if g:signify_locked
>s
" function sy#start
" line 3: endif
>
" function sy#start
" line 5: let sy_path = resolve(expand('%:p'))
>q
:breakdel *

ご覧のように、<cr>を使うと直前のデバッガーコマンドの繰り返しになります。この例ではsを繰り返しています。

:debugはverboseのオプションと組み合わせて使えます。

構文ファイルのデバッグ

誤ったandやorの複雑な正規表現のせいで、構文ファイルは頻繁に速度低下の原因となります。もし+profile機能が組み込まれていれば、Vimにはとても役に立つコマンド:syntimeが準備されています。

:syntime on
" hit <c-l> a few times to redraw the window which causes the syntax rules to get applied again
:syntime off
:syntime report

アウトプットには重要な指標が含まれています。例えば、処理時間が長く最適化しなければならないregexpや、常に使われているのにマッチさえしないregexpを見つけることができます。

:h :syntimeを実行して確認してください。