From 5e84eb428eacb95c967c1a3b82a583ffdf6abb59 Mon Sep 17 00:00:00 2001 From: csh Date: Sun, 12 Jul 2026 09:23:54 +0800 Subject: [PATCH] :memo: docs(spec): design UTF-16 text coordinate conversion --- ...026-07-12-text-coordinates-utf16-design.md | 103 ++++++++++++++++++ memory-bank/progress.md | 4 +- 2 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 docs/superpowers/specs/2026-07-12-text-coordinates-utf16-design.md diff --git a/docs/superpowers/specs/2026-07-12-text-coordinates-utf16-design.md b/docs/superpowers/specs/2026-07-12-text-coordinates-utf16-design.md new file mode 100644 index 0000000..9fde8c4 --- /dev/null +++ b/docs/superpowers/specs/2026-07-12-text-coordinates-utf16-design.md @@ -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 点位转换接口。 +- 新增坐标回归测试通过,相关服务器和协议测试通过。 diff --git a/memory-bank/progress.md b/memory-bank/progress.md index b58a45e..52386b2 100644 --- a/memory-bank/progress.md +++ b/memory-bank/progress.md @@ -48,8 +48,8 @@ ## Workflow State -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