《elisp 每日一练》——mode hook

在前面,我们分析minor mode 和 major mode 时忽略了hook相关的内容,一笔带过了。现在我们有了hook相关的知识,今天带着这些知识在回过头来看mode中的hook是如何被定义和调用的

背景知识

minor mode

我们先从 minor mode 开始, 下面是我使用最基本的 define-minor-mode 得到的部分内容。

在mode 的开关变量中我们发现下面几处与 hook 相关的内容

emacs-lisp 复制代码
(run-hooks 'my/focus-mode-hook
                 (if my/focus-mode 'my/focus-mode-on-hook
                   'my/focus-mode-off-hook))

run-hooks 后面可以接入多个hooks变量,它会依次执行变量中保存的hook函数。所以从这段代码中我们可以知道,不管当前是准备开启或者关闭minor mode,都会无条件的执行 my/focus-mode-hook。然后根据开启或者关闭的状态执行 my/focus-mode-on-hook 或者 my/focus-mode-off-hook

在开关函数定义结束之后,还有下面的代码

emacs-lisp 复制代码
(defvar my/focus-mode-hook nil)
(unless (get 'my/focus-mode-hook 'variable-documentation)
(put 'my/focus-mode-hook 'variable-documentation
         "Hook run after entering or leaving `my/focus-mode'.\nNo problems result if this variable is not bound.\n`add-hook' automatically binds it.  (This is true for all hook variables.)"))
(put 'my/focus-mode-hook 'custom-type 'hook)
(put 'my/focus-mode-hook 'standard-value (list nil))

我们可以看到这里它采用 defvar 定义hook变量,但是后面又调用了 (put 'my/focus-mode-hook 'custom-type 'hook) 设置它在 customize 界面中的type。又设置了 standard-value 属性,这个也是 customize 变量的属性。实际上这里是将一个由 defvar 定义的普通变量转化成了 customize 类型的变量。

也就是说Emacs区分变量是否是 customize 变量并不是根据是否调用 defcusom 定义,而是根据变量属性中是否有对应的属性。至于这里为什么不采用 defcustom 来定义,我也不太清楚,如果有了解的读者可以在评论区留言讨论。

当然我们也可以在外部使用 defcustom 或者 defvar 定义同名的hook变量。后面的这些属性可以保证它被正确归类到 customize 变量中的 hook 类型。

我们只看到了 define-minor-mode 定义 my/focus-mode-hook 变量,而没有定义 my/focus-mode-on-hook 和 my/focus-mode-off-hook。这意味着,开启和关闭的事件交由设计者自行处理,如果设计时需要关心开启或者关闭事件可以定义这两个变量,否则可以忽略。

major mode

major mode 中我们已继承的模板进行展开,观察一下它里面有关hook的代码

首先我们可以看到,启动函数中,大部分的代码被包裹在 delay-mode-hooks 中,它可以保证所有hook 在代码运行之后在运行。所以在它里面调用父类的mode启动函数并不会执行函数中定义的hook。此时父类的hook在 delay-mode-hooks 执行的最后被调用。

而关于hook 的定义,我们可以找到

emacs-lisp 复制代码
(defvar my/foucs-mode-hook nil)
(unless (get 'my/foucs-mode-hook 'variable-documentation)
    (put 'my/foucs-mode-hook 'variable-documentation
         "Hook run after entering `my/foucs-mode'.\nNo problems result if this variable is not bound.\n`add-hook' automatically binds it.  (This is true for all hook variables.)"))

也就是说在major mode中,它不再帮我们管理 hook 的定义,强行将它变为 customize 类型。或者换一个角度思考,对于 major mode中的hook,Emacs的设计者似乎更愿意使用 defvar 形式的普通变量。

我们发现最终调用 hook时不再使用 run-hooks 而是调用 (run-mode-hooks 'my/foucs-mode-hook)。它与普通的 run-hooks 有什么区别呢?

run-hook 会立即调用,但是 run-mode-hooks 更加复杂一点,我们这里不详细分析它的代码,我们只需要知道它配合 delay-mode-hooks 使用时,如果在 delay-mode-hooks 内部,它会将所有的hook暂时保存起来,在下一次调用 run-mode-hooks 时再调用。

所以在这个基础上,我们可以这么理解调用hook的流程:

  • 调用父类的mode函数,最后会调用父类的 run-mode-hooks
  • 但是它父类的mode函数被放置在 delay-mode-hooks 内部,此时它会暂存需要执行的父类hook
  • delay-mode-hooks 内部开始执行子类的初始化代码
  • 子类的初始化代码执行完毕,此时跳出 delay-mode-hooks 宏
  • 在 delay-mode-hooks 宏外部,子类调用 run-mode-hooks
  • 此时会先调用之前被暂存的hook,然后调用子类的hook

总结

到此我们详细分析了major mode 和minor mode中有关hook的处理。现在总结如下:

  • minor mode 中推荐使用 customize 类型的hook 变量,并且我们只要定义同名的hook变量,它会自动帮我们进行一些处理。调用hook的过程为,每次切换mode状态都会无条件的调用普通hook,但是如果我们关系开启和关闭状态,它还额外有调用off-hook 和 on-hook的机制
  • major mode 中推荐使用 defvar 定义普通的hook变量,我们可以在外界定义同名的 customize 变量进行覆盖。在执行顺序上虽然会首先调用父类的mode函数,但是delay-mode-hooks 和最后调用的 run-mode-hooks 会保证在所有初始化都完成之后先调用 父类的hook、再调用子类hook
相关推荐
智购科技自动售货机工厂11 小时前
省电40%的底层逻辑:冷热双温售货机基于SaaS的智能温控算法~YH
单片机·嵌入式硬件·百度·架构·emacs
Masimaro3 天前
《elisp》 每日一练——hook 机制详解
emacs
Masimaro3 天前
《elisp 每日一练》——major mode 综合练习
emacs
aluluka6 天前
《elisp 每日一练》—— syntax table 与 font lock
emacs·lisp·elisp
Masimaro7 天前
《elisp 每日一练》—— 基于treesitter 的语法高亮
emacs
智购科技自动售货机工厂19 天前
数字人民币硬钱包支付失败,排查发现是NFC读卡器功率不足~YH
python·面试·架构·eclipse·emacs
Masimaro21 天前
《elisp 每日一练》—— define-derived-mode 管理 syntax-table
emacs
Masimaro1 个月前
《elisp 每日一练》—— Major Mode 管理 abbrev-table
emacs
AI情绪识别开源1 个月前
检信AI学业生涯智选系统技术方案概述
emacs