可以在org mode中显示custom variable和它的值吗?

我在使用org mode写配置文件。我看到很多人设置 custom variable 是写在 use-package 中的 custom下,或者是custom set variable,或是 setq 写死。

但是我更习惯使用emacs自带的customize系统,因为它即改即得,虽然可能有一些bug,重启就好了。然而,使用custom系统并没有直接写在配置文件中写出来的解释性高。所以,我想取一个折中方案,在custom系统中定义变量后,其值自动在万能的 org mode中显示出来。

但org mode中有没有显示 custom variable 的值 这一功能呢?如果有的话请不吝赐教!如果没有的话,你觉得这是一个好的feature 吗?或者,有没有什么更好的实践呢?

ps: 我问了chatgpt,它给出的答案是写一个代码块求值显示在result下。

你的意思是通过 defcustom 定义变量后能以相似于 customize 的界面在 org 中显示出来吗?

对!我是这个意思。我觉得customize界面显示的东西太多了,想取一些关键的显示在配置文件里。

那我再次尝试描述一下哈

你想要将 配置文件 中的某些 变量 标记为被能够在某个 界面 中显示以方便修改或设定

Emacs 中的 custom 设定默认保存在 .emacs 文件中,可以使用 custom-file 指定保存位置,你也想要这些选项保存到这个位置吗,还是保存在自己的配置文件中呢?

我是单独分离生成custom.el来的。显然这个文件不适合手动修改,虽然可以看。不过我是想把custom variable 显示在生成配置文件的 org 文件中。

我记得doom里面定义了一个doom-package 的 org-link, 可以自动显示某个包有没有安装。我或许可以研究一下这方面。

如果只是为了让变量可以在custom中显示,且只显示自己想要的那些的话,不如创建一个 custom group,然后往里面添加变量。

简单包装一下customize-group, 写个命令就可以只显示这个group里的变量了。至于保存到配置文件,可以读一下 customize-save 的实现来自己写一个

现在手头没电脑,明天试试

上面的函数名可能不对,太久了记不清了

2 个赞

简单看了下代码,弄起来不是很复杂,主要分两块来讲吧,定义、使用 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-excursionsave-restriction 。我对于使用 org-mode 写配置的方法不是很熟悉,好像有什么 tangle 玩意。

话又说回来,把想用的变量注释好,再使用 setq 对配置文件来说绝对是够用的。通过将不同种类的配置放在不同文件中,或者使用 use-package 来分隔单个配置都是不错的做法。使用 defcustom 来可视化某些选项这个想法之前还真没有过。

3 个赞

很不错的教学!不给你个解决方案我都觉得不好意思,虽然我想知道的是在org文件显示custom变量的方法。

不过我后来想了想,作为一个泛用文件类型,org其实不必具有这种显示内置变量的功能,毕竟org的作用不是局限在作为配置文件的。所以你提供的方案或许更有参考意义。

那看来我理解有误 :rofl:,简单做的话确实可以通过遍历自己定义的custom 变量,然后把这些变量打印到 org buffer 中。

具体做么做的话,拿“如何执行org 中的代码并输出到指定位置”去问gpt应该没问题。自己想好打印格式就行

不过这种配置思路还是很怪 :joy: