如何在主模式钩子中访问目录本地变量?
我定义了一个名为 .dir-locals.el 的文件,里面有以下内容:
((python-mode . ((cr/virtualenv-name . "saas"))))
在我的 .emacs 文件里,我有一个函数用来获取这个值,并提供一个虚拟环境的路径:
(defun cr/virtualenv ()
(cond (cr/virtualenv-name (format "%s/%s" virtualenv-base cr/virtualenv-name))
((getenv "EMACS_VIRTUAL_ENV") (getenv "EMACS_VIRTUAL_ENV"))
(t "~/.emacs.d/python")))
最后,在我的 python-mode-hook 列表中,我有这个钩子函数:
(add-hook 'python-mode-hook 'cr/python-mode-shell-setup)
(defun cr/python-mode-shell-setup ()
(message "virtualenv-name is %s" cr/virtualenv-name)
(let ((python-base (cr/virtualenv)))
(cond ((and (fboundp 'ipython-shell-hook) (file-executable-p (concat python-base "/bin/ipython")))
(setq python-python-command (concat python-base "/bin/ipython"))
(setq py-python-command (concat python-base "/bin/ipython"))
(setq py-python-command-args '( "-colors" "NoColor")))
(t
(setq python-python-command (concat python-base "/bin/python"))
(setq py-python-command (concat python-base "/bin/python"))
(setq py-python-command-args nil)))))
当我打开一个新的 Python 文件时,cr/python-mode-shell-setup
记录的消息显示 cr/virtualenv-name
是 nil
。但是,当我用 C-h v 查看这个名字时,却得到了 "saas"。
显然,这里有一个加载顺序的问题;有没有办法让我的模式钩子语句能响应目录本地变量呢?
1 个回答
这个问题发生是因为 normal-mode
按顺序调用了 (set-auto-mode)
和 (hack-local-variables)
。
不过,hack-local-variables-hook
是在本地变量处理完后运行的,这样就有了一些解决方案:
第一个方案是让 Emacs 为每个主要模式运行一个新的“本地变量钩子”:
(add-hook 'hack-local-variables-hook 'run-local-vars-mode-hook) (defun run-local-vars-mode-hook () "Run a hook for the major-mode after the local variables have been processed." (run-hooks (intern (concat (symbol-name major-mode) "-local-vars-hook")))) (add-hook 'python-mode-local-vars-hook 'cr/python-mode-shell-setup)
(你原来的函数可以不做修改,直接用这个方法。)
第二个选项是利用
add-hook
的可选LOCAL
参数,这样可以让指定的函数只在当前缓冲区有效。用这种方法,你可以这样写你的钩子:(add-hook 'python-mode-hook 'cr/python-mode-shell-setup) (defun cr/python-mode-shell-setup () (add-hook 'hack-local-variables-hook (lambda () (message "virtualenv-name is %s" cr/virtualenv-name) (let ((python-base (cr/virtualenv))) (cond ((and (fboundp 'ipython-shell-hook) (file-executable-p (concat python-base "/bin/ipython"))) (setq python-python-command (concat python-base "/bin/ipython")) (setq py-python-command (concat python-base "/bin/ipython")) (setq py-python-command-args '( "-colors" "NoColor"))) (t (setq python-python-command (concat python-base "/bin/python")) (setq py-python-command (concat python-base "/bin/python")) (setq py-python-command-args nil))))) nil t)) ; buffer-local hack-local-variables-hook
也就是说,
python-mode-hook
首先运行,并为当前缓冲区注册了匿名函数到hack-local-variables-hook
;然后这个函数会在本地变量处理完后被调用。Lindydancer 的评论引出了第三种方法。这种方法没有前两种那么干净,但仍然很有趣。我不喜欢让
(hack-local-variables)
被调用两次的想法,但我发现如果你在缓冲区本地设置local-enable-local-variables
,就可以阻止(hack-local-variables)
做任何事情,所以你 可以 这样做:(defun cr/python-mode-shell-setup () (report-errors "File local-variables error: %s" (hack-local-variables))) (set (make-local-variable 'local-enable-local-variables) nil) (let ((python-base (cr/virtualenv))) ...))
显然,这会稍微改变正常的执行顺序,所以可能会有一些副作用。我担心如果文件中通过本地变量 注释 设置了相同的主要模式,这可能会导致无限递归,但实际上似乎并不是问题。
本地变量头部注释(例如
-*- mode: foo -*-
)是由(set-auto-mode)
处理的,所以这些没问题;但mode: foo
Local Variables:
注释似乎会有问题,因为它是由(hack-local-variables)
处理的,所以如果以这种方式设置模式,我担心会导致递归。实际上,我通过使用一个简单的函数作为“模式”来触发这个问题,这个函数只是尝试运行它的钩子;然而,用一个“合适”的模式测试并没有出现这个问题,所以在现实中可能是安全的。我没有进一步研究这个(因为其他两个解决方案比这个干净得多),但我猜延迟模式钩子的机制可能解释了这个问题?