Skip to content

Commit

Permalink
Add line numbers in sphinx.ext.viewdoc (#6319)
Browse files Browse the repository at this point in the history
Co-authored-by: Adam Turner <9087854+aa-turner@users.noreply.github.com>
  • Loading branch information
benkrikler and AA-Turner committed Jul 28, 2023
1 parent 6178163 commit 762ed85
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 26 deletions.
3 changes: 3 additions & 0 deletions CHANGES
Expand Up @@ -24,6 +24,9 @@ Features added
in many more places.
* #5474: coverage: Print summary statistics tables.
Patch by Jorge Leitao.
* #6319: viewcode: Add :confval:`viewcode_line_numbers` to control
whether line numbers are added to rendered source code.
Patch by Ben Krikler.

Bugs fixed
----------
Expand Down
8 changes: 8 additions & 0 deletions doc/usage/extensions/viewcode.rst
Expand Up @@ -73,6 +73,14 @@ Configuration
`epubcheck <https://github.com/IDPF/epubcheck>`_'s score
becomes worse even if the reader supports.

.. confval:: viewcode_line_numbers

Default: ``False``.

If set to ``True``, inline line numbers will be added to the highlighted code.

.. versionadded:: 7.2

.. event:: viewcode-find-source (app, modname)

.. versionadded:: 1.8
Expand Down
17 changes: 10 additions & 7 deletions sphinx/ext/viewcode.py
Expand Up @@ -253,7 +253,8 @@ def collect_pages(app: Sphinx) -> Generator[tuple[str, dict[str, Any], str], Non
lexer = env.config.highlight_language
else:
lexer = 'python'
highlighted = highlighter.highlight_block(code, lexer, linenos=False)
linenos = 'inline' * env.config.viewcode_line_numbers
highlighted = highlighter.highlight_block(code, lexer, linenos=linenos)
# split the code into lines
lines = highlighted.splitlines()
# split off wrap markup from the first line of the actual code
Expand All @@ -263,15 +264,16 @@ def collect_pages(app: Sphinx) -> Generator[tuple[str, dict[str, Any], str], Non
# now that we have code lines (starting at index 1), insert anchors for
# the collected tags (HACK: this only works if the tag boundaries are
# properly nested!)
maxindex = len(lines) - 1
max_index = len(lines) - 1
link_text = _('[docs]')
for name, docname in used.items():
type, start, end = tags[name]
backlink = urito(pagename, docname) + '#' + refname + '.' + name
lines[start] = (
'<div class="viewcode-block" id="%s"><a class="viewcode-back" '
'href="%s">%s</a>' % (name, backlink, _('[docs]')) +
lines[start])
lines[min(end, maxindex)] += '</div>'
lines[start] = (f'<div class="viewcode-block" id="{name}">\n'
f'<a class="viewcode-back" href="{backlink}">{link_text}</a>\n'
+ lines[start])
lines[min(end, max_index)] += '</div>\n'

# try to find parents (for submodules)
parents = []
parent = modname
Expand Down Expand Up @@ -327,6 +329,7 @@ def setup(app: Sphinx) -> dict[str, Any]:
app.add_config_value('viewcode_import', None, False)
app.add_config_value('viewcode_enable_epub', False, False)
app.add_config_value('viewcode_follow_imported_members', True, False)
app.add_config_value('viewcode_line_numbers', False, 'env', (bool,))
app.connect('doctree-read', doctree_read)
app.connect('env-merge-info', env_merge_info)
app.connect('env-purge-doc', env_purge_doc)
Expand Down
51 changes: 32 additions & 19 deletions tests/test_ext_viewcode.py
Expand Up @@ -7,10 +7,7 @@
import pytest


@pytest.mark.sphinx(testroot='ext-viewcode', freshenv=True)
def test_viewcode(app, status, warning):
app.builder.build_all()

def check_viewcode_output(app, warning):
warnings = re.sub(r'\\+', '/', warning.getvalue())
assert re.findall(
r"index.rst:\d+: WARNING: Object named 'func1' not found in include " +
Expand All @@ -32,23 +29,39 @@ def test_viewcode(app, status, warning):
assert result.count('this is the class attribute class_attr') == 2

result = (app.outdir / '_modules/spam/mod1.html').read_text(encoding='utf8')
result = re.sub('<span class=".*?">', '<span>', result) # filter pygments classes
result = re.sub('<span class="[^"]{,2}">', '<span>', result) # filter pygments classes
assert ('<div class="viewcode-block" id="Class1">\n'
'<a class="viewcode-back" href="../../index.html#spam.Class1">[docs]</a>\n') in result
assert '<span>@decorator</span>\n' in result
assert '<span>class</span> <span>Class1</span><span>:</span>\n' in result
if pygments.__version__ >= '2.14.0':
assert ('<div class="viewcode-block" id="Class1"><a class="viewcode-back" '
'href="../../index.html#spam.Class1">[docs]</a>'
'<span>@decorator</span>\n'
'<span>class</span> <span>Class1</span><span>:</span>\n'
'<span> </span><span>&quot;&quot;&quot;</span>\n'
'<span> this is Class1</span>\n'
'<span> &quot;&quot;&quot;</span></div>\n') in result
assert '<span> </span><span>&quot;&quot;&quot;</span>\n' in result
else:
assert ('<div class="viewcode-block" id="Class1"><a class="viewcode-back" '
'href="../../index.html#spam.Class1">[docs]</a>'
'<span>@decorator</span>\n'
'<span>class</span> <span>Class1</span><span>:</span>\n'
' <span>&quot;&quot;&quot;</span>\n'
'<span> this is Class1</span>\n'
'<span> &quot;&quot;&quot;</span></div>\n') in result
assert ' <span>&quot;&quot;&quot;</span>\n' in result
assert '<span> this is Class1</span>\n' in result
assert '<span> &quot;&quot;&quot;</span>\n' in result

return result


@pytest.mark.sphinx(testroot='ext-viewcode', freshenv=True,
confoverrides={"viewcode_line_numbers": True})
def test_viewcode_linenos(app, warning):
shutil.rmtree(app.outdir / '_modules', ignore_errors=True)
app.builder.build_all()

result = check_viewcode_output(app, warning)
assert '<span class="linenos"> 1</span>' in result


@pytest.mark.sphinx(testroot='ext-viewcode', freshenv=True,
confoverrides={"viewcode_line_numbers": False})
def test_viewcode(app, warning):
shutil.rmtree(app.outdir / '_modules', ignore_errors=True)
app.builder.build_all()

result = check_viewcode_output(app, warning)
assert 'class="linenos">' not in result


@pytest.mark.sphinx('epub', testroot='ext-viewcode')
Expand Down

0 comments on commit 762ed85

Please sign in to comment.