feat:接入波形查看器的工具

- 生成VCD文件后,就自动打开波形查看的工具显示波形
This commit is contained in:
Roe-xin
2025-12-15 11:51:35 +08:00
parent 22b9a0ed13
commit ab6d257df2
10 changed files with 642 additions and 11 deletions

View File

@ -1,6 +1,7 @@
import * as vscode from "vscode";
import { ICViewProvider } from "./views/ICViewProvider";
import { showICHelperPanel } from "./panels/ICHelperPanel";
import { VCDViewerPanel } from "./panels/VCDViewerPanel";
export function activate(context: vscode.ExtensionContext) {
console.log("🎉 IC Coder 插件已激活!");
@ -21,6 +22,34 @@ export function activate(context: vscode.ExtensionContext) {
}
);
// 注册命令:打开 VCD 波形查看器
const openVCDViewerCommand = vscode.commands.registerCommand(
"ic-coder.openVCDViewer",
async (vcdFilePath?: string) => {
if (!vcdFilePath) {
// 如果没有提供文件路径,让用户选择 VCD 文件
const fileUri = await vscode.window.showOpenDialog({
canSelectFiles: true,
canSelectFolders: false,
canSelectMany: false,
filters: {
"VCD 文件": ["vcd"],
"所有文件": ["*"],
},
title: "选择 VCD 文件",
});
if (fileUri && fileUri[0]) {
vcdFilePath = fileUri[0].fsPath;
} else {
return;
}
}
VCDViewerPanel.createOrShow(context.extensionUri, vcdFilePath);
}
);
// 注册侧边栏视图
const viewProvider = new ICViewProvider(context.extensionUri);
const viewRegistration = vscode.window.registerWebviewViewProvider(
@ -32,6 +61,7 @@ export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
openPanelCommand,
openChatCommand,
openVCDViewerCommand,
viewRegistration
);
}

View File

@ -0,0 +1,352 @@
import * as vscode from "vscode";
import * as path from "path";
import * as fs from "fs";
/**
* VCD 波形查看器面板
*/
export class VCDViewerPanel {
public static currentPanel: VCDViewerPanel | undefined;
private readonly _panel: vscode.WebviewPanel;
private readonly _extensionUri: vscode.Uri;
private _disposables: vscode.Disposable[] = [];
private constructor(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) {
this._panel = panel;
this._extensionUri = extensionUri;
// 设置初始 HTML 内容
this._panel.webview.html = this._getLoadingHtml();
// 监听面板关闭事件
this._panel.onDidDispose(() => this.dispose(), null, this._disposables);
// 监听来自 webview 的消息
this._panel.webview.onDidReceiveMessage(
(message) => {
switch (message.command) {
case "loadVCD":
if (message.filePath) {
this.loadVCDFile(message.filePath);
}
break;
}
},
null,
this._disposables
);
}
/**
* 创建或显示 VCD 查看器面板
*/
public static createOrShow(extensionUri: vscode.Uri, vcdFilePath?: string) {
const column = vscode.ViewColumn.One;
// 如果已经有面板打开,则显示它
if (VCDViewerPanel.currentPanel) {
VCDViewerPanel.currentPanel._panel.reveal(column);
if (vcdFilePath) {
VCDViewerPanel.currentPanel.loadVCDFile(vcdFilePath);
}
return;
}
// 创建新面板
const panel = vscode.window.createWebviewPanel(
"vcdViewer",
"VCD 波形查看器",
column,
{
enableScripts: true,
retainContextWhenHidden: true,
localResourceRoots: [extensionUri],
}
);
VCDViewerPanel.currentPanel = new VCDViewerPanel(panel, extensionUri);
// 如果提供了 VCD 文件路径,加载它
if (vcdFilePath) {
VCDViewerPanel.currentPanel.loadVCDFile(vcdFilePath);
}
}
/**
* 加载 VCD 文件
*/
public loadVCDFile(vcdFilePath: string) {
try {
// 检查文件是否存在
if (!fs.existsSync(vcdFilePath)) {
vscode.window.showErrorMessage(`VCD 文件不存在: ${vcdFilePath}`);
return;
}
// 更新面板标题
const fileName = path.basename(vcdFilePath);
this._panel.title = `VCD 波形查看器 - ${fileName}`;
// 设置 HTML 内容
this._panel.webview.html = this._getWebviewContent(vcdFilePath);
} catch (error) {
vscode.window.showErrorMessage(
`加载 VCD 文件失败: ${error instanceof Error ? error.message : "未知错误"}`
);
}
}
/**
* 清理资源
*/
public dispose() {
VCDViewerPanel.currentPanel = undefined;
this._panel.dispose();
while (this._disposables.length) {
const disposable = this._disposables.pop();
if (disposable) {
disposable.dispose();
}
}
}
/**
* 获取加载中的 HTML
*/
private _getLoadingHtml(): string {
return `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VCD 波形查看器</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
font-family: var(--vscode-font-family);
color: var(--vscode-foreground);
background-color: var(--vscode-editor-background);
}
.loading {
text-align: center;
}
.spinner {
border: 4px solid var(--vscode-progressBar-background);
border-top: 4px solid var(--vscode-progressBar-foreground);
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body>
<div class="loading">
<div class="spinner"></div>
<p>正在加载 VCD 波形查看器...</p>
</div>
</body>
</html>`;
}
/**
* 获取 Webview 的 HTML 内容
*/
private _getWebviewContent(vcdFilePath: string): string {
// 获取资源 URI
const vcdromJsUri = this._panel.webview.asWebviewUri(
vscode.Uri.joinPath(this._extensionUri, "media", "vcdrom", "vcdrom.js")
);
const vcdWasmUri = this._panel.webview.asWebviewUri(
vscode.Uri.joinPath(this._extensionUri, "media", "vcdrom", "vcd.wasm")
);
const fontRegularUri = this._panel.webview.asWebviewUri(
vscode.Uri.joinPath(this._extensionUri, "media", "vcdrom", "IosevkaDrom-Regular.woff2")
);
const fontObliqueUri = this._panel.webview.asWebviewUri(
vscode.Uri.joinPath(this._extensionUri, "media", "vcdrom", "IosevkaDrom-Oblique.woff2")
);
const fontItalicUri = this._panel.webview.asWebviewUri(
vscode.Uri.joinPath(this._extensionUri, "media", "vcdrom", "IosevkaDrom-Italic.woff2")
);
// 读取 VCD 文件内容并转换为 base64
const vcdContent = fs.readFileSync(vcdFilePath, "utf-8");
const vcdBase64 = Buffer.from(vcdContent).toString("base64");
return `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; font-src ${this._panel.webview.cspSource}; style-src 'unsafe-inline' ${this._panel.webview.cspSource}; script-src 'unsafe-inline' 'unsafe-eval' ${this._panel.webview.cspSource}; img-src ${this._panel.webview.cspSource} data:; connect-src ${this._panel.webview.cspSource};">
<title>VCD 波形查看器</title>
<style>
@font-face {
font-family: 'Iosevka Drom Web';
font-display: swap;
font-weight: 400;
font-stretch: normal;
font-style: normal;
src: url('${fontRegularUri}') format('woff2');
}
@font-face {
font-family: 'Iosevka Drom Web';
font-display: swap;
font-weight: 400;
font-stretch: normal;
font-style: oblique;
src: url('${fontObliqueUri}') format('woff2');
}
@font-face {
font-family: 'Iosevka Drom Web';
font-display: swap;
font-weight: 400;
font-stretch: normal;
font-style: italic;
src: url('${fontItalicUri}') format('woff2');
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Iosevka Drom Web', monospace;
color: var(--vscode-foreground);
background-color: var(--vscode-editor-background);
overflow: hidden;
}
#waveform-container {
width: 100vw;
height: 100vh;
overflow: auto;
}
#waveform1 {
width: 100%;
height: 100%;
}
.loading {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
flex-direction: column;
}
.spinner {
border: 4px solid var(--vscode-progressBar-background);
border-top: 4px solid var(--vscode-progressBar-foreground);
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin-bottom: 20px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.error-message {
padding: 20px;
color: var(--vscode-errorForeground);
background-color: var(--vscode-inputValidation-errorBackground);
border: 1px solid var(--vscode-inputValidation-errorBorder);
border-radius: 4px;
margin: 20px;
}
</style>
<script src="${vcdromJsUri}"></script>
</head>
<body>
<div id="waveform-container">
<div class="loading">
<div class="spinner"></div>
<p>正在加载 VCD 波形...</p>
</div>
<div id="waveform1"></div>
</div>
<script>
(async function() {
try {
// 设置 WASM 文件路径
window.wasmBinaryFile = '${vcdWasmUri}';
// 解码 base64 VCD 内容
const vcdBase64 = '${vcdBase64}';
const vcdContent = atob(vcdBase64);
// 隐藏加载提示
document.querySelector('.loading').style.display = 'none';
// 创建一个函数来提供 VCD 数据流
const vcdProvider = async (handler) => {
// 将 VCD 内容转换为 Uint8Array
const encoder = new TextEncoder();
const vcdData = encoder.encode(vcdContent);
// 创建一个 ReadableStream reader
const stream = new ReadableStream({
start(controller) {
controller.enqueue(vcdData);
controller.close();
}
});
const reader = stream.getReader();
// 调用 handler 并传递 reader
await handler([{
key: 'local',
value: 'waveform.vcd',
format: 'raw',
baseName: 'waveform.vcd',
ext: 'vcd',
reader: reader
}]);
};
// 初始化 VCDrom使用函数回调方式
if (typeof VCDrom === 'function') {
await VCDrom('waveform1', vcdProvider);
} else {
throw new Error('VCDrom 未正确加载');
}
} catch (error) {
console.error('加载 VCD 波形失败:', error);
document.getElementById('waveform-container').innerHTML =
'<div class="error-message">' +
'<h3>❌ 加载 VCD 波形失败</h3>' +
'<p>' + error.message + '</p>' +
'<p style="margin-top: 10px;">请确保 VCD 文件格式正确。</p>' +
'<pre style="margin-top: 10px; padding: 10px; background: rgba(0,0,0,0.1); overflow: auto;">' + error.stack + '</pre>' +
'</div>';
}
})();
</script>
</body>
</html>`;
}
}

View File

@ -595,16 +595,13 @@ async function handleVCDGeneration(
text: successMsg,
});
vscode.window.showInformationMessage(
`VCD 文件生成成功: ${result.vcdFilePath}`,
"打开文件"
).then((selection) => {
if (selection === "打开文件" && result.vcdFilePath) {
vscode.workspace.openTextDocument(result.vcdFilePath).then((doc) => {
vscode.window.showTextDocument(doc);
});
}
});
// 自动打开 VCD 波形查看器
if (result.vcdFilePath) {
vscode.commands.executeCommand("ic-coder.openVCDViewer", result.vcdFilePath);
vscode.window.showInformationMessage(
`VCD 文件生成成功,已自动打开波形查看器`
);
}
} else {
let errorMsg = `${result.message}`;