Files
tsl-devkit/docs/superpowers/specs/2026-07-12-text-coordinates-utf16-design.md
T

4.3 KiB
Raw Blame History

LSP UTF-16 文本坐标转换设计

背景

LSP Position.character 在服务端未协商其他编码时使用 UTF-16 code unit。 当前 lsp.utils.text_coordinates 按 Unicode 码点递增字符位置,并把 LSP 列号 直接作为 Tree-sitter 字节列。这会使 emoji 后的字节偏移发生偏移,也会使中文 或 emoji 前后的增量编辑向 Tree-sitter 提交错误点位。

当前 VSCode 扩展使用的 vscode-languageclient 9.0.1 只声明 utf-16,并拒绝 服务端选择其他位置编码,因此本设计完整支持 UTF-16,不增加编码协商。

目标

  • 将 LSP UTF-16 位置准确转换为 UTF-8 源码字节偏移。
  • 向 Tree-sitter 提供基于 UTF-8 字节的行列坐标。
  • 字节偏移和 Tree-sitter 点位来自同一次坐标解析,避免两套转换产生差异。
  • 为 ASCII、中文、emoji、跨行和越界位置增加自动化回归测试。

非目标

  • 不实现 UTF-8、UTF-16、UTF-32 的运行时协商。
  • 不修改 LSP 初始化能力。
  • 不处理 string.cppm 的空白裁剪或未使用接口。
  • 不改变源码的 UTF-8 存储方式。

坐标接口

导出一个同时包含字节偏移和 Tree-sitter 点位的解析结果:

struct BytePosition
{
    protocol::uinteger offset;
    TSPoint point;
};

BytePosition ToBytePosition(
    const protocol::Position& position,
    const protocol::string& content);

protocol::uinteger ToOffset(
    const protocol::Position& position,
    const protocol::string& content);

删除无法独立正确转换的 ToPoint(const Position&)。普通 provider 继续使用 ToOffset;增量解析调用 ToBytePosition,从同一结果取得 offsetpointCalculateEndPoint 保持现有职责,按插入文本的 UTF-8 字节更新 Tree-sitter 点位。

转换规则

转换从文档开头扫描到目标行,再扫描目标行中的 UTF-8 序列:

  • ASCII 和 BMP 码点消耗一个 UTF-16 code unit。
  • U+10000 及以上码点消耗两个 UTF-16 code unit。
  • Tree-sitter column 等于目标位置相对当前行首的 UTF-8 字节数。
  • 超过行尾或文档末尾的位置收敛到可表示的行尾或文档末尾。
  • 指向 UTF-16 surrogate pair 中间的位置收敛到该 Unicode 码点起点,绝不 返回 UTF-8 序列内部的字节偏移。
  • 不完整或无效的 UTF-8 起始字节按单个字节处理,转换过程不得越过行尾或 文档末尾。

返回的 offsetpoint 必须始终指向同一个源码位置。有效 LSP 位置保持 精确;无效或越界位置采用稳定的收敛行为,避免产生越界访问。

增量解析

SyntaxTree::ApplyEdit 对变更起点和终点分别调用 ToBytePosition

  • start_byte / old_end_byte 使用结果中的字节偏移。
  • start_point / old_end_point 使用同一结果中的 Tree-sitter 点位。
  • new_end_byte 仍由起始字节加插入文本字节长度得到。
  • new_end_point 仍由 CalculateEndPoint 计算。

文档内容在应用变更前传入,因此所有旧范围坐标都基于变更前文本解析。

测试设计

在现有 test_provider 测试程序中新增独立的文本坐标测试模块,避免复制整套 协议 Module 构建配置。测试至少覆盖:

  • ASCII 位置的字节偏移和 Tree-sitter 点位。
  • BMP 中文字符消耗一个 UTF-16 code unit、三个 UTF-8 字节。
  • emoji 消耗两个 UTF-16 code unit、四个 UTF-8 字节。
  • emoji 后的位置不会额外越过后续 ASCII 字符。
  • 第二行位置生成正确的绝对偏移、行号和字节列。
  • 行尾、文档末尾和超出范围的位置安全收敛。
  • surrogate pair 中间的位置不会落入 UTF-8 序列中间。
  • CalculateEndPoint 对多字节文本和换行继续产生 Tree-sitter 字节坐标。

测试先在当前实现上失败,再修改产品代码并确认通过。随后构建 tsl-servertest_provider,运行 test_provider 以及 LSP JSON 测试,确认增量同步和 协议启动行为未回归。

完成条件

  • ToOffset 对 UTF-16 BMP 与 supplementary-plane 字符返回正确 UTF-8 字节偏移。
  • 增量编辑的所有 TSInputEdit 字节位置与点位单位一致。
  • 不再存在只接收 LSP Position 的 Tree-sitter 点位转换接口。
  • 新增坐标回归测试通过,相关服务器和协议测试通过。