【问题标题】:How to keep dir-local variables when switching major modes?切换主要模式时如何保留 dir-local 变量?
【发布时间】:2013-10-17 07:59:08
【问题描述】:

我正在致力于一个标准缩进和制表符宽度为 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”中使用的所有模式,并将“dir-locals-set-class-variables / dir-locals-set-”添加到我的 .emacs 文件中目录类”。我很高兴地说它们都表现一致,最初设置 dir-local 变量,然后在我切换主要模式时丢失它们。

我使用的是 GNU Emacs 24.3.1。

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

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

【问题讨论】:

  • 一般情况下,您希望挂钩after-change-major-mode-hook 以在更改后重新读取目录局部变量,例如(add-hook 'after-change-major-mode-hook 'reread-dir-locals) -- 问题是没有函数reread-dir-locals,而且我在 Elisp 手册中没有找到任何好的方法来告诉 Emacs 它应该重新应用 dir-local 变量。我现在没有时间去挖掘 Emacs Lisp 库的源代码来找出它们在哪里以及如何被读取,但也许这会给你一个开始的地方。

标签: emacs elisp


【解决方案1】:

根据 Cmets 对 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如何选择调用模式的详细信息,请参见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-hooksnilrun-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)))

这样做的缺点?

主要问题是您不能再更改使用局部变量设置其主要模式的缓冲区的模式。或者更确切地说,它会作为局部变量处理的结果立即变回。

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

【讨论】:

  • 您中肯的解释对我非常有吸引力,我希望订阅您的时事通讯。 (我也将通过你在 Emacs 上的 SO 答案来解决问题,因为 wow。)
  • 谢谢你的夸奖。这将是我更冗长的努力之一,所以如果您希望从我的所有答案中获得相同级别的详细信息,您会非常失望:) 但我确实有我的时刻。我经常在回答问题时学到一些新东西,所以如果你去挖掘它们,我希望你也能这样做。
  • n.b.在 Emacs 26.1 中,该序列在调用 hack-local-variables 的方式和时间方面发生了一些变化。详情请参阅commit 25f455815bfacommit 26171e02773b
【解决方案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。

【讨论】:

  • 您肯定只是想要hack-local-variables,而不是您正在调用的函数?更改模式会杀死 所有 局部变量,因此您需要考虑的不仅仅是 dir-locals。
  • FWIW 我有一个 dir-local 配置,对于特定模式,它会评估不同的模式,然后 (hack-local-variables) 以获取其他模式的配置。我第一次尝试时有点困惑,但效果很好。
  • @phils 我可能确实想要hack-local-variables,但我不知道;我实际上并没有使用 dir-local 或 file-local 变量,所以 Emacs 的这种特殊裂缝对我来说是新的。 (没有一个最近似乎对看我工作很感兴趣的人,我是我组织中唯一的 Emacs 用户;配置我的 Emacs 以匹配本地约定比到处散布本地文件要容易得多,而且也让 vi 用户不会看我搞笑。嗯,至少比他们现在更频繁。)
  • @phils 实际上,我对此不太确定。文件局部变量 alist 是否在主要模式更改时被清空,或者 Emacs 只是在更改后不重新应用它?在前一种情况下,有必要重新读取 dir-local 变量,hack-dir-local-variables-non-file-buffer 会这样做,hack-local-variables 似乎不会。
  • fundamental-mode(这是任何标准主要模式的最终基础)调用kill-all-local-variables
【解决方案3】:

我对此的看法:

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

【讨论】:

    猜你喜欢
    • 2013-10-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-10
    • 2014-10-22
    • 2022-12-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多