From 0bcdc615e3667552a24ce5a347cd4393aecb303c Mon Sep 17 00:00:00 2001 From: Roe-xin Date: Thu, 8 Jan 2026 16:10:41 +0800 Subject: [PATCH] =?UTF-8?q?style:=E5=AF=B9=E8=AF=9D=E7=95=8C=E9=9D=A2?= =?UTF-8?q?=E7=9A=84=E6=A0=B7=E5=BC=8F=E4=BC=98=E5=8C=96=20-=20=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E9=AB=98=E4=BA=AE=20-=20=E9=97=B4=E8=B7=9D=E8=B0=83?= =?UTF-8?q?=E6=95=B4=20-=20=E5=B7=A5=E5=85=B7=E8=B0=83=E7=94=A8=E7=9A=84?= =?UTF-8?q?=E6=A0=B7=E5=BC=8F=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/codeHighlight.ts | 237 ++++++++++++++++++++++++++++++++ src/views/agentCard.ts | 11 +- src/views/messageArea.ts | 121 ++++++++-------- src/views/webviewContent.ts | 4 +- 4 files changed, 301 insertions(+), 72 deletions(-) create mode 100644 src/components/codeHighlight.ts diff --git a/src/components/codeHighlight.ts b/src/components/codeHighlight.ts new file mode 100644 index 0000000..35dd723 --- /dev/null +++ b/src/components/codeHighlight.ts @@ -0,0 +1,237 @@ +/** + * 代码高亮组件 + * + * 功能说明: + * - 使用 highlight.js 提供专业的代码语法高亮 + * - 支持多种编程语言(Verilog, JavaScript, Python 等) + * - 提供行内代码和代码块的不同样式 + * - 自动检测语言类型 + */ + +/** + * 获取 highlight.js 的 CDN 链接 + */ +export function getHighlightJsLinks(): string { + return ` + + + + + + + `; +} + +/** + * 获取代码高亮的样式 + */ +export function getCodeHighlightStyles(): string { + return ` + /* 代码块基础样式 */ + .segment-text pre { + background: var(--vscode-textCodeBlock-background); + border: 1px solid var(--vscode-panel-border); + border-radius: 6px; + padding: 12px; + overflow-x: auto; + margin: 12px 0; + position: relative; + white-space: pre; + } + + .segment-text pre code { + background: transparent !important; + padding: 0; + border: none; + display: block; + line-height: 1.5; + white-space: pre; + font-family: 'Courier New', Consolas, 'Monaco', monospace; + font-size: 0.9em; + } + + /* 行内代码样式 */ + .segment-text code:not(pre code) { + background: var(--vscode-textCodeBlock-background); + padding: 2px 6px; + border-radius: 3px; + color: var(--vscode-textPreformat-foreground); + border: 1px solid var(--vscode-panel-border); + font-family: 'Courier New', Consolas, 'Monaco', monospace; + font-size: 0.9em; + } + + /* 覆盖 highlight.js 的背景色,使用 VSCode 主题色 */ + .segment-text pre code.hljs { + background: transparent !important; + padding: 0 !important; + } + + /* 代码块语言标签 */ + .code-block-wrapper { + position: relative; + margin: 12px 0; + } + + .code-language-label { + position: absolute; + top: 8px; + right: 8px; + background: var(--vscode-badge-background); + color: var(--vscode-badge-foreground); + padding: 2px 8px; + border-radius: 3px; + font-size: 11px; + font-weight: 500; + text-transform: uppercase; + opacity: 0.8; + z-index: 1; + } + + /* 代码块复制按钮 */ + .code-copy-btn { + position: absolute; + top: 8px; + right: 8px; + background: var(--vscode-button-secondaryBackground); + color: var(--vscode-button-secondaryForeground); + border: 1px solid var(--vscode-button-border); + border-radius: 4px; + padding: 4px 8px; + font-size: 11px; + cursor: pointer; + opacity: 0; + transition: opacity 0.2s ease; + z-index: 2; + } + + .code-block-wrapper:hover .code-copy-btn { + opacity: 1; + } + + .code-copy-btn:hover { + background: var(--vscode-button-secondaryHoverBackground); + } + + .code-copy-btn.copied { + background: var(--vscode-button-background); + color: var(--vscode-button-foreground); + } + + /* 代码块滚动条样式 */ + .segment-text pre::-webkit-scrollbar { + height: 8px; + } + + .segment-text pre::-webkit-scrollbar-track { + background: var(--vscode-scrollbarSlider-background); + border-radius: 4px; + } + + .segment-text pre::-webkit-scrollbar-thumb { + background: var(--vscode-scrollbarSlider-hoverBackground); + border-radius: 4px; + } + + .segment-text pre::-webkit-scrollbar-thumb:hover { + background: var(--vscode-scrollbarSlider-activeBackground); + } + `; +} + +/** + * 获取代码高亮的脚本 + */ +export function getCodeHighlightScript(): string { + return ` + /** + * 使用 highlight.js 进行代码高亮 + */ + function highlightCodeBlocks() { + // 等待 highlight.js 加载完成 + if (typeof hljs === 'undefined') { + setTimeout(highlightCodeBlocks, 100); + return; + } + + const codeBlocks = document.querySelectorAll('.segment-text pre code:not(.hljs)'); + codeBlocks.forEach((block) => { + hljs.highlightElement(block); + }); + } + + /** + * 为代码块添加复制按钮 + */ + function enhanceCodeBlocks() { + const codeBlocks = document.querySelectorAll('.segment-text pre code'); + + codeBlocks.forEach((codeElement) => { + const preElement = codeElement.parentElement; + if (!preElement || preElement.classList.contains('enhanced')) { + return; + } + + // 标记为已增强,避免重复处理 + preElement.classList.add('enhanced'); + + // 应用语法高亮 + if (typeof hljs !== 'undefined' && !codeElement.classList.contains('hljs')) { + hljs.highlightElement(codeElement); + } + + // 创建包装器 + const wrapper = document.createElement('div'); + wrapper.className = 'code-block-wrapper'; + preElement.parentNode.insertBefore(wrapper, preElement); + wrapper.appendChild(preElement); + + // 添加复制按钮 + const copyBtn = document.createElement('button'); + copyBtn.className = 'code-copy-btn'; + copyBtn.textContent = '复制'; + copyBtn.onclick = function() { + const code = codeElement.textContent; + navigator.clipboard.writeText(code).then(() => { + copyBtn.textContent = '已复制'; + copyBtn.classList.add('copied'); + setTimeout(() => { + copyBtn.textContent = '复制'; + copyBtn.classList.remove('copied'); + }, 2000); + }); + }; + wrapper.appendChild(copyBtn); + }); + } + + /** + * 监听 DOM 变化,自动增强新添加的代码块 + */ + function observeCodeBlocks() { + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if (mutation.addedNodes.length > 0) { + enhanceCodeBlocks(); + } + }); + }); + + observer.observe(document.getElementById('messages'), { + childList: true, + subtree: true + }); + } + + // 初始化代码块增强 + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => { + enhanceCodeBlocks(); + observeCodeBlocks(); + }); + } else { + enhanceCodeBlocks(); + observeCodeBlocks(); + } + `; +} diff --git a/src/views/agentCard.ts b/src/views/agentCard.ts index fd19d7c..2dbd82e 100644 --- a/src/views/agentCard.ts +++ b/src/views/agentCard.ts @@ -172,15 +172,8 @@ export function getAgentCardScript(): string { const icon = step.status === 'completed' ? '✅' : step.status === 'error' ? '❌' : '🔄'; const displayName = getAgentToolDisplayName(step.toolName); const result = step.toolResult ? \`: \${step.toolResult.substring(0, 50)}\${step.toolResult.length > 50 ? '...' : ''}\` : ''; - // 为技术性工具调用添加低调样式(用户看不懂的) - const lowProfileTools = [ - 'knowledge_save', 'knowledge_load', - 'queryKnowledgeSummary', 'queryRules', 'querySignals', - 'setModule', 'addSignal', 'addSignalExample', - 'validateKnowledgeGraph', 'addPlan', 'addEdge', - 'showPlan', 'spawnExplorer' - ]; - const stepClass = lowProfileTools.includes(step.toolName) ? 'agent-step low-profile' : 'agent-step'; + // 所有工具调用都使用低调样式 + const stepClass = 'agent-step low-profile'; return \`
\${icon}\${displayName}\${result}
\`; }).join(''); diff --git a/src/views/messageArea.ts b/src/views/messageArea.ts index 4357ebe..9c836d1 100644 --- a/src/views/messageArea.ts +++ b/src/views/messageArea.ts @@ -30,6 +30,10 @@ import { } from "./waveformPreviewContent"; import { getAgentCardStyles, getAgentCardScript } from "./agentCard"; import { getPlanCardStyles, getPlanCardScript } from "./planCard"; +import { + getCodeHighlightStyles, + getCodeHighlightScript, +} from "../components/codeHighlight"; /** * 获取消息区域的 HTML 内容 @@ -294,7 +298,7 @@ export function getMessageAreaStyles(): string { padding: 0; } .message-segment { - padding: 10px 22px; + padding: 10px 20px; } .segment-text { line-height: 1.6; @@ -319,37 +323,14 @@ export function getMessageAreaStyles(): string { .segment-text h3 { font-size: 1.1em; } - .segment-text pre { - background: var(--vscode-textCodeBlock-background); - border: 1px solid var(--vscode-panel-border); - border-radius: 6px; - padding: 12px; - overflow-x: auto; - margin: 12px 0; - } - .segment-text code { - font-family: 'Courier New', Consolas, monospace; - font-size: 0.9em; - } - .segment-text pre code { - background: transparent; - padding: 0; - border: none; - } - .segment-text code:not(pre code) { - background: var(--vscode-textCodeBlock-background); - padding: 2px 6px; - border-radius: 3px; - color: var(--vscode-textPreformat-foreground); - } .segment-text ul, .segment-text ol { margin: 8px 0; padding-left: 24px; } .segment-text li { - margin: 4px 0; - line-height: 1.6; + margin: 0; + line-height: 0.4; } .segment-text strong { font-weight: 600; @@ -375,7 +356,7 @@ export function getMessageAreaStyles(): string { } /* 低调显示的工具调用 - 移除边距和背景 */ .segment-tool.low-profile { - margin: 2px 0; + margin: 2px 20px; padding: 0; background: none; } @@ -541,7 +522,7 @@ export function getMessageAreaStyles(): string { /* 低调显示的工具调用样式 */ .segment-tool.low-profile .tool-segment-header { opacity: 0.65; - font-size: 11px; + font-size: 12px; } .segment-tool.low-profile .tool-segment-icon { opacity: 0.55; @@ -642,6 +623,8 @@ export function getMessageAreaStyles(): string { ${getPlanCardStyles()} + ${getCodeHighlightStyles()} + ${getWaveformPreviewContent()} `; } @@ -1008,17 +991,8 @@ export function getMessageAreaScript(): string { return; } - // 为技术性工具调用添加低调样式 - const lowProfileTools = [ - 'knowledge_save', 'knowledge_load', - 'queryKnowledgeSummary', 'queryRules', 'querySignals', - 'setModule', 'addSignal', 'addSignalExample', - 'validateKnowledgeGraph', 'addPlan', 'addEdge', - 'showPlan', 'addRule', 'updateNode', 'addStateTransition' - ]; - if (lowProfileTools.includes(segment.toolName)) { - segmentDiv.className += ' low-profile'; - } + // 所有工具调用都使用低调样式 + segmentDiv.className += ' low-profile'; const statusIcon = segment.toolStatus === 'error' ? '❌' : '🔧'; const toolResult = segment.toolResult || ''; @@ -1270,17 +1244,8 @@ export function getMessageAreaScript(): string { return; } - // 为技术性工具调用添加低调样式 - const lowProfileTools = [ - 'knowledge_save', 'knowledge_load', - 'queryKnowledgeSummary', 'queryRules', 'querySignals', - 'setModule', 'addSignal', 'addSignalExample', - 'validateKnowledgeGraph', 'addPlan', 'addEdge', - 'showPlan', 'addRule', 'updateNode', 'addStateTransition' - ]; - if (lowProfileTools.includes(segment.toolName)) { - segmentDiv.className += ' low-profile'; - } + // 所有工具调用都使用低调样式 + segmentDiv.className += ' low-profile'; const statusIcon = segment.toolStatus === 'error' ? '❌' : '🔧'; const toolResult = segment.toolResult || ''; @@ -1406,21 +1371,41 @@ export function getMessageAreaScript(): string { function formatText(text) { if (!text) return ''; - // 先转义 HTML 特殊字符 - let html = text + let html = text; + + // 先提取并处理代码块(避免被转义) + const codeBlocks = []; + html = html.replace(/\`\`\`(\\w+)?\\n([\\s\\S]*?)\`\`\`/g, function(match, lang, code) { + const language = lang || 'plaintext'; + // 转义代码内容 + const escapedCode = code.trim() + .replace(/&/g, '&') + .replace(//g, '>'); + // 不再手动高亮,让 highlight.js 处理 + const placeholder = \`___CODE_BLOCK_\${codeBlocks.length}___\`; + codeBlocks.push('
' + escapedCode + '
'); + return placeholder; + }); + + // 提取行内代码(避免被转义) + const inlineCodes = []; + html = html.replace(/\`([^\`]+)\`/g, function(match, code) { + const escapedCode = code + .replace(/&/g, '&') + .replace(//g, '>'); + const placeholder = \`___INLINE_CODE_\${inlineCodes.length}___\`; + inlineCodes.push('' + escapedCode + ''); + return placeholder; + }); + + // 转义其他 HTML 特殊字符 + html = html .replace(/&/g, '&') .replace(//g, '>'); - // 处理代码块(三个反引号包裹的代码) - html = html.replace(/\`\`\`(\\w+)?\\n([\\s\\S]*?)\`\`\`/g, function(match, lang, code) { - const language = lang || 'plaintext'; - return '
' + code.trim() + '
'; - }); - - // 处理行内代码(单个反引号包裹) - html = html.replace(/\`([^\`]+)\`/g, '$1'); - // 处理标题 ### Title html = html.replace(/^### (.+)$/gm, '

