BLOG

Emacsのインデント設定

kuroda

社内では仕事柄、デモアプリを作るためにテキストエディタでhtmlやcss、jsファイルを書く事が多々あります。
お客様からさまざまなプロジェクトのファイルをお預かりして、デザイナーさんが作った画面イメージを実際のウェブページとして仕上げて、Javascriptで操作できるようにしたりするわけです。

この時の問題の1つがインデントです。
あるプロジェクトではスペース3個ずつのインデントをしていたと思えば、別のプロジェクトではタブ1個ずつのインデントになっていたりしますし、同じプロジェクトでもhtmlはスペースインデントだけどjsはタブインデントだったりして、その組み合わせがまたプロジェクトごとに違ったりするわけです。

個人的な好みでテキストエディタにはEmacsを使っているのですが、最近になってようやく「プロジェクトごとにインデントを切り替える」ということを簡単にする設定が整理できたので、今回は他にいくつかの細かいTIPSも交えて、これを紹介したいと思います。

対象はとりあえずWindowsのEmacs24.3( こちら からダウンロードできます)とUbuntu 14.04のemacs24としますが、多分、他のLinuxディストリビューションやOSXでも同じ設定で動作すると思います。
Windows版については、環境変数で適当にホームディレクトリを設定しているとします。

分割した設定ファイルのロード

~/.emacsでは最初に、基本的な設定を記述した~/local/conf/emacs.elをロードしています。

(load "~/local/conf/emacs")

読み込んでいる~/local/conf/emacs.elには滅多に変更しない変更を書いており、リポジトリで管理しています。
逆に ~/.emacs の残りの部分では、あとで説明しますがその時々で頻繁に変わる設定を書いているため、リポジトリ管理しません。

~/local/conf/emacs.elは以下のような内容になっています。

