Skip to content

Commit 7b20818

Browse files
authored
Port semantic and section highlighting from stdlib-reference to ReadTheDocs (#104)
Fixes #79 This change ports over the section highlighting from https://shader-slang.org/stdlib-reference/global-decls/ to the readthedocs site, as well as the semantic highlighting and styles as well. It also adjusts the relative sizing of the headings to more closely resemble that site as well. RTD does have section highlighting built-in, but not when clicking on links, only when clicking on the section's pilcrow (¶).
1 parent 05b688c commit 7b20818

File tree

4 files changed

+263
-54
lines changed

4 files changed

+263
-54
lines changed

docs/_ext/fix_links.py

Lines changed: 12 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -23,73 +23,32 @@ def fix_md_links_post_process(app, exception):
2323
output_dir = app.builder.outdir
2424
logger.info(f"[DEBUG] Post-processing HTML files in {output_dir}")
2525

26-
# Specifically focus on stdlib-reference directory
27-
stdlib_dir = os.path.join(output_dir, 'external', 'stdlib-reference')
28-
if not os.path.exists(stdlib_dir):
29-
logger.info(f"[DEBUG] stdlib-reference directory not found: {stdlib_dir}")
30-
return
31-
3226
count = 0
3327
fixed = 0
3428

35-
# Walk through HTML files
36-
for root, _, files in os.walk(stdlib_dir):
29+
# Walk through ALL HTML files in the output directory
30+
for root, _, files in os.walk(output_dir):
3731
for filename in files:
3832
if filename.endswith('.html'):
3933
filepath = os.path.join(root, filename)
4034
count += 1
41-
4235
try:
4336
with open(filepath, 'r', encoding='utf-8') as f:
4437
content = f.read()
45-
46-
# Original content for comparison
4738
original_content = content
4839

49-
# Look for href="#../path/to/file#fragment" pattern
50-
# This is the problematic pattern where a relative path is treated as a fragment
51-
pattern = r'href=(["\'])#(\.\.\/[^"\']+?)#([^"\']+?)\1'
52-
matches = re.findall(pattern, content)
53-
54-
if matches:
55-
logger.info(f"[DEBUG] Found {len(matches)} problematic links in {filepath}")
56-
57-
# Fix each match
58-
for quote, path, fragment in matches:
59-
# Create the correct path with .html extension
60-
if not path.endswith('/') and '.' not in path.split('/')[-1]:
61-
path_with_html = path + '.html'
62-
else:
63-
path_with_html = path
40+
# Fix any href="#something" where something looks like a path (contains / or .html)
41+
pattern = r'href=(["\'])#([^"\']+?)(#[^"\']+)?\1'
42+
def repl(match):
43+
quote, path, fragment = match.group(1), match.group(2), match.group(3) or ''
44+
# Only rewrite if path looks like a file or path, not just a fragment
45+
if '/' in path or '.html' in path:
46+
return f'href={quote}{path}{fragment}{quote}'
47+
else:
48+
return match.group(0) # leave as is for pure fragments
6449

65-
# Replace in content
66-
old = f'href={quote}#{path}#{fragment}{quote}'
67-
new = f'href={quote}{path_with_html}#{fragment}{quote}'
68-
content = content.replace(old, new)
69-
logger.info(f"[DEBUG] Fixed: {old} -> {new}")
50+
content = re.sub(pattern, repl, content)
7051

71-
# Also fix simpler case: href="#../path/to/file"
72-
pattern = r'href=(["\'])#(\.\.\/[^"\'#]+?)\1'
73-
matches = re.findall(pattern, content)
74-
75-
if matches:
76-
logger.info(f"[DEBUG] Found {len(matches)} simple problematic links in {filepath}")
77-
78-
# Fix each match
79-
for quote, path in matches:
80-
# Create the correct path with .html extension
81-
if not path.endswith('/') and '.' not in path.split('/')[-1]:
82-
path_with_html = path + '.html'
83-
else:
84-
path_with_html = path
85-
86-
# Replace in content
87-
old = f'href={quote}#{path}{quote}'
88-
new = f'href={quote}{path_with_html}{quote}'
89-
content = content.replace(old, new)
90-
logger.info(f"[DEBUG] Fixed: {old} -> {new}")
91-
92-
# Save the file if changes were made
9352
if content != original_content:
9453
with open(filepath, 'w', encoding='utf-8') as f:
9554
f.write(content)
@@ -98,7 +57,6 @@ def fix_md_links_post_process(app, exception):
9857

9958
except Exception as e:
10059
logger.info(f"[DEBUG] Error processing {filepath}: {e}")
101-
10260
logger.info(f"[DEBUG] Post-processed {count} HTML files, fixed {fixed} files")
10361

10462

docs/_static/section_highlight.js

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Add highlight to the target section
2+
const hash = window.location.hash.substring(1);
3+
4+
function highlightHashSection(hashName) {
5+
if (!hashName) return;
6+
const elements = document.querySelectorAll('[id="' + hashName + '"]');
7+
elements.forEach(anchor => {
8+
let target = null;
9+
// If the anchor is inside a heading, highlight the heading
10+
if (anchor.parentElement && /^H[1-6]$/.test(anchor.parentElement.tagName)) {
11+
target = anchor.parentElement;
12+
}
13+
// If the anchor is an empty <a> or <span>, try to find the next visible heading/field sibling
14+
if (!target && (anchor.tagName === 'A' || anchor.tagName === 'SPAN') && (!anchor.textContent.trim() || anchor.offsetHeight === 0)) {
15+
let sibling = anchor.nextSibling;
16+
while (sibling) {
17+
if (sibling.nodeType === 1 && // ELEMENT_NODE
18+
(
19+
['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'DT', 'DD', 'LI', 'PRE', 'TR'].includes(sibling.tagName) ||
20+
sibling.classList.contains('field') ||
21+
sibling.classList.contains('field-item') ||
22+
sibling.classList.contains('highlight') ||
23+
sibling.classList.contains('code')
24+
)
25+
) {
26+
target = sibling;
27+
break;
28+
}
29+
sibling = sibling.nextSibling;
30+
}
31+
}
32+
// Fallback: traverse up to find a field/code/line container, but not a generic div or section
33+
if (!target) {
34+
let up = anchor;
35+
while (up && up.parentElement) {
36+
if (
37+
['LI', 'DT', 'DD', 'TR', 'PRE', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6'].includes(up.tagName) ||
38+
up.classList.contains('field') ||
39+
up.classList.contains('field-item') ||
40+
up.classList.contains('highlight') ||
41+
up.classList.contains('code')
42+
) {
43+
target = up;
44+
break;
45+
}
46+
up = up.parentElement;
47+
}
48+
}
49+
// Only highlight if the target is visible, not whitespace, and not a section/article/div
50+
if (
51+
target &&
52+
target.offsetHeight > 0 &&
53+
target.textContent.trim() &&
54+
!['SECTION', 'ARTICLE', 'DIV', 'BODY', 'HTML'].includes(target.tagName)
55+
) {
56+
target.classList.add('goto_highlight');
57+
setTimeout(() => {
58+
target.classList.add('goto_highlight_fade_out');
59+
setTimeout(() => {
60+
target.classList.remove('goto_highlight');
61+
target.classList.remove('goto_highlight_fade_out');
62+
}, 2000);
63+
}, 5000);
64+
}
65+
});
66+
}
67+
68+
document.addEventListener('DOMContentLoaded', function() {
69+
const hash = window.location.hash.substring(1);
70+
highlightHashSection(hash);
71+
window.onhashchange = function() {
72+
const newHash = window.location.hash.substring(1);
73+
highlightHashSection(newHash);
74+
};
75+
});

docs/_static/theme_overrides.css

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,175 @@ https://github.com/readthedocs/sphinx_rtd_theme/issues/1301
2727
.py.property {
2828
display: block !important;
2929
}
30+
31+
/* Pygments Sytnax Highlighting for Furo Theme Override */
32+
.highlight .k, .highlight .kc, .highlight .kd, .highlight .kn, .highlight .kp, .highlight .kr { color: #1243d4 !important; } /* Keywords */
33+
.highlight .kt, .highlight .nc { color: #11abb9 !important; } /* Types, Class names */
34+
.highlight .nv { color: #0a1985 !important; } /* Variables, e.g. instance variables */
35+
.highlight .nb { color: #0a1985 !important; } /* Built-in names, can also be variable-like */
36+
.highlight .n, .highlight .nn { color: #000000 !important; } /* General names, namespace names - default to black */
37+
38+
.highlight .nf { color: #d14 !important; } /* Function names */
39+
.highlight .c, .highlight .c1, .highlight .cs, .highlight .cm { color: #149104 !important; } /* Comments - green */
40+
.highlight .s, .highlight .s1, .highlight .s2, .highlight .sd, .highlight .se, .highlight .sh, .highlight .si, .highlight .sx, .highlight .sr { color: #d14 !important; } /* Strings, Regex - using the red/pink */
41+
.highlight .m, .highlight .mf, .highlight .mh, .highlight .mi, .highlight .il, .highlight .mo { color: #7211c2 !important; } /* Numbers - purple */
42+
43+
/* Ensure other elements like operators are sensible */
44+
.highlight .o, .highlight .ow { color: #000000 !important; } /* Operators - black */
45+
.highlight .p { color: #000000 !important; } /* Punctuation - black */
46+
47+
/* Section highlighting styles */
48+
.goto_highlight {
49+
animation: highlight-fade-in 0.5s ease-in;
50+
background-color: rgba(255, 255, 128, 0.5);
51+
}
52+
53+
.goto_highlight_fade_out {
54+
animation: highlight-fade-out 2s ease-out;
55+
}
56+
57+
@keyframes highlight-fade-in {
58+
from {
59+
background-color: rgba(255, 255, 0, 0);
60+
}
61+
to {
62+
background-color: rgba(255, 255, 0, 0.2);
63+
}
64+
}
65+
66+
@keyframes highlight-fade-out {
67+
from {
68+
background-color: rgba(255, 255, 0, 0.2);
69+
}
70+
to {
71+
background-color: rgba(255, 255, 0, 0);
72+
}
73+
}
74+
75+
/* Code block styles */
76+
pre {
77+
color: #000000;
78+
background: #F8F8F8;
79+
}
80+
81+
pre code {
82+
color: #000000;
83+
background-color: #F8F8F8;
84+
}
85+
86+
.highlight {
87+
background: #F8F8F8;
88+
}
89+
90+
/* Custom syntax highlighting for Slang-specific classes */
91+
pre .code_keyword {
92+
color: #1243d4 !important; /* Blue - for keywords like 'int' */
93+
}
94+
95+
pre .code_type {
96+
color: #11abb9 !important; /* Teal/Cyan - for types like 'Array' */
97+
}
98+
99+
pre .code_param {
100+
color: #808080 !important; /* Gray */
101+
}
102+
103+
pre .code_var {
104+
color: #0a1985 !important; /* Color for variables like 'N' */
105+
}
106+
107+
/* Link styling within <pre> blocks */
108+
109+
/* Default/Method links (e.g., getCount) */
110+
pre a,
111+
pre a:link, /* Explicitly for unvisited */
112+
pre a:visited {
113+
color: #d14 !important; /* Dark Pink/Red - for method names */
114+
text-decoration: none !important;
115+
}
116+
117+
/* Type links (e.g., Array) */
118+
pre a.code_type,
119+
pre a.code_type:link,
120+
pre a.code_type:visited {
121+
color: #11abb9 !important; /* Teal/Cyan */
122+
}
123+
124+
/* Parameter links (e.g., a, b in dadd.md) */
125+
pre a.code_param,
126+
pre a.code_param:link,
127+
pre a.code_param:visited {
128+
color: #808080 !important; /* Gray */
129+
}
130+
131+
/* Variable links */
132+
pre a.code_var,
133+
pre a.code_var:link,
134+
pre a.code_var:visited {
135+
color: #0a1985 !important; /* Color for variable links */
136+
}
137+
138+
/* Common hover effect for all these links */
139+
pre a:hover {
140+
text-decoration: underline !important;
141+
}
142+
143+
/* General text in pre blocks, if not caught by other rules, should be black. */
144+
/* This might be slightly redundant given existing rules, but ensures our intent. */
145+
pre, pre code {
146+
color: black !important; /* Default code text color */
147+
/* background-color: #F8F8F8; /* Already set by existing rules */
148+
}
149+
150+
/* Custom Heading Sizes and Spacing */
151+
h1 {
152+
font-size: 2.0em !important;
153+
/* Furo specific line-height might need adjustment if text looks cramped */
154+
}
155+
156+
h2 {
157+
font-size: 1.75em !important;
158+
}
159+
160+
h3 {
161+
font-size: 1.5em !important;
162+
}
163+
164+
h4 {
165+
font-size: 1.25em !important;
166+
/* font-weight: bold !important; */
167+
}
168+
169+
h5 {
170+
font-size: 1.0em !important;
171+
/* font-weight: bold !important; */
172+
}
173+
174+
h6 {
175+
font-size: 0.75em !important;
176+
font-weight: bold !important;
177+
color: #555 !important; /* Often H6 is a bit de-emphasized */
178+
}
179+
180+
/* Heading styles for core-module-reference (Tactile-like) */
181+
/* Applied using the data-furo-page attribute */
182+
183+
body.core-module-reference-page h1 {
184+
font-size: 2.25em !important;
185+
font-weight: 700 !important;
186+
}
187+
188+
body.core-module-reference-page h2 {
189+
font-size: 1.5em !important;
190+
font-weight: 700 !important;
191+
}
192+
193+
body.core-module-reference-page h3 {
194+
font-size: 1.0em !important; /* Matches Tactile theme */
195+
font-weight: 700 !important;
196+
}
197+
198+
body.core-module-reference-page h4 {
199+
font-size: 1.2em !important; /* Matches Tactile theme */
200+
font-weight: 700 !important;
201+
}

docs/conf.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,10 @@ def setup(app):
114114
html_title = "Slang Documentation"
115115
html_static_path = ['_static']
116116
html_css_files = ["theme_overrides.css"]
117+
html_js_files = [
118+
"section_highlight.js",
119+
"custom_body_classes.js",
120+
]
117121
html_theme_options = {
118122
"light_css_variables": {
119123
"color-api-background": "#f7f7f7",

0 commit comments

Comments
 (0)