📝 docs(spec): design UTF-16 text coordinate conversion
This commit is contained in:
@@ -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 点位转换接口。
|
||||
- 新增坐标回归测试通过,相关服务器和协议测试通过。
|
||||
@@ -48,8 +48,8 @@
|
||||
## Workflow State
|
||||
|
||||
<!-- workflow-state:start -->
|
||||
phase: done
|
||||
spec: docs/superpowers/specs/2026-07-11-args-parser-startup-errors-design.md
|
||||
phase: planning
|
||||
spec: docs/superpowers/specs/2026-07-12-text-coordinates-utf16-design.md
|
||||
plan: docs/superpowers/plans/2026-07-11-args-parser-startup-errors.md
|
||||
executor: executing-plans
|
||||
constraints: karpathy-guidelines,.agents,AGENT_RULES
|
||||
|
||||
Reference in New Issue
Block a user