Skip to content

Commit 1c71e55

Browse files
committed
Merge branch 'master' into claude/issue-8206-20250718-1635
2 parents 061b38c + 8762d5c commit 1c71e55

File tree

486 files changed

+13009
-11320
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

486 files changed

+13009
-11320
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,8 @@ source/developer/localization.md
7070
*.key
7171
*.crt
7272
.aider*
73+
74+
# Sphinx build artifacts
75+
*.doctree
76+
_build/
77+
build/

extensions/sphinx_inline_tabs/directive.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
logger: logging.SphinxLoggerAdapter = logging.getLogger(__name__)
1818
LOG_PREFIX: Final[str] = "[sphinx_inline_tabs]"
1919

20+
INLINE_TAB_DOCNAMES: Final[str] = "inline_tab_docnames"
21+
"""The key in the Sphinx environment to store a list of documents with inline tabs"""
22+
2023

2124
class TabDirective(SphinxDirective):
2225
"""Tabbed content in Sphinx documentation."""
@@ -75,15 +78,14 @@ def run(self) -> list[nodes.Node]:
7578
)
7679

7780
"""
78-
Record the name of this tab in a list of tabs for this document in the
81+
Record the name of this document in a list of documents with tabs in the
7982
Sphinx environment.
8083
"""
81-
# TODO: is this valuable any longer?
82-
if not hasattr(self.env, "sphinx_tabs"):
83-
self.env.sphinx_tabs = {}
84-
if self.env.docname not in self.env.sphinx_tabs:
85-
self.env.sphinx_tabs[self.env.docname] = []
86-
self.env.sphinx_tabs[self.env.docname].append(self.arguments[0])
84+
if not hasattr(self.env, INLINE_TAB_DOCNAMES):
85+
setattr(self.env, INLINE_TAB_DOCNAMES, [])
86+
tab_docnames: list[str] = getattr(self.env, INLINE_TAB_DOCNAMES)
87+
if self.env.docname not in tab_docnames:
88+
tab_docnames.append(self.env.docname)
8789

8890
return [container]
8991

extensions/sphinx_inline_tabs/events.py

Lines changed: 66 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from sphinx.util import logging
1212
from sphinx.util.nodes import _make_id
1313

