在Neovim中支持LuaLaTeX高亮

1. 遇到的问题

我的博客文章里面的几何图形的源代码很多都使用了tkz-elements宏包。它利用Lua代码来求点的坐标,详细介绍见之前的文章

tkz-elements的缺点就是它的一些高级API还不稳定。

例如,这次我就发现之前写的很多代码都没法正常编译了,细查原来是它的c_l_pp函数(过直线外两点作与已知直线相切的圆)的语法变了。

不过在使用Neovim查看源代码的时候有一个问题,就是\directlua内部的Lua代码无法正确高亮。

最近在迁移nvim-treesitter的时候,发现Neovim的Tree-sitter支持注入功能(:h treesitter-language-injection),可以在一种语言的源代码中高亮另外一种语法。

例如,可以在HTML文件的<script>标签中高亮Javascript代码。

因此,我决定尝试解决这个问题。

第一个尝试是问AI,结果全军覆没,包括Claude/ChatGPT/DeepSeek/Qwen,给出的结果都不行。

这又印证了我对于AI的印象:

对于一些小众的需求,AI几乎没有办法完全解决问题。

然后我发现,Zed的LaTeX插件支持高亮\directlua,但是尝试把它对应的注入代码复制过来,也还是不行。

无奈,只好再去翻文档

2. 解决方法

这里先给出完整的正确代码,放到~/.config/nvim/after/queries/latex/injections.scm文件内即可:

1
2
3
4
5
6
7
8
; extends
((generic_command
command: (command_name) @_command
arg: (curly_group) @injection.content)
(#set! injection.language "lua")
(#set! injection.include-children)
(#offset! @injection.content 0 1 0 -1)
(#any-of? @_command "\\directlua" "\\latelua"))

其中最关键的一句是:(#set! injection.include-children)

这个命令的作用是把@injection.content节点的全部文本(包括子节点)全部重新解析。

所有的AI都栽在这句话上了。

Claude本来在补充了:InspectTree的信息之后想到了这一点,但是在解决花括号问题的时候,觉得这句话没用,结果又给扔了。

这次任务对AI的最终评分:Claude > ChatGPT > DeepSeek > Qwen。

  • Claude(Sonnet 4.6)最接近于解决问题。
  • 只有ChatGPT(GPT-5.5)想到了要处理多余的花括号的问题。
  • Qwen(3.7-Max)是唯一一个连匹配节点都没写对的。

之前我就发现,对于一些比较新的技术问题(例如C++20),Claude和ChatGPT的回答比DeepSeek和Qwen要靠谱很多。

猜测是中英文语料的原因。

3. 详细解释

第一句; extends是要求使用追加而不是覆盖模式。

接下来是匹配节点:

1
2
3
(generic_command
command: (command_name) @_command
arg: (curly_group) @injection.content)

特别注意最后一行,这是把\directlua { ... }的整个花括号区域都捕获到@injection.content里面。

Zed的代码和Qwen的代码都是在这里出了问题,写成

1
arg: (curly_group) (_) @injection.content

1
arg: (curly_group) (text) @injection.content

结果都不对。

下一句(#set! injection.language "lua")将匹配到的代码使用Lua语法进行解析。

再下一句前面解释过了。

然后(#offset! @injection.content 0 1 0 -1)是为了将匹配到的花括号去掉,只保留花括号内部的文本。

最后一句是确认前面的command_name匹配到的是\directlua等命令。

4. 关于注释

需要注意的是,\directlua里面仍然使用LaTeX的%作为注释标记,这会导致注释部分无法正确解析为Lua语法。

但是如果直接使用Lua的--作为注释标记,又会导致文件无法编译。

解决方法是使用catcode,这样就可以正常编译和高亮了:

1
2
3
4
5
6
\catcode`\^^M=12
\directlua {
local a = 1
-- this is a comment
a = 2
}

如果怕影响其它部分,可以再使用\begingroup ... \endgroup保护起来。

5. 另外一种解决方法

后来我发现,tree-sitter-latex是支持luacode环境的。因此可以不使用\directlua,而是改为使用lucode环境。

需要注意的是,luacode环境由luacode宏包提供,需要提前加载:

1
\usepackage{luacode}

然后使用:

1
2
3
4
5
\begin{luacode}
init_elements()
z.O = point(0,0)
...
\end{luacode}

这样不需要任何其它修改,Neovim和VSCode都能正确高亮里面的Lua代码了!

不知道为什么,\directlua在我的VSCode中不能正确高亮。

理论上,LaTeX-Workshop插件是支持高亮这个语法的。

破案了,两个原因。

其一,很久远之前,我安装了LaTeX language support插件,这个已经过时了。需要卸载这个插件。

其二,LaTeX-Workshop插件要求\directlua{{前面不能有空格。删掉这中间的空格之后就能正确高亮了。