Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for line numbering in the viewcode extention #11204

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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_show_lineos

If this is ``True``, viewcode extension will display line numbers on the page.

The default is ``False``.

.. versionadded:: 6.2

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

.. versionadded:: 1.8
Expand Down
17 changes: 11 additions & 6 deletions sphinx/ext/viewcode.py
Expand Up @@ -250,12 +250,16 @@ 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)
show_lineos = env.config.viewcode_show_lineos
highlighted = highlighter.highlight_block(code, lexer, linenos=show_lineos)
# split the code into lines
lines = highlighted.splitlines()
# find the first line of code. 0 if it's not nested in a table
# skip all row number and search for 2nd <td> if lineos are shown (class="code")
offset = next((i for i, line in enumerate(lines) if '<td class="code">' in line), 0)
# split off wrap markup from the first line of the actual code
before, after = lines[0].split('<pre>')
lines[0:1] = [before + '<pre>', after]
before, after = lines[offset].split('<pre>')
lines[offset:offset+1] = [before + '<pre>', after]
# nothing to do for the last line; it always starts with </pre> anyway
# 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
Expand All @@ -264,11 +268,11 @@ def collect_pages(app: Sphinx) -> Generator[tuple[str, dict[str, Any], str], Non
for name, docname in used.items():
type, start, end = tags[name]
backlink = urito(pagename, docname) + '#' + refname + '.' + name
lines[start] = (
lines[start+offset] = (
'<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+offset])
lines[min(end+offset, maxindex)] += '</div>'
# try to find parents (for submodules)
parents = []
parent = modname
Expand Down Expand Up @@ -324,6 +328,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_show_lineos', False, False)
app.connect('doctree-read', doctree_read)
app.connect('env-merge-info', env_merge_info)
app.connect('env-purge-doc', env_purge_doc)
Expand Down
9 changes: 9 additions & 0 deletions tests/roots/test-ext-viewcode-lineos/conf.py
@@ -0,0 +1,9 @@
import os
import sys

source_dir = os.path.abspath('.')
if source_dir not in sys.path:
sys.path.insert(0, source_dir)
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode']
exclude_patterns = ['_build']
viewcode_show_lineos = True
39 changes: 39 additions & 0 deletions tests/roots/test-ext-viewcode-lineos/index.rst
@@ -0,0 +1,39 @@
viewcode
========

.. py:module:: spam

.. autofunction:: func1

.. autofunction:: func2

.. autofunction:: spam.mod1.func1

.. autofunction:: spam.mod2.func2

.. autofunction:: Class1

.. autofunction:: Class2

.. autofunction:: spam.mod1.Class1

.. autofunction:: spam.mod2.Class2


.. literalinclude:: spam/__init__.py
:language: python
:pyobject: func1

.. literalinclude:: spam/mod1.py
:language: python
:pyobject: func1

.. autoclass:: spam.mod3.Class3
:members:

.. automodule:: spam.mod3
:members:

.. toctree::

objects
169 changes: 169 additions & 0 deletions tests/roots/test-ext-viewcode-lineos/objects.rst
@@ -0,0 +1,169 @@
Testing object descriptions
===========================

.. function:: func_without_module(a, b, *c[, d])

Does something.

.. function:: func_without_body()

.. function:: func_noindex
:noindex:

.. function:: func_with_module
:module: foolib

Referring to :func:`func with no index <func_noindex>`.
Referring to :func:`nothing <>`.

.. module:: mod
:synopsis: Module synopsis.
:platform: UNIX

.. function:: func_in_module

.. class:: Cls

.. method:: meth1

.. staticmethod:: meths

.. attribute:: attr

.. explicit class given
.. method:: Cls.meth2

.. explicit module given
.. exception:: Error(arg1, arg2)
:module: errmod

.. data:: var


.. currentmodule:: None

.. function:: func_without_module2() -> annotation

.. object:: long(parameter, \
list)
another one

.. class:: TimeInt

Has only one parameter (triggers special behavior...)

:param moo: |test|
:type moo: |test|

.. |test| replace:: Moo

.. class:: Time(hour, minute, isdst)

:param year: The year.
:type year: TimeInt
:param TimeInt minute: The minute.
:param isdst: whether it's DST
:type isdst: * some complex
* expression
:returns: a new :class:`Time` instance
:rtype: :class:`Time`
:raises ValueError: if the values are out of range
:ivar int hour: like *hour*
:ivar minute: like *minute*
:vartype minute: int
:param hour: Some parameter
:type hour: DuplicateType
:param hour: Duplicate param. Should not lead to crashes.
:type hour: DuplicateType
:param .Cls extcls: A class from another module.


C items
=======

.. c:function:: Sphinx_DoSomething()

.. c:member:: SphinxStruct.member

.. c:macro:: SPHINX_USE_PYTHON

.. c:type:: SphinxType

.. c:var:: sphinx_global


Javascript items
================

.. js:function:: foo()

.. js:data:: bar

.. documenting the method of any object
.. js:function:: bar.baz(href, callback[, errback])

:param string href: The location of the resource.
:param callback: Gets called with the data returned by the resource.
:throws InvalidHref: If the `href` is invalid.
:returns: `undefined`

.. js:attribute:: bar.spam

References
==========

Referencing :class:`mod.Cls` or :Class:`mod.Cls` should be the same.

With target: :c:func:`Sphinx_DoSomething()` (parentheses are handled),
:c:member:`SphinxStruct.member`, :c:macro:`SPHINX_USE_PYTHON`,
:c:type:`SphinxType *` (pointer is handled), :c:data:`sphinx_global`.

Without target: :c:func:`CFunction`. :c:func:`!malloc`.

:js:func:`foo()`
:js:func:`foo`

:js:data:`bar`
:js:func:`bar.baz()`
:js:func:`bar.baz`
:js:func:`~bar.baz()`

:js:attr:`bar.baz`


Others
======

.. envvar:: HOME

.. program:: python

.. cmdoption:: -c command

.. program:: perl

.. cmdoption:: -c

.. option:: +p

Link to :option:`perl +p`.


User markup
===========

.. userdesc:: myobj:parameter

Description of userdesc.


Referencing :userdescrole:`myobj`.


CPP domain
==========

.. cpp:class:: n::Array<T,d>

.. cpp:function:: T& operator[]( unsigned j )
const T& operator[]( unsigned j ) const
2 changes: 2 additions & 0 deletions tests/roots/test-ext-viewcode-lineos/spam/__init__.py
@@ -0,0 +1,2 @@
from .mod1 import Class1, func1
from .mod2 import Class2, func2
30 changes: 30 additions & 0 deletions tests/roots/test-ext-viewcode-lineos/spam/mod1.py
@@ -0,0 +1,30 @@
"""
mod1
"""


def decorator(f):
return f


@decorator
def func1(a, b):
"""
this is func1
"""
return a, b


@decorator
class Class1:
"""
this is Class1
"""


class Class3:
"""
this is Class3
"""
class_attr = 42
"""this is the class attribute class_attr"""
22 changes: 22 additions & 0 deletions tests/roots/test-ext-viewcode-lineos/spam/mod2.py
@@ -0,0 +1,22 @@
"""
mod2
"""


def decorator(f):
return f


@decorator
def func2(a, b):
"""
this is func2
"""
return a, b


@decorator
class Class2:
"""
this is Class2
"""
3 changes: 3 additions & 0 deletions tests/roots/test-ext-viewcode-lineos/spam/mod3.py
@@ -0,0 +1,3 @@
from spam.mod1 import Class3

__all__ = ('Class3',)