Vimマスターへの道:.vimrc

0
614

二週間程前にVimによるファイル編集のバッチ化に関する記事を投稿しましたが、改めてVimに関して調べていると、今まで使っていた機能は氷山の一角に過ぎないと言うことを痛感します。見方を変えれば、Vimを使いこなすべく真面目に精進すれば作業効率アップおよび面倒な操作によるストレスからの脱却が大いに期待できる状況にある訳で、これはもう頑張ってみるしかない!と決意を新たにした今日この頃です。

と言うことで、今回から不定期シリーズ「Vimマスターへの道」として日々の精進から得られた知識を投稿して行きたいと思います。

なお、Vimの機能全体を網羅的に記事にしていく気持ちは全くなく、長年そこそこレベルにviを使用してきた私が知らなかったり、知っていてもまともに使ってこなかったりした機能を整理・記録していく、つまりは個人的備忘録としての意味合いが強い内容になります(実のところ、他の記事もほとんど同様のスタンスですが…)。

さて、記念すべき第一回のネタとしてはやはりこれでしょう、と言うことで「.vimrc」を取り上げたいと思います。

.vimrcとは

.vimrcは簡単に言えばVimの設定ファイルですが、Vimを使用するために「必要」な設定を行う訳ではなく、Vimを自分が使いやすいようカスタマイズするためのものと考えれば良いでしょう。この趣旨から、.vimrcはユーザーごとに定義する形になっており、同じマシン上でもログインアカウントが異なれば参照している.vimrcが異なる、つまりは実行環境が異なっていると考えた方が良いです。

また、.vimrcなしでもVimは使えます。実際に新規に構築したLinux環境などで最初にviを開いた段階では.vimrcは存在していないケースが多いかと思います。viの画面から必要な設定を個別に実施することも可能です(実は今まではこの程度の操作で乗り切ってきました)が、やはり標準的な.vimrcを作成しておき、どの環境でも同じように使える状態にすることが得策でしょう。

.vimrcの管理

.vimrcの基本的な設置場所は当該ユーザーのホームディレクトリ直下です(つまり「~/.vimrc」を参照します)。しかし標準的な.vimrcを作成し、様々な環境で共通的に使用できるように管理しようと考えた場合、ホームディレクトリ直下は少々扱いづらい環境です。

実は「~/.vim/vimrc」も.vimrcとして認識されます。ディレクトリ「.vim」はデフォルトでは存在しない環境なので、こちらでの管理の方が自由度が高そうです。よって「~/.vim/vimrc」として.vimrcを実装する方法をお勧めします。
なお、こちらは「vimrc」のようにファイル名の先頭にドットが付かない(つまり隠しファイルではない)点に注意してください。

また、「~/.vimrc」と「~/.vim/vimrc」の両方が存在した場合、「~/.vimrc」の方が有効となり、「~/.vim/vimrc」の設定内容は無視されてしまいます。「~/.vimrc」の方が圧倒的にメジャーでありネット情報などでも「~/.vimrc」に対する設定内容として記述されているケースが大半だと思いますので、「~/.vim/vimrc」の存在に気づかずに「~/.vimrc」を生成してしまった結果、「~/.vim/vimrc」での設定が無効になってしまうと言う事故も想定されます(個人で使用する環境であればその心配は不要かと思いますが、共用環境などではあり得るかと)。よって、「~/.vimrc」を作成し、その中で下記のように「~/.vim/vimrc」を読み込むことで明示的に「~/.vim/vimrc」の存在を示しておくと安全かもしれません。

source ~/.vim/vimrc

.vimrcを様々な環境で共通的に利用するための管理手段としてはgitを使用するのが良いかと思います。
ディレクトリ「.vim」をそのままgitリポジトリにしてしまいましょう。この意味でもディレクトリ「.vim」として独立した環境であることが重要になります。

また、詳細は後述しますが、「.vim」配下に「swap」と言うディレクトリを生成し、viが生成するswapファイルを同ディレクトリ配下に作成するようにもしたいと思います。
よって、ディレクトリ「.vim/swap」を作成しておきますが、ディレクトリだけではgitの対象にならないので、空ファイル「.vim/swap/.gitkeep」を作成しておきます(この辺はgitのテクニックなので、詳しくはGoogle先生に)。

なお、「.vim/swap」に蓄積されるswapファイルはgitの対象外としたいですし、「.vim」環境の操作を行っていると「.vim/.netrwhist」なるファイルが生成されることがあります(条件は今一つ不明)が、これもgit対象外としたいです。
よって、以下のような.gitignoreを作成します。

