7

我正在致力于一个标准缩进和制表符为 3 个字符宽的项目,并且它使用 HTML、PHP 和 JavaScript 的组合。由于我对所有东西都使用 Emacs,并且只想要这个项目的 3 字符缩进,我在项目的根目录下设置了一个“.dir-locals.el”文件,以应用于它下面的所有文件/所有模式:

; Match projets's default indent of 3 spaces per level- and don't add tabs
(
 (nil .
        (
         (tab-width . 3)
         (c-basic-offset . 3)
         (indent-tabs-mode . nil)
         ))
 )

当我第一次打开文件时效果很好。切换主要模式时会出现问题 - 例如在 PHP 文件中处理一大块文字 HTML。然后我丢失了所有 dir-local 变量。

我还尝试明确说明我在“.dir-locals.el”中使用的所有模式,并添加到我的 .emacs 文件中“dir-locals-set-class-variables / dir-locals-set-directory-class ”。我很高兴地说它们都表现一致,最初设置 dir-local 变量,然后在我切换主模式时丢失它们。

我正在使用 GNU Emacs 24.3.1。

在切换缓冲区的主模式时重新加载 dir-local 变量的优雅方法是什么?

-- 编辑 -- 感谢 Aaron 和 phils 的出色回答和评论!在此处发布后,我认为它“闻起来”像一个错误,因此向 GNU 提交了一份报告——将向他们发送这些讨论的参考。

4

3 回答 3

15

根据对 Aaron Miller 的回答的评论,这里概述了调用模式函数时会发生什么(并解释了派生模式);手动调用模式与 Emacs 自动调用模式有何不同;以及在以下建议代码的上下文中的位置after-change-major-mode-hookhack-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-modewhich is derived from parent-modewhich 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真正运行钩子。在上面的例子中,这不是直到被调用。nilrun-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)

我们现在可以清楚地看到,如果我们这样做,有两种可能的注意顺序:

  1. 我们手动更改为foo-mode

    (foo-mode)
     => (kill-all-local-variables)
     => [...]
     => (run-hooks 'after-change-major-mode-hook)
         => (hack-local-variables)
    
  2. 我们访问一个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)))

这样做的缺点?

主要的一个是您不能再更改使用局部变量设置其主要模式的缓冲区的模式。或者更确切地说,由于局部变量处理,它会立即变回。

这并非不可能克服,但我暂时将其称为超出范围:)

于 2013-10-10T12:09:17.883 回答
2

请注意,我没有尝试过这个,所以它可能会产生不希望的结果,从你的 dir-local 变量没有被应用,到 Emacs 试图扼杀你的猫;根据对 Emacs 行为方式的任何合理定义,这几乎可以肯定是作弊。另一方面,这一切都在标准库中,所以它不可能是那么大的罪过。(我希望。)

评估以下内容:

(add-hook 'after-change-major-mode-hook
          'hack-dir-local-variables-non-file-buffer)

从那时起,当您更改主要模式时,应该(我认为)在更改后立即重新应用 dir-local 变量。

如果它不起作用或者您不喜欢它,您可以通过将 'add-hook' 替换为 'remove-hook' 并再次评估表单来撤消它而无需重新启动 Emacs。

于 2013-10-09T20:47:21.417 回答
1

我对此的看法:

(add-hook 'after-change-major-mode-hook #'hack-local-variables)

并且要么

(defun my-normal-mode-advice
    (function &rest ...)
  (let ((after-change-major-mode-hook
         (remq #'hack-local-variables after-change-major-mode-hook)))
    (apply function ...)))

如果你能忍受烦人

使 after-change-major-mode-hook 成为本地缓冲区,而本地让绑定!

留言或

(defun my-normal-mode-advice
    (function &rest ...)
  (remove-hook 'after-change-major-mode-hook #'hack-local-variables)
  (unwind-protect
      (apply function ...)
    (add-hook 'after-change-major-mode-hook #'hack-local-variables)))

否则,最后

(advice-add #'normal-mode :around #'my-normal-mode-advice)
于 2017-01-09T00:22:44.950 回答