切换主要模式时如何保留dir-local变量? [英] How to keep dir-local variables when switching major modes?

查看:129
本文介绍了切换主要模式时如何保留dir-local变量?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在致力于一个项目,标准的缩进和制表符是3个字符,它使用的是HTML,PHP和JavaScript。由于我使用Emacs的所有内容,只需要这个项目的3字符缩进,我在该项目的根目录下设置了一个.dir-locals.el文件,以应用于其下的所有文件/所有模式: / p>

 ;匹配项目的默认缩进每级别3个空格,并且不添加标签

(nil。

(tab-width。3)
-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局部变量的优雅方式?



感谢Aaron和phils的精彩答案和评论!在这里发布后,我以为它像一个bug那样闻起来,所以输入一个报告给GNU-会给他们参考这些讨论。

解决方案

根据Aaron Miller的回答,以下是一个调用模式函数时会发生什么的概述(对派生模式的解释);如何手动调用模式与Emacs自动调用不同;并且在上下文中, after-change-major-mode-hook hack-local-variables 以下建议的代码:

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

访问文件后,Emacs调用正常模式其中建立适当的主模式和缓冲区局部变量绑定为缓冲区。它首先调用 set-auto-mode ,然后立即调用 hack-local-variables 缓冲区的所有目录本地和文件局部变量,并相应地设置它们的值。



有关如何 set-auto-mode 选择要调用的模式,请参阅 Ch i g (elisp)自动主模式 RET 。它实际上涉及到一些早期的本地变量交互(它需要检查一个模式变量,所以有一个特定的查找,在模式设置之前发生)但是正确的局部变量处理发生在此之后。



当实际调用所选模式函数时,有一个很好的事件序列值得详细说明。这要求我们了解一些关于派生模式和延迟模式挂钩...



派生模式和模式挂钩



大多数主要模式都使用宏 define-derived-mode 定义。 (当然没有什么可以通过简单地编写(defun foo-mode ...),并且做任何你想要的,但是如果你想确保您的主要模式与Emacs的其余部分相当,您将使用标准宏。)



当您定义派生模式时,必须指定父模式它从导出。如果模式没有逻辑父级,您仍然使用此宏来定义它(为了获得所有的标准优点),并且您只需为父级指定 nil 。或者,您可以指定基本模式作为父级,因为效果与 nil 相同,因为我们



define-derived-mode 然后使用标准模板定义模式函数,调用模式函数时发生的第一件事是:

 (delay-mode-钩子
(PARENT-MODE)
,@ body
...)

或者没有设置父项:

 (delay-mode-hooks 
(kill-all-local-variables)
,@ body
...)

由于基本模式本身调用(kill-all-local-variables)然后立即返回在这种情况下被调用时,将其指定为父级的效果相当于如果父级为 nil



请注意, kill-all-在执行任何其他操作之前,局部变量运行 change-major-mode-hook ,这将是在整个序列中运行的第一个钩子(并且发生在上一个主要模式仍然有效时,在新模式的任何代码已被评估之前)。



所以这是第一件事情。模式功能所执行的最后一个事情是调用(run-mode-hooks MODE-HOOK)来自己的 MODE-HOOK 变量(此变量名字面上是模式函数的符号名称,带有 -hook 后缀)。



所以如果我们考虑从父模式 child-mode 的模式> 祖父模式,当我们调用(子模式)时,整个事件链看起来都是东西像这样:

 (delay-mode-hooks 
(delay-mode-hooks
(延迟模式钩子
(kill-all-local-variables);;运行change-major-mode-hook
,@ grandparent-body)
(run-
,@ parent-body)
(run-mode-hooks'parent-mode-hook)
,@ child-body)
(run-mode-hooks'child-mode-hook)

延迟模式钩做?它简单地绑定变量 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

  • 延迟模式挂钩(按其他方式运行的顺序)

  • HOOKS 作为 run-mode-hooks的参数

  • after-change-major-mode-hook



所以当我们调用(child-mode),完整的序列是:

 (run-hooks'change-major-mode-hook );;实际上由
(kill-all-local-variables)完成的第一件事< - 这个函数
,@ 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 是自动选择:

     (正常模式)
    = > (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 运行两次?也许没有。至少它的效率稍差一些,但这对大多数人来说可能并不重要。对我来说,主要的是,我不想依赖这种安排,在所有情况下,总是是正常的,因为它肯定不是预期的行为。



(个人我 do 实际上导致这种情况发生在某些具体情况下,它的工作正常;但当然这些情况很容易测试 - 而以标准的方式表示所有案例受到影响,测试是不切实际的。)



所以我会提出一个小小的调整技术,所以我们额外的调用函数不会发生如果正常模式正在执行:

 (defvar my-hack-local-variables-after-major-mode-change t 
是否在主要模式更改后处理局部变量
如果模式更改由正常模式触发,则通过建议禁用',
作为局部变量在这种情况下自动处理。)

(defadvice normal-mode(around my-do-no t-hack-local-variables-two)
防止`after-change-major-mode-hook'处理局部变量。
请参见`my-after-change-major-mode-hack-local-variables'。
(let((my-hack-local-variables-after-major-mode-change nil))
ad-do-it))
(广告激活'正常模式)

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

(defun my-after-change-major-mode-hack-local-variables()
回调
(当我的hack-local-variables-after-major-mode-change
(hack-local-variables)))

这个缺点?



是不能改变使用局部变量设置主模式的缓冲区的模式,或者是由于局部变量处理而立即改变。



这不是不可能克服的,但是我现在会把它称为范围:)


I'm committing to a project where standard indentations and tabs are 3-chars wide, and it's using a mix of HTML, PHP, and JavaScript. Since I use Emacs for everything, and only want the 3-char indentation for this project, I set up a ".dir-locals.el" file at the root of the project to apply to all files/all modes under it:

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

Which works fine when I first open a file. The problem happens when switching major modes- for example to work on a chunk of literal HTML inside of a PHP file. Then I lose all the dir-local variables.

I've also tried explicitly stating all of the modes I use in ".dir-locals.el", and adding to my .emacs file "dir-locals-set-class-variables / dir-locals-set-directory-class". I'm glad to say they all behave consistently, initially setting the dir-local variables, and then losing them as I switch the major mode.

I'm using GNU Emacs 24.3.1.

What's an elegant way of reloading dir-local variables upon switching a buffer's major-mode?

-- edit -- Thanks for the excellent answers and commentary both Aaron and phils! After posting here, I thought it "smelled" like a bug, so entered a report to GNU- will send them a reference to these discussions.

解决方案

As per comments to Aaron Miller's answer, here is an overview of what happens when a mode function is called (with an explanation of derived modes); how calling a mode manually differs from Emacs calling it automatically; and where after-change-major-mode-hook and hack-local-variables fit into this, in the context of the following suggested code:

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

After visiting a file, Emacs calls normal-mode which "establishes the proper major mode and buffer-local variable bindings" for the buffer. It does this by first calling set-auto-mode, and immediately afterwards calling hack-local-variables, which determines all the directory-local and file-local variables for the buffer, and sets their values accordingly.

For details of how set-auto-mode chooses the mode to call, see C-hig (elisp) Auto Major Mode RET. It actually involves some early local-variable interaction (it needs to check for a mode variable, so there's a specific look-up for that which happens before the mode is set), but the 'proper' local variable processing happens afterwards.

When the selected mode function is actually called, there's a clever sequence of events which is worth detailing. This requires us to understand a little about "derived modes" and "delayed mode hooks"...

Derived modes, and mode hooks

The majority of major modes are defined with the macro define-derived-mode. (Of course there's nothing stopping you from simply writing (defun foo-mode ...) and doing whatever you want; but if you want to ensure that your major mode plays nicely with the rest of Emacs, you'll use the standard macros.)

When you define a derived mode, you must specify the parent mode which it derives from. If the mode has no logical parent, you still use this macro to define it (in order to get all the standard benefits), and you simply specify nil for the parent. Alternatively you could specify fundamental-mode as the parent, as the effect is much the same as for nil, as we shall see momentarily.

define-derived-mode then defines the mode function for you using a standard template, and the very first thing that happens when the mode function is called is:

(delay-mode-hooks
  (PARENT-MODE)
  ,@body
  ...)

or if no parent is set:

(delay-mode-hooks
  (kill-all-local-variables)
  ,@body
  ...)

As fundamental-mode itself calls (kill-all-local-variables) and then immediately returns when called in this situation, the effect of specifying it as the parent is equivalent to if the parent were nil.

Note that kill-all-local-variables runs change-major-mode-hook before doing anything else, so that will be the first hook which is run during this whole sequence (and it happens while the previous major mode is still active, before any of the code for the new mode has been evaluated).

So that's the first thing that happens. The very last thing that the mode function does is to call (run-mode-hooks MODE-HOOK) for its own MODE-HOOK variable (this variable name is literally the mode function's symbol name with a -hook suffix).

So if we consider a mode named child-mode which is derived from parent-mode which is derived from grandparent-mode, the whole chain of events when we call (child-mode) looks something like this:

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

What does delay-mode-hooks do? It simply binds the variable delay-mode-hooks, which is checked by run-mode-hooks. When this variable is non-nil, run-mode-hooks just pushes its argument onto a list of hooks to be run at some future time, and returns immediately.

Only when delay-mode-hooks is nil will run-mode-hooks actually run the hooks. In the above example, this is not until (run-mode-hooks 'child-mode-hook) is called.

For the general case of (run-mode-hooks HOOKS), the following hooks run in sequence:

  • change-major-mode-after-body-hook
  • delayed-mode-hooks (in the sequence in which they would otherwise have run)
  • HOOKS (being the argument to run-mode-hooks)
  • after-change-major-mode-hook

So when we call (child-mode), the full sequence is:

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

Back to local variables...

Which brings us back to after-change-major-mode-hook and using it to call hack-local-variables:

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

We can now see clearly that if we do this, there are two possible sequences of note:

  1. We manually change to foo-mode:

    (foo-mode)
     => (kill-all-local-variables)
     => [...]
     => (run-hooks 'after-change-major-mode-hook)
         => (hack-local-variables)
    

  2. We visit a file for which foo-mode is the automatic choice:

    (normal-mode)
     => (set-auto-mode)
         => (foo-mode)
             => (kill-all-local-variables)
             => [...]
             => (run-hooks 'after-change-major-mode-hook)
                 => (hack-local-variables)
     => (hack-local-variables)
    

Is it a problem that hack-local-variables runs twice? Maybe, maybe not. At minimum it's slightly inefficient, but that's probably not a significant concern for most people. For me, the main thing is that I wouldn't want to rely upon this arrangement always being fine in all situations, as it's certainly not the expected behaviour.

(Personally I do actually cause this to happen in certain specific cases, and it works just fine; but of course those cases are easily tested -- whereas doing this as standard means that all cases are affected, and testing is impractical.)

So I would propose a small tweak to the technique, so that our additional call to the function does not happen if normal-mode is executing:

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

Disadvantages to this?

The major one is that you can no longer change the mode of a buffer which sets its major mode using a local variable. Or rather, it will be changed back immediately as a result of the local variable processing.

That's not impossible to overcome, but I'm going to call it out of scope for the moment :)

这篇关于切换主要模式时如何保留dir-local变量?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