- 文档
- 开发工具
【免费下载链接】sphinx
The Sphinx documentation generator
Sphinx 内置的literalinclude与code-block指令支持对代码块进行行号渲染,本文基于仓库中的测试文档 tests/roots/test-directive-code/linenos.rst 与核心实现 sphinx/directives/code.py,系统讲解linenos、lineno-start、lineno-match三个选项的语义、组合规则与底层实现,并延伸至highlight指令的linenothreshold自动阈值机制。读完本文,你将能在自己的 Sphinx 文档中精确控制代码块行号的显示起点、对齐方式与触发条件。
一、关联文档与功能定位
linenos.rst是 Sphinx 测试套件中专门用于验证「带行号的 literal 包含」场景的测试源文件,由 tests/roots/test-directive-code/index.rst 通过toctree的:glob:收集。它虽然以测试根文件形式存在,但其内容本身就是一份可运行的最小示例集:四种典型的行号配置组合,覆盖了普通启用、自定义起始行、行号对齐以及空文件边界。
这些功能由sphinx.directives.code模块实现,并通过 sphinx/directives/code.py 底部的setup()函数注册为highlight、code-block(别名sourcecode)与literalinclude三个指令。行号相关的测试断言则位于 tests/test_directives/test_directive_code.py 的test_literal_include_linenos与test_linenothreshold中。
二、四个核心示例逐行解读
linenos.rst原文共包含四个literalinclude实例,分别演示了不同的行号控制方式:
.. literalinclude:: literal.inc :language: python :linenos: .. literalinclude:: literal.inc :language: python :lineno-start: 200 .. literalinclude:: literal.inc :language: python :lines: 5-9 :lineno-match: .. literalinclude:: empty.inc :lineno-match:其中被包含的 tests/roots/test-directive-code/literal.inc 是一段 13 行的 Python 源码(含注释、类定义、Unicode 字符串),tests/roots/test-directive-code/empty.inc 则是只有空行的文件。
2.1:linenos::启用行号
第一个实例仅使用:linenos:标志。按源码实现,只要检测到该选项,Sphinx 就会在生成的literal_block节点上设置'linenos': True:
if ( 'linenos' in self.options or 'lineno-start' in self.options or 'lineno-match' in self.options ): retnode['linenos'] = True对应的测试断言(tests/test_directives/test_directive_code.py 第 411 行起)验证输出 HTML 中行号以<span class="linenos"> 1</span>的形式出现,即从第 1 行开始、默认以右侧对齐显示。
2.2:lineno-start: 200:自定义起始行号
第二个实例通过:lineno-start: 200把行号起点改为 200。这在拼接代码片段(例如从某个较大的源文件中截取片段,希望行号与源文件一致)时非常实用。
实现层面,LiteralIncludeReader在构造时读取该选项并作为默认起始值:
self.lineno_start = self.options.get('lineno-start', 1)最终在run()中把reader.lineno_start写入高亮参数extra_args['linenostart'],交给 Pygments 渲染。测试断言验证输出包含<span class="linenos">200</span>,即渲染出的首个行号是 200。
2.3:lineno-match::行号与源文件精确对齐
第三个实例同时使用:lines: 5-9与:lineno-match:。其语义是:被截取/过滤后的片段,其行号仍与原始文件保持一致。例如只包含原文件第 5~9 行时,展示的行号就是 5、6、7、8、9,而不是重新从 1 开始。
从源码看,lines_filter()在同时出现两个选项时,会先把行号起点加上第一个选中行:
if 'lineno-match' in self.options: first = linelist[0] if all(first + i == n for i, n in enumerate(linelist)): self.lineno_start += linelist[0] else: msg = __('Cannot use "lineno-match" with a disjoint set of "lines"') raise ValueError(msg)这里有一个重要的约束:lineno-match要求所选行是连续的(即5,6,7,8,9这种),如果写成0,3,5之类的非连续集合,会直接抛出Cannot use "lineno-match" with a disjoint set of "lines"错误。对应的单元测试见 tests/test_directives/test_directive_code.py 中test_LiteralIncludeReader_lines_and_lineno_match*系列。
同理,lineno-match与start-after/start-at/pyobject组合时也会自动修正起点:例如pyobject_filter()中定位到对象定义后self.lineno_start = start,start_filter()中命中标记行后self.lineno_start += lineno + 1(start-after模式)。这样无论是按对象、按注释标记还是按行号范围截取,行号都能与源文件对得上。
2.4:lineno-match:与空文件:边界安全
第四个实例对空文件 tests/roots/test-directive-code/empty.inc 使用lineno-match。此时文件中没有内容可供对齐,LiteralIncludeReader的过滤管道依次执行后得到 0 行文本,lineno_start保持默认值 1,Sphinx 输出一个空的行号代码块而不会报错。这一用例证明了lineno-match在边界条件下的容错性。
三、code-block指令中的行号选项
行号控制并非literalinclude专属。同文件 tests/roots/test-directive-code/index.rst 给出了code-block的用法:
.. code-block:: ruby :linenos: def ruby? false endCodeBlock指令的选项表(sphinx/directives/code.py)支持linenos(flag)与lineno-start(int)。需要注意:
linenos与lineno-start任一生效都会开启行号(if 'linenos' in self.options or 'lineno-start' in self.options: literal['linenos'] = True);lineno-start会通过extra_args['linenostart']传给 Pygments;- 但
code-block没有lineno-match选项,因为其内容直接写在文档中,不涉及「与源文件行号对齐」的场景。
四、highlight指令与linenothreshold:自动阈值行号
除了手动开启,Sphinx 还支持按代码块长度自动决定是否显示行号。测试文件 tests/roots/test-directive-code/linenothreshold.rst 展示了完整用法:
.. highlight:: python :linenothreshold: 5 .. code-block:: class Foo: pass class Bar: def baz(): pass .. code-block:: # comment value = True其语义是:当代码块行数达到或超过linenothreshold时自动显示行号,否则不显示。上述示例中第一段有 6 行(≥5),会显示行号;第二段仅 2 行,不显示行号。
实现上,Highlight指令(sphinx/directives/code.py)解析该选项并把它记录到当前文档的高亮设置中:
linenothreshold = self.options.get('linenothreshold', sys.maxsize)默认值取sys.maxsize,意味着不设置时所有代码块都不因阈值自动获得行号(即行为与code-block无linenos时一致)。该阈值不仅作用于后续code-block,同样作用于不带语言参数的literalinclude(如linenothreshold.rst中对literal.inc与literal-short.inc的包含),前者超过阈值显示行号、后者不足阈值不显示。
test_linenothreshold(tests/test_directives/test_directive_code.py 第 578 行起)分别对两种code-block与两种literalinclude做了断言,验证阈值判定在两条路径上行为一致。
五、选项组合规则与常见错误
结合 sphinx/directives/code.py 中LiteralIncludeReader.INVALID_OPTIONS_PAIR与各过滤器的实现,整理出与行号相关的组合约束:
| 选项组合 | 行为 |
|---|---|
lineno-match+lineno-start | 非法,报Cannot use both "lineno-match" and "lineno-start" options |
lineno-match+append/prepend | 非法(追加/前置内容会破坏行号对齐) |
lineno-match+diff | 非法(diff 输出为补丁文本,无原始行号语义) |
lineno-match+lines(连续区间) | 合法,行号从区间首行延续 |
lineno-match+lines(非连续集合) | 报Cannot use "lineno-match" with a disjoint set of "lines" |
lineno-match+start-after/start-at/pyobject | 合法,自动把起点调整到命中行 |
diff+lineno-start | 非法 |
这些规则在LiteralIncludeReader.__init__中被逐一校验,任何冲突都会在构建期以警告(warning)形式呈现(run()中的异常统一由document.reporter.warning捕获),而不是静默产生错误输出。
六、行号渲染的底层输出
LiteralInclude.run()最终把linenos标记写入nodes.literal_block,并把linenostart写入highlight_args。HTML 构建器交给 Pygments 渲染后,输出结构为带linenos类名的<span>元素:
<span class="linenos"> 1</span><span class="c1"># Literally included file...</span>行号1前面的空格是 Pygments 对个位数行号的右对齐填充;lineno-start: 200时则输出<span class="linenos">200</span>。测试文件 tests/test_directives/test_directive_code.py 的test_literal_include_linenos直接以这些片段作为断言,可作为理解输出细节的参考。
七、实践建议
- 片段引用保持真实行号:从大型源文件截取片段时优先使用
:lines:(或:start-after:/:end-before:)搭配:lineno-match:,让读者能回溯源文件定位,但务必保证所选行连续。 - 长代码自动加行号:在全文档统一使用
.. highlight:: python\n :linenothreshold: N设置阈值,短片段保持干净、长片段自动带行号,兼顾可读性与定位便利。 - 避免冲突选项:不要同时使用
lineno-match与lineno-start,也不要对prepend/append/diff修饰的片段使用lineno-match,否则构建期会产生警告。 - 先跑通测试用例:仓库中 tests/roots/test-directive-code/linenos.rst 与 tests/roots/test-directive-code/linenothreshold.rst 本身就是最小可复现示例,可直接在本地 Sphinx 项目中运行
sphinx-build验证上述行为,再迁移到自己的文档中。
- 文档
- 开发工具
【免费下载链接】sphinx
The Sphinx documentation generator
相关推荐
5分钟上手Rufus:免费制作Windows 11启动盘的完整指南
5分钟上手Rufus:免费制作Windows 11启动盘的完整指南 你装过 Windows 11 吗?老电脑没有 TPM 2.0 芯片,官方安装工具直接拒绝。R
文档开发工具Sphinx 代码指令实战:code-block 与 literalinclude 的语法高亮、行号与源码级实现解析
Sphinx 代码指令实战:code block 与 literalinclude 的语法高亮、行号与源码级实现解析 本篇指南以 Sphinx 官方测试根目录
文档开发工具Sphinx 代码行号阈值(linenothreshold)实战指南:自动为长代码块与 literalinclude 添加行号
Sphinx 代码行号阈值(linenothreshold)实战指南:自动为长代码块与 literalinclude 添加行号 本指南以 Sphinx 文档生成
文档开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考