import * as vscode from "vscode"; import * as path from "path"; import * as fs from "fs"; import { VCDFileServer } from "../services/vcdFileServer"; /** * VCD 波形查看器自定义编辑器提供者 */ export class VCDViewerEditorProvider implements vscode.CustomReadonlyEditorProvider { public static register(context: vscode.ExtensionContext, vcdFileServer: VCDFileServer): vscode.Disposable { const provider = new VCDViewerEditorProvider(context, vcdFileServer); const providerRegistration = vscode.window.registerCustomEditorProvider( "ic-coder.vcdViewer", provider, { webviewOptions: { retainContextWhenHidden: true, }, } ); return providerRegistration; } constructor( private readonly context: vscode.ExtensionContext, private readonly vcdFileServer: VCDFileServer ) {} async openCustomDocument( uri: vscode.Uri, openContext: vscode.CustomDocumentOpenContext, token: vscode.CancellationToken ): Promise { return { uri, dispose: () => {}, }; } async resolveCustomEditor( document: vscode.CustomDocument, webviewPanel: vscode.WebviewPanel, token: vscode.CancellationToken ): Promise { webviewPanel.webview.options = { enableScripts: true, localResourceRoots: [this.context.extensionUri], }; // 使用公共工厂方法创建 VCD 查看器实例 VCDViewerPanel.createFromWebviewPanel( webviewPanel, this.context.extensionUri, document.uri.fsPath, this.vcdFileServer ); } } /** * VCD 波形查看器面板 (使用 Surfer) */ export class VCDViewerPanel { public static currentPanel: VCDViewerPanel | undefined; private readonly _panel: vscode.WebviewPanel; private readonly _extensionUri: vscode.Uri; private _disposables: vscode.Disposable[] = []; private _currentVcdPath: string | undefined; private _vcdFileServer: VCDFileServer | undefined; private constructor(panel: vscode.WebviewPanel, extensionUri: vscode.Uri, vcdFileServer?: VCDFileServer) { this._panel = panel; this._extensionUri = extensionUri; this._vcdFileServer = vcdFileServer; // 设置初始 HTML 内容 this._panel.webview.html = this._getLoadingHtml(); // 监听面板关闭事件 this._panel.onDidDispose(() => this.dispose(), null, this._disposables); // 监听来自 webview 的消息 this._panel.webview.onDidReceiveMessage( (message) => { console.log("[VCDViewerPanel] 收到消息:", message); switch (message.command) { case "loadVCD": if (message.filePath) { this.loadVCDFile(message.filePath); } break; case "loaded": // Surfer iframe 加载完成,发送 VCD 文件 console.log("[VCDViewerPanel] Surfer 已加载,当前 VCD 路径:", this._currentVcdPath); if (this._currentVcdPath) { this.sendVcdToSurfer(this._currentVcdPath); } break; } }, null, this._disposables ); } /** * 创建或显示 VCD 查看器面板 */ public static createOrShow(extensionUri: vscode.Uri, vcdFilePath?: string, vcdFileServer?: VCDFileServer) { 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, vcdFileServer); // 如果提供了 VCD 文件路径,加载它 if (vcdFilePath) { VCDViewerPanel.currentPanel.loadVCDFile(vcdFilePath); } } /** * 从已有的 webview panel 创建 VCD 查看器(用于自定义编辑器) */ public static createFromWebviewPanel( panel: vscode.WebviewPanel, extensionUri: vscode.Uri, vcdFilePath: string, vcdFileServer?: VCDFileServer ) { const viewer = new VCDViewerPanel(panel, extensionUri, vcdFileServer); viewer.loadVCDFile(vcdFilePath); return viewer; } /** * 加载 VCD 文件 */ public loadVCDFile(vcdFilePath: string) { try { console.log("[VCDViewerPanel] 开始加载 VCD 文件:", vcdFilePath); // 检查文件是否存在 if (!fs.existsSync(vcdFilePath)) { vscode.window.showErrorMessage(`VCD 文件不存在: ${vcdFilePath}`); return; } // 保存当前 VCD 路径 this._currentVcdPath = vcdFilePath; console.log("[VCDViewerPanel] VCD 路径已保存:", this._currentVcdPath); // 更新面板标题 const fileName = path.basename(vcdFilePath); this._panel.title = `Surfer 波形查看器 - ${fileName}`; // 设置 HTML 内容 this._panel.webview.html = this._getWebviewContent(); console.log("[VCDViewerPanel] Webview HTML 已设置"); } catch (error) { vscode.window.showErrorMessage( `加载 VCD 文件失败: ${error instanceof Error ? error.message : "未知错误"}` ); } } /** * 解析 VCD 文件获取根模块及其直接子模块名称 */ private parseVcdRootScope(vcdFilePath: string): string[] { try { // 读取 VCD 文件 const buffer = fs.readFileSync(vcdFilePath, { encoding: 'utf8' }); const lines = buffer.split('\n'); const scopeNames: string[] = []; let scopeDepth = 0; const scopeStack: string[] = []; for (const line of lines) { const trimmed = line.trim(); // 遇到 $enddefinitions 就停止解析 if (trimmed.startsWith('$enddefinitions')) { break; } // 查找 $scope 定义 const scopeMatch = trimmed.match(/^\$scope\s+(\w+)\s+(\w+)/); if (scopeMatch) { const scopeType = scopeMatch[1]; const scopeName = scopeMatch[2]; // 记录顶层 module (depth = 0) if (scopeDepth === 0 && scopeType === 'module') { scopeStack.push(scopeName); console.log("[VCDViewerPanel] 找到顶层作用域:", scopeName); } // 记录顶层下的直接子模块 (depth = 1) else if (scopeDepth === 1 && scopeType === 'module') { const fullPath = [...scopeStack, scopeName]; scopeNames.push(fullPath.join('.')); console.log("[VCDViewerPanel] 找到子模块:", fullPath.join('.')); } scopeDepth++; } // 遇到 $upscope 减少深度 if (trimmed.startsWith('$upscope')) { scopeDepth--; if (scopeDepth === 0) { scopeStack.pop(); } } } return scopeNames; } catch (error) { console.error("[VCDViewerPanel] 解析 VCD 文件失败:", error); return []; } } /** * 发送 VCD 文件到 Surfer */ private sendVcdToSurfer(vcdFilePath: string) { try { console.log("[VCDViewerPanel] 准备发送 VCD 到 Surfer:", vcdFilePath); if (!this._vcdFileServer) { throw new Error("VCD 文件服务器未初始化"); } // 解析 VCD 文件获取根模块名称 const scopeNames = this.parseVcdRootScope(vcdFilePath); console.log("[VCDViewerPanel] 解析到的作用域名称:", scopeNames); // 注册文件到 HTTP 服务器 const fileId = this._vcdFileServer.registerFile(vcdFilePath); const httpUrl = this._vcdFileServer.getFileUrl(fileId); const fileName = path.basename(vcdFilePath); console.log("[VCDViewerPanel] 文件名:", fileName); console.log("[VCDViewerPanel] HTTP URL:", httpUrl); // 使用 LoadUrl 命令通过 HTTP 加载文件 this._panel.webview.postMessage({ command: "loadVcdUrl", url: httpUrl, fileName: fileName, scopeNames: scopeNames, // 传递解析到的作用域名称 }); console.log("[VCDViewerPanel] 已发送 loadVcdUrl 消息到 webview"); } catch (error) { console.error("[VCDViewerPanel] 发送 VCD 数据失败:", 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 ` VCD 波形查看器

正在加载 VCD 波形查看器...

`; } /** * 获取 Webview 的 HTML 内容 */ private _getWebviewContent(): string { // 获取 surfer 资源 URI const surferJsUri = this._panel.webview.asWebviewUri( vscode.Uri.joinPath(this._extensionUri, "media", "surfer", "surfer.js") ); const surferWasmUri = this._panel.webview.asWebviewUri( vscode.Uri.joinPath(this._extensionUri, "media", "surfer", "surfer_bg.wasm") ); const integrationJsUri = this._panel.webview.asWebviewUri( vscode.Uri.joinPath(this._extensionUri, "media", "surfer", "integration.js") ); return ` Surfer 波形查看器 `; } }