Code Blocks
2026-09-04 13:45:25作者:邓越浪Henry
Indent Code Block
This line won't *have any markdown* formatting applied.
I can even write <b>HTML</b> and it will show up as text.
This is great for showing program source code, or HTML or even
Markdown. <b>this won't show up as HTML</b> but
exactly <i>as you see it in this text file</i>.
Within a paragraph, you can use backquotes to do the same thing.
This won't be *italic* or **bold** at all.
Fence Code Block
#include <iostream>
int main(int argc, const char* argv[]) {
std::cout << "C++ code block test" << std::endl;
return 0;
}
This is a code block without language identifier.
它用最小篇幅覆盖了 CommonMark 中代码语法的三个关键分支:
1. **缩进代码块(Indent Code Block)**:以 4 个空格缩进的连续段落。块内 `*have any markdown*`、`<b>HTML</b>`、`<i>...</i>` 均按 CommonMark 规则作为纯文本呈现,不做任何 Markdown/HTML 解析。这是对“块内内容原样保留”这一语义的直接验证。
2. **行内反引号(backquotes)**:段落内用单反引号包裹的 `` `This won't be *italic* or **bold** at all.` ``,验证行内代码与块级代码两条独立路径互不干扰。
3. **围栏代码块(Fence Code Block)**:一个带语言标识 ```` ```cpp ```` 的围栏块(info string 为 `cpp`),一个不带语言标识的裸围栏块(info string 为空)。前者验证 info string 保留与序列化还原,后者验证空 info string 的往返。
## 三、解析侧:Markdown 到状态树
往返测试的“去程”由 [markdownToState.ts](https://gitcode.com/gh_mirrors/ma/marktext/blob/43bd8b77795fb27b1a9512737c000f7362031ea0/packages/muya/src/state/markdownToState.ts?utm_source=gitcode_repo_files) 中的 `MarkdownToState` 完成,代码块相关的核心是私有方法 `_buildCodeState`(约 L412-L464)。关键逻辑有四条:
**(1)info string 原样保留,语言取首词。** 解析后得到标记 token 的 `infoString`(即围栏后的完整信息串),方法先 `trim` 再用 `firstWordOfInfo()` 提取首个词作为高亮语言——这与 CommonMark §4.5 对 info string 的定义一致。注释明确写着:“Keep the whole info string; the language for highlighting / diagram detection is its first word”。状态中存储的 `meta.lang` 是完整 info string 原文(例如 `js title="x"` 或 Pandoc 的 `{...}` 块),而不是截取后的单词;`[types.ts](https://gitcode.com/gh_mirrors/ma/marktext/blob/43bd8b77795fb27b1a9512737c000f7362031ea0/packages/muya/src/state/types.ts?utm_source=gitcode_repo_files)` 中 `ICodeBlockState` 的注释重申了这一点:“The full fenced info string, verbatim ... The language for highlighting is its first word — derive via `firstWordOfInfo()`, never assume a single word.”
**(2)空行裁剪是可选行为。** 构造 `MarkdownToState` 时可传入 `trimUnnecessaryCodeBlockEmptyLines`(默认 `false`),开启后代码块首尾的多余空行会被 `replace(/\n+$/, '').replace(/^\n+/, '')` 裁掉,注释标注这是历史 issue #1265 的修复。往返测试恰好使用默认值 `false`,保证不改变输入语义。
**(3)图语言直通 diagram 状态。** 若首词命中 `^(mermaid|vega-lite|plantuml|flowchart|sequence)$`,不会构造 `code-block` 状态,而是直接生成 `diagram` 状态(`vega-lite` 映射为 `json`,其余映射为 `yaml`)。`CodeBlocks.md` 中的 `cpp` 不在此列,因此走普通代码块路径。
**(4)fenced / indented 二值分类与 fenceLength 记录。** 解析时通过 `walkTokens` 给 token 写入 `codeBlockStyle`:围栏块为 `'fenced'`,缩进块保留 `'indented'`。`_buildCodeState` 据此写出 `meta.type`;此外当围栏长度大于 3 时还会记录 `fenceLength`,供序列化时还原原始围栏长度。注意解析侧的一处细节:缩进代码块的文本会先 `text.replace(/\n$/, '')` 去掉 marked 附加的尾部换行,围栏块则原样保留。
## 四、状态模型:ICodeBlockState
两类代码块在 muya 中共享同一个状态类型,定义于 [types.ts](https://gitcode.com/gh_mirrors/ma/marktext/blob/43bd8b77795fb27b1a9512737c000f7362031ea0/packages/muya/src/state/types.ts?utm_source=gitcode_repo_files#L28-L39):
```ts
export interface ICodeBlockState {
name: 'code-block';
meta: {
type: string; // "indented" | "fenced";
// 完整围栏 info string 原文(如 `js`、`js title="x"` 或 Pandoc 的 `{…}`);
// 高亮语言是其首词——用 firstWordOfInfo() 派生,勿假设单词形态。
lang: string;
fenceLength?: number;
};
text: string;
}
```
夹具中的缩进块对应 `{ type: 'indented', lang: '' }`,`cpp` 围栏块对应 `{ type: 'fenced', lang: 'cpp' }`,裸围栏块对应 `{ type: 'fenced', lang: '' }`。`text` 字段承载代码原文(不含围栏本身)。这一“结构 + 元数据 + 纯文本”的三要素模型也是 muya 所有块级状态的统一范式,使得代码块可以像其他块一样参与 JSON 状态编辑(`editOperation`)、历史回滚与跨进程同步。
## 五、序列化侧:状态树回到 Markdown
“回程”由 [stateToMarkdown.ts](https://gitcode.com/gh_mirrors/ma/marktext/blob/43bd8b77795fb27b1a9512737c000f7362031ea0/packages/muya/src/state/stateToMarkdown.ts?utm_source=gitcode_repo_files) 的 `_serializeCodeBlock`(约 L352-L369)完成,分两条路径,恰好覆盖夹具的两类样本:
- **围栏路径**(`type === 'fenced'`):计算围栏长度 `this._codeFenceLength(text, meta.fenceLength)`——即取原始 `fenceLength` 与“能安全包住正文中反引号串”所需长度的较大值,用 `` `'`.repeat(fence) `` 生成围栏;info string 非空则围栏行写作 `` ```lang ``,为空则只写 `` ``` ``;正文按行输出,末尾再补一道闭合围栏。
- **缩进路径**:每行前缀 4 个空格(`${indent} ${text}`),即 CommonMark 的缩进代码块规则。
外层 `_serializeSimpleBlock` 对 `code-block` 的统一处理是:先 `_insertLineBreak` 插入与前一块之间的空行,再调用 `_serializeCodeBlock`,保证代码块在文档中前后都有空行分隔——这正是往返输出里 `## Fence Code Block` 与 `` ```cpp `` 之间出现空行的来源。
## 六、断言策略:为什么只验证“二趟稳定”
[roundTrip.spec.ts](https://gitcode.com/gh_mirrors/ma/marktext/blob/43bd8b77795fb27b1a9512737c000f7362031ea0/packages/muya/test/spec/roundTrip.spec.ts?utm_source=gitcode_repo_files) 对 `CodeBlocks.md` 这类夹具**不**要求与原文逐字节一致,而是断言往返收敛性:
```ts
function isStableUnderRoundTrip(markdown: string): boolean {
const once = roundTrip(markdown);
const twice = roundTrip(once);
return normalise(once) === normalise(twice);
}
```
其中 `normalise` 只做两件事:CRLF→LF 归一、去掉尾部换行。注释给出了两个明确的工程理由:其一,序列化器会把结尾规范化、输入可能带 CRLF,直接比原始字符串会被这些无关差异干扰;其二,**故意不**逐行去除行尾空白——CommonMark §6.7 中两个行尾空格是硬换行标记,若折叠掉会掩盖真实的往返不稳定。
而 `CodeBlocks.md` 之所以只进“稳定性”断言、不进“逐字节恒等”断言,是因为测试文件里单独维护了一个 `identityFixtures` 白名单(Images、Escapes、GFM Basic Text Formatting、GFM Tables 四个首轮输出即与原文一致的夹具),`Code Blocks` 不在其中。注释解释:严格的字节一致“过于严格,且 marktext 早期对这些夹具本来就是非确定性的——列表缩进与 ExportMarkdown 的规范化选择就不同”。对代码块而言,典型差异点就是空行布局:解析时缩进块去掉了尾部换行、序列化时又统一补空行与换行,首趟输出与原文在空行上可能略有出入,但第二趟必然收敛。
## 七、运行时视角:状态如何变成可编辑的 DOM 块
状态树最终由 [codeBlock/index.ts](https://gitcode.com/gh_mirrors/ma/marktext/blob/43bd8b77795fb27b1a9512737c000f7362031ea0/packages/muya/src/block/commonMark/codeBlock/index.ts?utm_source=gitcode_repo_files) 的 `CodeBlock` 块挂载为 `<pre>` DOM 节点,类名 `mu-code-block` 与 `mu-${meta.type}-code`(即 `mu-indented-code` / `mu-fenced-code`)——这也解释了为什么类型字符串必须保持这两个取值。两个与本文主题直接相关的运行时行为:
1. **语言 setter 的类型提升**:`set lang(value)` 中,如果当前 `meta.type !== 'fenced'`,会将其改写为 `'fenced'` 并通过 `jsonState.editOperation(path, diffToTextOp(diffs))` 下发 JSON 状态编辑,同时把 DOM 类名从 `mu-indented-code` 切换为 `mu-fenced-code`。也就是说,用户在缩进代码块顶部输入语言标识,块会自动“升级”为围栏代码块——这是夹具中 indented/fenced 两种 `type` 在编辑路径上的真实交汇点。
2. **高亮语言按需加载**:setter 用 `firstWordOfInfo(value)` 取首词,经 `loadLanguage()` 动态加载 Prism 语法,按 `loaded` / `noexist` / `cached` 三种状态决定是否重渲染;无语言、未知语言或缩进块则不加载任何语法——与序列化侧对空 info string 的处理保持一致。
## 八、如何运行这些测试
在 muya 包内用 vitest 运行往返测试即可复现本文全部结论:
```bash
cd packages/muya
npx vitest run test/spec/roundTrip.spec.ts
登录后查看全文
热门项目推荐
相关项目推荐
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
最新内容推荐
CodeGraph Agent 评测三反馈指标实战:Residual Occupancy、Explore Sufficiency 与 Allocation EfficiencyExpo 安卓正确性审查规范解析:correctness-android 代理如何拦截 Kotlin/Java 的八类缺陷claude-howto 的 /unit-test-expand 斜杠命令:基于覆盖率缺口系统化扩充单元测试Zed 贡献指南:PR 规范、AI 使用政策与核心 Crate 全景图Bruno 的 AI 代码评审契约:共享评审人 Persona 与机器可解析输出格式设计从 SCOPES.md 解读 tiptap 的 @tiptap Scope 体系与 Monorepo 包布局Axios 请求取消机制详解:AbortController 与 CancelToken 的完整实践Review SummaryMedusa 仓库的 Claude Agent 体系:四个代码库探索代理的选择、约束与编排requests 测试证书实战:用 OpenSSL 自制一张「已过期」TLS 证书来验证 SSL 校验行为
项目优选
收起
deepin linux kernel
C
33
18
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
590
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
暂无描述
Markdown
889
5.78 K
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
983
503
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384