在前面,我们分析minor mode 和 major mode 时忽略了hook相关的内容,一笔带过了。现在我们有了hook相关的知识,今天带着这些知识在回过头来看mode中的hook是如何被定义和调用的
背景知识
minor mode
我们先从 minor mode 开始, 下面是我使用最基本的 define-minor-mode 得到的部分内容。
在mode 的开关变量中我们发现下面几处与 hook 相关的内容
(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
在开关函数定义结束之后,还有下面的代码
(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 的定义,我们可以找到
(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
评论 (0)