14-
from .directive import LOG_PREFIX
14+
from .directive import LOG_PREFIX, INLINE_TAB_DOCNAMES
1515
from .nodes import TabContainer
1616
from .sectiondata import SectionData
1717
from .tab_id import TabId
@@ -33,9 +33,10 @@ def env_purge_doc(app: Sphinx, _: BuildEnvironment, docname: str) -> None:
3333
:param _: The Sphinx BuildEnvironment object; unused
3434
:param docname: The name of the document to purge
3535
"""
36-
if hasattr(app.env, "sphinx_tabs"):
37-
if docname in app.env.sphinx_tabs:
38-
app.env.sphinx_tabs.pop(docname)
36+
if hasattr(app.env, INLINE_TAB_DOCNAMES):
37+
tab_docnames: list[str] = getattr(app.env, INLINE_TAB_DOCNAMES)
38+
if docname in tab_docnames:
39+
tab_docnames.remove(docname)
3940

4041

4142
def env_merge_info(
@@ -50,73 +51,15 @@ def env_merge_info(
5051
:param docnames: A list of the document names to merge
5152
:param other: The Sphinx BuildEnvironment from the reader worker
5253
"""
53-
if not hasattr(app.env, "sphinx_tabs"):
54-
app.env.sphinx_tabs = {}
55-
if hasattr(other, "sphinx_tabs"):
54+
if not hasattr(app.env, INLINE_TAB_DOCNAMES):
55+
setattr(app.env, INLINE_TAB_DOCNAMES, [])
56+
tab_docnames: list[str] = getattr(app.env, INLINE_TAB_DOCNAMES)
57+
if hasattr(other, INLINE_TAB_DOCNAMES):
58+
other_tab_docnames: list[str] = getattr(other, INLINE_TAB_DOCNAMES)
5659
for docname in docnames:
57-
if docname in other.sphinx_tabs:
58-
if docname not in app.env.sphinx_tabs:
59-
app.env.sphinx_tabs[docname] = []
60-
if (
61-
len(app.env.sphinx_tabs[docname]) > 0
62-
and len(other.sphinx_tabs[docname]) == 0
63-
):
64-
logger.warning(
65-
f"{LOG_PREFIX} env_merge_info: {docname}; not overwriting app.env with empty list from other"
66-
)
67-
continue
68-
app.env.sphinx_tabs[docname] = other.sphinx_tabs[docname].copy()
69-
70-
71-
def merge_toctrees(original_toc: nodes.list_item, tab_based_toc: nodes.list_item) -> nodes.list_item:
72-
"""
73-
Merge the original toctree with the tab-based TOC to preserve both child pages and tab content headings.
74-
75-
:param original_toc: The original toctree (includes child pages)
76-
:param tab_based_toc: The tab-based TOC (includes tab content headings)
77-
:return: A merged toctree containing both types of content
78-
"""
79-
# If the tab-based TOC is empty, return the original TOC
80-
if not tab_based_toc or not hasattr(tab_based_toc, 'children') or not tab_based_toc.children:
81-
return original_toc
82-
83-
# If the original TOC is empty, return the tab-based TOC
84-
if not original_toc or not hasattr(original_toc, 'children') or not original_toc.children:
85-
return tab_based_toc
86-
87-
# Create a new list item to hold the merged content
88-
merged_toc = nodes.list_item()
89-
merged_bullet_list = nodes.bullet_list()
90-
91-
# Add the tab-based TOC content first (this includes the tab headings)
92-
if hasattr(tab_based_toc, 'children') and tab_based_toc.children:
93-
for child in tab_based_toc.children:
94-
if isinstance(child, nodes.bullet_list):
95-
# Extract items from the tab-based bullet list
96-
for item in child.children:
97-
if isinstance(item, nodes.list_item):
98-
merged_bullet_list.append(item)
99-
else:
100-
# Direct child - add it as a list item
101-
merged_bullet_list.append(child)
102-
103-
# Add the original TOC content (this includes child pages)
104-
if hasattr(original_toc, 'children') and original_toc.children:
105-
for child in original_toc.children:
106-
if isinstance(child, nodes.bullet_list):
107-
# Extract items from the original bullet list
108-
for item in child.children:
109-
if isinstance(item, nodes.list_item):
110-
merged_bullet_list.append(item)
111-
else:
112-
# Direct child - add it as a list item
113-
merged_bullet_list.append(child)
114-
115-
# Only add the bullet list if it has items
116-
if merged_bullet_list.children:
117-
merged_toc.append(merged_bullet_list)
118-
119-
return merged_toc
60+
if docname in other_tab_docnames:
61+
if docname not in tab_docnames:
62+
tab_docnames.append(docname)
12063

12164

