《elisp 每日一练》——使用正则表达式捕获组实现上下文精准高亮

《elisp 每日一练》——使用正则表达式捕获组实现上下文精准高亮

Masimaro
2026-10-09 / 0 评论 / 0 阅读 / 正在检测是否收录...

我们在上一节通过Emacs内置的font lock face 变量可以使用预定义的高亮显示face,如果需要修改高亮显示的颜色等可以考虑修改这些变量所在符号对应的face。但是我们上一节只实现了对于关键字这种类型的高亮,使用的是简单的正则表达式,主要匹配到对应的字符就高亮,相当于精确匹配。但是对于函数定义、变量定义、自定义类型这种高亮,前面的精确匹配就无能无力了,好在Emacs中我们可以使用正则表达式捕获组实现更加精确的匹配

基础知识

通过前面几节,我们发现定义语义与face高亮的映射关系实际上使用的是一个列表,每个列表组由匹配规则和对应的face符号组成。最简单的形式是一个cons cell

(REGEXP . FACE)

前面是一个正则表达式,后面跟一个face符号。我们在前面的练习中定义 while、for 等关键字时使用的是这种形式。

实际上它还支持正则表达式捕获组。它真正的形式实际为

(REGEXP SUBEXP FACE [OVERRIDE])

此时它变成了一个列表,在正则表达式和face中间多了一个 SUBEXP 表示捕获组的整数。OVERRIDE 表示是否覆盖之前已经被匹配并进行高亮的部分,如果设置为t表示强制覆盖。

如果一个正则表达式捕获的不同分组都是语法的一部分或者说要用不同的face显示,可以写为这样

(REGEXP
 (SUBEXP1 FACE1)
 (SUBEXP2 FACE2))

所以对于 int foo(...) 定义的函数来说,我们可以使用如下的高亮方案:

'("\\<\\([a-zA-Z0-9_]+\\)\\s-+\\([a-zA-Z0-9_]+\\)\\s-*("
  (1 font-lock-type-face)
  (2 font-lock-function-name-face))

首先是前面由任意字符组成,与函数名之间通过空白字符分隔。所以对于第一个捕获组 [a-zA-Z0-9_] 来说,表示类型名。后面一个捕获组 ([a-zA-Z0-9_]+\\) 表示函数名称,它与函数参数列表的 ( 之间可以有或者没有没有分隔符。

练习

这次的练习我们在之前的基础上进行一个扩充,我们对简单的无参函数进行着色,并且对于使用简单数据类型int、float、double、char 等进行定义的变量进行着色。

(defvar my-simplec-font-lock-keywords
  `(
    ("\\<\\(if\\|else\\|while\\|for\\|return\\)\\>" . font-lock-keyword-face)
    ("\\<\\(int\\|float\\|double\\|char\\|void\\|bool\\)\\>" . font-lock-type-face)
    ("\\<\\(true\\|false\\|null\\|NULL\\)\\>" . font-lock-constant-face)
    ("/\\*\\(?:.\\|\n\\)*?\\*/" 0 font-lock-comment-face)
    ("//.*$" 0 font-lock-comment-face)
    ;; 函数定义
    ("\\<\\([a-zA-Z0-9_]+\\)\\s-+\\([a-zA-Z0-9_]+\\)\\s-*("
     (1 font-lock-type-face)
     (2 font-lock-function-name-face))

    ;; 变量定义
    ("\\<\\(int\\|float\\|double\\|char)\\s-+\\([a-zA-Z0-9_]+\\)"
     (1 font-lock-keyword-face)
     (2 font-lock-variable-name-face))
    )
  "MySimpleC 的 Font Lock 正则匹配与 Face 绑定规则。")

(define-derived-mode my-simplec-mode text-mode "MySimpleC" ;; 这里为了避免 prog-mode 的干扰改成了 text-mode
  "配置简单的 Font Lock 关键字代码高亮"
  (setq-local font-lock-defaults '(my-simplec-font-lock-keywords nil nil)))
0

评论 (0)

取消