根据 Cmets 对 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如何选择调用模式的详细信息,请参见Chig(elisp) Auto Major ModeRET kbd>。它实际上涉及一些早期的局部变量交互(它需要检查mode 变量,因此在设置模式之前会进行特定的查找),但是“正确的”局部变量处理会在之后发生。
当实际调用所选模式函数时,有一系列值得详细说明的巧妙事件。这就需要我们对“派生模式”和“延迟模式钩子”有一点了解……
派生模式和模式挂钩
大多数主要模式是用宏define-derived-mode 定义的。 (当然,阻止你只需编写 (defun foo-mode ...) 并做你想做的任何事情;但如果你想确保你的主要模式与 Emacs 的其余部分很好地配合,你将使用标准宏。)
当您定义派生模式时,您必须指定它派生的父模式来自。如果模式没有逻辑父模式,您仍然使用这个宏来定义它(为了获得所有标准的好处),您只需为父模式指定nil。或者,您可以将fundamental-mode 指定为父级,因为效果与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,因此这将是在整个序列中运行的第一个钩子(它发生在前一个主要模式仍然处于活动状态时,在任何代码之前新模式已被评估)。
所以这是发生的第一件事。模式函数所做的最后件事是为它自己的MODE-HOOK 变量调用(run-mode-hooks MODE-HOOK)(这个变量名实际上是模式函数的符号名,带有-hook 后缀)。
因此,如果我们考虑一个名为 child-mode 的模式,它派生自 parent-mode,而 grandparent-mode 派生自 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)))
这样做的缺点?
主要问题是您不能再更改使用局部变量设置其主要模式的缓冲区的模式。或者更确切地说,它会作为局部变量处理的结果立即变回。
这不是不可能克服的,但我暂时将其称为超出范围:)