《elisp 每日一练》—— 关键字高亮

《elisp 每日一练》—— 关键字高亮

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

在节前的一篇文章中,我们探讨了fontlock 的基本机制。它主要起到一个桥梁的作用,将语法块和face样式做一个绑定,未来在扫描时发现语法块符合匹配时就采用对应的样式显示文本。

背景知识

符号与 face 绑定

在前面我们已经使用过face,我们知道要设置文本的显示样式,修改修改它的属性,而文本属性中有 face、font-lock-face、mouse-face 和 cursor 等等各种face。那么face 到底是什么?以及如何设置、修改一个face 呢?

首先我在之前介绍Emacs基础语法的文章中提到,elisp中的符号这一概念。符号是一个容器,你可以将它想象成一个柜子,里面有若干个格子。每个格子可以存储不同的内容。对于elisp 来说,符号可以存储具体的值, 这种情况是它最像变量的一种情况了。

(set 'num 10)
num ;; ==> 10

我们使用 setq 定义变量时执行的就是类似于 set 指令。setq 的全称是 set quote,它在一个符号存储值的格子中放入一个值。

另一种情况,格子可以放下函数定义,这个时候我们通过(sym) 调用函数,实际上就是在查找 sym 符号中函数格子里面的放入的内容。

它还可以放置一个 plist 属性列表,这个属性列表我们可以存储它的文档信息,也可以存储一个face表。

我们可以通过 defface 定义一个face

(defface v
  '((t :foreground "red"))
  "My face named v.")

但是此时产生的效果与 setq 不同,setq 是创建一个符号,并在符号 value 槽中添加一个值。但是符号中并没有名为 face 的槽,defface 是在 外部的 face 映射表中添加了一条记录,由符号 v 作为键,而我们定义的这个face 样式表作为值。在后续查询的时候实际上是通过在表中查找对应符号名是否绑定了face条目。

我们可以使用 face-attribute 来查询。通过 set-face-attribute 来修改。

这里我们只需要记住face 本身并没有绑定到一个变量上或者符号上,我们将face绑定到具体符号上,并不会影响符号之前保存的内容。至于更详细的内容,读者可以自行查阅相关文档。

Emacs 内置的 font-lock-face

首先,我们在上一篇的例子中定义了名为 my-c-block-comment-face 的变量,作为注释的face。Emacs提供了一系列内置的准的 Font Lock Face。为了使我们开发的mode能轻松适配其他的theme,我们应该尽量使用Emacs自定的这些变量名。而如果我们要开发新的theme,也可以只修改这些变量的值。我们列举一些常用的:

  • font-lock-keyword-face 关键字,例如 this、class、return 等等
  • font-lock-type-face 数据类型或者类名
  • font-lock-variable-name-face 变量名
  • font-lock-function-name-face 函数名或者方法名
  • font-lock-constant-face 常量或者字面量
  • font-lock-builtin-face 语言内置函数或者指令
  • font-lock-comment-face 注释

以 font-lock-keyword-face 为例,我们来看看该如何使用它们。我们查看它的定义发现,它被定义在Emacs的 font-lock.el 文件中:

(defvar font-lock-keyword-face        'font-lock-keyword-face
  "Face name to use for keywords.")

我们发现这个变量被定义为了同名的符号。对它求值我们得到这个符号本身。

对于c/c++ 我们可以这样定义关键字到具体face 的映射关系

("\\<\\(if\\|else\\|while\\|for\\|return\\)\\>" . font-lock-keyword-face)

这里我们定义了 if、else、while、for、return 关键字对应的face。

因为使用Emacs定义了 font-lock-keyword-face 变量实际返回的是同名的符号,所以它实际上对应着我们在在上一篇文章中定义的规则:

("/\\*\\(?:.\\|\n\\)*?\\*/" 0 'my-c-block-comment-face t)

Emacs 在实际渲染是,查询到这些关键字都被映射到一个名为 font-lock-keyword-face 的符号上,它会查找外部face映射表中是否有对应符号的face。如果有则采用face中的定义来渲染。Emacs已经为它提供了默认的face

(defface font-lock-keyword-face
  '((((class grayscale) (background light)) :foreground "LightGray" :weight bold)
    (((class grayscale) (background dark))  :foreground "DimGray" :weight bold)
    (((class color) (min-colors 88) (background light)) :foreground "Purple")
    (((class color) (min-colors 88) (background dark))  :foreground "Cyan1")
    (((class color) (min-colors 16) (background light)) :foreground "Purple")
    (((class color) (min-colors 16) (background dark))  :foreground "Cyan")
    (((class color) (min-colors 8)) :foreground "cyan" :weight bold)
    (t :weight bold))
  "Font Lock mode face used to highlight keywords."
  :group 'font-lock-faces)

所以我们如果想要修改显示效果,只需要通过 defface 来进行绑定,或者通过 set-face-attribute 设置。

练习

本次的练习,我们尝试通过上面介绍的变量定义一个简单的c语言的语法高亮:

  • 以关键字的形式显示 if、else、while、for、return、break、continue
  • 显示简单类型的语法高亮, int、float、double、char、void、bool
  • 高亮显示常量, true、false、null、NULL
  • 显示注释的高亮, 行注释和块注释,都是同一种样式
(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)
    )
  "MySimpleC 的 Font Lock 正则匹配与 Face 绑定规则。")

(define-derived-mode my-simplec-mode prog-mode "MySimpleC"
  "配置简单的 Font Lock 关键字代码高亮"
  (setq-local font-lock-defaults '(my-simplec-font-lock-keywords nil nil)))

总结

今天我们讨论了symbol 和 face 的绑定关系,symbol 本身可以绑定一个值、一个函数和一个plist 结构,但是无法绑定face,通过 defface 实际上是在外部建立了一个symbol 和 face 映射。在需要使用face时会查找这张表。

另外Emacs中提供了一系列默认的 font lock face变量,它们的值是同名的 symbol,我们直接使用它来进行语法层面的关联。如果开发theme可以修改这些符号绑定的 face 即可

0

评论 (0)

取消