12265
def doctree_read(app: Sphinx, doctree: nodes.document):
@@ -126,43 +69,69 @@ def doctree_read(app: Sphinx, doctree: nodes.document):
12669
:param app: The Sphinx Application instance
12770
:param doctree: The parsed document tree; unused
12871
"""
129-
if not hasattr(app.env, "sphinx_tabs"):
130-
app.env.sphinx_tabs = {}
131-
if (
132-
app.env.docname in app.env.sphinx_tabs
133-
and len(app.env.sphinx_tabs[app.env.docname]) > 0
134-
):
72+
if not hasattr(app.env, INLINE_TAB_DOCNAMES):
73+
setattr(app.env, INLINE_TAB_DOCNAMES, [])
74+
tab_docnames: list[str] = getattr(app.env, INLINE_TAB_DOCNAMES)
75+
if app.env.docname in tab_docnames:
13576
logger.debug(f"{LOG_PREFIX} doctree_read: {app.env.docname} has tabs")
13677

137-
# Store the original toctree structure before replacing it
138-
original_toc = None
139-
if (len(app.env.tocs[app.env.docname][0]) > 1 and
140-
app.env.tocs[app.env.docname][0][1] is not None):
141-
original_toc = app.env.tocs[app.env.docname][0][1]
142-
logger.debug(f"{LOG_PREFIX} doctree_read({app.env.docname}): preserving original toctree")
143-
14478
# Generate the tab-based TOC (includes headings from within tabs)
145-
tab_based_toc: nodes.list_item = sectiondata_to_toc(
79+
updated_tocs: nodes.list_item = sectiondata_to_toc(
14680
app.env.docname,
14781
collect_sections(app.env, doctree, app.env.docname, doctree),
14882
)
149-
150-
# Merge the original toctree with the tab-based TOC
151-
if original_toc is not None:
152-
logger.debug(f"{LOG_PREFIX} doctree_read({app.env.docname}): merging original toctree with tab-based TOC")
153-
updated_tocs = merge_toctrees(original_toc, tab_based_toc)
154-
else:
155-
logger.debug(f"{LOG_PREFIX} doctree_read({app.env.docname}): using tab-based TOC only")
156-
updated_tocs = tab_based_toc
15783

15884
logger.debug(
15985
f"{LOG_PREFIX} doctree_read({app.env.docname}): updated_tocs[0][1]={updated_tocs}"
16086
)
16187
if len(app.env.tocs[app.env.docname][0]) == 1:
16288
app.env.tocs[app.env.docname][0].append(updated_tocs)
16389
else:
164-
app.env.tocs[app.env.docname][0][1] = updated_tocs
165-
app.env.sphinx_tabs[app.env.docname].clear()
90+
# SAFE FIX: Check if the original structure has toctree nodes
91+
# If it does, leave it completely unchanged to preserve left navigation
92+
original_toc_item = app.env.tocs[app.env.docname][0][1]
93+
has_toctree = False
94+
95+
if hasattr(original_toc_item, 'children') and original_toc_item.children:
96+
for child in original_toc_item.children:
97+
if isinstance(child, addnodes.toctree):
98+
has_toctree = True
99+
break
100+
101+
if has_toctree:
102+
# Merge tab headings into the existing right-pane TOC while preserving
103+
# the original toctree (left navigation) structure.
104+
try:
105+
# Find the bullet list in the original toc item (holds section entries)
106+
original_bullets = None
107+
for child in getattr(original_toc_item, "children", []) or []:
108+
if isinstance(child, nodes.bullet_list):
109+
original_bullets = child
110+
break
111+
112+
# Find the bullet list produced by our updated tab-aware TOC
113+
updated_bullets = None
114+
for child in getattr(updated_tocs, "children", []) or []:
115+
if isinstance(child, nodes.bullet_list):
116+
updated_bullets = child
117+
break
118+
119+
# If we have tab headings to merge
120+
if updated_bullets is not None:
121+
if original_bullets is None:
122+
# No existing bullets: attach updated list directly
123+
original_toc_item.append(updated_bullets)
124+
else:
125+
# Append each new tab heading item to existing bullets
126+
for li in list(updated_bullets.children):
127+
original_bullets.append(li)
128+
except Exception as e:
129+
logger.warning(
130+
f"{LOG_PREFIX} doctree_read({app.env.docname}): failed merging tab headings into TOC: {e}"
131+
)
132+
else:
133+
# Only apply tab modifications if no toctree nodes are present
134+
app.env.tocs[app.env.docname][0][1] = updated_tocs
166135

167136

168137
def html_page_context(
@@ -182,8 +151,9 @@ def html_page_context(
182151
:param doctree:
183152
:return:
184153
"""
185-
if hasattr(app.env, "sphinx_tabs"):
186-
if pagename in app.env.sphinx_tabs:
154+
if hasattr(app.env, INLINE_TAB_DOCNAMES):
155+
tab_docnames: list[str] = getattr(app.env, INLINE_TAB_DOCNAMES)
156+
if pagename in tab_docnames:
187157
logger.debug(
188158
f"{LOG_PREFIX} html_page_context: {pagename}; context.keys()={context.keys()}"
189159
)

0 commit comments

Comments
 (0)