每种编程语言都有形式语法。编译器读取它,构建解析树,拒绝任何不符合的内容。编辑器完全无视语法,让你想输入什么就输入什么。

这种脱节是大量摩擦的惊人来源。自动补全建议的标识符在上下文中毫无意义。语法高亮在重构中途崩溃。编辑器眼睁睁看着你犯错,然后报出「Unexpected token」错误。我们将其视为正常,因为四十年来我们只有文本编辑器。

文本是一种中间表示。编译器在解析的那一刻就将其丢弃。那么我们为什么要编辑文本?

结构化编辑的实际含义

结构化编辑意味着真相来源是抽象语法树,而不是字符缓冲区。编辑器将树渲染为文本以方便你使用,但每个操作都直接操纵节点。FunctionDeclaration、BinaryExpression、IfStatement。这些就是原子。

想添加参数?你不是把光标定位在括号之间然后输入。你在 FunctionDeclaration 节点上调用「添加参数」操作。编辑器创建一个带有占位符的新 Parameter 节点。渲染的文本随之更新。你永远不会创建不平衡的括号,因为括号不是你输入的字符。它们是渲染器生成的视觉边界。

这不是科幻小说。自二十世纪六十年代以来,Lisp 就是这样工作的,因为 S 表达式按定义就是树。Smalltalk 将方法视为浏览器中的可选对象,而不是可 grep 的文本文件。更近一些,Unison 语言将代码存储为 AST 节点的 Merkle 树,JetBrains MPS 是一种投影编辑器,语法同时定义了语言和编辑体验。

结构安全保证

以下是树编辑器在重构时所做的。这个 Python 脚本近似其内部逻辑:

import ast

# The canonical representation is a tree, not text
tree = ast.parse("result = calculate(x, y)")

# Find the Call node and append a keyword argument
for node in ast.walk(tree):
    if isinstance(node, ast.Call):
        node.keywords.append(
            ast.keyword(arg='debug', value=ast.Constant(value=True))
        )

# Render to text only when needed (Python 3.9+)
print(ast.unparse(tree))
# result = calculate(x, y, debug=True)

该操作在结构上是安全的。你不会意外产生 calculate(x, y debug=True),因为 keywords 是一个类型化的 keyword 节点列表,而不是字符串缓冲区。渲染器插入逗号和等号。树始终语法有效。

这就是大多数格式化工具和代码检查工具已经工作的方式。Black、prettier 和 rustfmt 都解析到 AST,转换树,然后输出。区别在于它们读取文本、转换、再写入文本。树编辑器跳过了文本往返。

为什么没人使用树编辑器

如果这这么好,为什么每个专业开发者仍然使用 VS Code、Vim 或 Emacs?

交互速度是第一个原因。文本编辑针对一个紧密循环进行了优化:你想到了更改,手指输入字符,眼睛验证。每次按键都是相同的操作。在树编辑器中,「插入 if 语句」「重命名变量」「提取方法」都是不同的手势。你不能只是猛敲键盘。认知开销会累积。

生态系统壁垒是第二个原因。流水线中的每个工具都假设是文本。git diffgrep、GitHub 代码审查、代码搜索、互联网上的每个教程。结构化格式存在,但没有一个取代了源代码的原始文本。网络效应是绝对的。

视觉渲染问题是第三个原因。程序员以数十年排版惯例的文本形式阅读代码。当注释描述半个节点时,它放在哪里?当用户尚未完成指定其需求时,如何显示操作中的树?将树渲染为可读可编辑的文本比将文本渲染为树更难。

文本编辑器中的结构化命令

你不需要切换编辑器就能获得树感知的好处。多种工具已经在 AST 上运行,同时呈现文本界面。

Lisp 的 Paredit 和 Smartparens 将 S 表达式视为树。你可以将表达式「吞入」父作用域或将其「吐出」。括号仍然是文本,但编辑命令是树操作。你可以在从未产生语法错误的情况下移动整个子树。

现代 IDE 重构解析你的代码,构建 AST,确定哪些变量成为参数,并重写树。当你在 IntelliJ 中执行「提取方法」时,它并不是在进行文本替换。它正在执行结构转换并输出结果。

语言服务器为每个操作在内部构建 AST。转到定义、重命名和内联提示都遍历树。协议为兼容性传递文本位置,但智能是结构性的。

如何在不更换编辑器的情况下实验

如果你想缩小编写代码的方式与编译器理解代码的方式之间的差距,从这里开始:

  1. 使用 Python 的 ast 模块。 编写一个脚本,解析真实代码,用 ast.walk 遍历树并进行转换。大多数 Python 工具(flake8、mypy、Black)正是这样做的。

  2. 探索 Tree-sitter。 在在线游乐场中解析文件并检查具体语法树。看看解析器实际看到了什么。

  3. 尝试 Lisp 的结构化编辑。 如果你写 Clojure 或 Emacs Lisp,安装 Paredit。强迫自己一整天按表达式而不是字符导航。这种缓慢本身就是教育性的。

文本文件哪也不会去。工具太多,历史太长,肌肉记忆太深。但文本编辑与结构理解之间的差距是缺陷、摩擦和认知负荷的真正来源。你不需要树编辑器来修复它。你需要停止假装文本是真实的东西。