《elisp 每日一练》——minor mode 开关变量的管理

上一节我们将定义 minor mode 的宏 define-minor-mode 展开并分析了它的作用。它主要是定义用于切换mode启用开关的变量和函数,以及在切换时进行模式的管理和hook的切换。默认ninor mode 创建时处于管理状态,而且minor mode启动时只对当前buffer生效。本节我们来讨论如何通过控制开关变量来改变这两点。

背景知识

:init-value

在介绍之前,我们补充一个知识点。那就是 define-minor-mode 的 :init-value 关键字。我们还是将下面的定义展开,看看与默认的相比多了什么部分的代码

emacs-lisp 复制代码
(define-minor-mode my/focus-mode
  "This is a focus mode"
  :init-value t)

展开之后得到的代码实在是太多了,我们就不一一介绍了。我们只看关键的部分

emacs-lisp 复制代码
(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 机制显示和管理的宏。

emacs-lisp 复制代码
(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 ,它的签名如下:

emacs-lisp 复制代码
(defgroup SYMBOL MEMBERS DOC &rest ARGS)

SYMBOL 表示自定义组的名称、MEMBERS 表示组中的成员、ARGS 也是一个由关键字组成的属性定义。常用的属性有:

  • :group 父组
  • :prefix 变量名前缀建议

其他的属性含义则与 defcustom 的相同。

现在我们来看看 define-minor-mode 是如何管理我们的customize 变量的。

emacs-lisp 复制代码
(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都启用,为了管理这种情况,宏也在其他地方进行了适配

emacs-lisp 复制代码
(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 定义组。

练习

我们来定义一个提醒休息的小功能,它的作用是每隔一段时间就提醒我们休息一下,我们没有学定时器相关的内容,所以只能做一个手动触发的功能。无法

emacs-lisp 复制代码
(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 为它设置一个组。

相关推荐
智购科技自动售货机工厂3 天前
数字人民币硬钱包支付失败,排查发现是NFC读卡器功率不足~YH
python·面试·架构·eclipse·emacs
Masimaro4 天前
《elisp 每日一练》—— define-derived-mode 管理 syntax-table
emacs
Masimaro12 天前
《elisp 每日一练》—— Major Mode 管理 abbrev-table
emacs
AI情绪识别开源14 天前
检信AI学业生涯智选系统技术方案概述
emacs
Masimaro17 天前
《elisp 每日一练》—— Minor Mode 回顾与综合练习
emacs
aluluka18 天前
《elisp 每日一练》——状态栏显示管理与modeline 动态更新
emacs·lisp·elisp
aluluka19 天前
《elisp 每日一练》——minor mode 快捷键管理
emacs·lisp·elisp
Masimaro19 天前
《elisp 每日一练》——状态栏显示管理与modeline 动态更新
emacs
Masimaro22 天前
《elisp 每日一练》—— Minor Mode 变量生成机制与状态控制
emacs