feat:实现波形预览的功能

This commit is contained in:
Roe-xin
2025-12-16 16:58:35 +08:00
parent 4918399325
commit f2382a8eed
5 changed files with 1020 additions and 8 deletions

View File

@ -0,0 +1,444 @@
# 波形预览功能技术文档
## 功能概述
在对话界面中显示 VCD 波形文件的预览卡片,用户可以查看前几个信号的真实波形,并通过"展开查看"按钮打开完整的波形查看器。
## 功能流程
```
用户输入"生成VCD"命令
系统执行 iverilog 编译和仿真
生成 VCD 文件
在对话界面显示波形预览卡片
├─ 显示真实的波形图前3个信号
├─ 显示信号名称和波形
└─ "展开查看"按钮
点击"展开查看"按钮
打开完整的 VCDViewerPanel 波形查看器
```
## 文件结构
### 1. `src/views/waveformPreviewContent.ts`
**功能:** 波形预览组件的独立模块
**导出函数:**
- `getWaveformPreviewContent()` - 返回波形预览组件的 CSS 样式
- `getWaveformPreviewScript()` - 返回波形预览组件的 JavaScript 代码
**主要功能:**
- 创建波形预览卡片的 HTML 结构
- 从 VCD 文件中提取真实信号数据
- 使用 SVG 绘制波形图
- 单比特信号:绘制数字波形(高/低电平)
- 多比特信号:绘制总线波形(梯形)
- 处理"展开查看"按钮点击事件
**关键函数:**
```javascript
createWaveformPreview(vcdFilePath, fileName)
- 创建波形预览卡片的 DOM 结构
- 包含头部标题 + 展开按钮和内容区域
loadMiniWaveform(containerId, vcdFilePath, loadingDiv)
- 请求后端获取 VCD 文件信息
renderWaveformInfo(containerId, vcdInfo)
- 接收 VCD 信息并渲染波形
drawRealWaveform(signals)
- 根据真实信号数据绘制 SVG 波形图
- 支持单比特和多比特信号
- 使用不同颜色区分信号
openFullWaveform(vcdFilePath)
- 发送消息打开完整波形查看器
addWaveformPreviewToMessage(messageDiv, vcdFilePath, fileName)
- 将波形预览组件添加到消息中
```
---
### 2. `src/views/webviewContent.ts`
**功能:** 主 WebView 页面,集成波形预览组件
**修改内容:**
- 导入波形预览组件的样式和脚本
-`<style>` 标签中插入 `getWaveformPreviewContent()`
-`<script>` 标签中插入 `getWaveformPreviewScript()`
- 添加 `vcdGenerated` 消息处理逻辑
- 添加 `vcdInfo` 消息处理逻辑
**消息处理:**
```javascript
case 'vcdGenerated':
// VCD 文件生成成功,显示带波形预览的消息
- 创建消息 div
- 添加成功消息文本
- 调用 addWaveformPreviewToMessage() 添加波形预览卡片
case 'vcdInfo':
// 接收到 VCD 文件信息,渲染波形预览
- 调用 renderWaveformInfo() 渲染波形
```
---
### 3. `src/utils/messageHandler.ts`
**功能:** 处理用户消息和 VCD 生成请求
**修改内容:**
- 导入 `path` 模块
- 修改 VCD 生成成功后的消息发送逻辑
**关键代码:**
```typescript
if (result.success) {
if (result.vcdFilePath) {
const fileName = path.basename(result.vcdFilePath);
panel.webview.postMessage({
command: "vcdGenerated", // 发送 vcdGenerated 消息
text: successMsg,
vcdFilePath: result.vcdFilePath,
fileName: fileName,
});
}
}
```
---
### 4. `src/panels/ICHelperPanel.ts`
**功能:** IC 助手面板,处理 WebView 消息
**修改内容:**
- 导入 `VCDViewerPanel`
- 添加 `openWaveformViewer` 命令处理
- 添加 `getVCDInfo` 命令处理
- 新增 `getVCDFileInfo()` 函数
- 新增 `parseVCDSignals()` 函数
**新增函数:**
#### `getVCDFileInfo(panel, vcdFilePath, containerId)`
**功能:** 获取 VCD 文件信息并解析信号数据
**处理流程:**
1. 检查文件是否存在
2. 获取文件大小
3. 读取 VCD 文件内容
4. 解析信号数量
5. 解析时间范围
6. 调用 `parseVCDSignals()` 解析前 3 个信号的数据
7. 发送 `vcdInfo` 消息回前端
**返回数据结构:**
```typescript
{
command: "vcdInfo",
containerId: string,
vcdInfo: {
signalCount: string,
timeRange: string,
fileSize: string,
signals: Array<{
name: string,
identifier: string,
width: number,
values: Array<{ time: number, value: string }>
}>
}
}
```
#### `parseVCDSignals(content, maxSignals)`
**功能:** 解析 VCD 文件中的信号数据
**处理流程:**
1. 使用正则表达式解析信号定义部分(`$var ... $end`
2. 提取信号名称、标识符、位宽
3. 找到数据变化部分(`$dumpvars` 之后)
4. 解析每个信号的值变化
- 单比特信号:格式 `0!``1!`
- 多比特信号:格式 `b1010 !`
5. 限制最多 50 个采样点(避免数据过多)
6. 返回信号数据数组
**VCD 文件格式示例:**
```vcd
$var wire 1 ! clk $end
$var wire 8 " data $end
$dumpvars
0!
b00000000 "
#10
1!
#20
0!
b00000001 "
```
**消息处理:**
```typescript
case "openWaveformViewer":
// 打开完整波形查看器
VCDViewerPanel.createOrShow(context.extensionUri, message.vcdFilePath);
case "getVCDInfo":
// 获取 VCD 文件信息
getVCDFileInfo(panel, message.vcdFilePath, message.containerId);
```
---
### 5. `src/panels/VCDViewerPanel.ts`
**功能:** 完整的 VCD 波形查看器面板(已存在)
**作用:** 当用户点击"展开查看"按钮时,打开此面板显示完整的波形
---
## 数据流
### 1. VCD 生成流程
```
用户输入 → messageHandler.handleUserMessage()
检测到 VCD 生成命令
messageHandler.handleVCDGeneration()
iverilogRunner.generateVCD()
生成 VCD 文件
发送 vcdGenerated 消息到前端
webviewContent 接收消息
创建波形预览卡片
```
### 2. 波形预览加载流程
```
createWaveformPreview() 创建卡片
loadMiniWaveform() 请求 VCD 信息
发送 getVCDInfo 消息到后端
ICHelperPanel 接收消息
getVCDFileInfo() 读取并解析 VCD 文件
parseVCDSignals() 解析信号数据
发送 vcdInfo 消息到前端
renderWaveformInfo() 渲染波形
drawRealWaveform() 绘制 SVG 波形图
```
### 3. 展开查看流程
```
用户点击"展开查看"按钮
openFullWaveform() 发送消息
发送 openWaveformViewer 消息到后端
ICHelperPanel 接收消息
VCDViewerPanel.createOrShow()
打开完整波形查看器窗口
```
---
## 样式说明
### CSS 类名
- `.waveform-preview` - 波形预览卡片容器
- `.waveform-preview-header` - 卡片头部
- `.waveform-preview-title` - 标题区域
- `.waveform-expand-btn` - 展开按钮
- `.waveform-preview-content` - 内容区域
- `.waveform-mini-viewer` - 波形显示容器
- `.waveform-loading` - 加载提示
### 波形绘制
- **单比特信号**:使用 SVG `<path>` 绘制数字波形
- 高电平y = 顶部
- 低电平y = 底部
- 垂直跳变表示信号变化
- **多比特信号**:使用 SVG `<path>` 绘制总线波形
- 梯形表示数据变化
- 中间线表示稳定状态
- **颜色方案**
- 第1个信号蓝色 (`var(--vscode-charts-blue)`)
- 第2个信号绿色 (`var(--vscode-charts-green)`)
- 第3个信号橙色 (`var(--vscode-charts-orange)`)
---
## 配置参数
### 可调整参数
**在 `ICHelperPanel.ts` 中:**
```typescript
const signals = parseVCDSignals(content, 3); // 解析前3个信号
```
- 修改数字可以改变显示的信号数量
**在 `parseVCDSignals()` 中:**
```typescript
if (values.length >= 50) {
break; // 限制最多50个采样点
}
```
- 修改数字可以改变采样点数量
**在 `drawRealWaveform()` 中:**
```typescript
const signalHeight = 20; // 信号高度
const signalSpacing = 30; // 信号间距
const leftMargin = 80; // 左边距(信号名称区域)
const rightMargin = 20; // 右边距
```
- 修改这些参数可以调整波形显示样式
---
## 性能优化
1. **限制信号数量**:只解析前 3 个信号
2. **限制采样点**:每个信号最多 50 个采样点
3. **轻量级渲染**:使用 SVG 而不是 Canvas
4. **按需加载**:只在需要时读取和解析 VCD 文件
---
## 错误处理
### 文件不存在
```typescript
if (!fs.existsSync(vcdFilePath)) {
// 返回错误信息
vcdInfo: {
signalCount: 'N/A',
timeRange: 'N/A',
fileSize: 'N/A',
error: '文件不存在'
}
}
```
### 解析失败
```typescript
try {
// 解析逻辑
} catch (error) {
console.error('解析 VCD 信号数据失败:', error);
return []; // 返回空数组
}
```
### 无信号数据
```typescript
if (!signals || signals.length === 0) {
return `<svg>无波形数据</svg>`;
}
```
---
## 未来扩展
### 可能的改进方向
1. **交互功能**
- 鼠标悬停显示信号值
- 点击信号高亮显示
- 缩放和平移功能
2. **显示优化**
- 自动选择最有代表性的信号
- 智能采样(保留关键变化点)
- 支持更多信号类型(模拟信号等)
3. **性能优化**
- 使用 Web Worker 解析大文件
- 虚拟滚动显示大量信号
- 缓存解析结果
4. **功能增强**
- 支持信号搜索和过滤
- 导出波形图片
- 比较多个 VCD 文件
---
## 调试技巧
### 查看消息流
在浏览器开发者工具中查看 `vscode.postMessage()` 的调用:
```javascript
console.log('发送消息:', message);
vscode.postMessage(message);
```
### 查看解析结果
`parseVCDSignals()` 中添加日志:
```typescript
console.log('解析到的信号:', signals);
```
### 查看 SVG 输出
`drawRealWaveform()` 中添加日志:
```javascript
console.log('SVG 内容:', svgContent);
```
---
## 相关文件
- `src/utils/iverilogRunner.ts` - VCD 文件生成
- `src/panels/VCDViewerPanel.ts` - 完整波形查看器
- `media/vcdrom/` - VCDrom 波形查看库
---
## 版本历史
### v1.0 (当前版本)
- ✅ 创建独立的波形预览组件
- ✅ 解析 VCD 文件中的真实信号数据
- ✅ 绘制单比特和多比特信号波形
- ✅ 支持展开查看完整波形
- ✅ 轻量级预览,快速加载
---
## 总结
波形预览功能通过以下文件协同工作:
1. **`waveformPreviewContent.ts`** - 组件核心逻辑
2. **`webviewContent.ts`** - 集成到主页面
3. **`messageHandler.ts`** - 处理 VCD 生成
4. **`ICHelperPanel.ts`** - 解析 VCD 数据和消息处理
整个功能采用模块化设计,易于维护和扩展。

View File

@ -8,6 +8,7 @@ import {
handleRenameFile,
handleReplaceInFile
} from "../utils/messageHandler";
import { VCDViewerPanel } from "./VCDViewerPanel";
/**
* 创建并显示 IC 助手面板
@ -61,9 +62,190 @@ export function showICHelperPanel(context: vscode.ExtensionContext) {
case "showInfo":
vscode.window.showInformationMessage(message.text);
break;
case "openWaveformViewer":
// 打开波形查看器
if (message.vcdFilePath) {
VCDViewerPanel.createOrShow(context.extensionUri, message.vcdFilePath);
}
break;
case "getVCDInfo":
// 获取 VCD 文件信息
if (message.vcdFilePath && message.containerId) {
getVCDFileInfo(panel, message.vcdFilePath, message.containerId);
}
break;
}
},
undefined,
context.subscriptions
);
}
/**
* 获取 VCD 文件信息
*/
async function getVCDFileInfo(
panel: vscode.WebviewPanel,
vcdFilePath: string,
containerId: string
) {
try {
const fs = require('fs');
const path = require('path');
// 检查文件是否存在
if (!fs.existsSync(vcdFilePath)) {
panel.webview.postMessage({
command: "vcdInfo",
containerId: containerId,
vcdInfo: {
signalCount: 'N/A',
timeRange: 'N/A',
fileSize: 'N/A',
error: '文件不存在'
}
});
return;
}
// 获取文件大小
const stats = fs.statSync(vcdFilePath);
const fileSizeKB = stats.size / 1024;
const fileSize = fileSizeKB < 1024
? `${fileSizeKB.toFixed(2)} KB`
: `${(fileSizeKB / 1024).toFixed(2)} MB`;
// 读取 VCD 文件内容
const content = fs.readFileSync(vcdFilePath, 'utf-8');
// 解析信号数量
const varMatches = content.match(/\$var/g);
const signalCount = varMatches ? varMatches.length : 0;
// 解析时间范围
let timeRange = 'N/A';
const timeMatch = content.match(/#(\d+)/g);
if (timeMatch && timeMatch.length > 0) {
const times = timeMatch.map((t: string) => parseInt(t.substring(1)));
const minTime = Math.min(...times);
const maxTime = Math.max(...times);
timeRange = `${minTime} - ${maxTime}`;
}
// 解析前几个信号的真实数据
const signals = parseVCDSignals(content, 3); // 只解析前3个信号
// 发送信息回前端
panel.webview.postMessage({
command: "vcdInfo",
containerId: containerId,
vcdInfo: {
signalCount: signalCount.toString(),
timeRange: timeRange,
fileSize: fileSize,
signals: signals // 添加真实信号数据
}
});
} catch (error) {
console.error('获取 VCD 文件信息失败:', error);
panel.webview.postMessage({
command: "vcdInfo",
containerId: containerId,
vcdInfo: {
signalCount: 'N/A',
timeRange: 'N/A',
fileSize: 'N/A',
error: error instanceof Error ? error.message : '未知错误'
}
});
}
}
/**
* 解析 VCD 文件中的信号数据
*/
function parseVCDSignals(content: string, maxSignals: number = 3) {
const signals: Array<{
name: string;
identifier: string;
width: number;
values: Array<{ time: number; value: string }>;
}> = [];
try {
// 1. 解析信号定义部分
const varRegex = /\$var\s+(\w+)\s+(\d+)\s+(\S+)\s+([^\$]+?)\s+\$end/g;
let match;
const signalDefs: Array<{ name: string; identifier: string; width: number }> = [];
while ((match = varRegex.exec(content)) !== null && signalDefs.length < maxSignals) {
const width = parseInt(match[2]);
const identifier = match[3];
const name = match[4].trim();
signalDefs.push({ name, identifier, width });
}
// 2. 找到数据变化部分的起始位置
const dumpvarsIndex = content.indexOf('$dumpvars');
if (dumpvarsIndex === -1) {
return signals;
}
const dataSection = content.substring(dumpvarsIndex);
// 3. 解析每个信号的值变化
for (const signalDef of signalDefs) {
const values: Array<{ time: number; value: string }> = [];
let currentTime = 0;
// 分行处理数据
const lines = dataSection.split('\n');
for (const line of lines) {
const trimmedLine = line.trim();
// 解析时间戳
if (trimmedLine.startsWith('#')) {
currentTime = parseInt(trimmedLine.substring(1));
continue;
}
// 解析信号值变化
// 格式1: 单比特信号 "0!" 或 "1!"
// 格式2: 多比特信号 "b1010 !"
if (signalDef.width === 1) {
// 单比特信号
const singleBitMatch = trimmedLine.match(new RegExp(`^([01xz])${signalDef.identifier}$`));
if (singleBitMatch) {
values.push({ time: currentTime, value: singleBitMatch[1] });
}
} else {
// 多比特信号
const multiBitMatch = trimmedLine.match(new RegExp(`^b([01xz]+)\\s+${signalDef.identifier}$`));
if (multiBitMatch) {
values.push({ time: currentTime, value: multiBitMatch[1] });
}
}
// 限制采样点数量,避免数据过多
if (values.length >= 50) {
break;
}
}
signals.push({
name: signalDef.name,
identifier: signalDef.identifier,
width: signalDef.width,
values: values
});
}
} catch (error) {
console.error('解析 VCD 信号数据失败:', error);
}
return signals;
}

View File

@ -1,4 +1,5 @@
import * as vscode from "vscode";
import * as path from "path";
import { readFileContent } from "./readFiles";
import {
createFile,
@ -614,17 +615,24 @@ async function handleVCDGeneration(
successMsg += `\n\n仿真输出:\n${result.stdout}`;
}
panel.webview.postMessage({
command: "receiveMessage",
text: successMsg,
});
// 自动打开 VCD 波形查看器
// 发送带波形预览的消息
if (result.vcdFilePath) {
vscode.commands.executeCommand("ic-coder.openVCDViewer", result.vcdFilePath);
const fileName = path.basename(result.vcdFilePath);
panel.webview.postMessage({
command: "vcdGenerated",
text: successMsg,
vcdFilePath: result.vcdFilePath,
fileName: fileName,
});
vscode.window.showInformationMessage(
`VCD 文件生成成功,已自动打开波形查看器`
`VCD 文件生成成功: ${fileName}`
);
} else {
panel.webview.postMessage({
command: "receiveMessage",
text: successMsg,
});
}
} else {
let errorMsg = `${result.message}`;

View File

@ -0,0 +1,350 @@
/**
* 获取波形预览组件的样式内容(纯 CSS不包含 style 标签)
*/
export function getWaveformPreviewContent(): string {
return `
/* 波形预览组件样式 */
.waveform-preview {
margin-top: 12px;
border: 1px solid var(--vscode-panel-border);
border-radius: 8px;
overflow: hidden;
background: var(--vscode-editor-background);
}
.waveform-preview-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 12px;
background: var(--vscode-input-background);
border-bottom: 1px solid var(--vscode-panel-border);
}
.waveform-preview-title {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
font-weight: 500;
color: var(--vscode-foreground);
}
.waveform-preview-title svg {
width: 16px;
height: 16px;
color: var(--vscode-button-background);
}
.waveform-expand-btn {
padding: 4px 12px;
background: var(--vscode-button-background);
color: var(--vscode-button-foreground);
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
display: flex;
align-items: center;
gap: 4px;
transition: opacity 0.2s ease;
}
.waveform-expand-btn:hover {
opacity: 0.9;
}
.waveform-expand-btn svg {
width: 14px;
height: 14px;
}
.waveform-preview-content {
padding: 0;
min-height: 200px;
max-height: 300px;
overflow: hidden;
position: relative;
background: var(--vscode-editor-background);
}
.waveform-preview-canvas {
width: 100%;
height: 100%;
min-height: 200px;
}
.waveform-preview-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: var(--vscode-descriptionForeground);
font-size: 13px;
text-align: center;
padding: 20px;
}
.waveform-preview-placeholder svg {
width: 48px;
height: 48px;
margin-bottom: 12px;
opacity: 0.5;
}
.waveform-info {
margin-top: 8px;
font-size: 12px;
color: var(--vscode-descriptionForeground);
}
.waveform-mini-viewer {
width: 100%;
height: 200px;
background: var(--vscode-editor-background);
position: relative;
overflow: hidden;
}
.waveform-loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: var(--vscode-descriptionForeground);
font-size: 12px;
}
`;
}
/**
* 获取波形预览组件的 JavaScript 代码
*/
export function getWaveformPreviewScript(): string {
return `
/**
* 创建波形预览组件
*/
function createWaveformPreview(vcdFilePath, fileName) {
const previewDiv = document.createElement('div');
previewDiv.className = 'waveform-preview';
// 头部
const header = document.createElement('div');
header.className = 'waveform-preview-header';
const title = document.createElement('div');
title.className = 'waveform-preview-title';
title.innerHTML = \`
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<path d="M128 512h128l64-128 64 128 64-256 64 384 64-128h320"
stroke="currentColor"
stroke-width="64"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"/>
</svg>
<span>波形预览 - \${fileName}</span>
\`;
const expandBtn = document.createElement('button');
expandBtn.className = 'waveform-expand-btn';
expandBtn.innerHTML = \`
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<path d="M342 88.3h340c56.6 0 102.6 46 102.6 102.6v340c0 56.6-46 102.6-102.6 102.6H342c-56.6 0-102.6-46-102.6-102.6v-340c0-56.6 46-102.6 102.6-102.6z"
fill="none"
stroke="currentColor"
stroke-width="48"/>
<path d="M239.4 390.5v340c0 56.6 46 102.6 102.6 102.6h340"
fill="none"
stroke="currentColor"
stroke-width="48"
stroke-linecap="round"/>
</svg>
展开查看
\`;
expandBtn.onclick = () => openFullWaveform(vcdFilePath);
header.appendChild(title);
header.appendChild(expandBtn);
// 内容区域 - 创建一个唯一ID的容器用于显示波形
const content = document.createElement('div');
content.className = 'waveform-preview-content';
const miniViewerId = 'waveform-mini-' + Date.now();
const miniViewer = document.createElement('div');
miniViewer.id = miniViewerId;
miniViewer.className = 'waveform-mini-viewer';
// 添加加载提示
const loadingDiv = document.createElement('div');
loadingDiv.className = 'waveform-loading';
loadingDiv.textContent = '正在加载波形预览...';
miniViewer.appendChild(loadingDiv);
content.appendChild(miniViewer);
previewDiv.appendChild(header);
previewDiv.appendChild(content);
// 异步加载波形数据
loadMiniWaveform(miniViewerId, vcdFilePath, loadingDiv);
return previewDiv;
}
/**
* 加载迷你波形预览
*/
async function loadMiniWaveform(containerId, vcdFilePath, loadingDiv) {
try {
// 请求 VCD 文件信息
vscode.postMessage({
command: 'getVCDInfo',
vcdFilePath: vcdFilePath,
containerId: containerId
});
} catch (error) {
console.error('加载波形预览失败:', error);
loadingDiv.textContent = '波形预览加载失败';
loadingDiv.style.color = 'var(--vscode-errorForeground)';
}
}
/**
* 渲染波形预览信息
*/
function renderWaveformInfo(containerId, vcdInfo) {
const container = document.getElementById(containerId);
if (!container) return;
// 清空容器
container.innerHTML = '';
// 绘制真实波形
const waveformSvg = document.createElement('div');
waveformSvg.innerHTML = drawRealWaveform(vcdInfo.signals || []);
container.appendChild(waveformSvg);
}
/**
* 绘制真实波形
*/
function drawRealWaveform(signals) {
if (!signals || signals.length === 0) {
return \`
<svg width="100%" height="80" viewBox="0 0 800 80" style="background: var(--vscode-editor-background);">
<text x="400" y="40" fill="var(--vscode-descriptionForeground)" font-size="12" text-anchor="middle">
无波形数据
</text>
</svg>
\`;
}
const svgWidth = 800;
const svgHeight = Math.max(80, signals.length * 30 + 20);
const signalHeight = 20;
const signalSpacing = 30;
const leftMargin = 80;
const rightMargin = 20;
const waveformWidth = svgWidth - leftMargin - rightMargin;
const colors = ['var(--vscode-charts-blue)', 'var(--vscode-charts-green)', 'var(--vscode-charts-orange)'];
let svgContent = \`<svg width="100%" height="\${svgHeight}" viewBox="0 0 \${svgWidth} \${svgHeight}" style="background: var(--vscode-editor-background);">\`;
// 绘制每个信号
signals.forEach((signal, index) => {
const y = 10 + index * signalSpacing;
const color = colors[index % colors.length];
// 绘制信号名称
svgContent += \`<text x="5" y="\${y + signalHeight / 2 + 4}" fill="var(--vscode-foreground)" font-size="10" opacity="0.8">\${signal.name}</text>\`;
// 如果没有值变化数据,显示提示
if (!signal.values || signal.values.length === 0) {
svgContent += \`<text x="\${leftMargin + waveformWidth / 2}" y="\${y + signalHeight / 2 + 4}" fill="var(--vscode-descriptionForeground)" font-size="9" text-anchor="middle" opacity="0.5">无数据</text>\`;
return;
}
// 计算时间范围
const times = signal.values.map(v => v.time);
const minTime = Math.min(...times);
const maxTime = Math.max(...times);
const timeRange = maxTime - minTime || 1;
// 绘制波形
let pathData = '';
let lastX = leftMargin;
let lastValue = signal.values[0].value;
signal.values.forEach((point, i) => {
const x = leftMargin + ((point.time - minTime) / timeRange) * waveformWidth;
const value = point.value;
if (signal.width === 1) {
// 单比特信号 - 绘制数字波形
const yHigh = y;
const yLow = y + signalHeight;
const currentY = (value === '1') ? yHigh : yLow;
if (i === 0) {
pathData = \`M \${x} \${currentY}\`;
} else {
// 绘制垂直跳变
const prevY = (lastValue === '1') ? yHigh : yLow;
if (prevY !== currentY) {
pathData += \` L \${x} \${prevY} L \${x} \${currentY}\`;
} else {
pathData += \` L \${x} \${currentY}\`;
}
}
lastValue = value;
lastX = x;
} else {
// 多比特信号 - 绘制总线波形(梯形)
const yTop = y + 5;
const yBottom = y + signalHeight - 5;
const transitionWidth = 5;
if (i === 0) {
pathData = \`M \${x} \${yTop + (yBottom - yTop) / 2}\`;
} else {
// 绘制梯形过渡
pathData += \` L \${x - transitionWidth} \${yTop} L \${x} \${yTop + (yBottom - yTop) / 2}\`;
}
lastX = x;
}
});
// 延伸到右边界
if (signal.width === 1) {
const lastY = (lastValue === '1') ? y : (y + signalHeight);
pathData += \` L \${leftMargin + waveformWidth} \${lastY}\`;
} else {
const yMid = y + signalHeight / 2;
pathData += \` L \${leftMargin + waveformWidth} \${yMid}\`;
}
svgContent += \`<path d="\${pathData}" stroke="\${color}" stroke-width="1.5" fill="none"/>\`;
});
// 绘制时间轴
const timeAxisY = svgHeight - 5;
svgContent += \`<line x1="\${leftMargin}" y1="\${timeAxisY}" x2="\${leftMargin + waveformWidth}" y2="\${timeAxisY}" stroke="var(--vscode-foreground)" stroke-width="1" opacity="0.2"/>\`;
svgContent += \`</svg>\`;
return svgContent;
}
/**
* 打开完整波形查看器
*/
function openFullWaveform(vcdFilePath) {
vscode.postMessage({
command: 'openWaveformViewer',
vcdFilePath: vcdFilePath
});
}
/**
* 在消息中添加波形预览
*/
function addWaveformPreviewToMessage(messageDiv, vcdFilePath, fileName) {
const preview = createWaveformPreview(vcdFilePath, fileName);
messageDiv.appendChild(preview);
}
`;
}

View File

@ -1,3 +1,5 @@
import { getWaveformPreviewContent, getWaveformPreviewScript } from './waveformPreviewContent';
/**
* 获取 WebView 面板的 HTML 内容
*/
@ -381,6 +383,7 @@ export function getWebviewContent(iconUri?: string): string {
border-radius: 4px;
margin-top: 10px;
}
${getWaveformPreviewContent()}
.file-editor-section {
margin-bottom: 15px;
padding: 15px;
@ -954,6 +957,23 @@ export function getWebviewContent(iconUri?: string): string {
case 'receiveMessage':
addMessage(message.text, 'bot');
break;
case 'vcdGenerated':
// VCD 文件生成成功,显示带波形预览的消息
const messageDiv = document.createElement('div');
messageDiv.className = 'message bot-message';
const messageContent = document.createElement('div');
messageContent.textContent = message.text;
messageDiv.appendChild(messageContent);
// 添加波形预览组件
if (message.vcdFilePath && message.fileName) {
addWaveformPreviewToMessage(messageDiv, message.vcdFilePath, message.fileName);
}
messagesEl.appendChild(messageDiv);
messagesEl.scrollTop = messagesEl.scrollHeight;
break;
case 'fileContent':
displayFileContent(message.content, message.filePath);
break;
@ -969,6 +989,12 @@ export function getWebviewContent(iconUri?: string): string {
case 'fileUpdateError':
addMessage(\`\${message.error}\`, 'bot');
break;
case 'vcdInfo':
// 接收到 VCD 文件信息,渲染波形预览
if (message.containerId && message.vcdInfo) {
renderWaveformInfo(message.containerId, message.vcdInfo);
}
break;
}
});
@ -992,6 +1018,8 @@ export function getWebviewContent(iconUri?: string): string {
autoResizeTextarea();
messageInput.focus();
${getWaveformPreviewScript()}
</script>
</body>
</html>`;