切换主模式时如何保持dir-local变量?

时间:2013-10-09 19:26:03

标签: emacs elisp

我正在致力于一个标准缩进和标签宽度为3个字符的项目,并且它使用HTML,PHP和JavaScript的混合。由于我将Emacs用于所有内容,并且只想要这个项目的3-char缩进,我在项目的根目录设置了一个“.dir-locals.el”文件,以应用于它下面的所有文件/所有模式: / p>

; 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-目录级”。我很高兴地说他们都表现得很一致,最初设置dir-local变量,然后在切换主模式时丢失它们。

我正在使用GNU Emacs 24.3.1。

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

- 编辑 - 感谢Aaron和phils的出色答案和评论!在这里发帖之后,我认为它“闻到”就像一个bug,因此向GNU发送报告 - 将向他们发送这些讨论的参考。

3 个答案:

答案 0 :(得分:12)

根据对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如何选择要调用的模式的详细信息,请参阅 Ch i g (elisp) Auto Major Mode RET 。它实际上涉及一些早期的局部变量交互(它需要检查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,因此这将是在整个序列期间运行的第一个挂钩(并且它在前一个主要模式仍然处于活动状态时发生已经评估了新模式的代码。)

这是第一件事。模式函数执行的 last 事件是为(run-mode-hooks MODE-HOOK)变量调用MODE-HOOK(此变量名称实际上是模式函数的符号名称,带有-hook后缀)。

因此,如果我们考虑一个名为child-mode的模式,该模式源自parent-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

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

  1. 我们手动更改为(foo-mode) => (kill-all-local-variables) => [...] => (run-hooks 'after-change-major-mode-hook) => (hack-local-variables)

    foo-mode
  2. 我们访问(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
  3. 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))) 执行时不会发生对函数的额外调用:

    {{1}}

    这方面的缺点?

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

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

答案 1 :(得分:2)

警告我我没有尝试过这个,所以它可能会产生不需要的结果,范围从你的目录变量未被应用到Emacs试图扼杀你的猫;通过对Emacs应该如何表现的任何合理定义,这几乎肯定是作弊。另一方面,它都在标准库中,所以它不能 很多罪。 (我希望。)

评估以下内容:

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

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

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

答案 2 :(得分: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 buffer-local置于本地let-bound!

消息或

(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)