会写 Markdown 的人很多,但写得好 Markdown 的人却很少。有没有什么工具能充当「秘书」,检查文件中的 Markdown 语法和风格,并且提出解决方案、自动修复问题,甚至自动补齐中英文之间的「盘古之白」呢?本文介绍的 Markdown 语法检查器(linter)就能做到。
Markdown 标记语言 是如今互联网上写作的主流格式,被各类发布平台和创作工具广泛支持。哪怕不熟悉这一语言的用户,也很可能在潜移默化中对 #
(标题)、**
(加粗)、-
(列表)这些 Markdown 风格的标注方式建立起认知。
然而,会写 Markdown 的人很多,但写得好 Markdown 的人却很少。这一方面是 Markdown 生态系统自身的问题:语法变种和实现方式 五花八门,互不兼容甚至相互矛盾。
但另一方面,也鲜有人愿意花时间去仔细阅读 Markdown 的技术规范;大多数人都只是读了一两篇「速成」,就自我批准出师了,对于一些细节问题并未关注;如果在写作中遇到,也是凭想象和直觉随意判断。
由此,就产生了大量语法天马行空、版面张牙舞爪,让读者和排版软件都困惑不已的 Markdown 文件。
当然,每个人精力有限,强求所有 Markdown 用户都去研读标准是不切实际的。那么,有没有什么工具能充当「秘书」,检查文件中的 Markdown 语法和风格,并且提出解决方案、自动修复问题呢?
有编程经验的读者看到这里,或许已经联想到写代码时常用的一种辅助工具——语法检查器(linter)。没听说过也没关系:这个词来自于「lint」,本意是指「绒毛」——是的,就是毛衣上、口袋缝里那些讨厌的毛球。这些毛球既影响美观,又容易堵塞洗衣机;引申用来形容代码里的错误语法、混乱版式等问题,可谓形象。
(注:Linter 一词没有完全对应的中文翻译,实践中译为「校验器」或「检查器」的都有,但都无法充分表达 linter 既检查语法问题、又检查版式(风格)等「审美」问题的综合性,也常与另一种只检查是否合乎技术标准的工具 validator 混淆;本文姑且用「检查器」来称呼。)
这跟 Markdown 有什么关系呢?实际上,Markdown 属于一种「标记语言」(markup language),虽然与 Python、JavaScript 这类「编程语言」服务于截然不同的目的,但同样都是通过各种记号(token)来标记文件、表明各部分的不同作用(例如 Python 中的定义、循环控制;Markdown 中的被强调文本、引用段落)。文本编辑器在处理时,也是通过识别这些记号进行语法分析(lexical analysis),进而提供关键词高亮、提取大纲、渲染预览等语法功能。
换言之,在编辑器眼中,Markdown 只不过是另一种「语言」罢了。既然如此,如果能用 ESLint 检查 JavaScript,用 PyLint 检查 Python,当然也可以造出一个 Markdown 语法检查器,专门给 Markdown 文件「挑刺」。
下面,本文就将依次介绍两种 Markdown 格式检查器:一种通用于各种 Markdown 文档,主要用来检查和修复文件中 Markdown 格式标记本身的规范性;另一种则专用于中文 Markdown 文档,用来满足一种历史悠久的「强迫症」——中英文之间要加空格。
结构上,本文会考虑不同层次的需求:如果你是 Markdown 初学者,不妨先学习这些检查器的规则文档,了解 Markdown 使用中的常见错误,然后尝试使用所介绍的工具和插件,优化自己的文档。如果你是高级用户,则不妨尝试后续段落介绍的配置选项和命令行工具,定制出反映自己偏好和思考的自动化流程。
正如主流编程语言都有不止一种检查器,Markdown 的检查器也有很多竞品,但其中使用者最多、维护最为活跃的要数 markdownlint;这也是 WordPress、MDN 等很多知名开源项目的选择。
markdownlint 有两个版本,分别是 Mark Harrison 基于 Ruby 的 原版 和 David Anson 基于 Node.js 的 移植版。由于比较方便部署(例如下文介绍的网页版和 VSCode 插件),Node.js 版在人气和活跃程度上后来居上,本文也优先介绍该版本。
截至本文写作时,markdownlint(Node.js 版)总共有 50 条规则,编号从 MD001 到 MD050,其中有两条已被废弃;Ruby 版则有 39 条规则。两个版本的规则编号是兼容的,相同编号的规则内容也相同。
这些规则各有分工,有的规范 Markdown 中的空格与留白,有的规范链接的使用,有的则规范标题、列表等具体样式(可阅读文档了解完整的 分类标签)。
具体到内容上,有的规则显得很像「废话」,但确实容易在匆忙中忽略,例如:
title
属性);有的并不影响含义和最终渲染结果,只是为了文件整洁,满足「强迫症」,例如:
*
或 _
)内侧不应紧邻空格。还有的则见仁见智,更多是审美和「哲学」上的取舍,例如:
篇幅所限,这里不一一列举。markdownlint 的 规则文档 非常完整,对于每条规则都给出了描述、实例和说理,本文强烈建议花些时间逐条阅读。这既有助于自己写出更规范的 Markdown 文件,也可以在理解和思考的基础上做出取舍,为后文介绍的自定义配置做准备。
对于初识 markdownlint 的用户来说,最直观、最方便的体验方式,就是使用原作者提供的 网页版演示工具。
打开该工具后,点击右上角的「Browse…」打开任意一个 Markdown 格式文件(你可以下载本文 演示用的文件),或者手动将 Markdown 内容粘贴到左上角的文本框中。
(注:这个工具在加载后就只在本地运行,文本内容不会被上传,因此可以放心使用。)
这时,右下角的文本框就会以
<行数> - <规则名称> <规则内容> [ 出错原因 ]
的格式逐条列举输入文件存在的问题。
例如,在上图的例子中,第一条问题点:
9 - MD004 / ul-style Unordered list style [Expected: asterisk; Actual: dash]
是指文件第 9 行违反了 MD004(无序列表样式)。
根据这一规则,同一个 Markdown 文件中的无序列表标记应当保持一致,不能混用。这里,文件中第一个无序列表项(第 8 行)是用星号开头的;按照 MD004,后续的列表项也应当都用星号开头(Expected: asterisk),而第 9 行却使用了横线开头(Actual: dash),因此检查器报错。
对此,只需要点击这条错误信息末尾的「Fix」字样,markdownlint 就会将开头的横线改为星号,从而满足 MD004 的要求。
用同样的方法审阅其他错误信息,视情况决定是否接受即可。
网页版工具虽然方便,但也只适合演示和临时使用。如果要将 markdownlint 融入写作流程,还需要更为方便的调用方式。一种理想的选择,就是将其安装为编辑器扩展,边写文章、边检查错误。
本文以目前流行的 Visual Studio Code(VSCode)为例演示。
如开头所铺垫,作为一款代码编辑器,VSCode 是将 Markdown 作为一种「语言」提供支持的。此外,VSCode 也具有任何成熟代码编辑器必备的「快速修复」(Quick Fix)功能:检测和提示代码中的问题,并提供修改建议和「一键修复」的快捷方式。
而 markdownlint 插件的作用,正是将 Quick Fix 的支持范围延伸到 Markdown,让 VSCode「学会」这门语言边边拐拐的细节。
markdownlint 插件用起来非常简单。首先前往 VSCode 市场页面,点击「Install」按钮,跳转到 VSCode 完成安装。
还是以上文提供的测试文件为例,安装插件后再打开这个文件,就会看到界面上多出了很多波浪线,每处波浪线都对应一处发现的错误:
markdownlint 的错误检查是实时的;标记和建议会随着输入和编辑而不断刷新。
对于这些错误,可以进行的操作包括:
Command
-.
快捷键),就可以调用 markdownlint 引擎自动改正这条错误。Shift
-Command
-M
快捷键弹出「问题」面板,查看所有问题的列表,点击其中的条目可以跳转定位。Shift
-Command
-P
快捷键弹出命令面板,输入「format」,选择「格式化文档」,修复文件中的所有错误(初次运行可能需要先选择 markdownlint 作为默认引擎)。如上文所说,markdownlint 的规则并不是什么「神谕」或「铁律」,至多只是该项目作者基于较多经验和观察的「一家之言」,不可能适合每个人的口味、与每个使用场景兼容。因此,根据自己的偏好和需求予以调整是非常必要的。
对此,好消息是,markdownlint 提供了充分的调整空间,每一条规则都可以随意开关,其中很多还提供了细粒度的专用选项。坏消息是,作者似乎默认用户都是谙熟纯文本配置的高手,没有像大多数插件那样提供 UI 选项,所有设置都只能通过手写配置文件调整;下文将尽量用简单易理解的方式介绍其方法。
首先,按 Shift
-Command
-P
打开命令面板,输入「settings」,选择「首选项:打开设置(JSON)」,打开 VSCode 的配置文件。
然后,在这个文件中加入一个名为 markdownlint
的对象(位置任意,但可以与其他插件的配置写在一起方便管理)。形如:
{
// 其他插件设置
"markdownlint" : {
"config" : {
"<规则编号>" : { <配置内容> },
// 更多配置
},
"focusMode" : true
},
// 其他插件设置
}
其中,config
子对象即用来添加针对各个规则的配置。不同规则有不同的设置方法,具体需要查阅规则文档来确定:
例如,MD001 要求标题一次增加一级,文档 也没有为其列举更多参数(parameter)。如果你不喜欢这个规则,那么可以通过添加 { "MD001" : false }
将其直接关闭。
例如,MD007 要求无序列表开头缩进的空格数量保持一致,并且默认为每 2 个空格为一级。但实践中很多人习惯以 4 个空格为单位来缩进。根据 文档,该规则提供一个名为 indent
的整数型参数,默认值为 2(number; default 2
)。据此,在配置中添加 { "MD007" : { "indent" : 4 } }
即可将缩紧步进改为 4。
又如,MD024 禁止文中出现两个内容相同的标题。根据 文档,该规则有一个名为 siblings_only
的布尔型参数。据此,如果添加({ "MD024" : { "siblings_only" : true } }
),那么这条规则只会检查紧邻的标题是否重复。或者,也可以彻底禁用这条规则({ "MD024" : false }
)。
再注意到,上面的例子中,与 config
对象平行,还有一个 focusMode
键。这是我认为非常必要的一条配置:写作过程中,一行内容在完成输入之前,不符合 Markdown 语法是很正常的。 按照默认设置,这些还没写完的内容都会被当作「有问题」,打上波浪线。这显然是多此一举,也会造成视觉干扰。
将 focusMode
设为 true
后,插件就不会检查光标所在的当前行,回避了上述问题。此外,也可以将该键的值填为具体的数字,从而将忽略范围扩展到当前行上下的相应行数,进一步减少干扰。
最后,该插件还可以设置 仅在存盘时检查、根据路径忽略特定文件 等,限于篇幅在此不赘,有兴趣的读者可以进一步阅读 文档。
尽管 VSCode 插件已经可以满足大部分人的需求,但专业用户可能还希望有一个命令行界面,以实现批量化、自动化的处理。
markdownlint 确实提供了命令行版本。但比较幽默的是,它不仅有命令行工具,而且有两个命令行工具,第一个叫做 markdownlint-cli
,第二个……当然就叫做 markdownlint-cli2
。这两者的 开发背景和区别 过于琐碎,就本文目的,读者只需知道 markdownlint-cli2
相对较快,并且和 VSCode 插件版共用一套配置语法,结合使用比较方便;本文也将以其为例演示。
下面的操作假定读者熟悉终端的基本知识和使用方法,并且已经 安装好 Node.js。
首先,通过 npm
安装 markdownlint-cli2
:
npm i -g markdownlint-cli2
markdownlint-cli2
的使用方式主要有两种:
markdownlint-cli2 [文件路径] // 检查文件并报告问题
markdownlint-cli2-fix [文件路径] // 检查并直接修复文件
其中,文件路径可以是一个或多个实际的文件名,也可以是通配符。例如:
markdownlint-cli2 "**/*.md"
是指检查当前目录以及任意层级子目录中,所有以 md
为后缀名的文件。
markdownlint-cli2
与 VSCode 插件使用完全相同的配置语法。因此,在根据自己习惯配置好 VSCode 插件后,只要将其中 config 对象的内容复制出来,保存为一个名为 .markdownlint.json
的文件(也可以用一些其他的文件名和格式,详见 文档),与待检查的 Markdown 文件放在同一文件夹(如果要检查多个文件,则放在其共同的最上级文件夹),markdownlint-cli2
就会自动读取并执行。
在下图例子中,由于通过配置文件改变了部分规则,下方的终端的输出也少了相应条目:
值得一提的是,如果某个文件夹中存放了上述配置文件,而其配置内容与 VSCode 插件的设置不同,那么在编辑这个文件夹内的 Markdown 文件时,VSCode 也会优先使用这个外部配置文件的设置。这特别适合为不同项目微调配置。
最后,如果你有固定的设置想要搭配 markdownlint-cli2
使用,又不想每次都创建一个配置文件,也可以用:
markdownlint-cli2-config [配置文件路径] [Markdown 文件路径]
的语法来手动指定配置文件路径。
在很多中文社区,中英文之间要手动加空格——俗称「盘古之白」——都是不成文的风格要求。这项要求是否合理、又该如何满足,是很有价值的话题,但超出了本文的讨论范围。
这里,只简单概括通说:中英文之间加入空隙,是为了实现视觉上的区隔,更加美观和易读。理想情况下,这种「空隙」应当由排版引擎自动加入,宽度宜为 1/4 个全角空格(em)。但由于数字排版环境复杂多变,在大多数时候(包括最常见的网页环境)不能指望排版引擎有这种能力,因此只能退而求其次,手动插入一个半角空格(因其宽度通常接近于 1/4 em),达到类似效果。
(注:如果有进一步兴趣,请阅读知乎讨论「中英文混排时中文与英文之间是否要有空格?」,W3C 标准草案《中文排版需求》§3.2.2,以及收听《字谈字畅》播客 第 14 期。)
相比之下,本文关心的问题是:如果想要在中英文之间手动加空格,有什么自动检查和补全的方法吗?
答案是当然有,而且选择也不止一个。其中,最著名的可能是 pangu.js 项目。如果你用过一个叫做「为什么你们就是不能加个空格呢?」的浏览器插件,那你也就用过 pangu.js——它正是出自同一位作者之手、以 pangu.js 为底层支撑的。
另一个选择是 AutoCorrect。与主要关注文本内容的 pangu.js 相比,AutoCorrect 出生于 Ruby 语言的中文社区,因此从一开始就考虑到了编程代码中的中英混排场景(可以参见该项目的 测试文件),通用性更强。
不过,两者在实际使用的区别很小,唯一比较明显的差别是 pangu.js 会在链接文本的前后加上空格,而 AutoCorrect 不会;这也只是见仁见智的选择。此外,pangu.js 没有官方的 VSCode 插件,只有几个第三方插件,但已久不更新,命令行工具也依赖于 Node.js,一般用户初次运行起来可能有点繁琐;而 AutoCorrect 则提供了官方 VSCode 插件,命令行版本没有外部依赖,选项也更多。
因此,下文将同时介绍这两个工具,读者按偏好选择其一即可。
与 markdownlint 类似,AutoCorrect 也提供了一个 在线预览版,既可用来测试该工具的效果,也可用来处理实际文件。
打开该工具后,在左上角的下拉菜单中选择「Markdown」,粘入自己的内容,然后点击下方的「Format」按钮即可看到输出。
pangu.js 没有官方网页版。
AutoCorrent 提供了第一方的 VSCode 插件。安装后,建议在「设置」>「扩展」>「AutoCorrect」中按需调整该插件的两个选项:
这两个选项都是默认启用的,但我建议考虑关闭。理由也与上面配置 markdownlint 时类似:文档是否「整洁」并没有必要在写作阶段就特意关注,而是修订校对阶段的任务。特别是对于中英文空格这种「小节」,启用实时修复只会让界面充斥着提示问题的波浪线,非常干扰写作。
无论是否启用这些选项,都可以通过命令面板中的「AutoCorrect: Format document」命令来手动为文件添加中英文空格。如果想要更方便,还可以点击该命令右侧的齿轮图标,为其指定一个快捷键,随时满足自己的强迫症。
pangu.js 没有官方 VSCode 插件,但有几个第三方移植版,其中用户较多、效果也不错的是 xlthu 开发的 Pangu-Markdown。安装后,在命令面板选择「Pangu Format」即可检查全文并追加空格。
AutoCorrect 提供了为各个平台编译的命令行工具,官方文档提供的安装脚本如下:
curl -sSL https://git.io/JcGER | bash
(注:有义务提醒,curl | sh
风格的安装方式是有 内在风险 的,请务必逐行审阅 curl
下载的 安装脚本内容 再决定要不要运行。)
与 markdownlint 类似,AutoCorrect 也可以选择是只报告问题,还是直接修复文件:
autocorrect [文件路径] // 检查文件并报告问题
autocorrect --fix [文件路径] // 检查并直接修复文件
更完整的使用方法可以使用 autocorrect --help
查看,或者 阅读在线文档。
至于 pangu.js,其命令行工具则需通过 npm
安装:
npm i -g pangu
相比 AutoCorrect,其功能也比较简单,只能将补齐空格后的内容直接显示出来,必须配合 >
重定向才能写入到文件:
pangu -t [文本内容] // 检查指定文本内容
panfu -f [文件路径] // 检查指定文件
pangu -f [文件路径] > [写入路径] // 检查指定文件并将结果写入到文件