在Emacs Lisp中突出显示非本地变量

4
在js2-mode中,全局变量会自动高亮显示: enter image description here 我想在Emacs lisp中做同样的事情。我希望能够在以下代码中高亮显示 `flymake-log-level` 和 `barr`:
(defun foo ()
  (let (bar baz)
    (setq baz flymake-log-level) ;; flymake-log-level isn't locally bound
    (setq barr (1+ flymake-log-level)))) ;; misspelled bar
3个回答

3
可以通过安装flycheck来利用字节编译器。Flycheck是flymake的替代品,支持Elisp。
对于第一个示例无济于事,但对于第二个示例将会有所帮助(假设必要的require已经存在)。
(require 'flymake)

(defun foo ()
  (let (bar baz)
    (setq baz flymake-log-level) ;; no complaints here
    (setq barr (1+ flymake-log-level)))) ;; assignment to free variable `barr'

还有一个hl-defined.el,它是一个次要模式,几乎完全符合问题描述。安装它后,在emacs-lisp-mode缓冲区中运行hdefd-highlight-mode。然后可以运行hdefd-cycle命令,直到只显示未定义的变量为止。这会产生如下结果:

hl-defined screenshot

(这并不完美,hl-defined无法识别fn是参数而不是自由变量,并且它将此处使用的参数与函数list混淆。但对于问题描述中的用例非常有帮助。)

最后,一些软件包包括它们定义的函数的突出显示。例如,dash.el提供了其函数的突出显示,以及在anaphoric宏中使用的变量名称(即它突出显示it)。

;; Enable syntax highlighting of dash functions
(eval-after-load "dash" '(dash-enable-font-lock))

1
感谢您使用我的项目 :) 不过需要做一个小修正:Flycheck并不是Flymake的分支。虽然最初它是基于Flymake的API构建的,但现在它已经成为了一种独立的、干净的实时语法检查实现。 - user355252

2
我认为,准确地突出变量绑定范围甚至是不可能的,或者至少不会涉及到字节编译器(或其部分),或重新实现Emacs Lisp语义的部分。
关键问题在于宏。在Emacs Lisp中,宏不是卫生的。因此,任何宏都可以引入任意的本地绑定。事实上,许多宏都这样做,例如标准库中的dolistcondition-casepcase,以及第三方库dash.el中的anaphoric列表处理函数等。
对于这些宏,仅从句法上下文中确定变量作用域变得不可能。看下面的例子:
(condition-case err
    (--each my-fancy-list
      (my-fancy-function it nil t))
  (error (message "Error %S happened: %s" (car err) (cadr err))))

不知道condition-case--each的情况下,errit是否局部绑定?如果是的话,在哪些子表达式中绑定,例如err在所有子表达式中都绑定,还是只绑定处理程序形式(后者是这种情况)?
要确定这种情况下的变量作用域,您需要维护一个详尽的宏白名单以及它们的绑定属性,或者您需要扩展宏以动态确定它们的绑定属性(例如在扩展的主体中查找let)。
这两种方法在实现时都需要大量的工作,并且存在缺点。宏定义的白名单几乎自然而然地是不完整、不正确和过时的(只需看一下pcase的复杂绑定语义),而扩展宏需要宏定义存在,这并不总是事实,例如如果您正在使用上述dash.el编辑Emacs Lisp,而实际上没有安装此库。
扩展宏可能是最佳的解决方案,而且更好的是,您不需要自己实现。Emacs Lisp字节编译器已经完成了这项工作,并警告有关自由变量引用以及启用词法绑定后未使用的词法变量。因此,请对文件进行字节编译
如果可以的话,最好避免在运行中的Emacs中调用byte-compile-file,而是编写一个Makefile在新的Emacs实例中进行字节编译,以获得一个干净的环境:
SRCS = foo.el
OBJECTS = $(SRCS:.el=.elc)

.PHONY: compile
compile : $(OBJECTS)

%.elc : %.el
    $(EMACS) -Q --batch -f batch-byte-compile $<

在更复杂的库中,使用-L标志来设置适当的load-path以进行编译。

非常好的观点,谢谢。我的Emacs配置实际上正在以不同的颜色(使用font-lock-variable-face)着色“它”,但我不确定哪个更改导致了这种情况。不过这与上下文无关。看起来flycheck是最佳选择。 - Wilfred Hughes
@WilfredHughes 这种高亮是由dash.el自身安装的。 - user355252

2

我认为这是相当大的工作量...

最好的方法是使用 font-lock-mode 并添加一个新规则。通常,规则包含用于匹配某些内容的正则表达式,但也可以使用函数来实现。该函数可以搜索标识符,对于它找到的每个标识符,它可以通过检查变量是否出现在参数列表、letdolist或类似结构中来检查其是否被本地绑定。

一个类似的包示例是 cwarn mode,它会突出显示 C 语言中表达式内的赋值等内容。


1
值得注意的是,js2-mode之所以能够像识别变量作用域这样的技巧,是因为与其他语法高亮模式不同,它实际上实现了完整的JavaScript解析器。要可靠地识别Elisp全局变量需要同样大量的代码... - dodgethesteamroller
3
Emacs自带了一个完整的Emacs Lisp解析器,可以通过syntax-ppss等函数进行访问。因此,至少解析工作已经为您完成了。通过special-variable-p函数可以轻松地识别已声明的全局变量(例如defvar和相似函数)。 - user355252
哦,我不知道 special-variable-p。所以肯定可以突出显示已声明的变量。 - Wilfred Hughes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接