Emacsのカスタム変数について
Emacsには、カスタム変数という変数をかんたんに設定するための機能が用意されています。
Emacsの歴史(NEWS)を辿ると1997年にリリースされたEmacs 20.1から用意されている古い機能ですし、最近でもEmacs 29.1にsetopt
というカスタム変数を設定するための関数(マクロ)が追加されるなど、Emacs開発者によって重視され進化を続けている機能でもあります。
ですが、私含めてカスタム変数にあまり馴染みがない人も多いようですし、マニュアル以外の情報もあまり出ていないようです。
ここではカスタム変数の概要を紹介するとともに、カスタム変数周辺の技術を有効に用いる方法を考えていきます。
カスタム変数の概要
customizeなどからのGUIによるカスタム変数設定
EmacsでM-x customize
と入力すると、カスタム変数設定の画面が開きます。この画面に表示されているグループを選択していくか、上部の検索ボックスに設定したい変数の名前を入力することで設定したい変数の設定ができる画面になります。
または、M-x customize-variable
から設定するカスタム変数を指定する方法もあります。バッファー内で変数の名前のところにカーソルを移動させてからM-x customize-variable
を実行すると、変数名入力の手間が省けて便利です。
こうして変更した変数の設定は、[Apply and Save]ボタンをクリックすると後述するようにinit.elなどに保存できます。
ですが、私を含めてこうしたGUIによるカスタム変数の設定機能はあまり好まない人が多いように思います。
GUIで設定できるのはいいけど、あまり操作しやすいとは言えないですし、そもそもEmacsを使う人は、GUIよりもCUIで設定するのが好きあるいは慣れている、という人が多いのも影響しているでしょう。
また、設定できる項目が限られていることもカスタム変数が好まれない理由の一つだと考えています。例えば、init.elなどで設定する機会の多い変数auto-mode-alist
は通常はカスタム変数として設定できません。
変数設定の保存
カスタム変数設定の画面で[Apply and Save]ボタンをクリックすると、初期設定では~/.emacsや~/.emacs.d/init.elなどの初期化ファイルに変数の設定変更の記述が追加され、次回にEmacsを起動した時にも設定が反映されるようになります。この仕様は変数の設定を自分で記述する必要がないのでラクな反面、勝手に初期化ファイルが変更されることを鬱陶しく思う人もいると思います。私もそうです。例えば、Gitなどによる初期化ファイルの履歴管理が難しくなります。Qiitaの記事init.elにいつの間にかcustom-set-*が追加される件も、カスタム変数の挙動に悩まされている話だと思います。こうした初期化ファイルへの設定反映を避けるには、変数custom-fileを設定します。
(setq custom-file "~/.emacsd/emacs-custom.el")
次回起動時に設定を反映するには、手動でcustom-fileの設定を初期化ファイルにコピーするか、初期化ファイルの中で次のようにcustom-fileを読み込むように設定します。私は必要な設定を手動でコピーするようにしています。
(setq custom-file "~/.emacsd/emacs-custom.el")
(load custom-file)
カスタム変数の確認
変数がカスタム変数かどうかは、関数custom-variable-p
で確認できます。
この変数は、関数に最初に
結果が(括弧のない)nilの場合、変数はカスタム変数ではありません。
(custom-variable-p 'auto-mode-alist)
nil <= 結果
カスタム変数の定義と設定
カスタム変数のソースをヘルプなどから調べると、カスタム変数はdefcustom
関数で定義されていることがわかります。たとえば、バックアップファイルのディレクトリーを指定する変数backup-directory-alist
はfiles.el.gzファイルの中でdefcustom
関数によって定義されています。一方、カスタム変数ではない変数はdefvar
関数で定義されます。なお、カスタム変数もそうでない変数もC言語で定義されているものもあります。たとえばdefault-frame-alist
はC言語で定義されたカスタム変数になります。
カスタム変数設定で行われていること
カスタム変数を設定する時には、次のようなことが行われています。
- 変数の変更前の値が保存されていて、いつでも元に戻すことができる
- 変数の値を設定するときに、指定された型(type)を元に変数の値を検証(validation)できる
- setqなどではうまく設定できない変数でも、カスタム変数としてはうまく設定できる変数がある
カスタム変数はあまり使いたくないけど、こうした動作はうらやましい、初期化ファイルに変数を設定するときにこういう風にできないものかな、といろいろ調べてみたのでした。
カスタム変数を初期化ファイルで設定する
前提知識: シンボルプロパティ
カスタム変数を理解する時にはまず、シンボルのプロパティについて知る必要があります。
シンボルのプロパティとは、変数などのシンボルに設定される値です。変数は当然、setqなどで設定する変数の値を持ちます。それとは別にシンボルプロパティと呼ばれる値を持つわけです。
シンボルのプロパティの詳細は、GNU Emacs Lisp Reference Manual(日本語版)の9 Symbols(日本語版)を参照してください。
シンボルが持つすべてのプロパティをあらわすプロパティリストは、symbol-plist関数で確認できます。たとえば、変数auto-mode-alist
のプロパティリストは次で確認できます。
(symbol-plist 'auto-mode-alist)
(variable-documentation "Alist of file name patterns vs corresponding major mode functions. <= 結果
Each element looks like (REGEXP . FUNCTION) or (REGEXP FUNCTION NON-NIL).
(NON-NIL stands for anything that is not nil; the value does not matter.)
Visiting a file whose name matches REGEXP specifies FUNCTION as the
mode function to use. FUNCTION will be called, unless it is nil.
If the element has the form (REGEXP FUNCTION NON-NIL), then after
calling FUNCTION (if it's not nil), we delete the suffix that matched
REGEXP and search the list again for another match.
The extensions whose FUNCTION is `archive-mode' should also
appear in `auto-coding-alist' with `no-conversion' coding system.
See also `interpreter-mode-alist', which detects executable script modes
based on the interpreters they specify to run,
and `magic-mode-alist', which determines modes based on file contents." risky-local-variable t
ただし、symbol-plistの結果にはプロパティの名前(種類)と値が一緒に表示されてわかりにくいため、
私は次の関数を作成して、プロパティの名前だけのリストを出力するようにしています。
(defun my-symbol-properties (sym)
"Return the list of SYMBOL's PROPNAMEs."
(let (propnames (prop (symbol-plist sym)))
(dolist (i (number-sequence 0 (- (length prop) 1)))
(when (= (% i 2) 0)
(push (nth i prop) propnames)))
(reverse propnames)))
(my-symbol-properties 'auto-mode-alist)
(variable-documentation risky-local-variable) <= 結果
get
関数に変数名とプロパティ名を指定すると、プロパティの値を取得できます。
(get 'auto-mode-alist 'variable-documentation)
"Alist of file name patterns vs corresponding major mode functions. <= 以下、結果
Each element looks like (REGEXP . FUNCTION) or (REGEXP FUNCTION NON-NIL).
(NON-NIL stands for anything that is not nil; the value does not matter.)
Visiting a file whose name matches REGEXP specifies FUNCTION as the
mode function to use. FUNCTION will be called, unless it is nil.
If the element has the form (REGEXP FUNCTION NON-NIL), then after
calling FUNCTION (if it's not nil), we delete the suffix that matched
REGEXP and search the list again for another match.
The extensions whose FUNCTION is `archive-mode' should also
appear in `auto-coding-alist' with `no-conversion' coding system.
See also `interpreter-mode-alist', which detects executable script modes
based on the interpreters they specify to run,
and `magic-mode-alist', which determines modes based on file contents."
(get 'auto-mode-alist 'risky-local-variable)
t <= 結果
プロパティに値を設定するには、put
関数を使います。
変数をカスタム変数にする
前述したように、defcustom
関数で定義された変数はカスタム変数です。しかし、custom-variable-p
関数のヘルプを見ると、nil以外のstandard-valueプロパティを持つ変数はカスタム変数だと説明されています。ということは、後からでもput
関数を使ってstandard-valueプロパティを設定すれば、変数はカスタム変数にできるということになりそうです。実際、カスタム変数ではない変数auto-mode-alist
は、次の設定によりカスタム変数として扱われるようになります。M-x customize
などからのカスタム変数設定画面でauto-mode-alist
の設定もできます。
(put 'auto-mode-alist 'standard-value `(',(symbol-value 'auto-mode-alist)))
early-init.elへの設定
auto-mode-alist
をはじめとするユーザーが設定する関数は、カスタム変数にしてしまった方が良いのではと考えています。また、プロパティ値standard-valueには、init.elなどで変更される前の値が入力されるべきでしょう。というわけで、こうした設定はinit.elなどよりも早い段階で読み込まれる設定ファイルearly-init.elに記述するのが良さそうです。
私は、次のようにearly-init.elに8つの変数のstandard-value
プロパティーを設定し、これらの変数をカスタム変数としています。
;;; early-init.el -*- lexical-binding: t -*-
(mapc
(lambda (var)
(when (and (boundp var) (null (custom-variable-p var)))
(put var 'standard-value `(',(eval var)))))
'(
auto-mode-alist
default-directory
disabled-command-function
file-name-coding-system
indent-line-function
interpreter-mode-alist
locale-coding-system
magic-mode-alist
))
まとめ
Emacsの変数のうちユーザーが値を設定する変数には、カスタム変数というものがあります。カスタム変数は、カスタム変数設定画面から編集できるほか、以前の値に戻したり値を検証したりできます。Emacsの変数は、early-init.el
などでシンボルのプロパティstandard-valueを設定することでカスタム変数以外の変数をカスタム変数にできます。
なお、カスタム変数を便利に使うためには、変数の型(defcustomの:typeオプションおよび変数のcustom-typeプロパティ)を設定する必要があります。また、場合によってはセッター(defcustomの:setオプションおよび変数のcustom-setプロパティ)を理解して設定する必要もあります。これらの詳細については、Emacs Lisp Reference Manualの15.3 Defining Customization Variables(日本語版)などを参照してください。機会があれば、これらについての記事も作成したいと思っています。