Vimのカーソル移動まとめ 単語・行・画面・ファイル単位
Vimの移動キーは、覚えたそばから「どれを使えばいいのか」が分からなくなります。jを10回押すのと10jとGでは、どれも下に行けるのに手数も戻り方も違います。ここでは移動コマンドを距離の単位ごとに並べ直して、どの場面でどれを選ぶかを整理します。
| 動かしたい距離 | 使うキー |
|---|---|
| 1文字 | h j k l |
| 単語 | w b e |
| 行の中 | 0 ^ $ f t |
| 画面 | Ctrl-f Ctrl-b H M L |
| ファイル全体 | gg G {数字}G / |
この記事に出てくる桁数と行数は、120桁39行のターミナルでvim -n -Nu NONE -i NONE(Vim 9.1)とnvim -n -u NONE -i NONE(NVIM 0.12.4)を起動し、このときウインドウの高さはVimが38行、Neovimが37行になります。col(".")やline("w0")を表示させて読み取った値です。設定ファイルを読まない状態での測定なので、手元のvimrcでオプションを変えていると数字はずれます。
1文字ずつ動かす
h " 左
j " 下
k " 上
l " 右
ホームポジションから手を離さずに済むのが利点です。矢印キーとの使い分けや、矢印キーを封じる設定をすすめない理由はhjklと矢印キー、結局どっちを使うべきかにまとめています。
数字を前に置くと回数になります。10jで10行下です。何行下かを目で数えるのが面倒なら、行番号を表示する設定で相対表示にしておくと、その数字がそのまま画面に出ます。
jが数えているのはファイルの行であって、画面に見えている行ではありません。400文字の1行を120桁の画面で開くと画面上では4行に折り返されますが、行頭でjを押すと折り返しをまたいで次の行の1桁目に移りました。同じ位置でgjを押すと、同じ行のまま121桁目、つまり折り返しの2行目の先頭に止まります。NVIM 0.12.4でも同じ121桁でした。
使い分けの基準は、そのバッファで折り返しが起きるかどうかです。'wrap'が有効なまま長い文章を書くファイルではgj gkのほうが目で見た動きと一致します。コードのように1行が短いファイルなら折り返しはほとんど起きないので、行番号とそのまま対応するj kのほうが回数を数えやすくなります。
単語単位で動かす
w " 次の単語の先頭へ
b " 前の単語の先頭へ
e " 単語の末尾へ
foo-bar.baz qux endという行の1桁目からwを押し続けたときに止まった桁を順に読むと、4桁目の-、5桁目のb、8桁目の.、9桁目のbと刻んでいき、5回目でようやくquxの先頭の13桁目に着きました。同じ行の1桁目からWを押すと1回で13桁目です。末尾側も同じで、foo-bar.bazの末尾の11桁目に着くのはeなら5回、Eなら1回でした。
この差を作っているのは'iskeyword'です。既定値はVim 9.1でもNVIM 0.12.4でも@,48-57,_,192-255で、英字と数字とアンダースコアしか単語の文字として登録されていません。wはキーワード文字の連なりと記号の連なりを別々の単語と見るので、-と.のところで必ず区切られます。Wはこの設定を参照せず、空白だけを区切りにします。同じ設定が検索側にどう効くかはカーソル下の単語を検索する方法で実測しています。
選び方は、その行が何でできているかで決まります。アンダースコアはキーワード文字なのでuser_nameはw1回で飛び越えますが、ケバブケースのクラス名やドット区切りのパス、URLは記号の数だけ刻まれます。刻まれるのが煩わしい場所でだけWに持ち替えるのが実際的です。小文字と大文字を含めた8種類の止まり位置の違いは単語移動8種類の違いで扱っています。
行の中を動かす
0 " 行の先頭(インデントの空白も含む)
^ " 最初の非空白文字
$ " 行の末尾
g_ " 最後の非空白文字
4つの行き先は「行の端」と「中身の端」に分かれます。let x = 1;を4桁のインデントで書き、行末に半角空白を3つ残した17文字の行で測ると、次のようになりました。
| キー | 止まった桁 | そこにある文字 |
|---|---|---|
| 0 | 1 | インデントの空白 |
| ^ | 5 | l |
| $ | 17 | 行末の空白 |
| g_ | 14 | ; |
コードを読み書きしているあいだ、用があるのはたいてい中身の端のほうです。行末に空白が残っているファイルで$を押すと見えない空白の上に止まるので、そこからaで書き足すと空白をはさんだ位置に文字が入ります。g_なら;の上に止まるため、狙った位置に足せます。逆に行末の空白ごと消したいときはd$が正解で、dg_では空白が残ります。インデントの扱いも含めた4つの違いは行頭・行末へ移動するで詳しく比べています。行の中の特定の1文字を狙うならf と t による1文字検索のほうが短い手数で着きます。
画面単位で動かす
Ctrl-f " 1画面ぶん進む
Ctrl-b " 1画面ぶん戻る
Ctrl-d " 半画面ぶん進む
H M L " 画面の最上行 / 中央 / 最下行へ
Ctrl-fとHは見た目が似ていますが、前者は画面のほうを動かし、後者はカーソルを画面内で動かします。この違いとzzのような表示位置の調整は画面スクロールとジャンプ操作にまとめてあります。
Ctrl-dが何行進むかを持っているのは'scroll'です。明示的に設定しなければウインドウの高さの半分が使われ、高さが変わると自動で追従します。1行目から1回押したときの着地点を、環境を変えて測ると次のようになりました。
| 環境 | ウインドウの高さ | 'scroll' | 着地した行 |
|---|---|---|---|
| Vim 9.1 | 38 | 19 | 20 |
| NVIM 0.12.4 | 37 | 18 | 19 |
Vim 9.1(:newで分割後) | 18 | 9 | 10 |
同じ端末に同じファイルを開いてVimとNeovimで1行ずれるのは、ウインドウの高さが1行違うからです。歩幅をそろえたいなら:set scroll=5のように固定するか、回数を前に置きます。1行目で10Ctrl-dを押すと11行目に着き、そのとき'scroll'自体が10に書き換わりました。以降は素のCtrl-dも10行ずつ進みます。1回だけのつもりで付けた回数が残るので、半画面に戻したいときは:set scroll=0で高さ追従に戻します。
Ctrl-fのほうは'scroll'を見ず、画面の高さで動きます。高さ38のウインドウの1行目で押すと、画面の先頭が37行目になりました。ぴったり38行ぶんではなく、直前に読んでいた2行が次の画面の先頭に残る計算です。文章を上から読み下していくときに文脈が切れないのはこの重なりのおかげで、行数を正確に進めたい場面には向きません。
H M Lはファイルではなく画面の中の位置を指すので、スクロールすれば同じキーでも行き先が変わります。1行目を先頭にした画面ではそれぞれ1行目・19行目・38行目でした。ここに'scrolloff'が絡みます。既定値はVim 9.1でもNVIM 0.12.4でも0ですが、:set scrolloff=5にしてから18行目から55行目を映している画面で押すと、Hは18行目ではなく23行目、Lは55行目ではなく50行目に止まりました。カーソルの上下に5行を確保する設定なので、画面の端5行にはそもそも入れません。
回数を前に置くと、画面の端から何行目かを指定できます。82行目を先頭にした画面で3Hを押すと84行目、末尾が119行目の画面で3Lを押すと117行目でした。'scrolloff'を大きめにしている人がHで画面のいちばん上に行けないと感じるのは、この2つの仕組みが重なっているためです。
ファイル全体を動かす
gg " 先頭行へ
G " 最終行へ
42G " 42行目へ
{ } " 前後の空行へ
エラーメッセージが行番号を出してきたときは42Gが最短です。行番号ではなく中身で探すなら文字列検索(/ と ?)のほうが早く、戻る場所に印を付けておきたいならマーク機能が使えます。
行番号も検索語も持っていないときに効くのが{と}です。ヘルプでは段落単位の移動と説明されていますが、実際に境界として見ているのは空行です。関数を2つ並べ、それぞれの後ろに空行を置いた17行のファイルで1行目から}を押すと、8行目、16行目と止まり、3回目は空行が尽きて最終行の17行目でした。止まったのはどれも空行そのもので、関数の先頭ではありません。境界の判定は見た目より厳密です。
Note that a blank line (only containing white space) is NOT a paragraph boundary.
半角空白だけの行を3行目に置いたファイルで確かめると、1行目からの}は3行目を素通りして、本当に空の6行目に止まりました。コードで}が関数の切れ目にうまく効くのは、関数と関数のあいだに空行を入れる書き方が広く行われているからにすぎません。空行の無いファイルでは一気に末尾まで飛びますし、インデントを消し忘れた空行があるとそこを飛ばします。}が思ったところで止まらないときは、まずその行に文字が残っていないかを疑うと早く片付きます。
戻れる移動と、戻れない移動がある
ここが移動コマンドでいちばん見落とされる点です。Vimは大きく飛んだ移動だけをジャンプリストに記録していて、Ctrl-oで戻れるのは記録された移動だけです。距離が長いかどうかではなく、キーの種類で決まります。
200行のファイルの6行目にカーソルを置き、キーを押したあとに:jumpsの中身を読んで確かめました。
| キー | ジャンプとして記録されるか |
|---|---|
| G { } H L / | される(Ctrl-oで戻れる) |
| j 3j w 3w 0 Ctrl-f | されない |
つまり3jで3行下がっても、Ctrl-fで1画面ぶんスクロールしても、記録は残りません。一方でGや/で飛んだあとはCtrl-oで元の位置に戻れます。「さっきの場所に戻りたい」が効くかどうかは、移動した距離ではなく押したキーで決まります。
測っている途中で、リストが同じ行を二重に持たないことも分かりました。6行目からGで200行目へ飛び、Hで163行目へ、Lで200行目へ戻り、最後に/word100で検索した時点の:jumpsは1・6・163・200の4件で、2回積まれたはずの200行目が1件しか残っていません。同じ行が入るたびに古いほうが消えるので、Ctrl-oを連打しても同じ場所を二度踏まずに済みます。
長いファイルの末尾だけ見て元の場所に帰るつもりが、Ctrl-oを何度押しても数行しか動かず、結局Ctrl-bで画面を目で追いながら戻る羽目になったことがあります。降りるときにCtrl-fを連打していたせいだと気づくまでに、キーマッピングが壊れたのかとvimrcを読み直す時間まで使いました。今でも読みながら少しずつ降りる場面ではスクロールを使いますが、そのときは行番号を頭に入れてから降りる癖がつきました。
移動キーは削除やコピーの範囲にもなる
ここまでのキーの多くは、そのままdやyの後ろに置けます。dwで単語を削除、d$で行末まで削除、y}で次の空行までをコピーです。移動を覚えると編集コマンドの語彙が同時に増えるので、まず移動から手を付けるのが近道になります。組み合わせの考え方はオペレータ+モーションの練習ドリルで扱っています。
ただしCtrl-fのような画面スクロールはモーションではないため、dの後ろには置けません。300行のファイルの50行目でdに続けてCtrl-fを押しても、行数は300のままで'modified'も0のままでした。エラーが出るわけではなく、待ち状態のオペレータごと黙って捨てられます。dを押したつもりが何も起きないときは、後ろに置いたキーがモーションだったかを疑うところから始めます。
一方で、画面基準のHとLは行単位のモーションとして働きます。82行目を先頭にした画面の100行目でdHを押すと、82行目から100行目までの19行が消えて300行のファイルが281行になりました。「画面に見えているここまで」をそのまま範囲にできるので、行番号を数えたり数字を前置きしたりせずに削れます。
まとめ
移動キーは距離の単位で並べると覚えやすい一方、押すキーを選ぶ場面で効くのは別の2つの性質です。1つは位置の基準がファイル側か画面側かで、ggや42Gが行番号を見ているのに対し、HやLは今映っている範囲を見ています。もう1つはジャンプとして記録されるかどうかで、こちらがCtrl-oの効き方を決めます。同じ場所へ行けるキーが複数あるときは、この2つを当てはめると選ぶものが1つに絞れます。