diff --git a/pkgs/development/python-modules/sphinx/default.nix b/pkgs/development/python-modules/sphinx/default.nix
index b08e69a7ab6c..2f951573b860 100644
--- a/pkgs/development/python-modules/sphinx/default.nix
+++ b/pkgs/development/python-modules/sphinx/default.nix
@@ -62,6 +62,11 @@ buildPythonPackage rec {
hash = "sha256-AObNQz2gKoPHfvC5aoefynXfQMe3bnQpEx6KrLNQBoQ=";
};
+ patches = [
+ # https://github.com/sphinx-doc/sphinx/commit/5ff3740063c1ac57f17ecd697bcd06cc1de4e75c
+ ./pygments-2.19-compat.patch
+ ];
+
build-system = [ flit-core ];
dependencies =
diff --git a/pkgs/development/python-modules/sphinx/pygments-2.19-compat.patch b/pkgs/development/python-modules/sphinx/pygments-2.19-compat.patch
new file mode 100644
index 000000000000..37330e0f1f06
--- /dev/null
+++ b/pkgs/development/python-modules/sphinx/pygments-2.19-compat.patch
@@ -0,0 +1,213 @@
+diff --git a/tests/test_builders/test_build_html_code.py b/tests/test_builders/test_build_html_code.py
+index d32c0b3e2..02684b22e 100644
+--- a/tests/test_builders/test_build_html_code.py
++++ b/tests/test_builders/test_build_html_code.py
+@@ -1,3 +1,6 @@
++from __future__ import annotations
++
++import pygments
+ import pytest
+
+
+@@ -32,11 +35,16 @@ def test_html_codeblock_linenos_style_inline(app):
+
+ @pytest.mark.sphinx('html', testroot='reST-code-role')
+ def test_html_code_role(app):
++ if tuple(map(int, pygments.__version__.split('.')[:2])) >= (2, 19):
++ sp = ' '
++ else:
++ sp = ' '
++
+ app.build()
+ content = (app.outdir / 'index.html').read_text(encoding='utf8')
+
+ common_content = (
+- 'def foo'
++ f'def{sp}foo'
+ '('
+ '1 '
+ '+ '
+diff --git a/tests/test_builders/test_build_latex.py b/tests/test_builders/test_build_latex.py
+index ea6dc7475..6ba2a375c 100644
+--- a/tests/test_builders/test_build_latex.py
++++ b/tests/test_builders/test_build_latex.py
+@@ -8,6 +8,7 @@ from pathlib import Path
+ from shutil import copyfile
+ from subprocess import CalledProcessError
+
++import pygments
+ import pytest
+
+ from sphinx.builders.latex import default_latex_documents
+@@ -2127,12 +2128,16 @@ def test_latex_container(app):
+
+ @pytest.mark.sphinx('latex', testroot='reST-code-role')
+ def test_latex_code_role(app):
++ if tuple(map(int, pygments.__version__.split('.')[:2])) >= (2, 19):
++ sp = r'\PYG{+w}{ }'
++ else:
++ sp = ' '
++
+ app.build()
+ content = (app.outdir / 'projectnamenotset.tex').read_text(encoding='utf8')
+
+ common_content = (
+- r'\PYG{k}{def} '
+- r'\PYG{n+nf}{foo}'
++ r'\PYG{k}{def}' + sp + r'\PYG{n+nf}{foo}'
+ r'\PYG{p}{(}'
+ r'\PYG{l+m+mi}{1} '
+ r'\PYG{o}{+} '
+diff --git a/tests/test_directives/test_directive_code.py b/tests/test_directives/test_directive_code.py
+index 65e16b805..2876dbc05 100644
+--- a/tests/test_directives/test_directive_code.py
++++ b/tests/test_directives/test_directive_code.py
+@@ -2,6 +2,7 @@
+
+ import os.path
+
++import pygments
+ import pytest
+ from docutils import nodes
+
+@@ -393,6 +394,11 @@ def test_literal_include_block_start_with_comment_or_brank(app):
+
+ @pytest.mark.sphinx('html', testroot='directive-code')
+ def test_literal_include_linenos(app):
++ if tuple(map(int, pygments.__version__.split('.')[:2])) >= (2, 19):
++ sp = ' '
++ else:
++ sp = ' '
++
+ app.build(filenames=[app.srcdir / 'linenos.rst'])
+ html = (app.outdir / 'linenos.html').read_text(encoding='utf8')
+
+@@ -410,7 +416,7 @@ def test_literal_include_linenos(app):
+
+ # :lines: 5-9
+ assert (
+- '5class '
++ f'5class{sp}'
+ 'Foo:'
+ ) in html
+
+@@ -560,12 +566,17 @@ def test_code_block_highlighted(app):
+
+ @pytest.mark.sphinx('html', testroot='directive-code')
+ def test_linenothreshold(app):
++ if tuple(map(int, pygments.__version__.split('.')[:2])) >= (2, 19):
++ sp = ' '
++ else:
++ sp = ' '
++
+ app.build(filenames=[app.srcdir / 'linenothreshold.rst'])
+ html = (app.outdir / 'linenothreshold.html').read_text(encoding='utf8')
+
+ # code-block using linenothreshold
+ assert (
+- '1class '
++ f'1class{sp}'
+ 'Foo:'
+ ) in html
+
+diff --git a/tests/test_extensions/test_ext_viewcode.py b/tests/test_extensions/test_ext_viewcode.py
+index eeef391c1..996db00fd 100644
+--- a/tests/test_extensions/test_ext_viewcode.py
++++ b/tests/test_extensions/test_ext_viewcode.py
+@@ -6,6 +6,7 @@ import re
+ import shutil
+ from typing import TYPE_CHECKING
+
++import pygments
+ import pytest
+
+ if TYPE_CHECKING:
+@@ -13,6 +14,11 @@ if TYPE_CHECKING:
+
+
+ def check_viewcode_output(app: SphinxTestApp) -> str:
++ if tuple(map(int, pygments.__version__.split('.')[:2])) >= (2, 19):
++ sp = ' '
++ else:
++ sp = ' '
++
+ warnings = re.sub(r'\\+', '/', app.warning.getvalue())
+ assert re.findall(
+ r"index.rst:\d+: WARNING: Object named 'func1' not found in include "
+@@ -41,7 +47,7 @@ def check_viewcode_output(app: SphinxTestApp) -> str:
+ '[docs]\n'
+ ) in result
+ assert '@decorator\n' in result
+- assert 'class Class1:\n' in result
++ assert f'class{sp}Class1:\n' in result
+ assert ' """\n' in result
+ assert ' this is Class1\n' in result
+ assert ' """\n' in result
+diff --git a/tests/test_highlighting.py b/tests/test_highlighting.py
+index 430d569c0..2d08162aa 100644
+--- a/tests/test_highlighting.py
++++ b/tests/test_highlighting.py
+@@ -10,7 +10,8 @@ from pygments.token import Name, Text
+
+ from sphinx.highlighting import PygmentsBridge
+
+-if tuple(map(int, pygments.__version__.split('.')))[:2] < (2, 18):
++
++if tuple(map(int, pygments.__version__.split('.')[:2])) < (2, 18):
+ from pygments.formatter import Formatter
+
+ Formatter.__class_getitem__ = classmethod(lambda cls, name: cls) # type: ignore[attr-defined]
+diff --git a/tests/test_intl/test_intl.py b/tests/test_intl/test_intl.py
+index 6f343e03d..9125612af 100644
+--- a/tests/test_intl/test_intl.py
++++ b/tests/test_intl/test_intl.py
+@@ -10,6 +10,7 @@ import shutil
+ import time
+ from io import StringIO
+
++import pygments
+ import pytest
+ from babel.messages import mofile, pofile
+ from babel.messages.catalog import Catalog
+@@ -1473,6 +1474,11 @@ def test_xml_strange_markup(app):
+ @pytest.mark.sphinx('html', testroot='intl')
+ @pytest.mark.test_params(shared_result='test_intl_basic')
+ def test_additional_targets_should_not_be_translated(app):
++ if tuple(map(int, pygments.__version__.split('.')[:2])) >= (2, 19):
++ sp = ' '
++ else:
++ sp = ' '
++
+ app.build()
+ # [literalblock.txt]
+ result = (app.outdir / 'literalblock.html').read_text(encoding='utf8')
+@@ -1511,7 +1517,7 @@ def test_additional_targets_should_not_be_translated(app):
+ # doctest block should not be translated but be highlighted
+ expected_expr = (
+ """>>> """
+- """import sys """
++ f"""import{sp}sys """
+ """# sys importing"""
+ )
+ assert_count(expected_expr, result, 1)
+@@ -1556,6 +1562,11 @@ def test_additional_targets_should_not_be_translated(app):
+ },
+ )
+ def test_additional_targets_should_be_translated(app):
++ if tuple(map(int, pygments.__version__.split('.')[:2])) >= (2, 19):
++ sp = ' '
++ else:
++ sp = ' '
++
+ app.build()
+ # [literalblock.txt]
+ result = (app.outdir / 'literalblock.html').read_text(encoding='utf8')
+@@ -1605,7 +1616,7 @@ def test_additional_targets_should_be_translated(app):
+ # doctest block should not be translated but be highlighted
+ expected_expr = (
+ """>>> """
+- """import sys """
++ f"""import{sp}sys """
+ """# SYS IMPORTING"""
+ )
+ assert_count(expected_expr, result, 1)