简单看了下代码,弄起来不是很复杂,主要分两块来讲吧,定义、使用 group 和 保存 custom 变量。
定义和浏览 group
简单 xref
找下 customize
的定义:
(defun customize ()
"Select a customization buffer which you can use to set user options.
User options are structured into \"groups\".
Initially the top-level group `Emacs' and its immediate subgroups
are shown; the contents of those subgroups are initially hidden."
(interactive)
(customize-group 'emacs))
可见 customize
内部调用 customize-group
函数来访问 emacs
这个顶级 group,我们也可以自己定义一个 group,然后通过包装 customize-group
来达到 只 访问该 group 的目的,为了方便这里我直接把该 group 挂在 emacs group 下:
(defgroup my-config-group nil
"user config var group"
:group 'emacs)
(defcustom cust-var1 1 "test1")
(defcustom cust-var2 2 "test2")
(defcustom cust-var3 3 "test3")
(defun my-config-customize ()
(interactive)
(customize-group 'my-config-group))
执行 customize-browse
可以看到这个 group 在所有 group 的位置(最后一行)
执行 my-config-customize
可以看到所有属于 my-config-group
的变量(或者叫选项):
通过这个界面我们可以对变量值进行修改,不过不能将变量保存为未来使用了,毕竟我们不想让它保存在外部文件中。
在 config 界面中显示的值并不一定是变量的值,调用的设置命令也不一定将值 直接赋给 变量,这个可以参考文档,我两年前折腾过一点:emacs 的 customization,如果想整点花活,比如将其他 option 使用 My Config Group 来设置和查看,可以考虑折腾一下。
上面的 defcustom
中我都没有指定 :group
参数,这是因为 If a defcustom does not specify any :group, the last group defined with defgroup in the same file will be used. 上面 group 和 option 都在一个文件里我就没有写。
下面就是如何保存这些变量了。
保存 custom 变量
既然希望配置中的 option 单独保存到配置文件中,且不与其他 options 混杂,那就需要自己实现一下修改。 C-h f
一下 customize-save-customized
可以找到它的定义,并且可以看到它在最后调用了 custom-save-all
,在 custom-save-all
内部又可以看到它调用了 custom-save-variables
来保存变量(或者说 options),它在内部使用了一系列的 princ 来生成 (custom-set-variables...)
代码。
在 custom-save-variables
中调用了 mapatoms
来遍历所有 symbol 找到所有的 option,有点浪费,这里我们使用 custom-group-members
来获取单个 group 内的内容,这里假设所有的 option 都位于 'my-config-group 下,而没有嵌套的子 group(子 group 支持当然能做,不过懒得写了)
(custom-group-members 'my-config-group nil) =>
((cust-var1 custom-variable) (cust-var2 custom-variable) (cust-var3 custom-variable))
(mapcar 'car (custom-group-members 'my-config-group nil)) =>
(cust-var1 cust-var2 cust-var3)
整个 customize-save-variables
的保存逻辑是先删除先前的设定代码,然后在原处添加新的设定代码。如果使用的 org-mode 保存代码,那删除弄起来很容易,直接删掉某个 headline 即可。这里假设我们的 options 存储格式如下:
* CUSTOM-OPTION
#+BEGIN_SRC elisp
(setopt a b)
(setopt c d)
...
#+END_SRC
那么删除代码就是:
(progn (goto-char (org-find-exact-headline-in-buffer "CUSTOM-OPTION"))
(org-cut-subtree))
代码的生成的话,就在刚才删除的位置生成,这里我直接使用 emacs-29 引入的 setopt 了,很方便:
(insert (format
"* CUSTOM-OPTION\n#+BEGIN_SRC elisp\n%s\n#+END_SRC"
(mapconcat (lambda (x) (format "(setopt %s %s)" x (symbol-value x)))
(mapcar 'car (custom-group-members 'my-config-group nil))
"\n")))
把代码合起来大概就是这样:
(defun my-custom-save-1 ()
(progn (goto-char (org-find-exact-headline-in-buffer "CUSTOM-OPTION"))
(org-cut-subtree))
(insert (format
"* CUSTOM-OPTION\n#+BEGIN_SRC elisp\n%s\n#+END_SRC"
(mapconcat (lambda (x) (format "(setopt %s %s)" x (symbol-value x)))
(mapcar 'car (custom-group-members 'my-config-group nil))
"\n"))))
这段代码只有在配置文件中调用才有意义(而且一定要存在 * CUSTOM-OPTION
,你可能得先创建一个空的 headline),可以考虑创建一个存储配置文件目录的变量:
(defvar my-custom-file "your-path")
(defun my-custom-save ()
(save-excursion
(with-current-buffer (get-file-buffer my-custom-file)
(goto-char (point-min))
(my-custom-save-1))))
一个问题是即使我们没有修改 custom 变量,它们也会保存,另一个比较大的问题是它们 可能 会被 customize-save-customized
保存到 custom 文件中。这可能需要仔细注意不要在设定选项时选择未来使用:
…
把上面的名字改成你喜欢的名字就勉强可以用了,不过还有很多可以改进的地方(各种 save-excursion
和 save-restriction
。我对于使用 org-mode 写配置的方法不是很熟悉,好像有什么 tangle 玩意。
话又说回来,把想用的变量注释好,再使用 setq
对配置文件来说绝对是够用的。通过将不同种类的配置放在不同文件中,或者使用 use-package
来分隔单个配置都是不错的做法。使用 defcustom
来可视化某些选项这个想法之前还真没有过。