Elispファイルをタブ/スペース混在インデントからスペースインデントに修正する方法

インデントの話

そもそもEmacsはタブ/スペース混在インデントが初期設定です。

そのためEmacs本体のソースなどはすべて混在インデントでインデントされています。 しかし「空白8つよりタブ1つにしてファイル容量を削減しなくては」という時代は明らかに終わったので、スペースでインデントしましょう。して下さい(泣)。

Emacs lisp style guideにもスペースでインデントすることが太字で記載されていますし、 white-space-mode での見た目も良いです。

(ということでほとんどのユーザーはグローバルで indent-tabs-modenil に設定しています。 そのため本体のソースはタブ/スペース混在インデントの部分とスペースインデントの部分が混在しています。カオス。)

これからパッケージを作り始めるときはさておき、歴史のあるソースで修正するのは多少の痛みを伴います。 しかし、Blameで一回変なコミットを見てしまうことを甘んじて受け入れてでもスペースでインデントして欲しいのです。

そもそもgit loggit blameには空白の変更を無視するオプション( -w )があるので履歴の面でもデメリットは無視できます。 Magitのblameでは -w はデフォルトで指定されていますし、GitHubのdiffでも無視することができます。

メリットとデメリットは以上です。

インデントの変更

「よし、スペースインデントにしてやるぜ。」とタブ/スペース混在インデントから考えを変えて下さった方。ありがとうございます。

しかし、すでに成長しきっているプロジェクトで数十のファイルがあるんだけど。。というときには次のスニペットを使って頂ければと思います。

  1. diredでElispだけをフィルタリング(dired-filter)して、

  2. 最初のファイルにポインタを合わせ、

  3. 次のスニペットを M-: から実行するだけです。

    (while (dired-get-filename nil 'no-error)
      (let ((file (dired-get-filename))
            (indent-tabs-mode nil))
        (with-temp-file file
          (insert-file-contents file)
    
          (emacs-lisp-mode)
          (delete-trailing-whitespace)
          (untabify (point-min) (point-max))
          (indent-region (point-min) (point-max)))
        (dired-next-line 1)))
    

多少時間がかかるかもしれませんが、diredでリストされているファイルの末尾まで処理を実行してくれます。

今後の悲劇を防止する

タブ/スペース混在インデントでソースが管理されていたということは、レポジトリオーナーやコントリビュータによって、また混在インデントによるコミットが行われる可能性が高いです。

あるディレクトリ以下で特定の設定を強制することができます。 .dir-locals.el です。

以下の .dir-locals.el をレポジトリルートに置いて今後の悲劇を防止できます。この方法はmagitのレポジトリでも採用されています。

((emacs-lisp-mode
  (indent-tabs-mode . nil))

.dir-locals.el をレポジトリルートに置きたくないんだけど。という場合には以下を各Elispファイルの末尾付近に置いておけば大丈夫です。

;; Local Variables:
;; indent-tabs-mode: nil
;; End:

なお、いろいろなプロジェクトを見て慣習を探ったのですが、 provideends here の間に置くことが多いようです。

(provide '{{pkg-name}})

;; Local Variables:
;; indent-tabs-mode: nil
;; End:

;;; {{pkg-name}}.el ends here

まとめ

タブ/スペース混在インデントは役目を終えました。

Emacsは後方互換性を重視しているためデフォルト値が変更される望みは薄いですが、コミュニティとしてはスペースインデントを使うということでなにとぞ。。