feat: add syntax highlighting for R, Ruby, PHP, and SQL
Cherry-picked from upstream 022c75da with conflict resolution. Merged our codeClassName support with upstream per-line highlighting.
This commit is contained in:
parent
b87082a915
commit
aeed5a0873
2 changed files with 224 additions and 4 deletions
|
|
@ -39,6 +39,7 @@ import {
|
||||||
highlightSearchInChildren,
|
highlightSearchInChildren,
|
||||||
type SearchContext,
|
type SearchContext,
|
||||||
} from '../searchHighlightUtils';
|
} from '../searchHighlightUtils';
|
||||||
|
import { highlightLine } from '../viewers/syntaxHighlighter';
|
||||||
|
|
||||||
import { FileLink, isRelativeUrl } from './FileLink';
|
import { FileLink, isRelativeUrl } from './FileLink';
|
||||||
import { MermaidDiagram } from './MermaidDiagram';
|
import { MermaidDiagram } from './MermaidDiagram';
|
||||||
|
|
@ -535,12 +536,21 @@ function createViewerMarkdownComponents(
|
||||||
const isBlock = (hasLanguage ?? false) || isMultiLine;
|
const isBlock = (hasLanguage ?? false) || isMultiLine;
|
||||||
|
|
||||||
if (isBlock) {
|
if (isBlock) {
|
||||||
|
const lang = codeClassName?.replace('language-', '') ?? '';
|
||||||
|
const raw = typeof children === 'string' ? children : '';
|
||||||
|
const text = raw.replace(/\n$/, '');
|
||||||
|
const lines = text.split('\n');
|
||||||
return (
|
return (
|
||||||
<code
|
<code
|
||||||
className={`font-mono text-xs ${codeClassName ?? ''}`.trim()}
|
className={`font-mono text-xs ${codeClassName ?? ''}`.trim()}
|
||||||
style={{ color: COLOR_TEXT }}
|
style={{ color: COLOR_TEXT }}
|
||||||
>
|
>
|
||||||
{hl(children)}
|
{lines.map((line, i) => (
|
||||||
|
<React.Fragment key={i}>
|
||||||
|
{hl(highlightLine(line, lang))}
|
||||||
|
{i < lines.length - 1 ? '\n' : null}
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
</code>
|
</code>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -208,6 +208,200 @@ const KEYWORDS: Record<string, Set<string>> = {
|
||||||
'true',
|
'true',
|
||||||
'false',
|
'false',
|
||||||
]),
|
]),
|
||||||
|
r: new Set([
|
||||||
|
'if',
|
||||||
|
'else',
|
||||||
|
'for',
|
||||||
|
'while',
|
||||||
|
'repeat',
|
||||||
|
'function',
|
||||||
|
'return',
|
||||||
|
'next',
|
||||||
|
'break',
|
||||||
|
'in',
|
||||||
|
'library',
|
||||||
|
'require',
|
||||||
|
'source',
|
||||||
|
'TRUE',
|
||||||
|
'FALSE',
|
||||||
|
'NULL',
|
||||||
|
'NA',
|
||||||
|
'Inf',
|
||||||
|
'NaN',
|
||||||
|
'NA_integer_',
|
||||||
|
'NA_real_',
|
||||||
|
'NA_complex_',
|
||||||
|
'NA_character_',
|
||||||
|
]),
|
||||||
|
ruby: new Set([
|
||||||
|
'def',
|
||||||
|
'class',
|
||||||
|
'module',
|
||||||
|
'end',
|
||||||
|
'do',
|
||||||
|
'if',
|
||||||
|
'elsif',
|
||||||
|
'else',
|
||||||
|
'unless',
|
||||||
|
'while',
|
||||||
|
'until',
|
||||||
|
'for',
|
||||||
|
'in',
|
||||||
|
'begin',
|
||||||
|
'rescue',
|
||||||
|
'ensure',
|
||||||
|
'raise',
|
||||||
|
'return',
|
||||||
|
'yield',
|
||||||
|
'block_given?',
|
||||||
|
'require',
|
||||||
|
'require_relative',
|
||||||
|
'include',
|
||||||
|
'extend',
|
||||||
|
'attr_accessor',
|
||||||
|
'attr_reader',
|
||||||
|
'attr_writer',
|
||||||
|
'self',
|
||||||
|
'super',
|
||||||
|
'nil',
|
||||||
|
'true',
|
||||||
|
'false',
|
||||||
|
'and',
|
||||||
|
'or',
|
||||||
|
'not',
|
||||||
|
'then',
|
||||||
|
'when',
|
||||||
|
'case',
|
||||||
|
'lambda',
|
||||||
|
'proc',
|
||||||
|
'puts',
|
||||||
|
'print',
|
||||||
|
]),
|
||||||
|
php: new Set([
|
||||||
|
'function',
|
||||||
|
'class',
|
||||||
|
'interface',
|
||||||
|
'trait',
|
||||||
|
'extends',
|
||||||
|
'implements',
|
||||||
|
'namespace',
|
||||||
|
'use',
|
||||||
|
'public',
|
||||||
|
'private',
|
||||||
|
'protected',
|
||||||
|
'static',
|
||||||
|
'abstract',
|
||||||
|
'final',
|
||||||
|
'const',
|
||||||
|
'var',
|
||||||
|
'new',
|
||||||
|
'return',
|
||||||
|
'if',
|
||||||
|
'elseif',
|
||||||
|
'else',
|
||||||
|
'for',
|
||||||
|
'foreach',
|
||||||
|
'while',
|
||||||
|
'do',
|
||||||
|
'switch',
|
||||||
|
'case',
|
||||||
|
'break',
|
||||||
|
'continue',
|
||||||
|
'default',
|
||||||
|
'try',
|
||||||
|
'catch',
|
||||||
|
'finally',
|
||||||
|
'throw',
|
||||||
|
'as',
|
||||||
|
'echo',
|
||||||
|
'print',
|
||||||
|
'require',
|
||||||
|
'require_once',
|
||||||
|
'include',
|
||||||
|
'include_once',
|
||||||
|
'true',
|
||||||
|
'false',
|
||||||
|
'null',
|
||||||
|
'array',
|
||||||
|
'isset',
|
||||||
|
'unset',
|
||||||
|
'empty',
|
||||||
|
'self',
|
||||||
|
'this',
|
||||||
|
]),
|
||||||
|
sql: new Set([
|
||||||
|
'SELECT',
|
||||||
|
'FROM',
|
||||||
|
'WHERE',
|
||||||
|
'INSERT',
|
||||||
|
'INTO',
|
||||||
|
'UPDATE',
|
||||||
|
'SET',
|
||||||
|
'DELETE',
|
||||||
|
'CREATE',
|
||||||
|
'ALTER',
|
||||||
|
'DROP',
|
||||||
|
'TABLE',
|
||||||
|
'INDEX',
|
||||||
|
'VIEW',
|
||||||
|
'DATABASE',
|
||||||
|
'JOIN',
|
||||||
|
'INNER',
|
||||||
|
'LEFT',
|
||||||
|
'RIGHT',
|
||||||
|
'OUTER',
|
||||||
|
'FULL',
|
||||||
|
'CROSS',
|
||||||
|
'ON',
|
||||||
|
'AND',
|
||||||
|
'OR',
|
||||||
|
'NOT',
|
||||||
|
'IN',
|
||||||
|
'EXISTS',
|
||||||
|
'BETWEEN',
|
||||||
|
'LIKE',
|
||||||
|
'IS',
|
||||||
|
'NULL',
|
||||||
|
'AS',
|
||||||
|
'ORDER',
|
||||||
|
'BY',
|
||||||
|
'GROUP',
|
||||||
|
'HAVING',
|
||||||
|
'LIMIT',
|
||||||
|
'OFFSET',
|
||||||
|
'UNION',
|
||||||
|
'ALL',
|
||||||
|
'DISTINCT',
|
||||||
|
'COUNT',
|
||||||
|
'SUM',
|
||||||
|
'AVG',
|
||||||
|
'MIN',
|
||||||
|
'MAX',
|
||||||
|
'CASE',
|
||||||
|
'WHEN',
|
||||||
|
'THEN',
|
||||||
|
'ELSE',
|
||||||
|
'END',
|
||||||
|
'BEGIN',
|
||||||
|
'COMMIT',
|
||||||
|
'ROLLBACK',
|
||||||
|
'TRANSACTION',
|
||||||
|
'PRIMARY',
|
||||||
|
'KEY',
|
||||||
|
'FOREIGN',
|
||||||
|
'REFERENCES',
|
||||||
|
'CONSTRAINT',
|
||||||
|
'DEFAULT',
|
||||||
|
'VALUES',
|
||||||
|
'TRUE',
|
||||||
|
'FALSE',
|
||||||
|
'INTEGER',
|
||||||
|
'VARCHAR',
|
||||||
|
'TEXT',
|
||||||
|
'BOOLEAN',
|
||||||
|
'DATE',
|
||||||
|
'TIMESTAMP',
|
||||||
|
]),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Extend tsx/jsx to use typescript/javascript keywords
|
// Extend tsx/jsx to use typescript/javascript keywords
|
||||||
|
|
@ -296,8 +490,23 @@ export function highlightLine(line: string, language: string): React.ReactNode[]
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for comment (# style for Python/Shell)
|
// Check for comment (# style for Python/Shell/R/Ruby/PHP)
|
||||||
if ((language === 'python' || language === 'bash') && remaining.startsWith('#')) {
|
if (
|
||||||
|
(language === 'python' || language === 'bash' || language === 'r' || language === 'ruby' || language === 'php') &&
|
||||||
|
remaining.startsWith('#')
|
||||||
|
) {
|
||||||
|
segments.push(
|
||||||
|
React.createElement(
|
||||||
|
'span',
|
||||||
|
{ key: currentPos, style: { color: 'var(--syntax-comment)', fontStyle: 'italic' } },
|
||||||
|
remaining
|
||||||
|
)
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for comment (-- style for SQL)
|
||||||
|
if (language === 'sql' && remaining.startsWith('--')) {
|
||||||
segments.push(
|
segments.push(
|
||||||
React.createElement(
|
React.createElement(
|
||||||
'span',
|
'span',
|
||||||
|
|
@ -326,7 +535,8 @@ export function highlightLine(line: string, language: string): React.ReactNode[]
|
||||||
const wordMatch = /^([a-zA-Z_$][a-zA-Z0-9_$]*)/.exec(remaining);
|
const wordMatch = /^([a-zA-Z_$][a-zA-Z0-9_$]*)/.exec(remaining);
|
||||||
if (wordMatch) {
|
if (wordMatch) {
|
||||||
const word = wordMatch[1];
|
const word = wordMatch[1];
|
||||||
if (keywords.has(word)) {
|
// SQL keywords are case-insensitive
|
||||||
|
if (keywords.has(word) || (language === 'sql' && keywords.has(word.toUpperCase()))) {
|
||||||
segments.push(
|
segments.push(
|
||||||
React.createElement(
|
React.createElement(
|
||||||
'span',
|
'span',
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue