22. 两个空格引发的排版惨案 -- 探讨 Markdown 换行规则

问题的发现与解决

我现在才知道,Markdown 中的换行规则居然是两个空格加回车才算真正换行!之前我一直以为连续回车两次就是换行,但实际上,那只是开启了一个新的段落。

在 Obsidian 里,使用的是 GitHub Flavored Markdown (GFM),但是它却默认关闭了严格换行。它的换行方式非常直观 —— 只需要回车一次即可。而这样的 Markdown 文件在 VS Code 中打开,编辑时看起来确实是单次换行,但由于缺少末尾的两个空格,预览时却并不会真正换行。
也就是说,同样一份 Markdown 文件,在 Obsidian 里看是正常换行的,而在 VS Code 里看则只是源文件上的换行,预览时仍然连在一起。我一直以为 Obsidian 的渲染方式才是「正确」的,而 VS Code 可能只是渲染器有点小问题,结果直到现在才搞清楚真相。

后来更进一步研究,却发现并不是如上文那些画了删除线的文段这样的。我的 obsidian 默认是编辑视图,也就是说,其实我一直都是在预览源码,而源码和渲染其实并不一样,源码换行了不代表渲染是要有换行符的,只是单纯的给真的给这段换了一行。但是如果使用 obsidian 的阅读模式,它便会正常的渲染那些我写的不堪入目的渲染后的文件了,这和 vscode 上的渲染一摸一样。🤣 (我甚至连 obsidian 都没玩明白)

说起来,我用了这么多年 Markdown,「连按两个空格」才是换行这个观念,居然能错这么久,也真是太可笑了。不过话又说回来,直接开一个新段落其实也没什么问题,毕竟可读性更好 (狡辩)

于是,为了更好地规范 Markdown 语法,我在 Obsidian 里安装了 Linter,希望它能帮助我更合理地使用 Markdown,避免类似的坑。

还有就是!各位读者:
真的真的对不起!(>人<;)。十分抱歉我之前的文章都用了这么混乱的排版,之前发的文章排版简直像被猫咪滚过的毛线团!换行和换段落简直一团糟。现在已经修正了绝大部分的错误。

额外资料 (自用,可不看)

Daring Fireball: Markdown 中的资料

而且我在原始创建者 John Gruber 中找到了这么一句话:

When you do want to insert a <br /> break tag using Markdown, you end a line with two or more spaces, then type return.
当你想使用 Markdown 插入一个 <br /> 断行标签时,你需要在行尾输入两个或更多空格,然后按回车键。
Yes, this takes a tad more effort to create a <br />, but a simplistic “every line break is a <br />” rule wouldn’t work for Markdown. Markdown’s email-style blockquoting and multi-paragraph list items work best — and look better — when you format them with hard breaks.
是的,创建一个 <br /> 需要更多一点的努力,但简单的「每一行换行就是 <br />」规则并不适用于 Markdown。Markdown 的电子邮件风格块引用和多段落列表项,用硬换行格式化效果最好 —— 看起来也更美观。

哎呀,创始人早就写好了这个规则了啊。

GFM 中的规则

去找了一下 github flavored markdown,发现这里的说法是换行有软硬换行之分,

也就是说软换行就是

1
2
<p>换行
换行</p>

而 HTML 中,多个连续的空白字符(包括换行、空格和制表符)都会被浏览器解析为一个空格。所以会被解析为:

1
换行 换行

硬换行就是

1
2
<p>换行<br/>
换行</p>

这和原始的那个规则好像大差不差,总之就是,<br/> 的话就要用两次及以上的空格!

GitHub Flavored Markdown 上的中英对照

6.12 Hard line breaks
A line break (not in a code span or HTML tag) that is preceded by two or more spaces and does not occur at the end of a block is parsed as a hard line break (rendered in HTML as a <br /> tag):
换行符(不在代码跨度或 HTML 标签中)若前面有两个或更多空格且未出现在块末尾,则会被解析为硬换行(在 HTML 中渲染为 <br /> 标签):

1
2
foo(空格)(空格)
baz
1
2
<p>foo<br />
baz</p>

For a more visible alternative, a backslash before the line ending may be used instead of two spaces:
若需更直观的替代方案,可在行尾使用反斜杠代替两个空格:

1
2
foo\
baz
1
2
<p>foo<br />
baz</p>

More than two spaces can be used:
可以使用超过两个空格:

1
2
foo(空格)(空格)(空格)(空格)(空格)
baz
1
2
<p>foo<br />
baz</p>

Leading spaces at the beginning of the next line are ignored:
下一行开头的空格会被忽略:

1
2
foo(空格)(空格)
(空格)(空格)(空格)(空格)(空格)bar
1
2
<p>foo<br />
bar</p>
1
2
foo\
(空格)(空格)(空格)(空格)(空格)bar
1
2
<p>foo<br />
bar</p>

Line breaks can occur inside emphasis, links, and other constructs that allow inline content:
换行符可出现在允许内联内容的强调、链接等结构中:

1
2
*foo(空格)(空格)
bar*
1
2
<p><em>foo<br />
bar</em></p>
1
2
*foo\
bar*
1
2
<p><em>foo<br />
bar</em></p>

Line breaks do not occur inside code spans:
换行符不会出现在代码跨度内部:

1
2
`code(空格)(空格)
span`
1
<p><code>code   span</code></p>
1
2
`code\
span`
1
<p><code>code\ span</code></p>

or HTML tags:
或 HTML 标签中:

1
2
<a href="foo(空格)(空格)
bar">
1
2
<p><a href="foo(空格)(空格)
bar"></p>
1
2
<a href="foo\
bar">
1
2
<p><a href="foo\
bar"></p>

Hard line breaks are for separating inline content within a block. Neither syntax for hard line breaks works at the end of a paragraph or other block element:
硬换行用于在块内分隔内联内容。硬换行的语法在段落或其他块元素末尾无效:

1
foo\
1
<p>foo\</p>
1
foo(空格)(空格)
1
<p>foo</p>
1
### foo\
1
<h3>foo\</h3>
1
### foo(空格)(空格)
1
<h3>foo</h3>

6.13 Soft line breaks
A regular line break (not in a code span or HTML tag) that is not preceded by two or more spaces or a backslash is parsed as a softbreak. (A softbreak may be rendered in HTML either as a line ending or as a space. The result will be the same in browsers. In the examples here, a line ending will be used.)
普通换行符(不在代码跨度或 HTML 标签中)若未被两个以上空格或反斜杠修饰,则解析为软换行(在 HTML 中可能渲染为换行或空格。浏览器效果相同。以下示例使用换行符):

1
2
foo(无空格)
baz
1
2
<p>foo
baz</p>

Spaces at the end of the line and beginning of the next line are removed:
行尾和下一行开头的空格会被移除:

1
2
foo(空格)
(空格)baz
1
2
<p>foo
baz</p>

A conforming parser may render a soft line break in HTML either as a line break or as a space.
合规解析器可将软换行在 HTML 中渲染为换行或空格。

A renderer may also provide an option to render soft line breaks as hard line breaks.
渲染器也可提供将软换行渲染为硬换行的选项。

可能有用的链接

  • Daring Fireball: Markdown

  • CommonMark

  • GitHub Flavored Markdowm