上一节我们将定义 minor mode 的宏 define-minor-mode 展开并分析了它的作用。它主要是定义用于切换mode启用开关的变量和函数,以及在切换时进行模式的管理和hook的切换。默认ninor mode 创建时处于管理状态,而且minor mode启动时只对当前buffer生效。本节我们来讨论如何通过控制开关变量来改变这两点。
背景知识
:init-value
在介绍之前,我们补充一个知识点。那就是 define-minor-mode 的 :init-value 关键字。我们还是将下面的定义展开,看看与默认的相比多了什么部分的代码
(define-minor-mode my/focus-mode
"This is a focus mode"
:init-value t)展开之后得到的代码实在是太多了,我们就不一一介绍了。我们只看关键的部分
(defvar-local my/focus-mode t)默认定义时,切换开关的变量被定义成了 nil 也就是关闭状态。现在被设置成了打开。
其他的部分没有什么大的变化。所以据此我们可以得出结论 :init-value 用来控制定义时是否打开对应的mode。
customize 机制
现在我们正式开始讨论 Customize 机制。
Customize 机制是Emacs提供的一套配置UI框架,任何人都可以通过对应的函数将对应的变量作为配置通过UI界面显示内容并进行设置。我们可以通过 customize 函数打开该界面。
我们通过界面设置的值被保存到一个文件中,该文件由Emacs自己维护,默认放在我们的主配置文件中。我们可以通过 custom-file 设置这些值放置的位置。或者通过 custom-file 函数获取custom file配置文件的位置。
我们通过 customize 设置的值被放在 custom file 包裹在 custom-set-variables 函数中。
我们可以通过 defcustom 宏定义一个能被customize 机制显示和管理的宏。
(defcustom SYMBOL STANDARD DOC &rest ARGS)其中 symbol 是一个符号,表示一个 customize 类型的变量。STANDARD 表示默认值。DOC 表示变量得说明文档。ARGS 是一组由关键字组成得属性定义。常见得属性有:
:type变量得数据类型,有'string、'integer、'boolean'(choice ...):set是一个函数,当变量被赋值时会调用这个函数,通过函数来给变量赋值,而不是直接赋值。在函数中需要使用setq-default来为变量重新设置值,setq也会触发该函数,容易引起递归调用:version该标记用来提醒用户,这个变量得使用适用于哪个版本,可以显示Emacs版本或者对应 package 版本:tag覆盖显示界面中得变量名,用于给用户一个更好的提示:link在 Customize 缓冲区中显示一个可点击的链接,通常指向相关文档或 info 节点:group指定变量所属的自定义组。这将决定它在customize 树中显示的位置,一个变量可以属于多个组
Emacs 中customize group 作用是像文件目录一样,把成千上万的用户选项按类收集整理方便用户查看。Emacs 中有一些常用的默认组可以使用:
Editing:基础文本编辑功能相关的选项Convenience: 用于加快编辑功能的快捷选项External: 与外部工具交互的组
我们也可以自定义自己的组,自定义组使用 defgroup ,它的签名如下:
(defgroup SYMBOL MEMBERS DOC &rest ARGS)SYMBOL 表示自定义组的名称、MEMBERS 表示组中的成员、ARGS 也是一个由关键字组成的属性定义。常用的属性有:
:group父组:prefix变量名前缀建议
其他的属性含义则与 defcustom 的相同。
现在我们来看看 define-minor-mode 是如何管理我们的customize 变量的。
(defgroup my-focus-group nil
"My focus group"
:group 'tools
:prefix "my-focus-")
(define-minor-mode my/focus-mode
"This is my focus mode"
:init-value nil
:type 'boolean
:global t)我们展开这个宏,发现它将我们的切换开关变量改为了 (defcustom my/focus-mode nil),也就是它将变量设置成了一个customize 变量。如果我们在 gloabl 为 t 的情况下指定了 group 那么,它此时就是该组中的一个变量。
为什么只有gloabl 为 t 时才会设置为customize 变量呢,因为 minor mode 是挂载到单个buffer上的,它依附与buffer,所以默认情况下minor mode 的切换开关也是一个 buffer local 变量,如果我们希望切换对全局都有效。为了配置这种一处修改所有buffer都启用,为了管理这种情况,宏也在其他地方进行了适配
(when (boundp 'global-minor-modes)
(setq global-minor-modes
(delq 'my/focus-mode global-minor-modes))
(when (default-value 'my/focus-mode)
(push 'my/focus-mode global-minor-modes)))我们发现,它现在管理的不再是 local-minor-modes 而是全局的 global-minor-modes。
另外需要注意的是,既然我们的 minor mode 打开了 global 开关变成了全局可用的,那么为了一致性,mode 中使用的相关变量也尽量使用全局变量,如果使用 buffer local 变量会导致有的buffer有这个变量,而有的没有,或者不同buffer中同名的变量值不同,导致行为不一致。
如果我们希望定义customize 变量来控制minor mode 的一些行为,那么还是要回归到自己利用 defcustom defgroup 定义组。
练习
我们来定义一个提醒休息的小功能,它的作用是每隔一段时间就提醒我们休息一下,我们没有学定时器相关的内容,所以只能做一个手动触发的功能。无法
(defgroup my-timer-group nil
"自定义定时提醒模式的配置组"
:group 'convenience
:prefix "my-timer-")
(defcustom my-timer-message "时间到!休息一下吧"
"定时提醒时弹出的文本"
:type 'string
:group 'my-timer-group)
(defcustom my-timer-interval-seconds 30
"提醒时间间隔 (单位:秒)"
:type 'integer
:group 'my-timer-group)
(defcustom my-timer-enable-sound nil
"是否播放提示音"
:type 'boolean
:group 'my-timer-group)
(define-minor-mode my-timer-mode
"定时提醒"
:init-value nil
:group 'my-timer-group)
(defun my-timer-show-message()
(interactive)
"显示提示信息"
(let* ((buffer (get-buffer-create "MyTimer"))
(show-message (message "你已经坚持了%d, %s" my-timer-interval-seconds my-timer-message)))
(with-current-buffer buffer
(erase-buffer)
(goto-char (point))
(insert show-message))
(display-buffer buffer)))小结
本节我们讨论了minor mode 中是如何通过 :init-value 、 :global 和 :group 来管理开关变量。简单来说就是默认创建的 Minor Mode 都是关闭状态,想要创建时启动可以通过 :init-value 将它设置为 t,如果希望启用之后全局生效可以 设置 :global 为 t,它会将变量定义为 customize 中的变量,在这种情况下我们可以通过 :group 为它设置一个组。
评论 (0)