llm 商业版中发现 LLM 回复中的 emoji(如 😊)显示为乱码;在 mogan 自带的
llm 假插件(TeXmacs/plugins/llm,Insert → Session → LLM)可稳定复现。
用户提供的 你是谁.tmu 中,原应为 😊 的位置存储的是 C3 BF C2 98 C2 8A
(即 U+00FF U+0098 U+008A),屏幕上显示为 ÿ 加两个缺字形方块。
Mogan 会话输出链路:插件子进程 stdout → texmacs_input_rep::utf8_flush
(src/Data/Convert/Generic/input.cpp)→ tree_utf8_to_herk → 文档内部
herk 编码。UTF-8 → herk 方向(lolly::data::utf8_to_herk)把映射不到单字节的
码点正确逃逸为 <#XXXX>(如 😊 U+1F60A → <#1F60A>)。
但反向 lolly::data::herk_to_utf8 的辅助函数 append_utf8_code
(lolly/lolly/data/herk.cpp)只有 1/2/3 字节 UTF-8 分支,缺 4 字节分支。
对 ≥ U+10000 的码点按 3 字节形式截断编码:
- U+1F60A:
0xE0|(code>>12)=0xFF、0x80|((code>>6)&0x3F)=0x98、0x80|(code&0x3F)=0x8A → 输出非法 UTF-8 字节FF 98 8A; - 下游(聊天 tab 显示、.tmu 保存)把这三个坏字节按 Latin-1 逐字节再编码为
C3 BF C2 98 C2 8A,与 .tmu 文件中观察到的乱码逐字节吻合。
对比:cork 侧同功能的 cork_to_utf8 走 encode_as_utf8
(lolly/lolly/data/unicode.cpp),有完整 4 字节分支,不受影响;
全库仅 herk.cpp 的 append_utf8_code 一处缺该分支。
受影响路径(均经 herk_to_utf8/tree_herk_to_utf8):会话/聊天输出还原、
connection.cpp:266 插件输入序列化、edit_complete.cpp:359、
QTMMenuhelper.cpp:93、smart_font.cpp:865,以及 Scheme glue
herk->utf8 / tree-herk->utf8-tree。
lolly/lolly/data/herk.cpp:append_utf8_code补上code < 0x10000的 3 字节分支与code <= 0x1FFFFF的 4 字节分支, 超出 Unicode 上限的输入不再编码(与encode_as_utf8行为对齐)。
lolly/tests/lolly/data/herk_test.cpp新增herk_4byte_codepoints用例: U+1F60A/U+1F642/U+20000/U+10FFFF 的<#XXXX>→ UTF-8 往返,及混合文本A<#1F60A>B的还原。- 运行
xmake test lolly_tests/herk_test(仓库根目录):修复前新用例 失败(复现 bug),修复后 38 用例 635 断言全部通过。 - GUI 手工验证(Insert → Session → LLM,假插件回显含 😊 的消息)由用户执行。
lolly/lolly/data/herk.cpp(修复)lolly/tests/lolly/data/herk_test.cpp(回归测试)devel/0969.md(本文档)