feat: add custom C++ syntax highlighting support (#556)

This commit is contained in:
Julio C. Estrada
2025-12-12 16:20:55 -05:00
committed by GitHub
parent 6c9f5ae10c
commit 398b5a20a5
4 changed files with 318 additions and 1 deletions

View File

@@ -0,0 +1,36 @@
/**
* cpp-highlight.css - Custom C++ syntax highlighting styles
* This replaces highlight.js's C++ highlighting for consistent styling
*/
/* C++ Keywords - blue, bold */
code.cpp-highlight .cpp-keyword,
.doc pre.highlight code.cpp-highlight .cpp-keyword {
color: #00f;
font-weight: bold;
}
/* C++ Strings - dark red */
code.cpp-highlight .cpp-string,
.doc pre.highlight code.cpp-highlight .cpp-string {
color: #a31515;
}
/* C++ Preprocessor directives - purple */
code.cpp-highlight .cpp-preprocessor,
.doc pre.highlight code.cpp-highlight .cpp-preprocessor {
color: #6f008a;
}
/* C++ Comments - green, italic */
code.cpp-highlight .cpp-comment,
.doc pre.highlight code.cpp-highlight .cpp-comment {
color: #008000;
font-style: italic;
}
/* Base text in C++ blocks - plain black (identifiers, function names, etc.) */
code.cpp-highlight,
.doc pre.highlight code.cpp-highlight {
color: inherit;
}

View File

@@ -14,6 +14,7 @@
@import "header.css";
@import "footer.css";
@import "highlight.css";
@import "cpp-highlight.css";
@import "print.css";
@import "tailwindcss/base";
@import "tailwindcss/components";

199
antora-ui/src/js/vendor/cpp-highlight.js vendored Normal file
View File

@@ -0,0 +1,199 @@
/**
* cpp-highlight.js - Lightweight C++ syntax highlighter
*
* Categories:
* - keyword : Language keywords (const, while, if, etc.)
* - string : String and character literals
* - preprocessor: Preprocessor directives (#include, #define, etc.)
* - comment : C and C++ style comments
*/
const CppHighlight = (function () {
'use strict'
const KEYWORDS = new Set([
// Storage class
'auto', 'register', 'static', 'extern', 'mutable', 'thread_local',
// Type qualifiers
'const', 'volatile', 'constexpr', 'consteval', 'constinit',
// Type specifiers
'void', 'bool', 'char', 'short', 'int', 'long', 'float', 'double',
'signed', 'unsigned', 'wchar_t', 'char8_t', 'char16_t', 'char32_t',
// Complex types
'class', 'struct', 'union', 'enum', 'typename', 'typedef',
// Control flow
'if', 'else', 'switch', 'case', 'default', 'for', 'while', 'do',
'break', 'continue', 'return', 'goto',
// Exception handling
'try', 'catch', 'throw', 'noexcept',
// OOP
'public', 'private', 'protected', 'virtual', 'override', 'final',
'friend', 'this', 'operator', 'new', 'delete',
// Templates
'template', 'concept', 'requires',
// Namespace
'namespace', 'using',
// Other
'sizeof', 'alignof', 'alignas', 'decltype', 'typeid',
'static_cast', 'dynamic_cast', 'const_cast', 'reinterpret_cast',
'static_assert', 'inline', 'explicit', 'export', 'module', 'import',
'co_await', 'co_yield', 'co_return',
// Literals
'true', 'false', 'nullptr', 'NULL',
])
function escapeHtml (text) {
return text
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
}
function span (cls, content) {
return `<span class="cpp-${cls}">${escapeHtml(content)}</span>`
}
function highlight (code) {
const result = []
let i = 0
const n = code.length
while (i < n) {
// Line comment
if (code[i] === '/' && code[i + 1] === '/') {
let end = i + 2
while (end < n && code[end] !== '\n') end++
result.push(span('comment', code.slice(i, end)))
i = end
continue
}
// Block comment
if (code[i] === '/' && code[i + 1] === '*') {
let end = i + 2
while (end < n - 1 && !(code[end] === '*' && code[end + 1] === '/')) end++
end += 2
result.push(span('comment', code.slice(i, end)))
i = end
continue
}
// Preprocessor directive (at start of line or after whitespace)
if (code[i] === '#') {
// Check if # is at line start (allow leading whitespace)
let checkPos = i - 1
while (checkPos >= 0 && (code[checkPos] === ' ' || code[checkPos] === '\t')) checkPos--
if (checkPos < 0 || code[checkPos] === '\n') {
let end = i + 1
// Handle line continuation with backslash
while (end < n) {
if (code[end] === '\n') {
if (code[end - 1] === '\\') {
end++
continue
}
break
}
end++
}
result.push(span('preprocessor', code.slice(i, end)))
i = end
continue
}
}
// Raw string literal R"delimiter(...)delimiter"
if (code[i] === 'R' && code[i + 1] === '"') {
let delimEnd = i + 2
while (delimEnd < n && code[delimEnd] !== '(') delimEnd++
const delimiter = code.slice(i + 2, delimEnd)
const endMarker = ')' + delimiter + '"'
let end = delimEnd + 1
while (end < n) {
if (code.slice(end, end + endMarker.length) === endMarker) {
end += endMarker.length
break
}
end++
}
result.push(span('string', code.slice(i, end)))
i = end
continue
}
// String literal (with optional prefix)
if (code[i] === '"' ||
((code[i] === 'L' || code[i] === 'u' || code[i] === 'U') && code[i + 1] === '"') ||
(code[i] === 'u' && code[i + 1] === '8' && code[i + 2] === '"')) {
const start = i
if (code[i] === 'u' && code[i + 1] === '8') i += 2
else if (code[i] !== '"') i++
i++ // skip opening quote
while (i < n && code[i] !== '"') {
if (code[i] === '\\' && i + 1 < n) i += 2
else i++
}
i++ // skip closing quote
result.push(span('string', code.slice(start, i)))
continue
}
// Character literal
if (code[i] === '\'' ||
((code[i] === 'L' || code[i] === 'u' || code[i] === 'U') && code[i + 1] === '\'') ||
(code[i] === 'u' && code[i + 1] === '8' && code[i + 2] === '\'')) {
const start = i
if (code[i] === 'u' && code[i + 1] === '8') i += 2
else if (code[i] !== '\'') i++
i++ // skip opening quote
while (i < n && code[i] !== '\'') {
if (code[i] === '\\' && i + 1 < n) i += 2
else i++
}
i++ // skip closing quote
result.push(span('string', code.slice(start, i)))
continue
}
// Identifier or keyword
if (/[a-zA-Z_]/.test(code[i])) {
let end = i + 1
while (end < n && /[a-zA-Z0-9_]/.test(code[end])) end++
const word = code.slice(i, end)
if (KEYWORDS.has(word)) {
result.push(span('keyword', word))
} else {
result.push(escapeHtml(word))
}
i = end
continue
}
// Default: single character
result.push(escapeHtml(code[i]))
i++
}
return result.join('')
}
function highlightElement (el) {
el.innerHTML = highlight(el.textContent)
}
function highlightAll (selector = 'code.cpp, code.c++, pre.cpp, pre.c++') {
document.querySelectorAll(selector).forEach(highlightElement)
}
return {
highlight,
highlightElement,
highlightAll,
KEYWORDS,
}
})()
// CommonJS / ES module support
if (typeof module !== 'undefined' && module.exports) {
module.exports = CppHighlight
}

View File

@@ -2,11 +2,92 @@
'use strict'
var hljs = require('highlight.js/lib/common')
var CppHighlight = require('./cpp-highlight')
// Only register languages not included in common bundle
hljs.registerLanguage('cmake', require('highlight.js/lib/languages/cmake'))
hljs.highlightAll()
// Replace C++ highlighting AFTER highlight.js processes blocks
// Let hljs work initially, then replace C++ blocks with custom highlighter
function processCppBlocks () {
// Selectors for C++ code blocks that highlight.js has already processed
var cppSelectors = [
'code.language-cpp.hljs',
'code.language-c++.hljs',
'code[data-lang="cpp"].hljs',
'code[data-lang="c++"].hljs',
'.doc pre.highlight code[data-lang="cpp"].hljs',
'.doc pre.highlight code[data-lang="c++"].hljs',
]
var processedCount = 0
cppSelectors.forEach(function (selector) {
try {
document.querySelectorAll(selector).forEach(function (el) {
// Skip if already processed
if (el.classList.contains('cpp-highlight')) return
// Replace highlight.js's C++ highlighting with our custom highlighter
// This gives us full control over C++ syntax highlighting
CppHighlight.highlightElement(el)
// Mark as processed with our custom highlighter
el.classList.add('cpp-highlight')
processedCount++
})
} catch (e) {
console.warn('cpp-highlight error:', selector, e)
}
})
if (processedCount > 0) {
console.log('cpp-highlight: Replaced ' + processedCount + ' C++ code blocks')
}
}
// Process C++ blocks after highlight.js runs
function initHighlighting () {
// First, let highlight.js process everything
hljs.highlightAll()
// Then, replace C++ blocks with our custom highlighter
// Use setTimeout to ensure highlight.js is completely done
setTimeout(function () {
processCppBlocks()
}, 0)
}
// Process when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initHighlighting)
} else {
// DOM already loaded
initHighlighting()
}
// Also use MutationObserver to catch dynamically added content
// Process C++ blocks after highlight.js processes new content
if (typeof window.MutationObserver !== 'undefined') {
var observer = new window.MutationObserver(function (mutations) {
var shouldProcess = false
mutations.forEach(function (mutation) {
if (mutation.addedNodes.length > 0) {
shouldProcess = true
}
})
if (shouldProcess) {
// Wait a bit for highlight.js to process new content
setTimeout(function () {
processCppBlocks()
}, 100)
}
})
observer.observe(document.body || document.documentElement, {
childList: true,
subtree: true,
})
}
window.hljs = hljs
})()