Vim のバッファと Quickfix という機能に調べたことのメモ。
記事の中身は Vim の基本的な操作の話なので、 「Vim 初心者がなんか躓いたことの記録」くらいのつもりで読んで貰えればと思う。
背景
最近時間が余っていることもあって、 AtCoder を触っている。 プログラミングからだいぶ離れていたこともありアルゴリズム以前の部分で引っかかることも多く、正直 ABC の C 問題を正答できるかすら怪しいというレベルではあるが、 学生時代のプログラミングの講義の気分を思い出しつつそれなりに楽しんでいる。
とまぁ競プロに関する話は一旦置いといて、コードを書く環境はエディタに Neovim 、言語は Python を使っている。
で、テストにはこんな感じの Makefile を書いて Neovim から :make test1
する感じでテストしつつコードを書いている。
test1:
python answer.py < input/test1
ただ、これでエラーが出た場合に空のファイルが開いているような感じになるのだが、それがどういう状況なのかがよく分からず、
編集中のファイルへの戻り方も分からなかったのでとりあえず :q!
で Vim ごと閉じてファイルを開き直すという微妙なことをやっていた。
実際の画面としてはこんな感じ
で、これがどういう状況なのかも分からないのでググるにしてもキーワードの検討が付かなかったのだが、 ChatGPT と少し問答したところ Vim に Quickfix という機能があってそれによって別のバッファに切り替わっているっぽいというヒントを得たので調べた。
というのが今回の背景。
Vim におけるバッファについて
まず Vim のバッファに関する基本的な概念、操作について簡単にまとめて、今回の場合どうすれば良かったかについて触れる。
バッファの基本的な概念
Vim でファイルを操作する場合、
- ファイルの中身を一旦メモリ上に展開して
- そのメモリ上の領域に対して操作を行い
:w
等で保存操作を行った時にファイル実体への反映を行う
という感じのことをやっている。 1
そのファイルの中身を展開したメモリ上の領域1つ1つを Vim ではバッファと呼んでいる。 またファイルに紐付かない状態でバッファが作られることもある。
具体的な例として
- シェルから
vim
とかでファイルを指定せず Vim を起動した場合、ファイルに紐付いてない空のバッファが開いた状態で Vim が起動している。 - シェルから
vim hoge.txt fuga.txt
とかで複数のファイルを指定して起動した場合、複数のファイルそれぞれを別のバッファに開いた状態で Vim が起動している。 - Vim で、あるファイルを編集している最中に
:e .
とかで別のファイルを開いた場合、編集中だったバッファはそのままで新しいバッファに別のファイルの中身を展開して表示している。
みたいな感じ。
なので、バッファを意識してるしてないに関わらず Vim を使っている間はあらゆる場面でバッファに対して操作を行っている。
バッファ操作コマンドのまとめ
バッファ周りの基本的な操作コマンドについてまとめる。
詳しいことはヘルプに書いているので各コマンドのヘルプを見れば良いのだが、 いきなり全部は覚えられなそうなのとあまり頻繁に help 開いてると面倒になって使わなくなりそうな気がしたので、 ひとまず覚えられそうな範囲かつ基本的な操作に困らなさそうな程度に書き下しておく。
コマンド | 省略形 | 内容 |
---|---|---|
:ls | - | バッファリストを表示する |
:buffer [N] | :b [N] | N番のバッファに切り替え(N省略時はカレントバッファ、つまり何もしない) |
:bnext [N] | :bn [N] | N個後のバッファに切り替え(省略時のNは1) |
:bprevious [N] | :bp [N] | N個前のバッファに切り替え(省略時のNは1) |
:bfirst | :bf | 1番目のバッファに切り替え |
:blast | :bl | 最後のバッファに切り替え |
:bdelete [N] | :bd [N] | N番のバッファをバッファリストから削除する(N省略時はカレントバッファ)、バッファ自体は残る |
:bwipeout [N1 N2 …] | :bw [N1 N2 …] | N1, N2, … のバッファを削除する(N省略時はカレントバッファ) |
それぞれのコマンドには別の指定方法があったりするが、今回は省略(各コマンドのヘルプ参照)
:ls
また、 :ls
の見方について少し補足。
Vim のヘルプからの転記にはなるが、実際の出力としてはこんな感じになる。
1 #h "/test/text" line 1
2u "asdf" line 0
3 %a + "version.c" line 1
これの見方もヘルプに詳細を書いているが、
- 一番左の数字がバッファの番号
- その右の謎の文字列がバッファの状態を示すフラグ群
- 更に右のダブルクォートで囲まれた部分がバッファの名前
- 一番右がバッファで現在フォーカスしている行
という感じになっている。
フラグについて、よく見かけそうなものをピックアップすると以下あたり。
flag | 意味 |
---|---|
u | :bd で閉じたバッファ( :ls! すると閉じたバッファも表示する) |
# | 代替バッファ(大抵の場合直前に開いていたバッファ) |
% | 現在フォーカスしているウィンドウに表示しているバッファ |
a | どこかのウィンドウに表示されているバッファ |
h | どのウィンドウにも表示されていないバッファ |
- | modifiable が off に設定されているバッファ (プラグインとかが使うバッファでよく見るらしい) 2 |
= | ReadOnly なバッファ(変更権限の無いファイルを開いた時とか、 vim -R で起動した時等) 2 |
+ | ファイルに反映されてない変更を含むバッファ |
冒頭のケース
ということで、ここまでを理解した上で冒頭の話に戻ると、Vim で :make test1
してエラーが発生した状態から、 :ls
でバッファリストを確認すると以下の様な感じになる。
なので単純に make: *** [Makefile
という名前のバッファを :bd
や :bw
で消したり、 :bn
や :bp
等で元のファイルを開いているバッファに戻ればよかった、
ということが分かる。
Quickfix 機能について
ここからはそもそも何故別のバッファが開いて、それに切り替わったのかという部分になる。
Quickfix の概要
まずは Quickfix という機能の概要から。
例によって詳細は Vim のドキュメント参照ではあるが、 自分の事前理解が足りてないのか分かりにくかったので自分の言葉で書き下してみる。
この機能はもともと編集、コンパイル、編集という開発サイクルの中で、 「コンパイル時に発生したエラーを Vim の画面から素早く確認して修正作業に反映する」という部分のサポートを目的とした機能になる。 とある C コンパイラにあった機能を参考にして作られたものらしい。
もう少し具体的には、編集と編集の間に発生する
- ファイルの保存
- コンパイル処理の実行
- エラーメッセージの検出とユーザへの表示
等の処理をある程度自動化するための機能で、この一連の処理は Vim の :make
コマンドにより呼び出すことができる。
ただ、Quickfix はこういった目的で作られた機能ではあるが、 単純に Quickfix と言うと特に3つ目の部分を指していることが多い。 というのも、コンパイル時に発生したエラーメッセージの確認をサポートする部分は「テキストファイル内の位置(行、列)のリストを作成しジャンプする」という形で実現されているのだが、 これは単純な Vim で文字列検索( vimgrep )をする場面でも便利で、そういった目的でもよく使われているためと思われる。
というのが Quickfix という機能(群?)のざっくりした概要になる。
:make について
この Quickfix の一連の処理を呼び出す :make
コマンドについて。
これは Vim から外部コマンドである make
コマンド( Makefile を使うアレ)を直接実行しているわけでは無くて、 Vim の :make
コマンドになる。
(混乱しそうなので Makefile を使うアレを make
、 Vim の make コマンドを :make
と統一する)
:make
が行う処理の内容は Vim のドキュメントの中の make の項に書いてある。
そのまま引用するとこんな感じ。
- QuickFixCmdPre に関連付けられた自動コマンドが全て実行される
- オプション ‘autowrite’ がonならば変更のあるバッファは保存される。
- ‘makeef’ からエラーファイルの名前が生成される。 ‘makeef’ が “##” を含まずかつ既に名前が存在する場合 それは削除される。
- オプション ‘makeprg’ で与えられたプログラム (省略時 “make”) が [argument]をオプションにして実行され、出 力がerrorfileに保存される (Unixではそれも画面にecho される)。
- ’errorformat’ を使ってerrorfileが読みこまれる。
- QuickFixCmdPost に関連付けられた自動コマンドが全 て実行される。後述のサンプルを参照。
- [!]が与えられていないときは最初のエラーに移動する。
- エラーファイルが削除される。
- :cnextや:cprevious などのコマンドでエラー間を移 動できる。上を参照。
今回自分は冒頭で書いたとおり python answer.py < input/test1
という処理を実行するために Makefile を書いて、
Vim から :make test1
と実行していたのだが、これは前述したとおり make
ではなく :make
である。
そのため上記の諸々の処理が実行されていたのだが、
上記引用の 4 に書いている通り :make
実行時の makeprg を省略する形になっていたことで make
が実行されるので、
結果的に意図したコマンド make test1
が実行されているように見えていた、ということになる。 3
冒頭のケース
と、ここまでで分かった通り、意図せず Quickfix 機能を使って :make
経由で make
を実行していたことが原因なので、
ちゃんと Vim から :!make test1
と外部コマンドとして make
を実行することで、そもそもバッファが切り替わったりすることもなくなった。
:make
がエラーメッセージを拾ってバッファを切り替えるまでの仕組みを追うという意味で errorformat 周りを少し調べたりはしたのだが、
理解するまでに結構な時間がかかりそうだったのでこっちは見送った。
もう少し Vim の経験値がたまったら Quickfix 辺りは改めて調べたい。
感想
Vim の基本的な操作(バッファなり外部コマンドなり)をちゃんと理解してれば、 冒頭で書いた様な状況でも全部閉じて開き直すパワープレイをしなくてもちゃんと対応できる。
今まで Vim についてちゃんと使い方を勉強したりはしてなくて、経験上必要になった操作を組み合わせた要するに雰囲気で使ってたのだが、 今回ドキュメントを色々眺めていて操作方法のチュートリアルのようなものも見つけたので、一通りやってみても良いかもなと思った。