Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions lib/rdoc/generator/template/aliki/_head.rhtml
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,11 @@
defer
></script>

<script
src="<%= h asset_rel_prefix %>/js/bash_highlighter.js?v=<%= h RDoc::VERSION %>"
defer
></script>

<script
src="<%= h asset_rel_prefix %>/js/aliki.js?v=<%= h RDoc::VERSION %>"
defer
Expand Down
64 changes: 28 additions & 36 deletions lib/rdoc/generator/template/aliki/css/rdoc.css
Original file line number Diff line number Diff line change
Expand Up @@ -39,24 +39,14 @@
--color-neutral-800: #292524;
--color-neutral-900: #1c1917;

/* Code highlighting colors */
/* Code highlighting colors - neutral palette for all syntax highlighters */
--code-blue: #1d4ed8;
--code-green: #047857;
--code-orange: #d97706;
--code-purple: #7e22ce;
--code-red: #dc2626;

/* C syntax highlighting */
--c-keyword: #b91c1c;
--c-type: #0891b2;
--c-macro: #ea580c;
--c-function: #7c3aed;
--c-identifier: #475569;
--c-operator: #059669;
--c-preprocessor: #a21caf;
--c-value: #92400e;
--c-string: #15803d;
--c-comment: #78716c;
--code-cyan: #0891b2;
--code-gray: #78716c;

/* Color Palette - Green (for success states) */
--color-green-400: #4ade80;
Expand Down Expand Up @@ -186,24 +176,14 @@