swap/*
!swap/.gitkeep
.netrwhist

上記結果、「.vim」ディレクトリ配下には以下のファイルが存在する形になります。

.vim/swap/.gitkeep
.vim/vimrc
.vim/.gitignore

この環境をgitリポジトリとして管理し、共用リポジトリにも反映しておきます。
新規環境では同環境用アカウントのホームディレクトリでclone、pullして「.vim」配下を展開することで、.vimrcを参照できるようになります。

.vimrcの基本形

最初に触れたように.vimrcは個人的カスタマイズのためのものであり必然的に個人の好みが反映されますが、私自身もそうであったように他者の設定内容は参考になるものなので以下に私の設定(特に基本的な部分)を紹介しておきます。
なお、それぞれの設定はVimの画面から直接実行し、結果を確認できますので、下記説明を参考に実際に設定を切り替えていただくことで理解が深まるかと思います。

syntax on
set autoindent
set expandtab
set tabstop=4
set shiftwidth=4
set backspace=2
set directory=$HOME/.vim/swap//
set wildmenu
let mapleader = "\<space>"

syntax

「syntax」は編集する文書の書式に合わせた補助機能に関する設定で「on」を指定すると有効になります。
有効にした場合、具体的には以下のような効果があります(他にあるかもしれませんが、とりあえず目立つところで)。

1つは要素によって表示色を変えてくれます。PHPの場合、関数名は黒、変数名は青(水色)、「if」や「while」などの制御文はオレンジと言った感じです(色に関しては環境によって異なるかもしれませんが)。

また、書式に合わせたインデントを付加してくれたりもします。PHPの場合、クラスや関数の最初の行を記述し改行すると自動的にインデントが一段深くなります。蛇足ながらクラスに関しては「class」と書いた場合は次の行にインデントは付加されません。これは次の行は「{」で始まる必要があり、この時のインデントは「class」と合わせるのが適切だからだと思われます。一方、「class {」と書くと次の行にはインデントが付加されます。
関数に関しては「function」「function xxx」と書いただけでは次の行にインデントは付加されず、「function xxx()」のように書いて初めてインデントが付加されました。適切な書式でなければインデントは付加されないようです。
どの程度かは分かりませんが、かなり頑張って書式の適正さを確認してくれているようで、先の要素による表示色変更と合わせて書式チェックの手段としても有効かもしれません。

さらには、コメントにおいても書式に合わせた補完が行われます。
例えばVim Scriptを書いていて、コメント行(先頭が「”」)を書いて改行すると、次の行の先頭にも「”」が付加されます(これが鬱陶しいケースもありますが…)。
同じコメントでもPHPの場合はコメント行「/*」(アスタリスクは複数あってもOK)を書いて改行すると「*」が付加されますが、良くできたことに「*」の位置を「/*」の「*」の位置と合わせてくれます。また、PHPでは「//」や「#」でもコメントが書けますが、これらに関しても次の行に同じ書式が自動的に付加されます。
なお、上記コメントに関する振る舞いはあくまでコメントのみの行を書いた場合であって、他の有効な書式の後ろにコメントを書いたケースには適用されません。

ちなみに、大雑把に「書式に合わせて」と表現しましたが、これはファイルの拡張子で判断しているようです。例えば「sample.php」では前述したようなPHPの書式を意識した補助機能が働きますが、「sample.txt」では機能しません。蛇足ながらVim Scriptの拡張子は「.vim」です。

無効にしたい場合は「syntax off」と設定します。

autoindent

「autoindent」は自動的にインデントを付加する設定ですが、前述した「syntax」のように賢い機能ではなく、あくまで前の行のインデントを踏襲するといっただけのものです。
とは言っても、多くのプログラミング言語やHTMLなど必須か慣習的かは別にして構造を示すためにインデントを使用するケースではかなり有効な機能です(と言うか、ないと話にならないレベル)。
無効にしたい場合は「set noautoindent」と設定します。

expandtab

「expandtab」は挿入モードでTABキーを押した際にタブではなく半角スペースが入力されるようにする設定です。
何個の半角スペースが挿入されるかは後述する「tabstop」「shiftwidth」の設定に依存します。
単純にはタブがそのまま入力できた方が良さそうですが、言語仕様的にインデントをスペースで設定する必要があるものもあり、PHPでもPSR-2に準拠するのであればインデントは半角スペース4個なので、その辺を考慮してデフォルトをどうしておくかを決定することになるかと思います。
無効にしたい場合は「set noexpandtab」と設定します。

tabstop

「tabstop」はタブによるインデント数の設定です。
正確にはタブを何文字分の幅で表示するかと言う、表示に主眼を置いた機能らしいです。一方で後述する「shiftwidth」などの挙動にも影響するようです。よって「tabstop」と「shiftwidth」の数は合わせておくのが無難かと思います。
デフォルトは8ですが、8文字分もインデントすると編集内容が横に長くなり過ぎますし、PSR-2やPythonの仕様でもインデントを半角スペース4個としていることから4文字分がインデントとして適当かと思います。
また、前述した「set expandtab」と組み合わせることで、タブではなく半角スペースでインデントが付けられるようになります。

shiftwidth

「shiftwidth」もタブによるインデント数の設定です。
ただし「shiftwidth」は自動的にタブが挿入されるような局面に関してのみ影響するようです。
例えば「set tabstop=4」「set shiftwidth=8」としてTABキーや「autoindent」の結果を見ると、いずれも「tabstop」に合わせた内容になっています。
一方で、「syntax on」による自動インデントでは「shiftwidth」によってインデントが確定されていました。
このように状況によってどちらが適用されるかが直感的に分かり難いこともあり、根本的に両者の数字を別にしておく必要性もないであろうことから、「tabstop」と「shiftwidth」は同じく4で設定しておくのが良いと思います。

backspace

「set backspace=2」は若干謎のある設定です。
本設定の意味は「set backspace=indent,eol,start」と設定した場合と同じ意味で、Vim上でのバックスペースの振る舞いを指定するものですが、基本的にはデフォルトで前述の状態になっています。
ただ、環境によってはデフォルトのままでは期待した挙動にならないケースがある模様で、そのような環境にも対応できるように設定してあるものです。とは言っても、この設定をするようになったのはごく最近で、今まで本設定なしでもバックスペースの挙動が問題になった記憶はないのですが。
まぁ、お守り代わりと言ったところでしょうか。

directory

「set directory=$HOME/.vim/swap//」は管理環境の説明で触れたswapファイルの格納場所を指定するものです。
デフォルト状態ではswapファイルは開いたファイルと同じディレクトリ配下に作成されますが、gitにおいて本swapファイルが不要ながらも対象になってしまったり、Vimが異常終了した際にswapファイルが残ったままになってしまったりと、いろいろと邪魔なケースがあります。swapファイル自体の生成を抑止することもできますが、本ファイルはVim異常終了後の再起動時にリカバリファイルとしても使用されるので、本設定のように邪魔にならない場所に置いておくのが得策と言うことになります。

なお、本設定で指定したディレクトリが存在しなければVim起動時にそれっぽいメッセージが出ます。ただ、メッセージが表示されているのは一瞬で、その後Vimの起動自体はできてしまうので、メッセージの内容は確認できませんが(それじゃ意味ないと思うんですが…)。swapファイルが生成されないか、デフォルトと同じく開いたファイルと同じディレクトリに作成されるかのいずれかだと推測しますが、いずれにしてもその辺の心配をしなくても良いように、gitからの展開時に自動的に該当ディレクトリを生成するようにしておくのが良いでしょう。と言うことで、先に示したgit環境になる訳です。

また、指定しているパスの最後が「//」とスラッシュ2つになっています。ネット情報では意外とこのような例は少ないのですが、実は重要な意味を持ちます。
具体的に「/tmp/sample」と言うファイルを例に両者の違いを見ていきたいと思います。
まず「//」なしの設定を行うと「.vim/swap」には「sample.swp 」と言うファイルが生成されます。
一方「//」ありの設定を行うと「.vim/swap」には「%tmp%sample.swp」と言うファイルが生成されます。
つまり「//」指定であれば絶対パス情報を含んだファイル名になる訳です。
前者の場合、異なるディレクトリ配下の同名のファイル、例えば「/tmp/sample」と「/tmp/sub/sample」を同時に開いた場合にファイル名が同じ「sample.swp」になってしまうと言う問題があるのですが、この点は後から開いた方の拡張子を「.swp」から「.swo」などに変化させて対応するようです。ただ、swapファイル名から元ファイルを一意に特定できない状態ではあるのでリカバリに問題が生じます。実験結果としては「/tmp/sample」のswapファイル「sample.swp」と「/tmp/sub/sample」のswapファイル「sample.swo」が共に「/tmp/sample」起動時のリカバリファイルとして扱われ、「/tmp/sub/sample」起動時にはリカバリ関連の動きはなく、何事もなかったようにVimが起動しました(つまり/tmp/sub/sample」のリカバリはできない状態)。また、「/tmp/sample」のリカバリファイルとして「sample.swo」を選択してしまうと、当然ながら「/tmp/sample」の内容が「/tmp/sub/sample」の内容で置換されてしまいます。

swapファイルの元ファイルとしてなぜ「/tmp/sample」だけが認識され「/tmp/sub/sample」は対象とならないのかと言う点は謎であり、今一つ釈然としない挙動ですが、そもそもこのような状況にならないように末尾の「//」は忘れずに付けておくことが重要だと言うことですね。

wildmenu

「wildmenu」は起動されたVimから別ファイルを開く際のファイル名の補完を見やすくするものです。
例えば「/tmp」配下に複数のファイルが存在する状態でコマンドモードで「:r /tmp」と入力しTABキーを押すと、「/tmp」配下にあるファイル名が補完され、TABキーを押すごとに対象ファイルが切り替わります。本機能自体は「wildmenu」の設定に関わらず利用可能です。ただ、デフォルトの状態では常に1つのファイル名が表示されているだけなので、自分が本当に開きたいファイルがどの段階で対象となるかが予測できません。勢い余って対象ファイルを通り過ぎてしまう可能性もあります。
「set wildmenu」によって、コマンド入力エリアの上の情報表示部分(「ステータスライン」と言うらしいですが)にファイル名のリストが表示されるようになります。あくまでステータスラインに収まる範囲での表示で、ファイル数が多ければファイルリスト自体も切り替わりますが、それでもいくつかのファイル名をまとめて確認できるだけで選択しやすさが変わります。
無効にしたい場合は「set nowildmenu」と設定します。

mapleader

「let mapleader = “\<space>”」ですが、この書式自体は実はVim Scriptにおける変数の設定であり特殊なものではありません。重要なのは「mapleader」と言う変数であって、これはVimのマッピングにおけるLeaderの文字を指定するものです。
これだけでは「何のこっちゃ?」ですね。
と言うことで、マッピングに関しても整理しておきたいと思います。

マッピング

マッピングはVimにおける既存キー操作を組み合わせに対して独自のキー操作を対応づける(マッピングする)機能です。
具体例を見てみましょう。
例えば、1行1件でファイルパスが列記されているされているファイルがあったとします。

/tmp/sample1
/tmp/sample2
...

上記ファイルにおいて、各行で実行すると同行に記載されたファイルを同Vimの画面分割結果として開くような操作を考えたとします。
この操作は以下のように定義できます。

nnoremap <space>fo ^vE"fy:vs <C-r>f<CR>

上記は以下の構造になっています。

nnoremap 独自キー操作 既存キー操作

例示した既存キー操作は「当該行の先頭に移動し(^)、ビジュアルモードに切り替えて(v)ファイルパスの末尾までカーソルを移動し(E)、選択範囲をレジスタfに記録し(”fy)、コマンドモードに切り替えて(:)、Vimの画面分割を(vs)レジスタfに記録したファイルパスを指定しながら実行(<C-r>f)、結果として新規画面で指定したファイルが編集可能となる(最後の<CR>はvsコマンドの入力終了)」と言う、なかなかに面倒な手順を示したものです。これを「スペース」「f」「o」の3つのキーを連打する操作として定義しているのが先の定義の意味です。

元操作を各行ごとに実行していたら程なく指に相応のダメージを受けることになると思いますが、「<space>fo」程度であればかなり負担が少なくなるかと思います。

再帰/非再帰的マッピング

なお、そもそもマッピング方法には大きく「map」系と「noremap」系があります。「map」系は再帰的で「noremap」は非再帰的です。

「再帰的」の意味は以下になります。
例えば、ノーマルモードで「j」を押せば一行下に移動しますが、以下のようにマッピングしたとします。

map k j

この結果、「k」を押した時に「j」を押した時と同様に一行下に移動できるようになります(ただし「k」の本来の動きである一行上への移動ができなくなるので、実際にこのようなことはしませんが)。

上記状態で改めて以下のようにマッピングしたとします。

map h k

この結果、「h」を押しても「k」と同じ動きをするようになりますが、「k」は「j」と同じ動きになるようマッピングされているため、「h」を押すことは「j」と同じく「一行下に移動」の意味になります。
これが再帰的なマッピングです。

では、2番目の操作を「非再帰的」に行ってみましょう。

map k j
noremap h k

今度は「h」を押すと一行上に移動するようになります。これは本来の「k」の動きであり、その前のマッピングの影響を受けていません。
これが非再帰的なマッピングです。

つまり再帰的マッピングでは元操作が他の操作をマッピングしたものであれば、元操作のさらに元操作にマッピングするようにと「再帰的」に解釈が続いていきます。一方で非再帰的マッピングでは元操作自体をVimオリジナルの操作と解釈して操作内容を決定します。

一般的には非再帰的マッピングを使用した方が無難かと思います。マッピングが増えてきた時に再帰的マッピングでは因果関係の整合性が取れなくなる危険性が大いにあります。なお、例外的にプラグインの操作を材料にマッピングする場合は再帰的マッピングを使った方が良いようです。プラグインの操作自体がマッピングで定義されている可能性があるためと言うことで理にかなっています。

モード特化のマッピング

上記説明において、さりげなく「nnoremap」と「noremap」を使用しました。これはどちらかがタイポと言う訳ではなく、両方が正しいコマンドで、意味が若干異なります。

まず「noremap」は前述の非再帰的マッピング全般を意味するものです。
一方で「nnoremap」はノーマルモードに限定して有効となる非再帰的マッピングの意味です。最初の一文字「n」は「normal」を意味する訳です。当然ながら他のモード向けの非再帰的マッピングの記述方法もありますし、再帰的マッピングにおいても同様にモード特化のマッピングができます。

もともとマッピングを考える際には特定のモードを想定していることがほとんどで、複数のモードで同じマッピングが使えるようにすることはレア(皆無?)かと思います。逆にモードを意識せずにマッピングを行った場合、他のモードで予期せぬ動きをしてしまうかもしれません。よって、マッピングを行う際にはモード特化のマッピングを行うべきかと思います。

Leader

マッピングがある程度整理できたところで、元ネタである「Leader」に話を戻したいと思います。

前述のようにマッピングを行う際に気をつけるべき点として、新規に定義する操作が既存操作を疎外しないようにすると言うことが上げられます。
先に例示したように「k」を押したら「j」を押した場合と同じ操作をするようにマッピングしてしまうと、「k」本来の機能である一行上への移動ができなくなります。
よって、新規キー操作は既存操作と重複しないような工夫が必要になります。長いキー操作を採用すれば重複のリスクは低くなりますが、操作の手間が増えます。できるだけ少ない文字数で既存操作と重複しないように考えると、既存操作で未使用もしくは重要でないキーを含む文字列にすることが思いつきますし、そのようなキーはマッピング全般で共通的に使いたくなります。このような目的で使用するキーが「Leader」です。

Leaderの具体的な使用方法を以下に示します。

nnoremap <Leader>k j

「<Leader>」と言う文字列の部分がLeaderに該当するキーとして解釈されます。Leaderのデフォルト値はバックスラッシュです。よって上記例では「\k」と押すと「j」と同じ動きをすることになります。Vimの操作を全て把握できている訳ではないので正確なことは分かりませんが、バックスラッシュを使うことはレア(もしくは皆無)なのでしょう。

と言うことで、様々なマッピングにおいて「<Leader>」+適当な文字(文字列)で新規操作を定義すれば安全かつ簡易な操作を定義できることは分かりました。
ただ、Leaderとしてバックスラッシュが本当に最適なのか?と言うことを誰かが考えたんでしょう。Leaderも変更できるようになっており、そのLeaderを指定するための変数が「mapleader」です。

さて、Leaderを独自に決められるとなると、改めてLeaderとして何が最適なのかを考えたくなる訳ですが、どなたか賢い方が思いついたようです。
「スペースなんじゃね?」
一説によるとカンマも人気だと言うことですが、キーが大きく押しやすいことと、ノーマルモードでの操作において押すことがほぼないと言う理由がスペース推しの根拠だそうで、スペース最強説に私も一票です(逆になんでカンマが人気なのかが不明です)。

以上、長い紆余曲折を経ましたが、やっと「let mapleader = “\<space>”」に戻ってきました。
これが、Leaderとしてスペースキーを採用すると言う設定なんですね。

なお、当然ながらLeaderを設定しただけでは意味がなく、この後にマッピングの定義を行い、そこでLeaderを使って初めて有効になるのですが、今回は.vimrcの基本形の紹介と言うことで上記Leaderの設定部分までの説明にしておきます。

今後は

今回は.vimrcの初級編的内容でしたが、特にマッピング辺りの活用は作業効率に大きく関係しそうな予感です。
実は過去にもこの辺を充実させようと思ったことはあるのですが、当時は実益優先で理屈を把握せずネット情報のつまみ食いでマッピングを行っていた結果、なんとなくグチャグチャっとなってフェードアウトしてしまっていました。今後は再度真面目に取り組んでみたいと思っています。

また途中で軽く触れましたが、Vimの画面分割も軽く使ってみて、なかなか使えそうな感触を持っています。この辺もどこかで紹介(整理・記録)できればと思います。