;; Load settings
(add-to-list 'load-path "~/local/common")
(mapcar
 (lambda (path)
   (add-to-list 'load-path path))
 (directory-files "~/opt/" t "^[^\.]"))

(load "conf/emacs/backup")
(load "conf/emacs/global")
(load "tabbar")
(load "conf/emacs/tabbar")
(load "conf/emacs/color-theme")
(load "conf/emacs/windows")

(load "conf/emacs/lisp")
(load "conf/emacs/html")
(load "conf/emacs/scss")
(load "conf/emacs/js")
(load "conf/emacs/ruby")

2行目のadd-to-listですが、手元の環境では大まかなカテゴリーごとに分割した設定ファイルを ~/local/common/conf/emacs/XXX.el として配置しており、これらのファイルを(load "conf/emacs/XXX")のようにlaodできるようにしています。
直接~/local/common/conf/emacsまでをadd-to-listしていないのは、設定ではなく同名のライブラリのelファイルと名前が被っている場合があるためで、設定ファイルの方にはconf/emacsをつけることで区別するようにしています。
実際、上記の例ではライブラリのtabbarloadと、それ用に自分で書いた設定ファイルのconf/emacs/tabbarloadが混在しています。

3行目からのmapcarでは、~/opt/XXXとか~/opt/YYYなどの、~/opt/の下に配置している全サブディレクトリをload-pathadd-to-listしています。
このディレクトリには、emacsに標準添付されていないライブラリをそれぞれ専用のサブディレクトリを作って入れており(実際にはリポジトリからのチェックアウトやcloneです)、それらの~/opt/XXX/foo.el等のライブラリファイルを(load "foo")としてロードできるようにしています。
また、ファイル1個だけをダウンロードしてきたようなライブラリは同じ方法でloadできるように、~/opt/misc/の下に入れています。
手元の環境では以下のようになっています。

opt/
├── misc
│   ├── feature-mode.el
│   ├── rhtml-minor-mode.el
│   ├── tabbar.el
│   └── two-mode-mode.el
└── scss-mode
    ├── .git
    ├── .gitignore
    ├── README.org
    └── scss-mode.el

8行目のconf/emacs/backupからconf/emacs/windowsまでは、バックアップファイルの作り方の設定やWindwos環境用のフォント設定などが入っていますが今回は省略します。

インデント設定関数の定義

16行目でlaodしているconf/emacs/htmlが、htmlファイルに対するインデントの設定になります。
その前の15行目でもconf/emacs/lispでelispファイルに対するインデントの設定を書いているのですが、こちらは単に「インデントにはタブを使わない」としか書いていないのでやはり省略します(いつかの日か、elispを編集するお仕事を引き受けるときが来たら、このファイルにも改良が必要になるでしょう)。

さて、conf/emacs/html.elはhtmlファイルに対するインデント設定で、以下のようになっています。

(defun setting-html-tab ()
  (interactive)

  (let* ((tab-width 3)
        (hook `(lambda ()
                 (custom-set-variables
                  '(tab-width ,tab-width)
                  '(indent-tabs-mode t)
                  '(sgml-basic-offset ,tab-width)))))

    (remove-hook 'html-mode-hook hook)
    (add-hook 'html-mode-hook hook t)))

(defun setting-html-space (width &optional tab-width)
  (interactive "nWidth:")

  (let* ((tab-width (if tab-width tab-width 8))
        (hook `(lambda ()
                 (custom-set-variables
                  '(tab-width ,tab-width)
                  '(indent-tabs-mode nil)
                  '(sgml-basic-offset ,width)))))

    (remove-hook 'html-mode-hook hook)
    (add-hook 'html-mode-hook hook t)))

このファイルでは2つの関数setting-html-tabsetting-html-spaceを定義しています。

setting-html-tabはインデントをタブで行う設定を行う関数で、M-xでも実行できるようにinteractiveにしています。

変数hooklambdaを定義するときに、バッククオート(`)でエスケープし、tab-width変数を参照するときにカンマ(,)を付けることで値を展開するようにしています。
こうしないと、hookにはtab-widthが変数シンボルのままで与えられるため、フック実行時にはtab-width変数に同じtab-width変数を代入する(結果として何もしない)という事になってしまうからです。

また、変数名を変えて(しかし値展開せずに)hookを定義した場合には、

Error setting sgml-basic-offset: (void-variable tmp-width)

といったエラーになってしまいます。
これは、例えばRubyやJavascriptだと、いわゆるブロックとかコールバック関数はローカル変数も含めたコンテキストで実行できるのですが、Emacsのlispでは定義した時のローカル変数がコンテキストに含まれないためです。
Emacs-24で新しく追加されたlexical-bindingを使うとそのように実行させる事も可能なのですが、Emacs-23な環境を使う場合があるため、このような形になっています。

hookを定義したletの中では、一度remove-hookをしてから、appendフラグにtを指定してadd-hookします。
これは、作業の途中で一時的にsetting-html-space関数などでスペースインデント用のフックをadd-hookしてから再び戻すときのためで、remove-hookしなければタブインデント用のフックがフック列の最後に登録し直されない(add-hookは登録済みのフックが再登録された場合は何もしない)ためです。

2つ目の関数setting-html-spaceでは、インデントをスペースで行う設定を行う関数です。

(setting-html-space 3 6)

という使い方をして、1つ目の引数でインデント1段分のスペースの数を、2つ目の引数でタブ幅を指定します。
タブ幅は省略可能で、その場合は8が設定されます。

1つのファイル中のインデントは大抵の場合タブかスペースで統一されていますが、たまにタブとスペースを混在させたインデントが使われる場合があり、さらに想定しているタブ幅が8以外という事もあったりします。
そのようなファイルに手を加える場合に備えて、スペースインデントでもタブ幅だけは指定できるようにしています。

ここで紹介したのはhtml用の設定を行う関数の定義ですが、ほかにもcss(scss-modeで代用しています)やJavascript用の設定を行う関数も一通り用意しておきます。

インデント設定関数の呼び出し

実際のインデント設定は~/.emacsでそれらの関数を適当な引数で実行することで行います。

先ほど先頭しか書かなかった~/.emacsは実際にはこんな風になっています。

(load "~/local/conf/emacs")

(defun settings-foo ()
  (interactive)
  (setting-html-space 2 4)
  (setting-css-space 4 4)
  (setting-js-space 4 4))

(defun settings-bar ()
  (interactive)
  (setting-html-space 2)
  (setting-css-tab)
  (setting-js-tab))
  
;; (setting-ruby-tab)
;; (setting-scss-tab)
;; (setting-html-tab)
(settings-bar)

前半で定義している2つの関数は、特定のプロジェクト用にインデント設定関数の組み合わせをまとめたものです。
interactiveにしているので、途中で一時的に以前のプロジェクトのファイルを編集する必要があるときにも、M-xで関数を呼び出すだけで反映されます。ただし、すでに開いているファイルに対して新しいインデント設定を反映されるためには、M-xでそのファイル用のモードに切り替えなおす必要があります(htmlを編集中にインデントだけ別の方法に変えたときには、(すでにhtml-modeになっている状態で改めて) M-x html-modeと実行します)。

これで、「タブ・スペース混じりのインデントで書け」という指示がない限りは大抵のインデントに一発で対応できます。