4.3 KiB
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,从同一结果取得 offset 和
point。CalculateEndPoint 保持现有职责,按插入文本的 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 起始字节按单个字节处理,转换过程不得越过行尾或 文档末尾。
返回的 offset 与 point 必须始终指向同一个源码位置。有效 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-server
和 test_provider,运行 test_provider 以及 LSP JSON 测试,确认增量同步和
协议启动行为未回归。
完成条件
ToOffset对 UTF-16 BMP 与 supplementary-plane 字符返回正确 UTF-8 字节偏移。- 增量编辑的所有
TSInputEdit字节位置与点位单位一致。 - 不再存在只接收 LSP
Position的 Tree-sitter 点位转换接口。 - 新增坐标回归测试通过,相关服务器和协议测试通过。