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

104 lines
4.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 点位的解析结果:
```cpp
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 点位转换接口。
- 新增坐标回归测试通过,相关服务器和协议测试通过。