diff --git a/antora-ui/src/css/cpp-highlight.css b/antora-ui/src/css/cpp-highlight.css new file mode 100644 index 00000000..f23f16e3 --- /dev/null +++ b/antora-ui/src/css/cpp-highlight.css @@ -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; +} diff --git a/antora-ui/src/css/site.css b/antora-ui/src/css/site.css index 227b0ba0..c8d88c1f 100644 --- a/antora-ui/src/css/site.css +++ b/antora-ui/src/css/site.css @@ -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"; diff --git a/antora-ui/src/js/vendor/cpp-highlight.js b/antora-ui/src/js/vendor/cpp-highlight.js new file mode 100644 index 00000000..930a3614 --- /dev/null +++ b/antora-ui/src/js/vendor/cpp-highlight.js @@ -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, '>') + } + + function span (cls, content) { + return `${escapeHtml(content)}` + } + + 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 +} diff --git a/antora-ui/src/js/vendor/highlight.bundle.js b/antora-ui/src/js/vendor/highlight.bundle.js index a1fd737f..232bc6d0 100644 --- a/antora-ui/src/js/vendor/highlight.bundle.js +++ b/antora-ui/src/js/vendor/highlight.bundle.js @@ -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 })()