16.2 使用 defcustom 定义变量

你可以使用 defcustom 定义变量,方便自己和他人通过 Emacs 的 customize 功能设置其值。(你不能使用 customize 编写函数定义,但可以在 .emacs 文件中编写 defun;事实上,你可以在 .emacs 中编写任意 Lisp 表达式。)

customize 功能依赖 defcustom 宏。虽然你可以使用 defvarsetq 定义用户可设置的变量,但 defcustom 宏是为此专门设计的。

你可以沿用编写 defvar 的经验来编写 defcustom 的前三个参数。defcustom 的第一个参数是变量名,第二个参数是变量初始值(若有),且仅在变量未被设置时生效,第三个参数是文档说明。

defcustom 的第四个及后续参数用于指定类型和选项,这些是 defvar 不具备的。(这些参数为可选。)

每个参数均由关键字和对应值组成,关键字以冒号 ‘:’ 开头。

例如,可自定义的用户选项变量 text-mode-hook 定义如下:

(defcustom text-mode-hook nil
  "进入文本模式及诸多相关模式时运行的标准钩子。"
  :type 'hook
  :options '(turn-on-auto-fill flyspell-mode)
  :group 'wp)

变量名为 text-mode-hook,无默认值,文档字符串说明了其用途。

:type 关键字告诉 Emacs 该变量应设置为何种类型的数据,以及如何在自定义缓冲区中显示该值。

:options 关键字指定变量的推荐值列表,通常用于钩子变量。该列表仅为建议而非强制限制,用户可将变量设为其他值;:options 后的列表旨在为用户提供便捷选项。

最后,:group 关键字告诉 Emacs 自定义命令该变量所属的组,方便查找定位。

defcustom 宏支持十多个关键字,更多信息可参考 编写自定义定义 in GNU Emacs Lisp 参考手册

text-mode-hook 为例。

自定义该变量有两种方式:使用自定义命令,或手动编写对应表达式。

使用自定义命令时,输入:

M-x customize

找到文本编辑相关的组名为“Text”。进入该组后,Text Mode Hook 是第一个选项。你可以点击各个选项(如 turn-on-auto-fill)设置值。点击按钮:

Save for Future Sessions

后,Emacs 会向你的 .emacs 文件写入表达式,形式如下:

(custom-set-variables
  ;; custom-set-variables 由 Customize 自动添加。
  ;; 手动编辑可能导致出错,请注意。
  ;; 初始化文件应只包含一个该实例。
  ;; 多个实例会导致功能异常。
 '(text-mode-hook '(turn-on-auto-fill text-mode-hook-identify)))

text-mode-hook-identify 函数用于告知 toggle-text-mode-auto-fill 哪些缓冲区处于文本模式,会自动启用。)

custom-set-variables 函数的工作方式与 setq 略有不同。我虽未深究差异,但会手动修改 .emacs 中的 custom-set-variables 表达式,以自认为合理的方式调整,从未出现问题。其他用户更倾向于使用自定义命令,让 Emacs 自动完成配置。

另一个 custom-set-… 系列函数是 custom-set-faces,用于设置各类文本的视觉样式。我长期以来设置了大量样式,有时会通过 customize 重新设置,有时则直接编辑 .emacs 中的 custom-set-faces 表达式。

第二种自定义 text-mode-hook 的方式是在 .emacs 文件中手动编写与 custom-set-… 函数无关的代码。

这样设置后,后续使用 customize 时会看到提示信息:

CHANGED outside Customize; operating on it here may be unreliable.

该信息仅为警告。点击

Save for Future Sessions

按钮后,Emacs 会在 .emacs 文件末尾附近写入 custom-set-… 表达式,该表达式会在你手写的表达式之后求值,从而覆盖手写配置,不会造成损坏。但这样做时需记住哪个表达式生效,否则可能造成混淆。

只要记住值的设置位置,就不会出现问题。无论如何,所有配置值最终都保存在初始化文件中,通常为 .emacs

我个人几乎不使用 customize,大部分配置都是手动编写表达式。

顺带一提,更完整的定义类宏包括:defsubst 用于定义内联函数,语法与 defun 一致;defconst 用于将符号定义为常量,设计意图是程序和用户都不应修改其值。(你 technically 可以修改,因为它本质仍是变量,但建议不要这样做。)