diff --git a/docs/src/release_notes.rst b/docs/src/release_notes.rst
index d603b3f..f66e1db 100644
--- a/docs/src/release_notes.rst
+++ b/docs/src/release_notes.rst
@@ -12,6 +12,7 @@ Unreleased
----------
- Declare support for Python 3.12 and 3.13 (:issue:`150`)
- Remove support for Python 3.7 and 3.8 (:issue:`150`)
+- Fix changed whitespace handling in Pygments 2.19 (:issue:`152`)
0.15.2 (2024-06-03)
-------------------
diff --git a/pyproject.toml b/pyproject.toml
index cf76e98..63ea423 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -13,7 +13,6 @@ requires-python = ">=3.9"
dependencies = [
"sphinx>=3.2.0",
"beautifulsoup4>=4.8.1",
- "pygments<2.19",
]
# Keep extras in sync with requirements manually
optional-dependencies = {ipython = ["ipython!=8.7.0"]}
diff --git a/src/sphinx_codeautolink/extension/block.py b/src/sphinx_codeautolink/extension/block.py
index d19b25c..4e08203 100644
--- a/src/sphinx_codeautolink/extension/block.py
+++ b/src/sphinx_codeautolink/extension/block.py
@@ -371,20 +371,22 @@ def link_html(
# The builtin re doesn't support variable-width lookbehind,
# so instead we use a match groups in all pre patterns to remove the non-content.
-no_dot_prere = r'(?\.)()'
+no_dot_pre = r'(?\.)()'
# Potentially instead assert an initial closing parenthesis followed by a dot.
-call_dot_prere = r'(\)\s*\.\s*)'
-import_prere = (
- r'((import\s+(\(\s*)?)'
- r'|(,\s*))'
+call_dot_pre = r'(\)\s*\.\s*)'
+no_dot_post = r'(?!(\.)|())'
+
+# Pygments 2.19 changed import whitespace highlighting so we need to support both
+# with "w" class and raw whitespace for now (see #152)
+whitespace = r'(\s*)|(\s*)'
+import_pre = (
+ rf'((import{whitespace}(\(\s*)?)'
+ rf'|(,{whitespace}))'
)
-from_prere = r'(from\s+)'
+import_post = r'(?=($)|(\s+)|())(?!)'
-no_dot_postre = r'(?!(\.)|())'
-import_postre = (
- r'(?=($)|(\s+)|(,)|(\)))(?!)'
-)
-from_postre = r'(?=\s*import)'
+from_pre = rf'(from{whitespace})'
+from_post = rf'(?={whitespace}import)'
def construct_name_pattern(name: Name) -> str:
@@ -395,17 +397,17 @@ def construct_name_pattern(name: Name) -> str:
[first_name_pattern.format(name=parts[0])]
+ [name_pattern.format(name=p) for p in parts[1:]]
)
- return no_dot_prere + pattern + no_dot_postre
+ return no_dot_pre + pattern + no_dot_post
elif name.context == LinkContext.after_call:
parts = name.code_str.split(".")
pattern = period.join(
[first_name_pattern.format(name=parts[0])]
+ [name_pattern.format(name=p) for p in parts[1:]]
)
- return call_dot_prere + pattern + no_dot_postre
+ return call_dot_pre + pattern + no_dot_post
elif name.context == LinkContext.import_from:
pattern = import_from_pattern.format(name=name.code_str)
- return from_prere + pattern + from_postre
+ return from_pre + pattern + from_post
elif name.context == LinkContext.import_target:
pattern = import_target_pattern.format(name=name.code_str)
- return import_prere + pattern + import_postre
+ return import_pre + pattern + import_post