设置 `minibuffer-prompt-properties` 不起效果

默认情况下,光标可以进入 minibuffer 的提示符中。比如按一下 M-x 然后往左退就可以看到。如果在 customize-set-group -> minibuffer -> Minibuffer Prompt Properties 中勾选 Don’t Enter,就可以阻止这一行为。这会在 init.el 中生成以下代码:

(custom-set-variables
 ;; custom-set-variables was added by Custom.
 ;; If you edit it by hand, you could mess it up, so be careful.
 ;; Your init file should contain only one such instance.
 ;; If there is more than one, they won't work right.
 '(minibuffer-prompt-properties
   (quote
    (read-only t cursor-intangible t face minibuffer-prompt))))

但是,如果使用 setqsetq-default 改写成这样:

(setq minibuffer-prompt-properties
 (quote (read-only t cursor-intangible t face minibuffer-prompt)))

就不再会起作用。奇怪的是使用 eval-last-sexp 对上述表达式求值也没有任何异样。

请问各位知道这个问题是怎么回事吗?

有些选项会在设置值的时候做些操作,譬如当你选择 Don’t Enter 时,需要执行这样一个函数才能真正达到目的:

(defun minibuffer-prompt-properties--setter (symbol value)
  (set-default symbol value)
  (if (memq 'cursor-intangible value)
      (add-hook 'minibuffer-setup-hook 'cursor-intangible-mode)
    ;; Removing it is a bit trickier since it could have been added by someone
    ;; else as well, so let's just not bother.
    ))

使用 custom-set-variables 能达到这样目的,而 setq 则不能。如果你赶在一个变量定义前就使用 setq,那么更使用 custom-set-variables 类似;但像 minibuffer-prompt-properties 这样的变量,在执行任何配置之前就已经定义了,用 setq 怎么都来不及,只能用 custom-set-variables

感谢回复,这个问题清楚了。并且经过调查,Emacs 似乎只会往 custom-file(默认就是 init.el)这一个文件中自动生成这样的代码段,注释中所说的「不要编辑」、「不要多于一个」也是仅对这个代码段而言。我的配置是模块化的,那么在其他的文件中随意使用 custom-set-variables 似乎也没有任何问题了。

不过又产生了新的疑惑:通过 (custom-variable-p 'var) 这个函数可以知道 var 是不是 customizable variable,通过试验发现很多很多变量(比如 fill-column)都是 customizable 的,但是大部分配置似乎也都是通过 setq 来直接修改之。这是不好的做法吗?您怎么看呢?

关于 custom-file,你的理解没错,一般建议设置一个单独的文件,如:

(setq custom-file "~/.emacs.d/custom.el")

这样随便 Emacs 怎么折腾了,不用担心相互影响。


关于你的新困惑:用户该修改的 var 几乎都是 customizable variable,只是有少量变量设置值时还需要做些其他动作,比如上面 minibuffer-prompt-properties 每次设置新值时都需要执行:

  (if (memq 'cursor-intangible value)
      (add-hook 'minibuffer-setup-hook 'cursor-intangible-mode)
    ;; Removing it is a bit trickier since it could have been added by someone
    ;; else as well, so let's just not bother.
    )

这是通过 custom-set 属性实现的

(plist-get
 (symbol-plist 'minibuffer-prompt-properties)
 'custom-set)
;; => minibuffer-prompt-properties--setter

一般是通过 defcustom:set 参数设置这个属性,比如较为出名的:

(defcustom org-babel-load-languages '((emacs-lisp . t))
  "..."
  :group 'org-babel
  :set 'org-babel-do-load-languages
  :version "24.1"
  ...)

minibuffer-prompt-properties 是在 C 代码中定义的,猜测是直接用 put 设置这个属性。

1 个赞

我明白了,所以大部分时间用 setq 还是没有任何问题的吧。不过话又说回来了,有没有什么方便的办法可以知道某个变量变动时还要顺便做别的动作呢?在下能想到的只有:

  • 看源码。不是很方便,特别是在 C 代码中定义的话。
  • 先用 setq 设置看看效果。这个让人不太放心。

Update:哦抱歉问了蠢问题 :joy: 只要像您的示例中那样查看 custom-set 属性就好了。再次感谢!

general.el 里面偷了一个非常好用的宏:

(defmacro general-setq (&rest settings)
  `(progn
     ,@(cl-loop for (var val) on settings by 'cddr
                collect `(funcall (or (get ',var 'custom-set) #'set)
				  ',var ,val))))

相当于 setq + 运行 setter,从文档来看比 customize-set-variables 快很多,而且可以像 setq 那样一次设定很多个变量。另外,简单地把 #'set 换成 #'set-default 就可以得到类似 setq-default 的版本。

@xuchunyang 您可以参考下这个。