Emacsの設定は ~/.emacs.d/init.el
に記述しますが、複数のマシンを利用することが当然な昨今、 init.el
を複数環境で共有するのはよくあるシチュエーションです。そこで問題になるのは、動作するマシンによってOSやインストールしている外部ライブラリが異なることです。多くの init.el
では、ライブラリの存在を確認してからロードしたり、安全にライブラリをロードする自作マクロを活用しています。
またEmacsは起動時間を短縮するために、ライブラリの遅延ロードが可能です。しかしその設定が複雑になってくると、可読性は低くなってしまいます。
use-package は init.el
の設定を、統一的なわかりやすい記述で書くことができるようになるライブラリです。ユーザは use-package
マクロを利用するだけで、ライブラリのロード、遅延ロード、設定をわかりやすく記述することができます。
この記事では use-package
を利用して、可読性が高く、高速に起動し、環境に非依存な init.el
を書く方法を紹介します。なるべくわかりやすくなるよう、use-package
を使った場合と、使わない場合のコードを併記し、典型的なユースケースなどについても解説します。
use-package
の使い方
requireの置き換え
まずはじめは単純な require
の置き換えです。置き換えの前後を比較してみます。
(require 'xxx)
(use-package xxx)
Beforeの記述では xxx
ライブラリが存在しない場合エラーが発生し、評価がそこで終わってしまいます。 use-package
マクロは、 xxx
ライブラリの有無を自動でチェックします。ライブラリが存在する場合はそれをロードし、存在しない場合は何もしません(エラーも起こりません)。このためユーザはライブラリの有無を意識することなく init.el
を書くことができます。 use-package
マクロはライブラリ名にクォートが必要ないことに注意しましょう。
これが use-package
マクロの一番シンプルな使い方となります。さらにキーワード引数をつけることで、 use-package
マクロに様々な動作を付け加えることができます。
:config
- ライブラリの設定を記述
:config
キーワードはライブラリをロードした後の設定などを記述します。
(when (require 'uniquify nil t)
(setq uniquify-buffer-name-style 'post-forward-angle-brackets)
(setq uniquify-ignore-buffers-re "*[^*]+*"))
(use-package uniquify
:config
(setq uniquify-buffer-name-style 'post-forward-angle-brackets)
(setq uniquify-ignore-buffers-re "*[^*]+*"))
Beforeの (when (require 'uniquify nil t) ...)
は init.el
でよくあるイディオムです。 require
は第3引数が t
の時、ライブラリが存在しない場合にエラーを発生させる代わりに nil
を返します。条件分岐と組み合わせることにより、ライブラリが存在するときだけ後続のライブラリ設定を評価することができます。
use-package
の記述では先ほど同様 uniquify
の有無は自動でチェックされます。存在する場合はライブラリをロードした後に :config
に記述された式1を評価します。当然存在しない場合は何もしません。
:if
- 条件分岐
:if
キーワードはライブラリをロードする条件を記述します。 条件が nil
と評価される場合は use-package
マクロは何もしません。
典型的なユースケースはEmacsが動作しているOSによってライブラリをロードするか切り替える場合です。例えばcygwin-mount.elは、WindowsのEmacsでCygwinのパスを解釈できるようにするライブラリです。これを動作OSがWindowsの場合のみロードするには以下のように記述します。
(when (eq system-type 'windows-nt)
(when (require 'cygwin-mount nil t)
;; cygwin-mountの設定
))
(use-package cygwin-mount
:if (eq system-type 'windows-nt)
:config
;; cygwin-mountの設定
)
Beforeの記述は条件文や入れ子によってやや見難くなっています。 use-package
の場合、キーワードによって条件部分や設定部分が明確に分かれており非常に理解しやすい記述となります。
他にも
- 外部コマンドの有無を
executable-find
で判定(migemo、magitなど) - 端末で動作しているかを
window-system
で判定
などの外的環境によって条件分岐するユースケースがありそうです。
遅延ロード
require
は評価した瞬間にライブラリをロードするため、 init.el
で大量にrequire
を書くとEmacsの起動時間が長くなってしまいます。Emacsには起動時ではなく、必要とされた時にライブラリをロードする autoload という遅延ロードの仕組みがあります。
その使い方はシンプルで以下のように設定します。
(autoload 'xxx-command "xxx")
この設定により xxx-command
という関数を実行しようとした瞬間に、 xxx
ライブラリラリがロードされます。
use-package
マクロで遅延ロードの設定をするには、 :commands
, :bind
, :mode
, :interpreter
, :defer
の5つのキーワードを利用します(これら5つをまとめて遅延キーワードと呼ぶことにします)。遅延キーワードがつけられたライブラリはEmacs起動時にはロードされず、必要なタイミングで遅延ロードされることになります。ここからは遅延キーワードと、それに非常に関連深い :init
キーワードの具体的な使い方を見てみます。
:commands
- autoload
するコマンドを指定
例えば winner 2を遅延ロードする設定を見てみましょう。
(autoload 'winner-undo "winner" "" t)
(autoload 'winner-redo "winner" "" t)
(eval-after-load 'winner
'(progn
;; winnerの設定
))
winner
で利用するコマンドは winner-undo
と winner-redo
があるので、それらを autoload
します。また winner
の設定は、 winner
のロード後に実行されるように eval-after-load
を使います。これで M-x winner-undo
などでコマンドを実行しようとした時に、 winner
が遅延ロードされます。
一方の use-package
を使った記述はこうなります。
(use-package winner
:commands (winner-undo winner-redo)
:config
;; winnerの設定
)
:commands
キーワードには autoload
するコマンド群をリストで指定します3。上述した :config
キーワードを使えば、 winner
をロードした後の設定が記述できます。 autoload
を列挙する必要もなく、 eval-after-load
の記述もなくなっているので、読みやすくなりました。
:bind
- キー割り当てを指定
autoload
したコマンドをキー割り当てすれば、ショートカットキーでコマンドを呼び出そうとした瞬間に遅延ロードされます。
例えば multiple-cursors を遅延ロードしてみましょう。
(autoload 'mc/mark-next-like-this "multiple-cursors")
(autoload 'mc/mark-previous-like-this "multiple-cursors")
(autoload 'mc/mark-all-dwim "multiple-cursors")
(global-set-key (kbd "C->") 'mc/mark-next-like-this)
(global-set-key (kbd "C-<") 'mc/mark-previous-like-this)
(global-set-key (kbd "M-R") 'mc/mark-next-all-dwim)
(eval-after-load 'multiple-cursors
'(progn
;; multiple-cursorsの設定
))
この設定をして C->
キーを押下すると、以下の様な流れで multiple-cursors
が遅延ロードされます。
-
C->
押下 -
mc/mark-next-like-this
実行、する前にmultiple-cursors
ライブラリをロード -
eval-after-load
で書かれた設定を評価 -
mc/mark-next-like-this
実行
autoload
や global-set-key
が繰り返されており、いかにも冗長な記述です。この設定を use-package
で書くとこうなります。
(use-package multiple-cursors
:bind (("C->" . mc/mark-next-like-this)
("C-<" . mc/mark-previous-like-this)
("M-R" . mc/mark-all-dwim))
:config
;; multiple-cursorsの設定
)
:bind
には (割り当てキー . 実行コマンド)
のリストを指定します。ここで指定された実行コマンドは、 use-package
マクロが自動的に autoload
とキー割り当てをしてくれます。
典型的なユースケースはメジャーモードでもマイナーモードでもない、便利コマンドを提供しているようなライブラリです。私の場合 direx
, open-junk-file
, color-moccur
, bm
などのライブラリで使っています。
:mode
- auto-mode-alist
の設定
Emacsには拡張子とメジャーモードの関連付けを設定する auto-mode-alist
という変数があります。例えばhtmlやテンプレートを編集するメジャーモードであるweb-modeの設定を見てみましょう。
(autoload 'web-mode "web-mode")
(add-to-list 'auto-mode-alist '("\\.html?\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.jsp\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.gsp\\'" . web-mode))
(eval-after-load 'web-mode
'(progn
;; web-modeの設定
))
これでhtmlファイルを開くと以下の様な流れで web-mode
が遅延ロードされます。
- htmlファイルを開く
-
auto-mode-alist
から関連付されたメジャーモードがweb-mode
とわかる -
web-mode
実行、する前にweb-mode
ライブラリをロード -
eval-after-load
で書かれた設定を評価 -
web-mode
を実行し、メジャーモードがweb-mode
になる
auto-mode-alist
への追加設定が繰り返されており、これまた冗長な記述です。この設定を use-package
で書くとこうなります。
(use-package web-mode
:mode (("\\.html?\\'" . web-mode)
("\\.jsp\\'" . web-mode)
("\\.gsp\\'" . web-mode))
:config
;; web-modeの設定
)
:mode
には (拡張子の正規表現 . メジャーモード)
のリストを指定します。典型的なユースケースはずばりメジャーモードです。
:interpreter
- interpreter-mode-alist
の設定
auto-mode-alist
は拡張子とメジャーモードの関連付けでしたが、シバンに書かれたインタープリタとメジャーモードを関連付ける interpreter-mode-alist
という変数があります。使い方はほぼ :mode
キーワードと同じなので、 ruby-mode
の記述例だけ載せておきます。
(autoload 'ruby-mode "ruby-mode")
(dolist (name (list "ruby" "rbx" "jruby" "ruby1.9" "ruby1.8"))
(add-to-list 'interpreter-mode-alist (cons name 'ruby-mode)))
(eval-after-load 'ruby-mode
'(progn
;; ruby-modeの設定
))
(use-package ruby-mode
:interpreter (("ruby" . ruby-mode)
("rbx" . ruby-mode)
("jruby" . ruby-mode)
("ruby1.9" . ruby-mode)
("ruby1.8" . ruby-mode))
:config
;; ruby-modeの設定
)
:interpreter
には (インタープリタ名 . メジャーモード)
のリストを指定します。典型的なユースケースは、スクリプト言語のメジャーモードです。
:init
- 初期化コードの設定
:init
キーワードは :config
と同様にライブラリに関する設定を記述するキーワードですが、遅延キーワードと同時に使用されるかによって評価順がかわります。遅延キーワードがない場合は、以下の順で評価されます。
- ライブラリのロード
-
:init
キーワードの設定を評価 -
:config
キーワードの設定を評価
すべてのコードはEmacs起動時に評価されます。
一方遅延キーワードと同時に使用した場合は、以下の順になります。
-
:init
キーワードの設定を評価 - (
autoload
された関数が実行されるタイミングで)ライブラリの遅延ロード -
:config
キーワードの設定を評価
Emacs起動時に評価されるのは :init
の設定のみです。この性質により :init
キーワードを利用すれば、 遅延キーワードでは表現しきれない遅延ロードの設定が可能になります。
その典型的なユースケースが、特定のキーマップに対するキー割り当てです。:bind
キーワードは global-map
に対するキー割り当てしかできないため、他のキーマップに割り当てをしたい場合は :bind
が使えません。そこで :init
キーワードを使ってキー割り当てを記述します。
:bind
キーワードの例に出した multiple-cursors
のコマンドを、 global-map
ではなく mode-specific-map (C-c)
以下に割り当てる場合は以下の様な記述になります。
(use-package multiple-cursors
:commands (mc/mark-next-like-this mc/mark-previous-like-this mc/mark-all-dwim)
:init
(bind-keys :map mode-specific-map
("C->" . mc/mark-next-like-this)
("C-<" . mc/mark-previous-like-this)
("M-R" . mc/mark-all-dwim))
:config
;; multiple-cursorsの設定
)
:commands
キーワードを使って必要なコマンドを autoload
し、 bind-key
4でキー割り当ての設定をしています。
別のユースケースとしては hook
の設定が必要なケースです。例えば highlight-symbol を ruby-mode
のバッファで有効にする場合は以下のように記述します。
(use-package highlight-symbol
:commands highlight-symbol-mode
:init
(add-hook 'ruby-mode-hook 'highlight-symbol-mode)
:config
;; highlight-symbolの設定
)
この設定によりEmacs起動時には highlight-symbol
はロードされず、 ruby
のファイルを開いた時(あるいは明示的に ruby-mode
を実行した時など)に遅延ロードされます。多くのマイナーモードの設定はこのような形で記述できると思います。
:defer
- 遅延ロードの宣言
:defer
キーワードはライブラリが遅延ロードされることを宣言するだけで、特に遅延ロードの設定はしません。なぜこのようなキーワードがあるかというと、遅延ロードの設定をするまでもなく、Emacsを起動するだけで遅延ロードの設定が完了しているライブラリがあるからです。
例えばEmacsでInfoを読む info
というライブラリがあります。このライブラリはソースコード中に ;;;#autoload
というマジックコメントが付いており、必要なコマンドは自動で autoload
されます5。また info
を呼び出すキーバインドもデフォルトで設定されているため、ユーザ側で use-package
マクロの遅延ロード設定する必要がありません。だからといって以下のように記述すると問題があります。
(use-package info
:config
;; infoの設定
)
この記述ではEmacs起動時に info
ライブラリがロードされてしまい、せっかくの autoload
の意味が無くなってしまいます。
そこで以下のように :defer
キーワードで遅延ロードであること明示すると、起動時にロードされることはなくなり、M-x info
を呼び出したり C-h i
を押下したタイミングで遅延ロードすることができます。
(use-package info
:defer t
:config
;; infoの設定
)
多くの標準ライブラリは autoload
マジックコメントが使われており、ユーザが明示的に autoload
を書く必要があるケースはあまりありません。どのコマンドをautoload
するべきかは当然ライブラリ開発者が一番理解しているので、それに任せるのが妥当です。
また package
を使っていれば非標準ライブラリでも同様のことが可能になります。package
が自動で autoload
マジックコメントを抽出し、 autoload
の設定をしてくれます。上述した multiple-cursors
や highlight-symbol
にもautoload
マジックコメントが付けられているため6、実を言うと :defer t
をつけるだけでいいのです。
(use-package multiple-cursors
:defer t
:init
(bind-keys :map mode-specific-map
("C->" . mc/mark-next-like-this)
("C-<" . mc/mark-previous-like-this)
("M-R" . mc/mark-all-dwim))
:config
;; multiple-cursorsの設定
)
(use-package highlight-symbol
:defer t
:init
(add-hook 'ruby-mode-hook 'highlight-symbol-mode)
:config
;; highlight-symbolの設定
)
遅延ロードとライブラリの存在確認
ライブラリが遅延ロードされる場合、ライブラリの存在確認も遅延されます。これは結構困った問題を引き起こします。例えば以下の設定を記述した上で highlight-symbol
ライブラリが存在しない場合どうなるでしょうか?
(use-package highlight-symbol
:defer t
:init
(add-hook 'ruby-mode-hook 'highlight-symbol-mode)
:config
;; highlight-symbolの設定
)
rubyのファイルを開くと、 Symbol's function definition is void: highlight-symbol-mode
とエラーが起きてしまいます。これは :init
キーワードの設定が highlight-symbol
の有無にかかわらず実行されてしまうためです7。
これを避けるには遅延ロードの場合でも、Emacs起動時にライブラリの存在を確認する必要があります。実はそのような機能がこのプルリクで実装されたみたいなのですが、起動時間が遅くなってしまうというのが原因でrevertされちゃってます。
ライブラリの存在確認をする locate-library
が結構遅いので、すべてのuse-package
マクロで locate-library
するのは時間がかかってしまうとのこと。プルリクでは最終的に、起動時間を優先するか存在確認を優先するか設定するオプションつくろうぜ、っていう話になってるんですがそのまま音沙汰が無いようです。
無理やりやろうと思えば :if
キーワードを使って自分で存在確認をしてしまえばいいです。
(use-package highlight-symbol
:if (locate-library "highlight-symbol")
:defer t
:init
(add-hook 'ruby-mode-hook 'highlight-symbol-mode)
:config
;; highlight-symbolの設定
)
ただあんまり美しい方法ではないので、やっぱりオプションがほしいですね。
その他こまごましたこと
use-package
が存在しない場合
use-package
ライブラリが存在しない場合、当然 use-package
マクロが存在しないので init.el
の評価が途中で失敗します。 use-package
ライブラリが存在しない時は、何もしない use-package
マクロを定義しておくと失敗しなくなるので精神衛生上よろしいと思います。
(unless (require 'use-package nil t)
(defmacro use-package (&rest args)))
:disabled
- 設定の無効化
use-package
マクロに :disabled t
キーワードをつけると、そのライブラリの設定はまるまる無視されます。一時的にライブラリを無効化したい場合などに便利です。
まとめ
use-package
を使って可読性の高い init.el
を書く方法を紹介しました。まとめとして各キーワードの簡単な説明を表にしておきます。
キーワード | 説明 | 遅延 | 形式 |
---|---|---|---|
:config |
設定コード | ロード後に評価する式(複数でもOK) | |
:init |
初期化コード | ロード前に評価する式(複数でもOK) | |
:if |
場合分け | 条件文 | |
:commands |
autoload | ○ | command or そのリスト |
:bind |
autoload+キー割り当て | ○ | (key . command) or そのリスト |
:mode |
autoload+auto-mode-alist設定 | ○ | regex or (regex . mode) or そのリスト |
:interpreter |
autoload+interpreter-mode-alist設定 | ○ | regex or (regex . mode) or そのリスト |
:defer |
遅延ロード | ○ | boolean |
:disabled |
無効化 | boolean |
すべてのキーワードを使うのは大変ですが、既存の設定を use-package
マクロと :config
キーワードを使って書き換えるのは非常に簡単です。その後は少しずつ遅延キーワードを使っていって、起動時間を短くしていけばいいと思います。
最後に私も2日めの@tadsan同様@kawabataさんのdotfiles/init.el at master · kawabata/dotfilesに大いに影響を受けています。そもそも use-package
の存在を知ったのが、8月に開催された関東Emacsでの@kawabataさんのお話でした。use-package
の書き方の参考にもさせていただきました。ここに多大なる感謝を申し上げます。
参考URL
-
dotfiles/init.el at master · kawabata/dotfiles
-
@kawabataさんの
init.el
-
@kawabataさんの
-
dot-emacs/init.el at master · jwiegley/dot-emacs
-
use-package
作者のinit.el
-
-
require/loadに成功/失敗したらメッセージを出すマクロ - とりあえず暇だったし何となく始めたブログ
- 安全な
require
,load
- 安全な
-
.emacs.elで定義しておくと便利なマクロ - 八発白中
-
lazyload
遅延ロードマクロ
-
-
dot.emacs
-
.emacs
読み込みを途中で止めないマクロなど
-
Footnotes:
winner
はウィンドウ構成のアンドゥ、リドゥが可能になるライブラリです。autoload
するコマンドが1つの場合はリストでなくても良いです。bind-key
は use-package
に同梱されているライブラリで、キー割り当てを簡潔に記述することができます。詳しい使い方はemacs bind-key.el : るびきち「日刊Emacs」でどうぞ。:init
キーワードなだけで、遅延キーワードでも起こりえます。