$1

'); html = html.replace(/^## (.+)$/gm, '

$1

'); @@ -1442,9 +1427,19 @@ export function getMessageAreaScript(): string { // 处理链接 [text](url) html = html.replace(/\\[([^\\]]+)\\]\\(([^\\)]+)\\)/g, '$1'); - // 处理换行 + // 处理换行(在恢复代码块之前) html = html.replace(/\\n/g, '
'); + // 恢复代码块(在最后恢复,避免被其他处理影响) + codeBlocks.forEach((block, index) => { + html = html.replace(\`___CODE_BLOCK_\${index}___\`, block); + }); + + // 恢复行内代码 + inlineCodes.forEach((code, index) => { + html = html.replace(\`___INLINE_CODE_\${index}___\`, code); + }); + return html; } @@ -1594,5 +1589,7 @@ export function getMessageAreaScript(): string { } ${getWaveformPreviewScript()} + + ${getCodeHighlightScript()} `; } diff --git a/src/views/webviewContent.ts b/src/views/webviewContent.ts index 0888592..b6285e3 100644 --- a/src/views/webviewContent.ts +++ b/src/views/webviewContent.ts @@ -23,6 +23,7 @@ import { getProgressBarStyles, getProgressBarScript, } from "./progressBar"; +import { getHighlightJsLinks } from "../components/codeHighlight"; import { getCurrentEnv } from "../config/settings"; /** * 获取 WebView 面板的 HTML 内容 @@ -44,6 +45,7 @@ export function getWebviewContent( IC Coder + ${getHighlightJsLinks()}