📝 docs(spec): design UTF-16 text coordinate conversion

This commit is contained in:
csh
2026-07-12 09:23:54 +08:00
parent 80fca5d11c
commit 5e84eb428e
2 changed files with 105 additions and 2 deletions
@@ -0,0 +1,103 @@
# 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 点位转换接口。
- 新增坐标回归测试通过,相关服务器和协议测试通过。
+2 -2
View File
@@ -48,8 +48,8 @@
## Workflow State ## Workflow State
<!-- workflow-state:start --> <!-- workflow-state:start -->
phase: done phase: planning
spec: docs/superpowers/specs/2026-07-11-args-parser-startup-errors-design.md spec: docs/superpowers/specs/2026-07-12-text-coordinates-utf16-design.md
plan: docs/superpowers/plans/2026-07-11-args-parser-startup-errors.md plan: docs/superpowers/plans/2026-07-11-args-parser-startup-errors.md
executor: executing-plans executor: executing-plans
constraints: karpathy-guidelines,.agents,AGENT_RULES constraints: karpathy-guidelines,.agents,AGENT_RULES