pygments / pygments/pygments

Feature request: Allow specification of arbitrary line numbers

Open
#2,322 2 comments 3 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
2.2k
Forks
885
PR merge metrics
No merged PRs in 30d

Description

Overview of feature request

I'm raising this request in the context of the HTML formatter provided by pygments. Unsure if this applies to other formatters as well.

As of now, the pygments HtmlFormatter provides a couple of options related to attaching line numbers to the formatted code blocks, namely linenos (False, True, "table", "inline"), hl_lines, linenostart, linenostep (with linenospecial, anchorlinenos tangentially related).
linenostart allows one to start from the specified number, not necessarily from 1, and linenostep allows one to specify line numbers allows us to print every nth line in the line number column.

However, sometimes, we might want to print arbitrary lines (not necessarily stepped) from the source code, so the request here is to expose one more option, say linenoprint which specifies a list of line numbers that will be printed from the provided source. See API Details below.

Motivation

mkdocstrings uses pygments (indirectly) when displaying source code in the generated documentation, we want to be able to expose an option to remove docstrings from the source code. See discussion in https://github.com/mkdocstrings/mkdocstrings/issues/249#issuecomment-1399264566 for more details.

The intention is to remove the docstrings from the source before passing it into pygments, but in order to still present the code as a contiguous code-block, we need support for specifying arbitrary line numbers. As opposed to assuming line numbers are always sequential.

API Details

This is just my proposal:

  • Implemented behaviour described below should be available for both table and inline lineno styles. If linenos is not provided or is False, then linenoprint should be ignored.
  • linenoprint should be mutually exclusive with linenostart. Throw an error if both are provided.
  • Normal Behaviour:
    from pygments import highlight
    from pygments.lexers import PythonLexer
    from pygments.formatters import HtmlFormatter
    code = """\
    def foo(x) -> None:
        print(x)
    """
    highlight(code, PythonLexer(), HtmlFormatter(linenos="table", linenoprint=[50, 52]))
    
    after formatting, should return an output that looks like
    50 | def foo(x) -> None:
    52 |     print(x)
    
  • If the provided linenoprint list is shorter / longer than the number of lines in the source, I don't really have a preference on what pygments should do. If I had to decide, I'ld be more permissive, that is:
    • if len(linenoprint) < len(source), do an equivalent of zip_longest and pad linenoprint with ""
      highlight(code, PythonLexer(), HtmlFormatter(linenos="table", linenoprint=[50,]))
      
      can probably give
      50 | def foo(x) -> None:
         |     print(x)
      
    • if len(linenoprint) > len(source), then do an equivalent of zip(strict=False) and ignore the rest of linenoprint.
      highlight(code, PythonLexer(), HtmlFormatter(linenos="table", linenoprint=[50, 52, 99]))
      
      can probably give
      50 | def foo(x) -> None:
      52 |     print(x)
      
  • Using linenoprint should be independent from the other line number options (e.g. linenostep, hl_lines), E.g.
    highlight(code, PythonLexer(), HtmlFormatter(linenos="table", linenoprint=[50, 52], linenostep=4))
    
    after formatting, should return an output that looks like
       | def foo(x) -> None:
    52 |     print(x)
    
    If there are other potential conflicts that I missed, we can discuss how to deal with them below.
Additional, optional, good-to-have

It would be nice if we don't have to strictly require the line numbering to be all integers, so that we might be able to format something like this

code = """\
def foo(x) -> None:
    …
    print(x)
"""
highlight(code, PythonLexer(), HtmlFormatter(linenos="table", linenoprint=[50, "…", 52]))  # shouldn't throw an error

after formatting, should return an output that looks like

50 | def foo(x) -> None:
…  |     …
52 |     print(x)

to signify that we've truncated some code off.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at the HtmlFormatter entry point and review how its existing linenos, linenostart, linenostep, table, and inline options are handled. Trace the formatter's line-number rendering for both requested styles and determine how arbitrary labels, option conflicts, and length mismatches should behave. Done means the agreed API is implemented consistently and covered for the listed interactions.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
tooling
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.