From 8a63c1b7165c26256ddcafa2f42101c3af887489 Mon Sep 17 00:00:00 2001 From: Simon <10131203+gaomeng1900@users.noreply.github.com> Date: Fri, 24 Oct 2025 00:50:51 +0800 Subject: [PATCH] feat: Enhance code highlighting --- pages/components/HighlightSyntax.module.css | 82 +++++++++++ pages/components/HighlightSyntax.tsx | 143 +++++++++++++++++--- 2 files changed, 203 insertions(+), 22 deletions(-) diff --git a/pages/components/HighlightSyntax.module.css b/pages/components/HighlightSyntax.module.css index 75f0b81..2c2f614 100644 --- a/pages/components/HighlightSyntax.module.css +++ b/pages/components/HighlightSyntax.module.css @@ -10,6 +10,7 @@ color: #e0e0e0; } +/* JavaScript/TypeScript 关键字 */ .keyword { color: #d73a49; font-weight: 600; @@ -19,6 +20,27 @@ color: #ff6b6b; } +/* TypeScript 特定关键字 (interface, type, enum, etc.) */ +.tsKeyword { + color: #af00db; + font-weight: 600; +} + +:global(.dark) .tsKeyword { + color: #c792ea; +} + +/* TypeScript 内置类型 */ +.type { + color: #267f99; + font-weight: 500; +} + +:global(.dark) .type { + color: #4ec9b0; +} + +/* 字符串 */ .string { color: #1d6eca; } @@ -27,6 +49,7 @@ color: #4fc3f7; } +/* 数字 */ .number { color: #00c583; } @@ -35,6 +58,17 @@ color: #66bb6a; } +/* 布尔值和字面量 (true, false, null, undefined) */ +.literal { + color: #0000ff; + font-weight: 500; +} + +:global(.dark) .literal { + color: #569cd6; +} + +/* 注释 */ .comment { color: #6a737d; font-style: italic; @@ -43,3 +77,51 @@ :global(.dark) .comment { color: #9e9e9e; } + +/* 装饰器 (@decorator) */ +.decorator { + color: #e0aa00; + font-weight: 500; +} + +:global(.dark) .decorator { + color: #dcdcaa; +} + + +/* 箭头函数 (=>) */ +.arrow { + color: #d73a49; + font-weight: bold; +} + +:global(.dark) .arrow { + color: #ff6b6b; +} + +/* 标识符(变量名、函数名等) */ +.identifier { + color: #171717; +} + +:global(.dark) .identifier { + color: #e0e0e0; +} + +/* 属性访问 (.property) */ +.property { + color: #0550ae; +} + +:global(.dark) .property { + color: #9cdcfe; +} + +/* 运算符 */ +.operator { + color: #5a5a5a; +} + +:global(.dark) .operator { + color: #d4d4d4; +} diff --git a/pages/components/HighlightSyntax.tsx b/pages/components/HighlightSyntax.tsx index 6d0d671..7632500 100644 --- a/pages/components/HighlightSyntax.tsx +++ b/pages/components/HighlightSyntax.tsx @@ -9,58 +9,157 @@ interface HighlightSyntaxProps { code: string } +// JavaScript/TypeScript 关键字 const keywords = - 'async|await|function|const|let|var|if|else|for|while|return|try|catch|finally|class|extends|from|import|export|default|undefined|throw|true|false|null|this|new|in|of|instanceof|break|continue|switch|case|default|do|while|with|yield' + 'async|await|function|const|let|var|if|else|for|while|return|try|catch|finally|class|extends|from|import|export|default|undefined|throw|break|continue|switch|case|do|with|yield|delete|typeof|void|static|get|set|super|debugger' -// 语法高亮函数,先整体提取字符串/注释等token再高亮 +// TypeScript 特定关键字 +const tsKeywords = + 'interface|type|enum|namespace|module|declare|abstract|implements|public|private|protected|readonly|as|satisfies|infer|keyof|is' + +// 布尔值和空值 +const literals = 'true|false|null|undefined|NaN|Infinity' + +// TypeScript 内置类型 +const tsTypes = + 'string|number|boolean|any|unknown|never|void|object|symbol|bigint|Array|Promise|Record|Partial|Required|Readonly|Pick|Omit|Exclude|Extract|NonNullable|ReturnType|Parameters|ConstructorParameters|InstanceType|ThisType|Uppercase|Lowercase|Capitalize|Uncapitalize' + +// 辅助函数:转义 HTML 特殊字符 +function escapeHtml(text: string): string { + return text.replace(/&/g, '&').replace(//g, '>') +} + +// 语法高亮函数,先提取 token 再转义和高亮 function highlightSyntax(code: string): string { - // 先转义HTML特殊字符 - const escaped = code.replace(/&/g, '&').replace(//g, '>') - - // 单行字符串,所有反斜杠双重转义,保证正则安全 + // 构建正则模式,包含更多 token 类型(在原始文本上匹配) const pattern = new RegExp( - '("([^"\\\\]|\\\\.)*"|\'([^\'\\\\]|\\\\.)*\'|`([^`\\\\]|\\\\.)*`|//[^\\n]*|/\\*[\\s\\S]*?\\*/|\\b\\d+\\.?\\d*\\b|\\b(?:' + + '(' + + // 1. 字符串(双引号、单引号、模板字符串) + '"([^"\\\\]|\\\\.)*"|' + + "'([^'\\\\]|\\\\.)*'|" + + '`([^`\\\\]|\\\\.)*`|' + + // 2. 注释(单行和多行) + '//[^\\n]*|' + + '/\\*[\\s\\S]*?\\*/|' + + // 3. 装饰器 + '@[a-zA-Z_$][\\w$]*|' + + // 4. 数字(包括小数、十六进制、科学计数法) + '\\b0[xX][0-9a-fA-F]+\\b|' + + '\\b\\d+\\.?\\d*(?:[eE][+-]?\\d+)?\\b|' + + // 5. TypeScript/JavaScript 关键字 + '\\b(?:' + keywords + - ')\\b)', + '|' + + tsKeywords + + '|' + + literals + + ')\\b|' + + // 6. TypeScript 内置类型 + '\\b(?:' + + tsTypes + + ')\\b|' + + // 7. 箭头函数 + '=>|' + + // 8. 函数调用(函数名后跟括号) + '\\b[a-zA-Z_$][\\w$]*(?=\\()|' + + // 9. 属性访问 + '\\.[a-zA-Z_$][\\w$]*|' + + // 10. 运算符和特殊符号 + '[+\\-*/%&|^!~<>=?:]+|' + + '[{}\\[\\]();,.]' + + ')', 'g' ) const tokens: string[] = [] let lastIndex = 0 let match: RegExpExecArray | null - while ((match = pattern.exec(escaped)) !== null) { + while ((match = pattern.exec(code)) !== null) { if (match.index > lastIndex) { - tokens.push(...escaped.slice(lastIndex, match.index).split(/([ \t\n\r.])/)) + const gap = code.slice(lastIndex, match.index) + // 将间隙按空白符分割,保留空白符 + tokens.push(...gap.split(/(\s+)/)) } tokens.push(match[0]) lastIndex = pattern.lastIndex } - if (lastIndex < escaped.length) { - tokens.push(...escaped.slice(lastIndex).split(/([ \t\n\r.])/)) + if (lastIndex < code.length) { + tokens.push(...code.slice(lastIndex).split(/(\s+)/)) } const highlighted = tokens .map((token) => { + // 空白符直接返回 + if (/^\s+$/.test(token)) { + return token + } + + // 1. 注释(单行和多行) + if (/^\/\/.*$/.test(token) || /^\/\*[\s\S]*?\*\/$/.test(token)) { + return `${escapeHtml(token)}` + } + + // 2. 字符串 if ( /^"([^"\\]|\\.)*"$/.test(token) || /^'([^'\\]|\\.)*'$/.test(token) || /^`([^`\\]|\\.)*`$/.test(token) ) { - return `${token}` + return `${escapeHtml(token)}` } - if (/^\b\d+\.?\d*\b$/.test(token)) { - return `${token}` + + // 3. 数字 + if (/^(0[xX][0-9a-fA-F]+|\d+\.?\d*(?:[eE][+-]?\d+)?)$/.test(token)) { + return `${escapeHtml(token)}` } - if (/^\/\/.*$/.test(token)) { - return `${token}` + + // 4. 布尔值和特殊字面量 + if (new RegExp(`^(?:${literals})$`).test(token)) { + return `${escapeHtml(token)}` } - if (/^\/\*[\s\S]*?\*\/$/.test(token)) { - return `${token}` + + // 5. JavaScript/TypeScript 关键字 + if (new RegExp(`^(?:${keywords})$`).test(token)) { + return `${escapeHtml(token)}` } - if (new RegExp(`\\b(?:${keywords})\\b`).test(token)) { - return `${token}` + + // 6. TypeScript 特定关键字 + if (new RegExp(`^(?:${tsKeywords})$`).test(token)) { + return `${escapeHtml(token)}` } - return token + + // 7. TypeScript 内置类型 + if (new RegExp(`^(?:${tsTypes})$`).test(token)) { + return `${escapeHtml(token)}` + } + + // 8. 装饰器 + if (/^@[a-zA-Z_$][\w$]*$/.test(token)) { + return `${escapeHtml(token)}` + } + + // 9. 箭头函数 + if (token === '=>') { + return `${escapeHtml(token)}` + } + + // 10. 函数调用和标识符 + if (/^[a-zA-Z_$][\w$]*$/.test(token)) { + return `${escapeHtml(token)}` + } + + // 11. 属性访问 + if (/^\.[a-zA-Z_$][\w$]*$/.test(token)) { + return `${escapeHtml(token)}` + } + + // 12. 运算符 + if (/^[+\-*/%&|^!~<>=?:]+$/.test(token)) { + return `${escapeHtml(token)}` + } + + // 13. 其他符号,需要转义 + return escapeHtml(token) }) .join('')