根据对 Aaron Miller 的回答的评论,这里概述了调用模式函数时会发生什么(并解释了派生模式);手动调用模式与 Emacs 自动调用模式有何不同;以及在以下建议代码的上下文中的位置after-change-major-mode-hook
和hack-local-variables
适合度:
(add-hook 'after-change-major-mode-hook 'hack-local-variables)
访问一个文件后,Emacs 调用normal-mode
它为缓冲区“建立正确的主模式和缓冲区局部变量绑定”。它首先调用set-auto-mode
,然后立即调用hack-local-variables
,确定缓冲区的所有目录本地和文件本地变量,并相应地设置它们的值。
关于如何set-auto-mode
选择调用模式的详细信息,请参见C-hig (elisp) Auto Major Mode
RET。它实际上涉及一些早期的局部变量交互(它需要检查一个mode
变量,因此在设置模式之前发生的事情有一个特定的查找),但是“正确的”局部变量处理发生在之后。
当实际调用选定的模式函数时,有一系列值得详细说明的巧妙事件。这需要我们对“派生模式”和“延迟模式钩子”有一点了解……
派生模式和模式挂钩
大多数主要模式都是用宏定义的define-derived-mode
。(当然,没有什么能阻止你简单地编写(defun foo-mode ...)
和做任何你想做的事情;但如果你想确保你的主要模式与 Emacs 的其余部分很好地配合,你将使用标准宏。)
定义派生模式时,必须指定它派生自的父模式。如果模式没有逻辑父模式,你仍然使用这个宏来定义它(为了获得所有标准的好处),你只需nil
为父模式指定。或者,您可以指定fundamental-mode
为父级,因为效果与 for 非常相似nil
,我们稍后会看到。
define-derived-mode
然后使用标准模板为您定义模式函数,调用模式函数时发生的第一件事是:
(delay-mode-hooks
(PARENT-MODE)
,@body
...)
或者如果没有设置父母:
(delay-mode-hooks
(kill-all-local-variables)
,@body
...)
在这种情况下fundamental-mode
调用它本身(kill-all-local-variables)
并在调用时立即返回,将其指定为父级的效果等同于如果父级是nil
。
请注意,在执行任何其他操作之前kill-all-local-variables
运行change-major-mode-hook
,因此这将是在整个序列期间运行的第一个钩子(并且它发生在前一个主要模式仍处于活动状态时,在新模式的任何代码被评估之前)。
所以这是发生的第一件事。模式函数所做的最后一件事是调用(run-mode-hooks MODE-HOOK)
它自己的MODE-HOOK
变量(这个变量名实际上是模式函数的符号名,带有-hook
后缀)。
因此,如果我们考虑一个名为child-mode
which is derived from parent-mode
which is derived from的模式grandparent-mode
,我们调用时的整个事件链(child-mode)
看起来像这样:
(delay-mode-hooks
(delay-mode-hooks
(delay-mode-hooks
(kill-all-local-variables) ;; runs change-major-mode-hook
,@grandparent-body)
(run-mode-hooks 'grandparent-mode-hook)
,@parent-body)
(run-mode-hooks 'parent-mode-hook)
,@child-body)
(run-mode-hooks 'child-mode-hook)
做什么delay-mode-hooks
?它只是绑定变量delay-mode-hooks
,由run-mode-hooks
. 当此变量为非nil
时,run-mode-hooks
只需将其参数推送到要在将来某个时间运行的钩子列表中,然后立即返回。
只有在什么时候才会delay-mode-hooks
真正运行钩子。在上面的例子中,这不是直到被调用。nil
run-mode-hooks
(run-mode-hooks 'child-mode-hook)
对于 的一般情况(run-mode-hooks HOOKS)
,以下钩子按顺序运行:
change-major-mode-after-body-hook
delayed-mode-hooks
(按照他们本来应该运行的顺序)
HOOKS
(作为 的论据run-mode-hooks
)
after-change-major-mode-hook
所以当我们调用 时(child-mode)
,完整的序列是:
(run-hooks 'change-major-mode-hook) ;; actually the first thing done by
(kill-all-local-variables) ;; <-- this function
,@grandparent-body
,@parent-body
,@child-body
(run-hooks 'change-major-mode-after-body-hook)
(run-hooks 'grandparent-mode-hook)
(run-hooks 'parent-mode-hook)
(run-hooks 'child-mode-hook)
(run-hooks 'after-change-major-mode-hook)
回到局部变量...
这让我们回到after-change-major-mode-hook
并使用它来调用hack-local-variables
:
(add-hook 'after-change-major-mode-hook 'hack-local-variables)
我们现在可以清楚地看到,如果我们这样做,有两种可能的注意顺序:
我们手动更改为foo-mode
:
(foo-mode)
=> (kill-all-local-variables)
=> [...]
=> (run-hooks 'after-change-major-mode-hook)
=> (hack-local-variables)
我们访问一个foo-mode
自动选择的文件:
(normal-mode)
=> (set-auto-mode)
=> (foo-mode)
=> (kill-all-local-variables)
=> [...]
=> (run-hooks 'after-change-major-mode-hook)
=> (hack-local-variables)
=> (hack-local-variables)
这是一个hack-local-variables
运行两次的问题吗?也许,也许不是。至少它的效率有点低,但这对大多数人来说可能不是一个重要的问题。对我来说,主要的是我不想依赖这种安排在所有情况下都很好,因为这肯定不是预期的行为。
(我个人确实在某些特定情况下确实会导致这种情况发生,并且效果很好;但当然,这些情况很容易测试——而作为标准这样做意味着所有情况都会受到影响,并且测试是不切实际的。)
所以我建议对这项技术做一个小的调整,这样我们对函数的额外调用就不会在normal-mode
执行时发生:
(defvar my-hack-local-variables-after-major-mode-change t
"Whether to process local variables after a major mode change.
Disabled by advice if the mode change is triggered by `normal-mode',
as local variables are processed automatically in that instance.")
(defadvice normal-mode (around my-do-not-hack-local-variables-twice)
"Prevents `after-change-major-mode-hook' from processing local variables.
See `my-after-change-major-mode-hack-local-variables'."
(let ((my-hack-local-variables-after-major-mode-change nil))
ad-do-it))
(ad-activate 'normal-mode)
(add-hook 'after-change-major-mode-hook
'my-after-change-major-mode-hack-local-variables)
(defun my-after-change-major-mode-hack-local-variables ()
"Callback function for `after-change-major-mode-hook'."
(when my-hack-local-variables-after-major-mode-change
(hack-local-variables)))
这样做的缺点?
主要的一个是您不能再更改使用局部变量设置其主要模式的缓冲区的模式。或者更确切地说,由于局部变量处理,它会立即变回。
这并非不可能克服,但我暂时将其称为超出范围:)