Markdown 代码块:围栏、语言标记与嵌套

Markdown 写作约 4 分钟

用三个反引号(或三个波浪线)把代码包起来,在开头的围栏后面写语言名,结尾用同一种字符、长度不短于开头的围栏收住;代码里本身带围栏时,外层就用更长的围栏。语言名是高亮器判断怎么上色的唯一线索,所以要权衡的是,你写的这个块是到哪都能用,还是只在自己的预览里好看。

围栏代码块和缩进代码块

围栏块以三个或更多反引号、波浪线开头,并以一行匹配的围栏结束。

```python
def greet(name):
    return f"hello {name}"
```

开头围栏后面的文字叫信息串,第一个词约定俗成是语言名,CommonMark 规范会把它渲染成 <code> 上的 language-python 类,高亮器认的就是这个类。反引号围栏的信息串里不能再出现反引号,波浪线围栏则可以,这也是少数几个值得选 ~~~ 的理由。

缩进代码块是空行之后、每行缩进四个空格的那一段。

    make build
    make test

它在所有解析器里都能用,但没有信息串,没法指定语言,也很容易误触发,比如在列表项下面缩进写续段的时候。收尾方式也不同,缩进块遇到第一个没缩进的非空行就结束,而围栏漏了结尾会一直吞到文末,所以少一个结尾围栏,下面所有内容都会变成代码。

反引号和波浪线围栏行为一致,只有一条规则,结尾围栏必须与开头用同一种字符,并且不能比开头短,~~~ 关不掉反引号围栏,反过来也一样。CommonMark 和 GFM 都定义了这两种写法,反引号更常见,写给别的工具里的人看时更保险。

围栏里再放围栏

要展示一段本身带代码块的 Markdown,就让外层围栏比里面任何围栏都长。本文的例子外层用四个反引号,里层用三个。

````markdown
```js
console.log("inner");
```
````

里层围栏比外层短,关不掉外层;如果连四个反引号的写法也要展示,外层就用五个。换成波浪线也能避开冲突,外层 ~~~、里层反引号,因为结尾字符必须匹配,互相不会干扰。

行内代码里带反引号

行内代码是一串反引号包住的文字。文字里有反引号时,用更长的一串来开头和结尾,并在内容两侧各补一个空格,免得分隔符贴到内容上。

用 `` `git log` `` 查看历史。

按 CommonMark 规范,内容两端都是空格且不全是空格时,会各去掉一个空格,所以补的空格不会出现在渲染结果里。代码片段里的反斜杠转义不生效,反斜杠后面跟星号会原样显示两个字符。

diff 代码块

语言名写 diff,以 + 和 - 开头的行会分别按新增和删除上色。

```diff
- timeout: 30
+ timeout: 60
```

这是高亮器对文本约定的着色,不是 Markdown 的语法,所以只在高亮器带有 diff 定义的地方有效;妙言内置的高亮器里 patch 也是它的别名。

妙言怎么渲染代码块

妙言用 cmark-gfm 解析 Markdown,围栏块会变成 <pre><code class="language-...">,预览再加载 highlight.js 11.5.1(V4.5.0 里 vendor 清单记录的版本)给每个 pre code 上色。下面三个行为都来自这个库本身的工作方式。

第一,内置的是裁剪过的版本,不是 highlight.js 的完整语言库,一共注册了 34 种语言定义,Bash、C、C++、C#、CSS、Diff、Go、HTML 和 XML、INI 和 TOML、Java、JavaScript、JSON、Kotlin、Less、Lua、Makefile、Markdown、Objective-C、Perl、PHP、PHP 模板、纯文本、Python、Python REPL、R、Ruby、Rust、SCSS、Shell 会话、SQL、Swift、TypeScript、Visual Basic .NET 和 YAML。js、ts、py、sh、yml、md、rs、rb 这些常见短名是别名,像 Dockerfile 和 GraphQL 就不在里面。

第二,写了一个库里不认识的语言名,这个块会关闭高亮,按普通等宽字体显示;不写语言名则是另一种情况,highlight.js 会根据内容猜,贴一小段代码时省事,短代码上并不可靠,颜色不对就补上语言名。

第三,长行会换行而不是横向滚动,样式表给代码块设了 white-space: pre-wrap,长命令不用拖滚动条也能读完,代价是换行出来的行和真正的新行不太好区分。

编辑区的代码着色是另一套高亮器,而且只认反引号写的围栏,波浪线围栏和四个反引号的嵌套写法在预览里渲染正确,编辑区里的着色可能对不上。单个代码块超过 3000 个字符时它会跳过着色,一篇笔记里围栏块超过 20 个时整篇都不再着色,最终导出成什么样,以预览为准。

Mermaid、PlantUML、Markmap 和公式

有些语言名做的不只是上色,mermaid、plantuml 和 markmap 会被妙言的图表脚本接管,换成一张图。Mermaid 和 Markmap 用内置的 JavaScript 渲染,PlantUML 则会生成指向 plantuml.com 公共服务的链接,需要联网,图的文本也会离开你的 Mac。Mermaid 解析失败时,预览会把源码留在块里,不会弹出错误页,细节见 Markdown 里画 Mermaid 图。

公式不是围栏语法,美元符号包起来的公式在代码之外处理。妙言的公式处理会跳过行内代码和围栏块,所以代码块里的 $ 是安全的,写法见 Markdown 里写 LaTeX 公式。

复制和导出

V4.5.0 的预览里代码块没有复制按钮,选中文字照常复制即可。反方向上,从网页复制带结构的 HTML 再粘贴进来,妙言会转成 Markdown,代码块会变成带语言类的围栏,代码里自带三个反引号时还会自动用更长的围栏。

「导出」子菜单里有 HTML、图片和 PDF,PDF 的细节见 在 Mac 上把 Markdown 转成 PDF。导出的 HTML 文件会从妙言的 CDN 加载高亮和图表脚本,所以打开那个文件时,要联网才有颜色和图。

容易出错的地方

  • 漏写结尾围栏,后面整篇笔记都会变成代码,看到文字突然变成等宽字体就去数围栏。
  • 列表项里的围栏要和列表文字对齐,否则列表会在这里断开。
  • 语言名高亮器不认识就没有颜色,换成短别名试试,或对照上面的语言列表。
  • 不同应用颜色不一样,因为各自带的高亮器和主题不同,Markdown 本身可移植,颜色不可移植。

其余语法见 Markdown 语法速查与 GFM 差异。

常见问题

一定要写语言名吗?

不用,不写也会渲染成代码块,想要颜色稳定再补上,因为妙言没有语言名时会根据内容去猜。

可以用波浪线代替反引号吗?

可以,前提是结尾围栏也用波浪线,代码里带反引号围栏时,波浪线特别方便。

为什么我的代码没有颜色?

要么没写语言名而且猜错了,要么写的语言名高亮器不认识。妙言的内置库注册了 34 种语言,Dockerfile 这类不在其中的语言会按普通等宽字体显示。