読者です 読者をやめる 読者になる 読者になる

package.elを使う場合の設定ファイルの書き方

emacs package.el

init-loader.el と package.el を導入して快適 Emacs ライフ - Qiita


に影響されて書きます。

package.elの設定ははじめの方に書く

;; Emacs package system
(require 'package)
(add-to-list 'package-archives '("melpa" . "http://melpa.milkbox.net/packages/"))
(package-initialize)

package.elを使っているような人であれば、上記のような設定がどこかしらに
あると思うのですが、これは初めの方に書くべきです。具体的には早い段階で
package-initializeを呼ぶべきだからです。

package.elはパッケージ管理だけのツールではない

package.elを使ってパッケージをインストールした場合, デフォルトだと,
"~/.emacs.d/elpa/"以下にパッケージがインストールされますが、単にパッケージを
取得して、バイトコンパイルするだけではなく、autoloadに関するファイルも
生成しています(ファイル名は (パッケージ名)-autoloads.elとなります).


autoloadは遅延ロードを実現するための仕組みで、高速な起動を可能にします。
package.elなしでは手動で行わなければいけない、autoloadの設定を package-initialize
でやってくれるので早い段階で呼び出すべきなのです。

autoload補足

autoloadとはある関数を実行した場合に、指定したファイルをロードすることを
支持する関数です。これにより、まだロードしていない段階でも M-x等で関数が
見えるようになります。下記の例では quickrun関数を実行したら, "quickrun.el"を
ロードする、ということになり, quickru関数を初めて実行したときに quickrun.elの
ロードが行われます。

;; 他にもいろいろ出力されています
(autoload 'quickrun "quickrun" "\
Run commands quickly for current buffer
   With universal prefix argument(C-u), select command-key,
   With double prefix argument(C-u C-u), run in compile-only-mode

\(fn &rest PLIST)" t nil)
autoload cookie

package.el等が autoloadに関する設定を自動生成するために autoload cookie
呼ばれるものが使われます。以下の ";;;###autoload"がそれです。これは任意の
S式の前に記述することができます。autoload cookieについては別途記事を書く
予定です。

;;;###autoload
(defun quickrun-set-default (lang key)
  "Set `key' as default key in programing language `lang'"
  (unless (assoc key quickrun/language-alist)
    (error "%s is not registered." key))
  (puthash lang key quickrun/command-key-table)

設定ファイルを読み込む時点では load, requireは原則呼ばない

上記で述べたように package.elを利用することで, 必要となるものは概ね
autoloadの設定が行われているはずなので、設定ファイルの段階で明示的に
load, requireを行う必要は基本ありません。特にプログラミング言語
major-modeや minor-modeに関するパッケージについては確実に不要です
(Emacs Lispは例外).


もし requireしないと何かしらおかしいという場合は, autoload cookie
設定が適切でない可能性があります。その場合は作者の方に知らせてあげると
よいでしょう。


ただ以下のようなものは結局するロードすると思うので, load, requireしても
別に問題ないと思います。helmとか dired周りとか Emacs Lisp関係が該当する
かと思います。

  • 起動後すぐに使うもの
  • システム全般で利用するユーティリティ

遅延ロードされた設定

autoloadとセットでeval-after-loadを使うのが良い? - すぎゃーんメモ


autoloadを利用した設定で注意しないといけないのは, 設定ファイルの
時点ではパッケージのロードが行われていないので、内部関数や変数、
faceを参照するとエラーになることです。該当のパッケージがロードされた
時点でこの設定を行なって欲しいというように書く必要があります。


それを実現するのが eval-after-loadです。例を以下に示します。
以下は diff-mode.elが呼ばれたとき, faceの設定やキーバインド
設定を行うようになっています。注意して欲しいのは prognの部分の
quoteです。これがないとその場で評価されてしまい、意味がなくなります。

(eval-after-load "diff-mode"
  '(progn
     (set-face-attribute 'diff-added-face nil
                         :background nil :foreground "green"
                         :weight 'normal)
     (set-face-attribute 'diff-removed-face nil
                         :background nil :foreground "firebrick1"
                         :weight 'normal)

     ;; key bindings
     (define-key diff-mode-map (kbd "C-M-n") 'diff-file-next)
     (define-key diff-mode-map (kbd "C-M-p") 'diff-file-prev)))

追記: あるパッケージの関数や変数を参照している箇所があれば,
それは eval-after-loadに入れるべきと基本考えてください。


eval-after-loadは使い勝手があんまりということで、楽に書くための
拡張を書いている方や、改善策をブログ記事にされている方などいるので
気になる方は調べてみるとよいでしょう。

eval-after-loadと hook関数の違い

eval-after-loadは一回しか実行されませんが、hook関数は新規にバッファを
開いたりする度に何度も呼ばれます。なので faceの設定やキーバインドの設定、
関連するパッケージのロード(python-mode.elが呼ばれたら jediを loadする等)
eval-after-loadで行うと良いでしょう(追記 関連するパッケージのロードも
極力 autoload経由で loadすることが望ましいです)。一方 minor-modeの有効化や
buffer local変数の設定はhook関数で行うのが良いでしょう。

具体的な設定例

quickrun

quickrunであれば, キーバインドの設定だけで基本良いです。(独自のコマンドを
定義するような場合は loadする必要があります。loadはしなくていいですが、
設定のための関数を呼ぶと autoloadにより自動的にロードされます)

(global-set-key (kbd "M-g M-q") 'quickrun)
go言語

go-mode.elがロードされた時点(goファイルを開いた時点), gocodeを利用した
go-autocomplete.elを loadして、キーバインドを設定しています。

(eval-after-load "go-mode"
  '(progn
     ;; 追記 go-autocompleteは autoloadの設定が現状適切でないため
     (require 'go-autocomplete)

     (define-key go-mode-map (kbd "C-c C-d") 'my/helm-go)
     (define-key go-mode-map (kbd "M-.") 'godef-jump)))

おわりに

package.elは初心者に大変優しいツールではありますが、それを活かした
設定をしようとする上で述べたようにいくらかポイントを押さえる必要が
あります。初めはパッケージが簡単にインストールできるというツール
いう認識でも良いと思いますが、さらにその先を目指してみるのも良いのでは
ないかと思います。それなりに Emacsに慣れている方であれば、遅延ロードを
考慮した設定ファイルの書き方になっていると思いますので、github等で
その人の dotfilesを見てみるのも良いかと思います。