## 环境 - Operit AI 1.12.2(versionCode 51,targetSdk 34) - 设备:vivo V2520A(iQOO Neo11),Android 16 ## 复现步骤 1. 发送一行文本,句中包含 3 个连续半角反引号(U+0060 ×3),且不做闭合。示例(代码块内为示例句的字面内容): ````text 停止条件比如 ``` 收尾、工具调用的结束标记。 ```` 2. 观察渲染结果。 ## 实际现象 - 代码块从该行中间开启;同一行剩余文字被当作语言标识,显示为代码块标题。 - 从该处起直到出现下一个三连反引号或消息结束,全部内容被吞进代码块。 - 一条消息内出现两个未配对的三连反引号时,围栏角色整体错位:本应开启块的位置被当成闭合,末尾留下未闭合块,吞掉消息尾部。 ## 期望行为(对齐 CommonMark) - 只有位于行首(最多 3 个空格缩进)的连续 3 个及以上反引号才能开启围栏。 - 行内出现的三连反引号应作为普通文本。 ## 代码定位(反编译自 v1.12.2 dex) ### 1. com.ai.assistance.operit.util.WaifuMessageProcessor 静态正则(smali .line 27 / .line 28): ````text FENCED_CODE_BLOCK_REGEX = ```[^\r\n`]*[\r\n]?[\s\S]*?``` UNCLOSED_FENCED_CODE_BLOCK_REGEX = ```[^\r\n`]*[\r\n]?[\s\S]*$ ```` 两条正则均以裸的三连反引号开头,无行首锚点、无多行模式,因此任意位置的三连反引号都会被匹配为围栏起始。 另有一条用于剥离围栏标记的正则(.line 800):三连反引号 + [a-zA-Z]* + 可选换行,或可选换行 + 三连反引号。 ### 2. com.ai.assistance.operit.util.stream.plugins.StreamMarkdownFencedCodeBlockPlugin 流式渲染的围栏检测器,基于自研 KMP 扫描器(StreamKmpGraph,字段 startMatcher / endMatcher,状态 hasStartedMatchingFence / isMatchingEndFence)。模式由 repeat(0x60)(3 个反引号)与 greedyStar 组合构造,与上述正则同构,同样缺少行首/换行约束。 ### 3. 渲染侧(本身逻辑正确,但会放大上游错误) - com.ai.assistance.operit.ui.common.markdown.CanvasMarkdownNodeRendererKt(约 590 行):对块首行 trim 后 startsWith 三连反引号,判定语言标识。 - com.ai.assistance.operit.ui.common.markdown.MarkdownPlainTextRendererKt(约 101 行):同一逻辑。 由于传入的块文本从行内三连反引号处开始,其首行即「行内剩余文字」,被 trim 后成了语言标识并显示为标题。 ## 建议修复 1. 正则加行首约束(多行模式 + 行首最多 3 个空白);未闭合分支同样加 (?m)^ 前缀。 2. 流式插件 startMatcher 要求三连反引号之前处于行首(或前一字符是换行)。 3. 围栏未闭合时按普通文本降级,不吞剩余消息(可复用 UNCLOSED_FENCED_CODE_BLOCK_REGEX)。 4. 附带问题:当前实现不解码 HTML 数字字符实体(如 ` 原样显示五个字符),与 CommonMark 不同。 5. 附带观察:波浪线围栏(3 个 ~)在整个 APK 的 dex 中无实现,不会误触发,但也不支持该语法。 ## 附加信息 - 该 APK 未使用 Markwon 或 commonmark-java(dex 中不存在 io.noties.markwon / org.commonmark 相关类),Markdown 解析为自研实现。 - 该现象在本机 100% 稳定复现(每次发送含未闭合三连反引号的句子)。 ## 备注 本 issue 由 AI 辅助分析生成(现象为用户实测复现,代码定位由 AI 反编译 v1.12.2 得出),如有错误,敬请谅解。
Activity
3316891527 commentedon Oct 4, 2026
感谢反馈,以及详细的复现步骤和代码定位。我们核对了当前
dev的源码,确认这是代码围栏识别边界的问题:Kotlin 和 C++ 流式解析器在匹配开始围栏时缺少行首约束,Waifu 文本清理正则也存在同样的问题,因此行内三连反引号会被误判为代码块,影响后续正文显示或清理结果。我们已在 #1334 提交修复,统一这三处的围栏边界:开始围栏仅允许位于行首或最多三个前导空格之后;结束围栏需要满足独立行、反引号长度及尾部空白要求。同时保留合法未闭合代码块持续到消息末尾的行为,这符合 CommonMark,也适用于流式输出过程中结束围栏尚未到达的情况。
本次新增了 11 个 JVM 回归用例,Android Tests 和 Android Build 已派发,结果尚待验证,修复目前还未合并发布。数字字符实体和波浪线围栏属于另外的 Markdown 兼容性问题,本次修复集中处理行内反引号误触发。
感谢你的细致反馈,也感谢理解。