import * as http from "http"; import * as fs from "fs"; import * as path from "path"; import * as vscode from "vscode"; /** * VCD 文件 HTTP 服务器 * 用于为 波形查看器提供 VCD 文件访问 */ export class VCDFileServer { private server: http.Server | null = null; private port: number = 0; private vcdFiles: Map = new Map(); // fileId -> filePath private extensionUri: vscode.Uri; constructor(extensionUri: vscode.Uri) { this.extensionUri = extensionUri; } /** * 启动服务器 */ public async start(): Promise { if (this.server) { return this.port; } return new Promise((resolve, reject) => { this.server = http.createServer((req, res) => { this.handleRequest(req, res); }); // 监听随机端口 this.server.listen(0, "127.0.0.1", () => { const address = this.server!.address(); if (address && typeof address === "object") { this.port = address.port; console.log(`[VCDFileServer] 服务器已启动,端口: ${this.port}`); resolve(this.port); } else { reject(new Error("无法获取服务器端口")); } }); this.server.on("error", (error) => { console.error("[VCDFileServer] 服务器错误:", error); reject(error); }); }); } /** * 停止服务器 */ public stop(): void { if (this.server) { this.server.close(); this.server = null; this.port = 0; this.vcdFiles.clear(); console.log("[VCDFileServer] 服务器已停止"); } } /** * 注册 VCD 文件 */ public registerFile(filePath: string): string { const fileId = this.generateFileId(filePath); this.vcdFiles.set(fileId, filePath); console.log(`[VCDFileServer] 注册文件: ${fileId} -> ${filePath}`); return fileId; } /** * 获取文件 URL */ public getFileUrl(fileId: string): string { return `http://127.0.0.1:${this.port}/vcd/${fileId}`; } /** * 获取波形查看器 URL */ public getViewerUrl(fileId: string): string { return `http://127.0.0.1:${this.port}/viewer/${fileId}`; } /** * 生成文件 ID */ private generateFileId(filePath: string): string { const timestamp = Date.now(); const fileName = path.basename(filePath); return `${timestamp}-${fileName}`; } /** * 处理 HTTP 请求 */ private handleRequest( req: http.IncomingMessage, res: http.ServerResponse, ): void { const url = req.url || ""; console.log(`[VCDFileServer] 收到请求: ${url}`); // 设置 CORS 头 res.setHeader("Access-Control-Allow-Origin", "*"); res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS"); res.setHeader("Access-Control-Allow-Headers", "Content-Type"); // 处理 OPTIONS 请求 if (req.method === "OPTIONS") { res.writeHead(200); res.end(); return; } // 路由处理 if (url.startsWith("/viewer/")) { this.handleViewerRequest(url, res); } else if (url.startsWith("/vcd/")) { this.handleVcdFileRequest(url, res); } else if (url.startsWith("/static/")) { this.handleStaticFileRequest(url, res); } else { res.writeHead(404, { "Content-Type": "text/plain" }); res.end("Not Found"); } } /** * 处理查看器页面请求 */ private handleViewerRequest(url: string, res: http.ServerResponse): void { const match = url.match(/^\/viewer\/(.+)$/); if (!match) { res.writeHead(404, { "Content-Type": "text/plain" }); res.end("Not Found"); return; } const fileId = match[1]; const filePath = this.vcdFiles.get(fileId); if (!filePath) { console.error(`[VCDFileServer] 文件 ID 不存在: ${fileId}`); res.writeHead(404, { "Content-Type": "text/plain" }); res.end("File Not Found"); return; } // 生成 HTML 页面 const html = this.generateViewerHtml(fileId, filePath); res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Content-Length": Buffer.byteLength(html), }); res.end(html); } /** * 处理 VCD 文件请求 */ private handleVcdFileRequest(url: string, res: http.ServerResponse): void { const match = url.match(/^\/vcd\/(.+)$/); if (!match) { res.writeHead(404, { "Content-Type": "text/plain" }); res.end("Not Found"); return; } const fileId = match[1]; const filePath = this.vcdFiles.get(fileId); if (!filePath) { console.error(`[VCDFileServer] 文件 ID 不存在: ${fileId}`); res.writeHead(404, { "Content-Type": "text/plain" }); res.end("File Not Found"); return; } // 检查文件是否存在 if (!fs.existsSync(filePath)) { console.error(`[VCDFileServer] 文件不存在: ${filePath}`); res.writeHead(404, { "Content-Type": "text/plain" }); res.end("File Not Found"); return; } // 读取并发送文件 try { const fileContent = fs.readFileSync(filePath); res.writeHead(200, { "Content-Type": "text/plain", "Content-Length": fileContent.length, }); res.end(fileContent); console.log(`[VCDFileServer] 成功发送文件: ${filePath}`); } catch (error) { console.error(`[VCDFileServer] 读取文件失败:`, error); res.writeHead(500, { "Content-Type": "text/plain" }); res.end("Internal Server Error"); } } /** * 处理静态文件请求(Surfer 资源) */ private handleStaticFileRequest(url: string, res: http.ServerResponse): void { const match = url.match(/^\/static\/(.+)$/); if (!match) { res.writeHead(404, { "Content-Type": "text/plain" }); res.end("Not Found"); return; } const fileName = match[1]; const filePath = path.join( this.extensionUri.fsPath, "media", "surfer", fileName, ); if (!fs.existsSync(filePath)) { console.error(`[VCDFileServer] 静态文件不存在: ${filePath}`); res.writeHead(404, { "Content-Type": "text/plain" }); res.end("File Not Found"); return; } try { const fileContent = fs.readFileSync(filePath); const contentType = this.getContentType(fileName); res.writeHead(200, { "Content-Type": contentType, "Content-Length": fileContent.length, }); res.end(fileContent); } catch (error) { console.error(`[VCDFileServer] 读取静态文件失败:`, error); res.writeHead(500, { "Content-Type": "text/plain" }); res.end("Internal Server Error"); } } /** * 获取文件的 Content-Type */ private getContentType(fileName: string): string { const ext = path.extname(fileName).toLowerCase(); const contentTypes: { [key: string]: string } = { ".js": "application/javascript", ".wasm": "application/wasm", ".html": "text/html", ".css": "text/css", }; return contentTypes[ext] || "application/octet-stream"; } /** * 解析 VCD 文件获取根模块及其直接子模块名称 */ private parseVcdRootScope(vcdFilePath: string): string[] { try { 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(); if (trimmed.startsWith("$enddefinitions")) { break; } const scopeMatch = trimmed.match(/^\$scope\s+(\w+)\s+(\w+)/); if (scopeMatch) { const scopeType = scopeMatch[1]; const scopeName = scopeMatch[2]; if (scopeDepth === 0 && scopeType === "module") { scopeStack.push(scopeName); } else if (scopeDepth === 1 && scopeType === "module") { const fullPath = [...scopeStack, scopeName]; scopeNames.push(fullPath.join(".")); } scopeDepth++; } if (trimmed.startsWith("$upscope")) { scopeDepth--; if (scopeDepth === 0) { scopeStack.pop(); } } } return scopeNames; } catch (error) { console.error("[VCDFileServer] 解析 VCD 文件失败:", error); return []; } } /** * 生成波形查看器 HTML 页面 */ private generateViewerHtml(fileId: string, vcdFilePath: string): string { const vcdUrl = this.getFileUrl(fileId); const fileName = path.basename(vcdFilePath); const scopeNames = this.parseVcdRootScope(vcdFilePath); const scopeNamesJson = JSON.stringify(scopeNames); const htmlPart1 = this.getHtmlPart1(fileName); const htmlPart2 = this.getHtmlPart2(vcdUrl, scopeNamesJson); const htmlPart3 = this.getHtmlPart3(); return htmlPart1 + htmlPart2 + htmlPart3; } private getHtmlPart1(fileName: string): string { return ` 波形查看器 - ${fileName} `; } private getHtmlPart2(vcdUrl: string, scopeNamesJson: string): string { return ` `; } private getHtmlPart3(): string { return ` `; } }