对于编程语言而言,主模式的一个重要功能就是提供自动缩进。
该功能分为两部分:一是确定一行代码的正确缩进量,二是确定何时重新缩进该行。
默认情况下,每当你输入 electric-indent-chars 中的字符时,Emacs 都会重新缩进当前行,
该变量默认只包含换行符。主模式可以根据语言的语法向 electric-indent-chars 添加更多字符。
在 Emacs 中,确定正确缩进量由 indent-line-function 控制(see Indentation Controlled by Major Mode)。
在某些模式下,“正确”的缩进无法被可靠判定,典型情况是缩进本身具有语法意义,
多种缩进都合法但含义不同。这种情况下,该模式应当设置 electric-indent-inhibit,
确保不会违背用户意图反复自动缩进。
编写一个优秀的缩进函数往往比较困难,在很大程度上仍是一门需要经验的技巧。 许多主模式作者会先编写一个能处理简单情况的基础缩进函数, 例如直接参照上一行文本的缩进。对于大多数并非严格基于行的编程语言, 这种方式扩展性很差:想要改进这类函数以支持更多复杂场景,难度会越来越大, 最终会变成一个庞大、复杂、难以维护,以至于没人敢改动的缩进函数。
一个优秀且可维护的缩进函数通常需要根据语言语法真正解析文本。 幸运的是,不必像编译器那样做精细解析;但另一方面,缩进代码中内置的解析器 需要对语法不合法的代码有一定容忍度。
优秀且可维护的缩进函数通常分为两类: 一类是从某个安全起点 向前解析 直到目标位置,另一类是从目标位置 向后解析。 两者并没有绝对优劣:向后解析通常比向前解析更难,因为编程语言本身是按向前解析设计的; 但对缩进需求来说,它的优点是不需要猜测安全起点, 并且通常只分析最少的文本即可确定一行缩进, 因此缩进受前面无关代码中语法错误的影响更小。 而向前解析通常更简单,并且支持一次解析、高效地对整个区域重新缩进。
与其从零编写自己的缩进函数,通常更推荐复用已有实现或依赖通用缩进引擎。 遗憾的是这类引擎并不多。CC-mode 缩进代码(用于 C、C++、Java、Awk 等类似模式) 多年来已经变得更加通用,因此如果你的语言与其中某种语言较为相似, 可以尝试使用该引擎。另一个是 SMIE,它采用类似 Lisp 符号表达式的思路,并适配到非 Lisp 语言。 还有一种方式是依赖完备的解析器,例如 tree-sitter 库。