Skip to content

Commit 398b5a2

Browse files
authored
feat: add custom C++ syntax highlighting support (#556)
1 parent 6c9f5ae commit 398b5a2

File tree

4 files changed

+318
-1
lines changed

4 files changed

+318
-1
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* cpp-highlight.css - Custom C++ syntax highlighting styles
3+
* This replaces highlight.js's C++ highlighting for consistent styling
4+
*/
5+
6+
/* C++ Keywords - blue, bold */
7+
code.cpp-highlight .cpp-keyword,
8+
.doc pre.highlight code.cpp-highlight .cpp-keyword {
9+
color: #00f;
10+
font-weight: bold;
11+
}
12+
13+
/* C++ Strings - dark red */
14+
code.cpp-highlight .cpp-string,
15+
.doc pre.highlight code.cpp-highlight .cpp-string {
16+
color: #a31515;
17+
}
18+
19+
/* C++ Preprocessor directives - purple */
20+
code.cpp-highlight .cpp-preprocessor,
21+
.doc pre.highlight code.cpp-highlight .cpp-preprocessor {
22+
color: #6f008a;
23+
}
24+
25+
/* C++ Comments - green, italic */
26+
code.cpp-highlight .cpp-comment,
27+
.doc pre.highlight code.cpp-highlight .cpp-comment {
28+
color: #008000;
29+
font-style: italic;
30+
}
31+
32+
/* Base text in C++ blocks - plain black (identifiers, function names, etc.) */
33+
code.cpp-highlight,
34+
.doc pre.highlight code.cpp-highlight {
35+
color: inherit;
36+
}

antora-ui/src/css/site.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
@import "header.css";
1515
@import "footer.css";
1616
@import "highlight.css";
17+
@import "cpp-highlight.css";
1718
@import "print.css";
1819
@import "tailwindcss/base";
1920
@import "tailwindcss/components";
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
/**
2+
* cpp-highlight.js - Lightweight C++ syntax highlighter
3+
*
4+
* Categories:
5+
* - keyword : Language keywords (const, while, if, etc.)
6+
* - string : String and character literals
7+
* - preprocessor: Preprocessor directives (#include, #define, etc.)
8+
* - comment : C and C++ style comments
9+
*/
10+
11+
const CppHighlight = (function () {
12+
'use strict'
13+
14+
const KEYWORDS = new Set([
15+
// Storage class
16+
'auto', 'register', 'static', 'extern', 'mutable', 'thread_local',
17+
// Type qualifiers
18+
'const', 'volatile', 'constexpr', 'consteval', 'constinit',
19+
// Type specifiers
20+
'void', 'bool', 'char', 'short', 'int', 'long', 'float', 'double',
21+
'signed', 'unsigned', 'wchar_t', 'char8_t', 'char16_t', 'char32_t',
22+
// Complex types
23+
'class', 'struct', 'union', 'enum', 'typename', 'typedef',
24+
// Control flow
25+
'if', 'else', 'switch', 'case', 'default', 'for', 'while', 'do',
26+
'break', 'continue', 'return', 'goto',
27+
// Exception handling
28+
'try', 'catch', 'throw', 'noexcept',
29+
// OOP
30+
'public', 'private', 'protected', 'virtual', 'override', 'final',
31+
'friend', 'this', 'operator', 'new', 'delete',
32+
// Templates
33+
'template', 'concept', 'requires',
34+
// Namespace
35+
'namespace', 'using',
36+
// Other
37+
'sizeof', 'alignof', 'alignas', 'decltype', 'typeid',
38+
'static_cast', 'dynamic_cast', 'const_cast', 'reinterpret_cast',
39+
'static_assert', 'inline', 'explicit', 'export', 'module', 'import',
40+
'co_await', 'co_yield', 'co_return',
41+
// Literals
42+
'true', 'false', 'nullptr', 'NULL',
43+
])
44+
45+
function escapeHtml (text) {
46+
return text
47+
.replace(/&/g, '&')
48+
.replace(/</g, '&lt;')
49+
.replace(/>/g, '&gt;')
50+
}
51+
52+
function span (cls, content) {
53+
return `<span class="cpp-${cls}">${escapeHtml(content)}</span>`
54+
}
55+
56+
function highlight (code) {
57+
const result = []
58+
let i = 0
59+
const n = code.length
60+
61+
while (i < n) {
62+
// Line comment
63+
if (code[i] === '/' && code[i + 1] === '/') {
64+
let end = i + 2
65+
while (end < n && code[end] !== '\n') end++
66+
result.push(span('comment', code.slice(i, end)))
67+
i = end
68+
continue
69+
}
70+
71+
// Block comment
72+
if (code[i] === '/' && code[i + 1] === '*') {
73+
let end = i + 2
74+
while (end < n - 1 && !(code[end] === '*' && code[end + 1] === '/')) end++
75+
end += 2
76+
result.push(span('comment', code.slice(i, end)))
77+
i = end
78+
continue
79+
}
80+
81+
// Preprocessor directive (at start of line or after whitespace)
82+
if (code[i] === '#') {
83+
// Check if # is at line start (allow leading whitespace)
84+
let checkPos = i - 1
85+
while (checkPos >= 0 && (code[checkPos] === ' ' || code[checkPos] === '\t')) checkPos--
86+
if (checkPos < 0 || code[checkPos] === '\n') {
87+
let end = i + 1
88+
// Handle line continuation with backslash
89+
while (end < n) {
90+
if (code[end] === '\n') {
91+
if (code[end - 1] === '\\') {
92+
end++
93+
continue
94+
}
95+
break
96+
}
97+
end++
98+
}
99+
result.push(span('preprocessor', code.slice(i, end)))
100+
i = end
101+
continue
102+
}
103+
}
104+
105+
// Raw string literal R"delimiter(...)delimiter"
106+
if (code[i] === 'R' && code[i + 1] === '"') {
107+
let delimEnd = i + 2
108+
while (delimEnd < n && code[delimEnd] !== '(') delimEnd++
109+
const delimiter = code.slice(i + 2, delimEnd)
110+
const endMarker = ')' + delimiter + '"'
111+
let end = delimEnd + 1
112+
while (end < n) {
113+
if (code.slice(end, end + endMarker.length) === endMarker) {
114+
end += endMarker.length
115+
break
116+
}
117+
end++
118+
}
119+
result.push(span('string', code.slice(i, end)))
120+
i = end
121+
continue
122+
}
123+
124+
// String literal (with optional prefix)
125+
if (code[i] === '"' ||
126+
((code[i] === 'L' || code[i] === 'u' || code[i] === 'U') && code[i + 1] === '"') ||
127+
(code[i] === 'u' && code[i + 1] === '8' && code[i + 2] === '"')) {
128+
const start = i
129+
if (code[i] === 'u' && code[i + 1] === '8') i += 2
130+
else if (code[i] !== '"') i++
131+
i++ // skip opening quote
132+
while (i < n && code[i] !== '"') {
133+
if (code[i] === '\\' && i + 1 < n) i += 2
134+
else i++
135+
}
136+
i++ // skip closing quote
137+
result.push(span('string', code.slice(start, i)))
138+
continue
139+
}
140+
141+
// Character literal
142+
if (code[i] === '\'' ||
143+
((code[i] === 'L' || code[i] === 'u' || code[i] === 'U') && code[i + 1] === '\'') ||
144+
(code[i] === 'u' && code[i + 1] === '8' && code[i + 2] === '\'')) {
145+
const start = i
146+
if (code[i] === 'u' && code[i + 1] === '8') i += 2
147+
else if (code[i] !== '\'') i++
148+
i++ // skip opening quote
149+
while (i < n && code[i] !== '\'') {
150+
if (code[i] === '\\' && i + 1 < n) i += 2
151+
else i++
152+
}
153+
i++ // skip closing quote
154+
result.push(span('string', code.slice(start, i)))
155+
continue
156+
}
157+
158+
// Identifier or keyword
159+
if (/[a-zA-Z_]/.test(code[i])) {
160+
let end = i + 1
161+
while (end < n && /[a-zA-Z0-9_]/.test(code[end])) end++
162+
const word = code.slice(i, end)
163+
if (KEYWORDS.has(word)) {
164+
result.push(span('keyword', word))
165+
} else {
166+
result.push(escapeHtml(word))
167+
}
168+
i = end
169+
continue
170+
}
171+
172+
// Default: single character
173+
result.push(escapeHtml(code[i]))
174+
i++
175+
}
176+
177+
return result.join('')
178+
}
179+
180+
function highlightElement (el) {
181+
el.innerHTML = highlight(el.textContent)
182+
}
183+
184+
function highlightAll (selector = 'code.cpp, code.c++, pre.cpp, pre.c++') {
185+
document.querySelectorAll(selector).forEach(highlightElement)
186+
}
187+
188+
return {
189+
highlight,
190+
highlightElement,
191+
highlightAll,
192+
KEYWORDS,
193+
}
194+
})()
195+
196+
// CommonJS / ES module support
197+
if (typeof module !== 'undefined' && module.exports) {
198+
module.exports = CppHighlight
199+
}

antora-ui/src/js/vendor/highlight.bundle.js

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,92 @@
22
'use strict'
33

44
var hljs = require('highlight.js/lib/common')
5+
var CppHighlight = require('./cpp-highlight')
56

67
// Only register languages not included in common bundle
78
hljs.registerLanguage('cmake', require('highlight.js/lib/languages/cmake'))
89

9-
hljs.highlightAll()
10+
// Replace C++ highlighting AFTER highlight.js processes blocks
11+
// Let hljs work initially, then replace C++ blocks with custom highlighter
12+
function processCppBlocks () {
13+
// Selectors for C++ code blocks that highlight.js has already processed
14+
var cppSelectors = [
15+
'code.language-cpp.hljs',
16+
'code.language-c++.hljs',
17+
'code[data-lang="cpp"].hljs',
18+
'code[data-lang="c++"].hljs',
19+
'.doc pre.highlight code[data-lang="cpp"].hljs',
20+
'.doc pre.highlight code[data-lang="c++"].hljs',
21+
]
22+
23+
var processedCount = 0
24+
25+
cppSelectors.forEach(function (selector) {
26+
try {
27+
document.querySelectorAll(selector).forEach(function (el) {
28+
// Skip if already processed
29+
if (el.classList.contains('cpp-highlight')) return
30+
31+
// Replace highlight.js's C++ highlighting with our custom highlighter
32+
// This gives us full control over C++ syntax highlighting
33+
CppHighlight.highlightElement(el)
34+
35+
// Mark as processed with our custom highlighter
36+
el.classList.add('cpp-highlight')
37+
processedCount++
38+
})
39+
} catch (e) {
40+
console.warn('cpp-highlight error:', selector, e)
41+
}
42+
})
43+
44+
if (processedCount > 0) {
45+
console.log('cpp-highlight: Replaced ' + processedCount + ' C++ code blocks')
46+
}
47+
}
48+
49+
// Process C++ blocks after highlight.js runs
50+
function initHighlighting () {
51+
// First, let highlight.js process everything
52+
hljs.highlightAll()
53+
54+
// Then, replace C++ blocks with our custom highlighter
55+
// Use setTimeout to ensure highlight.js is completely done
56+
setTimeout(function () {
57+
processCppBlocks()
58+
}, 0)
59+
}
60+
61+
// Process when DOM is ready
62+
if (document.readyState === 'loading') {
63+
document.addEventListener('DOMContentLoaded', initHighlighting)
64+
} else {
65+
// DOM already loaded
66+
initHighlighting()
67+
}
68+
69+
// Also use MutationObserver to catch dynamically added content
70+
// Process C++ blocks after highlight.js processes new content
71+
if (typeof window.MutationObserver !== 'undefined') {
72+
var observer = new window.MutationObserver(function (mutations) {
73+
var shouldProcess = false
74+
mutations.forEach(function (mutation) {
75+
if (mutation.addedNodes.length > 0) {
76+
shouldProcess = true
77+
}
78+
})
79+
if (shouldProcess) {
80+
// Wait a bit for highlight.js to process new content
81+
setTimeout(function () {
82+
processCppBlocks()
83+
}, 100)
84+
}
85+
})
86+
observer.observe(document.body || document.documentElement, {
87+
childList: true,
88+
subtree: true,
89+
})
90+
}
1091

1192
window.hljs = hljs
1293
})()

0 commit comments

Comments
 (0)