Emacsのcustom-theme-load-pathとload-theme
custom-theme-load-pathで気になったところ。
環境: Emacs 26.3
custom-theme-load-path変数
Emacsのzenburn-theme.el - つうさにメモブログの記事でEmacsにおけるカスタムテーマについて紹介したが、テーマ機能で用いられる変数としてcustom-theme-load-path
変数がある。
custom-theme-load-path
はディレクトリ名(もしくはt)を含むリストであり、このディレクトリ直下にないカスタムテーマをload-theme
関数でロードすることはできない。つまり、カスタムテーマをEmacsで使うためにはなんとか-theme.el
をload-path
下に置くのではなく、custom-theme-load-path
下に置く必要がある。
しかし、package.elでインストールしたzenburnテーマはこの変数をなんやら設定しなくてもload-theme
することができた。これはどういうことか。
package.elのautoload処理
zenburn-theme.elを見ればわかるが、その設定はすでにzenburn-theme.el内に書かれている。そして、そのフォームにはautoloadクッキーがつけられている。やっていることは、load-theme-file
の値がnilでないとき、そのファイルが置かれているディレクトリをcustom-theme-load-path
に追加するというものだ。
;;;###autoload (and load-file-name (boundp 'custom-theme-load-path) (add-to-list 'custom-theme-load-path (file-name-as-directory (file-name-directory load-file-name))))
package.elはパッケージのインストール時に、zenburn-theme.elと同じディレクトリにzenburn-theme-autoloads.el(autoloadsファイル)を作り、autoloadsファイルにこのフォームをコピーする。Emacs起動時に(package-initialize)
1がautoloadsファイルをload
する2ため、Emacs起動後にはcustom-theme-load-path
にzenburn-themeのパスが追加された状態になっているのであった。
MELPAで配布されているカスタムテーマパッケージのほとんどは同様のテクニックを用いていて、ユーザにcustom-theme-load-path
を設定させる手間を無くしている。
custom-theme-load-pathが意図しない値になる
ところで、initファイルを以下のようにして、起動時にzenburnテーマをload-theme
でロード(かつ有効化)するようにしたとする。
;; my init.el (package-initialize) ;; ごにょごにょ ;; テーマ (load-theme 'zenburn t) ;; ごにょごにょ
このとき、custom-theme-load-path
が意図しない値になっていることを発見した。
上記のようなinitファイルで起動したEmacsでcustom-theme-load-path
の値を見ると、明らかに意図しないディレクトリが追加されていたのだ(ユーザ名は<username>に置き換えて表記している)。
;; package.elでzenburnをインストールし、initファイルでzenburnテーマをロードした状態 custom-theme-load-path ("/Users/<username>/.emacs.d/" "/Users/<username>/.emacs.d/elpa/zenburn-theme-20190809.2224/" custom-theme-directory t)
"/Users/<username>/.emacs.d/"
がcustom-theme-load-path
の要素なのは意図しないものである。なぜなら、initファイルにcustom-theme-load-path
をゴニョゴニョするコードは書いていないからだ。それに、そもそもcustom-theme-directory
要素が~/.emacs.dを指すため、これは余計である。
なぜこうなってしまっているのかを調べた。
原因
原因はload-theme
関数だった。
実はload-theme
関数はload
関数を使っていない。実装を見ればわかるが、やってることはtemp-bufferにファイルの内容をコピーして、それをeval-buffer
しているのであった。
initファイルはEmacs起動時にload
される。その際load-file-name
の値は~/.emacs.d/init.elになる。(load-theme 'zenburn t)
がload
を用いてファイルをロードするならload-file-name
にはまたそのファイルの絶対パスが入るが、上に書いたようにload-theme
はload
していない。よってinitファイル内のload-theme
でテーマファイルの中身が評価される際load-file-name
の値は~/.emacs.d/init.elであり、某テクニックのフォームが評価されて~/.emacs.dをcustom-theme-load-path
に追加してしまっていたのだ。
ちなみに、initファイルが~/.emacs.d/init.elではなく~/.emacsだった場合、同様のことが起こってcustom-theme-load-path
には~が追加される。
対策
謎が解けてスッキリした。
さてどうしたものかと思ったら、Emacs 27でのload-theme
の実装 では、安全なテーマではload-theme
はload
するようになっていた!
ということで、Emacs 27以降では(load-theme 'zenburn t)
はload
するため、custom-theme-load-path
に意図せずinitファイルのディレクトリが追加されることはなくなった。めでたしめでたし。
終わり
Emacs 27は結構いろいろ変わってるようだ。リリースされるのが楽しみ。
補足
上の現象が起きるのは以下を全て満たすときである。
- Emacs 26以下を使っている
- initファイルで
(load-theme '<theme-name> t)
している (<theme-name>にはテーマ名が入る)(もちろんload-theme
前にcustom-theme-load-path
が正しく設定されているものとする) load-theme
で評価されるカスタムテーマファイル(なんとか-theme.el)に某テクニックのフォームが書かれている
最後の条件を満たさないパッケージとして、doom-themesパッケージなどがある。doom-themesパッケージでは、カスタムテーマファイル自体にはcustom-theme-load-path
の設定をするフォームが書かれていないため、今回の記事で話したような意図しない現象は起こらない。