Vimのタブをスペースに変換する :retab と expandtab の違い
set expandtab を .vimrc に書いたのに、開いたファイルのインデントがタブのままで戸惑ったことがあります。この設定はこれから打つキーにしか効きません。すでに入っているタブを置き換えるには、別のコマンドが要ります。
目次(8項目)
| やること | コマンド |
|---|---|
| これから打つタブをスペースにする | set expandtab |
| すでにあるタブをスペースにする | :retab |
| 行頭のタブだけを変換する | :retab -indentonly |
| スペースをタブに戻す | set noexpandtab + :retab! |
set expandtab は既存のタブを変えない
set expandtab " Tabキーを押したらスペースを入れる
set tabstop=4 " タブ1つを何桁として表示するか
set shiftwidth=4 " >> や自動インデントの1段の幅
expandtab が効くのはTabを押した瞬間だけです。手元のNeovim 0.12.4で、タブでインデントされたファイルを set expandtab の状態で開いて保存し直しても、中身は1バイトも変わりませんでした。設定を入れた覚えがあるのにタブが残っているなら、たいていこれが理由です。
すでにあるタブをスペースにする :retab
:set expandtab tabstop=4
:%retab
:retab は現在の tabstop の幅でタブを展開し、expandtab が有効ならスペースに置き換えます。範囲を書かなければ % と同じでファイル全体が対象です。2行目から3行目だけなら :2,3retab と書きます。
文字列の中のタブまで潰れる
ここが :retab のいちばん危ないところです。行頭のインデントだけを直したいつもりでも、行の途中にあるタブまで書き換えられます。Neovim 0.12.4で次のファイルを試しました。
def f():
→ print("a→b") " → がタブ
→ if x:
→ → return "c→d"
:%retab をかけると、インデントに加えて "a→b" の中のタブまでスペースになりました。文字列やコメントにタブを含むファイルでは、見た目が変わらないまま中身だけが書き換わります。差分を見るまで気づけません。
:%retab -indentonly
-indentonly を付けると、行頭の空白だけが対象になります。同じファイルで試すと、インデントは4スペースになり、"a→b" のタブはタブのまま残りました。Vim 9.1.1752とNeovim 0.12.4の両方で同じ結果です。
:retab -indentonly を使います。素の :retab は文字列の中まで書き換えます。
tabstop は自分の好みではなくファイルに合わせる
:retab がスペースを何個入れるかは、そのとき設定されている tabstop で決まります。タブ1つが必ず4スペースになるわけではありません。
行頭にタブが1つと2つ入ったファイルを、設定だけ変えて変換しました。
| 設定 | 変換後のインデント |
|---|---|
tabstop=4 | 4スペース / 8スペース |
tabstop=8 | 8スペース / 16スペース |
つまり、8桁タブで書かれたファイルを tabstop=4 のまま変換すると、インデントが半分の深さになります。自分の好みの幅ではなく、そのファイルがもともと何桁で書かれていたかに合わせてから実行してください。書いた人の設定が分からないときは、変換せずタブのまま扱うほうが安全です。
スペースをタブに戻す
:set noexpandtab tabstop=4
:%retab!
逆向きは ! を付けます。expandtab を切ってから :retab! を実行すると、tabstop の幅ぶんのスペースがタブに戻ります。4スペースのインデントで書かれたファイルで試したところ、1段目がタブ1つ、2段目がタブ2つになりました。
! なしの :retab ではスペースはタブに戻りません。逆変換のときだけ必要です。
変換できたかを目で確かめる
:set list
:set listchars=tab:>-,space:.
タブとスペースは画面上で見分けが付かないので、変換したあとは記号で表示させて確認します。設定の詳しい書き方はタブ・改行・行末スペースを可視化するにまとめています。インデントの深さ自体を変えたいだけなら、インデントを増減する方法のほうが目的に合います。
共同で触っているリポジトリで、インデントをそろえるつもりで :%retab をかけたことがあります。差分を見たら、テストデータのタブ区切り文字列まで置き換わっていて、そこだけ元に戻す羽目になりました。それ以来インデントだけを触りたいときは -indentonly を必ず付けています。tabstop を確かめずに実行して、インデントが浅くなった差分を出してしまったこともあるので、いまは変換の前に :set tabstop? で今の値を見るようにしています。
まとめ
set expandtab はこれから打つタブ、:retab はすでにあるタブを扱います。インデントだけを直したいなら -indentonly を付け、実行前に tabstop がそのファイルの幅に合っているかを確かめてください。この2つを押さえておけば、意図しない差分を出さずに変換できます。