/* Dark Theme */
[data-theme="dark"] {
/* Code highlighting colors */
/* Code highlighting colors - neutral palette for all syntax highlighters */
--code-blue: #93c5fd;
--code-green: #34d399;
--code-orange: #fbbf24;
--code-purple: #c084fc;
--code-red: #f87171;

/* C syntax highlighting */
--c-keyword: #f87171;
--c-type: #22d3ee;
--c-macro: #fb923c;
--c-function: #a78bfa;
--c-identifier: #94a3b8;
--c-operator: #6ee7b7;
--c-preprocessor: #e879f9;
--c-value: #fcd34d;
--c-string: #4ade80;
--c-comment: #a8a29e;
--code-cyan: #22d3ee;
--code-gray: #a8a29e;

/* Semantic Colors - Dark Theme */
--color-text-primary: var(--color-neutral-50);
Expand Down Expand Up @@ -1064,18 +1044,30 @@ main h6 a:hover {
[data-theme="dark"] .ruby-string { color: var(--code-green); }

/* C Syntax Highlighting */
.c-keyword { color: var(--c-keyword); }
.c-type { color: var(--c-type); }
.c-macro { color: var(--c-macro); }
.c-function { color: var(--c-function); }
.c-identifier { color: var(--c-identifier); }
.c-operator { color: var(--c-operator); }
.c-preprocessor { color: var(--c-preprocessor); }
.c-value { color: var(--c-value); }
.c-string { color: var(--c-string); }
.c-keyword { color: var(--code-red); }
.c-type { color: var(--code-cyan); }
.c-macro { color: var(--code-orange); }
.c-function { color: var(--code-purple); }
.c-identifier { color: var(--color-text-secondary); }
.c-operator { color: var(--code-green); }
.c-preprocessor { color: var(--code-purple); }
.c-value { color: var(--code-orange); }
.c-string { color: var(--code-green); }

.c-comment {
color: var(--c-comment);
color: var(--code-gray);
font-style: italic;
}

/* Shell Syntax Highlighting */
.sh-prompt { color: var(--code-gray); }
.sh-command { color: var(--code-blue); }
.sh-option { color: var(--code-cyan); }
.sh-string { color: var(--code-green); }
.sh-envvar { color: var(--code-purple); }

.sh-comment {
color: var(--code-gray);
font-style: italic;
}

Expand Down
167 changes: 167 additions & 0 deletions lib/rdoc/generator/template/aliki/js/bash_highlighter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/**
* Client-side shell syntax highlighter for RDoc
* Highlights: $ prompts, commands, options, strings, env vars, comments
*/

(function() {
'use strict';

function escapeHtml(text) {
return text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}

function wrap(className, text) {
return '<span class="' + className + '">' + escapeHtml(text) + '</span>';
}

function highlightLine(line) {
if (line.trim() === '') return escapeHtml(line);

var result = '';
var i = 0;
var len = line.length;

// Preserve leading whitespace
while (i < len && (line[i] === ' ' || line[i] === '\t')) {
result += escapeHtml(line[i++]);
}

// Check for $ prompt ($ followed by space or end of line)
if (line[i] === '$' && (line[i + 1] === ' ' || line[i + 1] === undefined)) {
result += wrap('sh-prompt', '$');
i++;
}

// Check for # comment at start
if (line[i] === '#') {
return result + wrap('sh-comment', line.slice(i));
}

var seenCommand = false;
var afterSpace = true;

while (i < len) {
var ch = line[i];

// Whitespace
if (ch === ' ' || ch === '\t') {
result += escapeHtml(ch);
i++;
afterSpace = true;
continue;
}

// Comment after whitespace
if (ch === '#' && afterSpace) {
result += wrap('sh-comment', line.slice(i));
break;
}

// Double-quoted string
if (ch === '"') {
var end = i + 1;
while (end < len && line[end] !== '"') {
if (line[end] === '\\' && end + 1 < len) end += 2;
else end++;
}
if (end < len) end++;
result += wrap('sh-string', line.slice(i, end));
i = end;
afterSpace = false;
continue;
}

// Single-quoted string
if (ch === "'") {
var end = i + 1;
while (end < len && line[end] !== "'") end++;
if (end < len) end++;
result += wrap('sh-string', line.slice(i, end));
i = end;
afterSpace = false;
continue;
}

// Environment variable (ALLCAPS=)
if (afterSpace && /[A-Z]/.test(ch)) {
var match = line.slice(i).match(/^[A-Z][A-Z0-9_]*=/);
if (match) {
result += wrap('sh-envvar', match[0]);
i += match[0].length;
// Read unquoted value
var valEnd = i;
while (valEnd < len && line[valEnd] !== ' ' && line[valEnd] !== '\t' && line[valEnd] !== '"' && line[valEnd] !== "'") valEnd++;
if (valEnd > i) {
result += escapeHtml(line.slice(i, valEnd));
i = valEnd;
}
afterSpace = false;
continue;
}
}

// Option (must be after whitespace)
if (ch === '-' && afterSpace) {
var match = line.slice(i).match(/^--?[a-zA-Z0-9_-]+(=[^\s]*)?/);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming this input

git commit --message="initial commit"

How about (=[^\s]*)?(=[^"\s]*)?
"" part will be handled in // Double-quoted string logic above.

if (match) {
result += wrap('sh-option', match[0]);
i += match[0].length;
afterSpace = false;
continue;
}
}

// Command (first word: regular, ./path, ../path, ~/path, @scope/pkg)
if (!seenCommand && afterSpace) {
var isCmd = /[a-zA-Z0-9@~]/.test(ch) ||
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about adding / to accept /bin/sh as command?

(ch === '.' && (line[i + 1] === '/' || (line[i + 1] === '.' && line[i + 2] === '/')));
if (isCmd) {
var end = i;
while (end < len && line[end] !== ' ' && line[end] !== '\t') end++;
result += wrap('sh-command', line.slice(i, end));
i = end;
seenCommand = true;
afterSpace = false;
continue;
}
}

// Everything else
result += escapeHtml(ch);
i++;
afterSpace = false;
}

return result;
}

function highlightShell(code) {
return code.split('\n').map(highlightLine).join('\n');
}

function initHighlighting() {
var selectors = [
'pre.bash', 'pre.sh', 'pre.shell', 'pre.console',
'pre[data-language="bash"]', 'pre[data-language="sh"]',
'pre[data-language="shell"]', 'pre[data-language="console"]'
];

var blocks = document.querySelectorAll(selectors.join(', '));
blocks.forEach(function(block) {
if (block.getAttribute('data-highlighted') === 'true') return;
block.innerHTML = highlightShell(block.textContent);
block.setAttribute('data-highlighted', 'true');
});
}

if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initHighlighting);
} else {
initHighlighting();
}
})();
Loading