完成波形保存、另存为、载入的功能

This commit is contained in:
锦恢 2024-10-22 21:19:28 +08:00
parent 78c98bdb21
commit 473207b78b
10 changed files with 321 additions and 56 deletions

View File

@ -16,4 +16,5 @@ resources/**/*.wasm
resources/dide-lsp/server
tsconfig.json
design
lib
lib
*.vcd

1
images/svg/dark/view.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1729580625832" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="18157" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M256 0A256 256 0 0 0 0 256v211.748571h303.177143l96.475428-161.353142a45.714286 45.714286 0 0 1 83.309715 11.410285l83.382857 305.590857 90.477714-135.314285a45.714286 45.714286 0 0 1 38.034286-20.333715H1024V256A256 256 0 0 0 768 0h-512zM1024 559.177143H719.286857L586.605714 757.540571a45.714286 45.714286 0 0 1-82.139428-13.385142L422.985143 445.513143l-54.637714 91.428571a45.714286 45.714286 0 0 1-39.204572 22.235429H0V768A256 256 0 0 0 256 1024h512a256 256 0 0 0 256-256V559.177143z" fill="#4CAF50" p-id="18158"></path></svg>

After

Width:  |  Height:  |  Size: 866 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1729580625832" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="18157" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M256 0A256 256 0 0 0 0 256v211.748571h303.177143l96.475428-161.353142a45.714286 45.714286 0 0 1 83.309715 11.410285l83.382857 305.590857 90.477714-135.314285a45.714286 45.714286 0 0 1 38.034286-20.333715H1024V256A256 256 0 0 0 768 0h-512zM1024 559.177143H719.286857L586.605714 757.540571a45.714286 45.714286 0 0 1-82.139428-13.385142L422.985143 445.513143l-54.637714 91.428571a45.714286 45.714286 0 0 1-39.204572 22.235429H0V768A256 256 0 0 0 256 1024h512a256 256 0 0 0 256-256V559.177143z" fill="#4CAF50" p-id="18158"></path></svg>

After

Width:  |  Height:  |  Size: 866 B

View File

@ -11,6 +11,14 @@
"progress.choose-best-download-source": "Choose Best Download Source",
"progress.extract-digital-lsp": "Extract Digital LSP",
"error.download-digital-lsp": "Fail to download digital lsp server, check your network and reload vscode. You can also visit following site and download manually: https://github.com/Digital-EDA/Digital-IDE/releases/tag/",
"fail.save-file": "fail to save file",
"fail.save-file": "保存文件失败",
"save": "保存",
"save-as-view": "另存为视图文件",
"vcd-view-file": "vcd 视图文件",
"all-file": "所有文件",
"load": "加载",
"load-view-file": "加载视图文件",
"bad-view-file": "视图文件已损坏",
"unexist-direct-vcd-file": "视图文件指向的 vcd 文件不存在",
"click.join-qq-group": "Click the link to join the QQ group"
}

View File

@ -12,5 +12,13 @@
"progress.extract-digital-lsp": "解压 Digital LSP 语言服务器",
"error.download-digital-lsp": "无法下载 Digital LSP 语言服务器,检查你的网络后重启 vscode或者请手动去下方地址下载 https://github.com/Digital-EDA/Digital-IDE/releases/tag/",
"fail.save-file": "保存文件失败",
"save": "保存",
"save-as-view": "另存为视图文件",
"vcd-view-file": "vcd 视图文件",
"all-file": "所有文件",
"load": "加载",
"load-view-file": "加载视图文件",
"bad-view-file": "视图文件已损坏",
"unexist-direct-vcd-file": "视图文件指向的 vcd 文件不存在",
"click.join-qq-group": "点击链接加入QQ群一起讨论"
}

View File

@ -11,6 +11,14 @@
"progress.choose-best-download-source": "Choose Best Download Source",
"progress.extract-digital-lsp": "Extract Digital LSP",
"error.download-digital-lsp": "Fail to download digital lsp server, check your network and reload vscode",
"fail.save-file": "保存文件失敗",
"fail.save-file": "保存文件失败",
"save": "保存",
"save-as-view": "另存为视图文件",
"vcd-view-file": "vcd 视图文件",
"all-file": "所有文件",
"load": "加载",
"load-view-file": "加载视图文件",
"bad-view-file": "视图文件已损坏",
"unexist-direct-vcd-file": "视图文件指向的 vcd 文件不存在",
"click.join-qq-group": "点击链接加入QQ群一起讨论"
}

View File

@ -669,7 +669,7 @@
"group": "navigation@3"
},
{
"when": "editorLangId == vcd",
"when": "editorLangId == vcd || editorLangId == view",
"command": "digital-ide.waveviewer.show",
"group": "navigation@4"
},
@ -716,7 +716,7 @@
"group": "navigation@6"
},
{
"when": "resourceLangId == vcd",
"when": "resourceLangId == vcd || resourceLangId == vcd",
"command": "digital-ide.waveviewer.show",
"group": "navigation@7"
},
@ -763,7 +763,7 @@
"group": "navigation@9"
},
{
"when": "resourceLangId == vcd",
"when": "resourceLangId == vcd || resourceLangId == view",
"command": "digital-ide.waveviewer.show",
"group": "navigation@10"
},
@ -791,6 +791,9 @@
"selector": [
{
"filenamePattern": "*.vcd"
},
{
"filenamePattern": "*.view"
}
],
"priority": "default"
@ -971,6 +974,16 @@
"light": "./images/svg/light/vcd.svg"
}
},
{
"id": "view",
"extensions": [
".view"
],
"icon": {
"dark": "./images/svg/dark/view.svg",
"light": "./images/svg/light/view.svg"
}
},
{
"id": "digital-ide-output",
"mimetypes": [

View File

@ -1,7 +1,172 @@
import * as fs from 'fs';
import * as vscode from 'vscode';
import * as url from 'url';
import { BSON } from 'bson';
import { hdlPath } from '../../hdlFs';
import * as path from 'path';
export async function saveView(file: string, payload: any) {
export interface SaveViewData {
originVcdFile: string,
originVcdViewFile: string,
payload: any
}
export interface LaunchFiles {
vcd: string,
view: string,
worker: string,
root: string
}
const payloadCache = new Map<string, any>();
function mergePayloadCache(file: string, payload: any) {
if (!payloadCache.has(file)) {
payloadCache.set(file, payload);
}
const originPayload = payloadCache.get(file);
Object.assign(originPayload, payload);
return originPayload;
}
function extractFilepath(webviewUri: string) {
if (webviewUri === undefined || webviewUri.length === 0) {
return '';
}
const parsedUrl = new url.URL(webviewUri);
const pathname = decodeURIComponent(parsedUrl.pathname);
if (pathname.startsWith('/')) {
return pathname.slice(1);
}
return pathname;
}
// api 与 https://github.com/Digital-EDA/digital-vcd-backend 同构
export async function saveView(data: any, uri: vscode.Uri, panel: vscode.WebviewPanel) {
try {
let { originVcdFile, originVcdViewFile, payload } = data as SaveViewData;
// webview uri 转换为绝对路径
originVcdFile = extractFilepath(originVcdFile);
originVcdViewFile = extractFilepath(originVcdViewFile);
const rootPath = path.dirname(uri.fsPath);
payload.originVcdFile = path.isAbsolute(originVcdFile) ? originVcdFile : path.join(rootPath, originVcdFile).replace(/\\/g, '/');
const originPayload = mergePayloadCache(originVcdViewFile, payload);
const savePath = path.isAbsolute(originVcdViewFile) ? originVcdViewFile : path.join(rootPath, originVcdViewFile);
const buffer = BSON.serialize(originPayload);
fs.writeFileSync(savePath, buffer);
} catch (error) {
console.error('error happen in saveView ' + error);
}
}
function getFilename(file: string) {
const base = path.basename(file);
const spls = base.split('.');
if (spls.length === 1) {
return base;
}
return spls[0];
}
export async function saveViewAs(data: any, uri: vscode.Uri, panel: vscode.WebviewPanel) {
const { t } = vscode.l10n;
try {
// 先保存原来的文件 payload 一定是 all
let { originVcdFile, originVcdViewFile, payload } = data;
// webview uri 转换为绝对路径
originVcdFile = extractFilepath(originVcdFile);
originVcdViewFile = extractFilepath(originVcdViewFile);
const rootPath = path.dirname(uri.fsPath);
payload.originVcdFile = path.isAbsolute(originVcdFile) ? originVcdFile : path.join(rootPath, originVcdFile).replace(/\\/g, '/');
const originPayload = mergePayloadCache(originVcdViewFile, payload);
// 询问新的路径
const defaultFilename = getFilename(payload.originVcdFile);
const vcdFilters: Record<string, string[]> = {};
vcdFilters[t('vcd-view-file')] = ['view'];
vcdFilters[t('all-file')] = ['*'];
const saveUri = await vscode.window.showSaveDialog({
title: t('save-as-view'),
defaultUri: vscode.Uri.file(path.join(rootPath, defaultFilename)),
saveLabel: t('save'),
filters: vcdFilters
});
if (saveUri) {
const savePath = saveUri.fsPath;
const buffer = BSON.serialize(originPayload);
fs.writeFileSync(savePath, buffer);
// 创建新的缓存 savePath 会成为新的 originVcdViewFile
mergePayloadCache(savePath, payload);
panel.webview.postMessage({
command: 'save-view-as',
viewPath: savePath.replace(/\\/g, '/')
});
} else {
panel.webview.postMessage({
command: 'save-view-as',
viewPath: undefined
});
}
} catch (error) {
console.error('error happen in saveViewAs ' + error);
}
}
export async function loadView(data: any, uri: vscode.Uri, panel: vscode.WebviewPanel) {
const { t } = vscode.l10n;
try {
let { originVcdFile } = data;
originVcdFile = extractFilepath(originVcdFile);
const rootPath = path.dirname(uri.fsPath);
const vcdPath = path.isAbsolute(originVcdFile) ? originVcdFile : path.join(rootPath, originVcdFile).replace(/\\/g, '/');
const defaultFolder = path.dirname(vcdPath);
const vcdFilters: Record<string, string[]> = {};
vcdFilters[t('vcd-view-file')] = ['view'];
vcdFilters[t('all-file')] = ['*'];
const viewUri = await vscode.window.showOpenDialog({
title: t('load-view-file'),
defaultUri: vscode.Uri.file(defaultFolder),
openLabel: t('load'),
canSelectFiles: true,
canSelectMany: false,
canSelectFolders: false,
filters: vcdFilters
});
if (viewUri) {
const viewPath = viewUri[0].fsPath;
const buffer = fs.readFileSync(viewPath);
const recoverJson = BSON.deserialize(buffer);
panel.webview.postMessage({
command: 'load-view',
recoverJson,
viewPath
});
} else {
panel.webview.postMessage({
command: 'load-view',
recoverJson: undefined,
viewPath: undefined
});
}
} catch (error) {
console.error('error happen in loadView ' + error);
}
}

View File

@ -1,11 +1,14 @@
import * as vscode from 'vscode';
import * as fspath from 'path';
import * as fs from 'fs';
import { hdlFile, hdlPath } from '../../hdlFs';
import { opeParam, ReportType, WaveViewOutput } from '../../global';
import { LaunchFiles, loadView, saveView, saveViewAs } from './api';
import { BSON } from 'bson';
function getWebviewContent(panel?: vscode.WebviewPanel): string | undefined {
const dideviewerPath = hdlPath.join(opeParam.extensionPath, 'resources', 'dide-viewer', 'view');
function getWebviewContent(context: vscode.ExtensionContext, panel?: vscode.WebviewPanel): string | undefined {
const dideviewerPath = hdlPath.join(context.extensionPath, 'resources', 'dide-viewer', 'view');
const htmlIndexPath = hdlPath.join(dideviewerPath, 'index.html');
const html = hdlFile.readFile(htmlIndexPath)?.replace(/(<link.+?href="|<script.+?src="|<img.+?src=")(.+?)"/g, (m, $1, $2) => {
const absLocalPath = fspath.resolve(dideviewerPath, $2);
@ -50,23 +53,26 @@ class WaveViewer {
console.log(message);
}, null, this.context.subscriptions);
const previewHtml = getWebviewContent(this.panel);
const context = this.context;
const previewHtml = getWebviewContent(context, this.panel);
if (this.panel && previewHtml) {
const dideviewerPath = hdlPath.join(opeParam.extensionPath, 'resources', 'dide-viewer', 'view');
const workerAbsPath = hdlPath.join(dideviewerPath, 'worker.js');
const webviewUri = this.panel.webview.asWebviewUri(uri);
const workerUri = this.panel.webview.asWebviewUri(vscode.Uri.file(workerAbsPath));
const workerRootUri = this.panel.webview.asWebviewUri(vscode.Uri.file(dideviewerPath));
const launchFiles = getViewLaunchFiles(context, uri, this.panel);
if (launchFiles instanceof Error) {
vscode.window.showErrorMessage(launchFiles.message);
return;
}
const { vcd, view, worker, root } = launchFiles;
let preprocessHtml = previewHtml
.replace('test.vcd', webviewUri.toString())
.replace('worker.js', workerUri.toString())
.replace('<workerRoot>', workerRootUri.toString());
.replace('test.vcd', vcd)
.replace('test.view', view)
.replace('worker.js', worker)
.replace('<root>', root);
this.panel.webview.html = preprocessHtml;
const iconPath = hdlPath.join(opeParam.extensionPath, 'images', 'icon.svg');
const iconPath = hdlPath.join(context.extensionPath, 'images', 'icon.svg');
this.panel.iconPath = vscode.Uri.file(iconPath);
this.registerMessageEvent();
registerMessageEvent(this.panel, uri);
} else {
WaveViewOutput.report('preview html in <WaveViewer.create> is empty', ReportType.Warn);
}
@ -78,21 +84,6 @@ class WaveViewer {
});
}
// vscode 前端接受 webview 的消息
private registerMessageEvent() {
this.panel?.webview.onDidReceiveMessage(message => {
const { command, data } = message;
switch (command) {
case 'save-view':
break;
default:
break;
}
});
}
}
async function openWaveViewer(context: vscode.ExtensionContext, uri: vscode.Uri) {
@ -113,8 +104,12 @@ class VcdViewerDocument implements vscode.CustomDocument {
class VcdViewerProvider implements vscode.CustomEditorProvider {
private readonly _onDidChangeCustomDocument = new vscode.EventEmitter<vscode.CustomDocumentEditEvent<VcdViewerDocument>>();
public readonly onDidChangeCustomDocument = this._onDidChangeCustomDocument.event;
context: vscode.ExtensionContext;
constructor(context: vscode.ExtensionContext) {
this.context = context;
}
async resolveCustomEditor(document: VcdViewerDocument, webviewPanel: vscode.WebviewPanel, token: vscode.CancellationToken) {
async resolveCustomEditor(document: VcdViewerDocument, webviewPanel: vscode.WebviewPanel, token: vscode.CancellationToken) {
webviewPanel.webview.options = {
enableScripts: true,
enableForms: true,
@ -124,26 +119,26 @@ class VcdViewerProvider implements vscode.CustomEditorProvider {
webviewPanel.dispose();
}, null);
webviewPanel.webview.onDidReceiveMessage(message => {
console.log(message);
}, null);
const previewHtml = getWebviewContent(webviewPanel);
if (webviewPanel && previewHtml) {
const dideviewerPath = hdlPath.join(opeParam.extensionPath, 'resources', 'dide-viewer', 'view');
const workerAbsPath = hdlPath.join(dideviewerPath, 'worker.js');
const webviewUri = webviewPanel.webview.asWebviewUri(document.uri);
const workerUri = webviewPanel.webview.asWebviewUri(vscode.Uri.file(workerAbsPath));
const workerRootUri = webviewPanel.webview.asWebviewUri(vscode.Uri.file(dideviewerPath));
const context = this.context;
const previewHtml = getWebviewContent(context, webviewPanel);
registerMessageEvent(webviewPanel, document.uri);
if (webviewPanel && previewHtml) {
const launchFiles = getViewLaunchFiles(context, document.uri, webviewPanel);
if (launchFiles instanceof Error) {
vscode.window.showErrorMessage(launchFiles.message);
return;
}
const { vcd, view, worker, root } = launchFiles;
let preprocessHtml = previewHtml
.replace('test.vcd', webviewUri.toString())
.replace('worker.js', workerUri.toString())
.replace('<workerRoot>', workerRootUri.toString());
.replace('test.vcd', vcd)
.replace('test.view', view)
.replace('worker.js', worker)
.replace('<root>', root);
webviewPanel.webview.html = preprocessHtml;
const iconPath = hdlPath.join(opeParam.extensionPath, 'images', 'icon.svg');
const iconPath = hdlPath.join(context.extensionPath, 'images', 'icon.svg');
webviewPanel.iconPath = vscode.Uri.file(iconPath);
} else {
WaveViewOutput.report('preview html in <WaveViewer.create> is empty', ReportType.Warn);
@ -182,9 +177,71 @@ class VcdViewerProvider implements vscode.CustomEditorProvider {
}
}
const vcdViewerProvider = new VcdViewerProvider();
// vscode 前端接受 webview 的消息
function registerMessageEvent(panel: vscode.WebviewPanel, uri: vscode.Uri) {
panel.webview.onDidReceiveMessage(message => {
const { command, data } = message;
switch (command) {
case 'save-view':
saveView(data, uri, panel);
break;
case 'save-view-as':
saveViewAs(data, uri, panel);
break;
case 'load-view':
loadView(data, uri, panel);
default:
break;
}
});
}
/**
* @description webview
* @param context
* @param uri
* @param panel
* @returns
*/
function getViewLaunchFiles(context: vscode.ExtensionContext, uri: vscode.Uri, panel: vscode.WebviewPanel): LaunchFiles | Error {
const { t } = vscode.l10n;
console.log(uri.fsPath);
const entryPath = uri.fsPath;
const dideviewerPath = hdlPath.join(context.extensionPath, 'resources', 'dide-viewer', 'view');
const workerAbsPath = hdlPath.join(dideviewerPath, 'worker.js');
const worker = panel.webview.asWebviewUri(vscode.Uri.file(workerAbsPath)).toString();
const root = panel.webview.asWebviewUri(vscode.Uri.file(dideviewerPath)).toString();
// 根据打开文件的类型来判断资源加载方案
if (entryPath.endsWith('.vcd')) {
const defaultViewPath = entryPath.slice(0, -4) + '.view';
const vcd = panel.webview.asWebviewUri(uri).toString();
const view = panel.webview.asWebviewUri(vscode.Uri.file(defaultViewPath)).toString();
return { vcd, view, worker, root };
} else if (entryPath.endsWith('.view')) {
const buffer = fs.readFileSync(entryPath);
const recoverJson = BSON.deserialize(buffer);
if (recoverJson.originVcdFile) {
const vcdPath = recoverJson.originVcdFile;
if (!fs.existsSync(vcdPath)) {
return new Error(t('unexist-direct-vcd-file') + ':' + vcdPath);
}
const vcd = panel.webview.asWebviewUri(vscode.Uri.file(recoverJson.originVcdFile)).toString();
const view = panel.webview.asWebviewUri(uri).toString();
return { vcd, view, worker, root };
} else {
return new Error(t('bad-view-file') + ':' + entryPath);
}
}
return new Error('unsupported languages');
}
export {
openWaveViewer,
vcdViewerProvider
VcdViewerProvider
};

View File

@ -130,7 +130,10 @@ function registerNetlist(context: vscode.ExtensionContext) {
function registerWaveViewer(context: vscode.ExtensionContext) {
vscode.commands.registerCommand('digital-ide.waveviewer.show', uri => WaveView.openWaveViewer(context, uri));
vscode.window.registerCustomEditorProvider('digital-ide.vcd.viewer', WaveView.vcdViewerProvider,
// 通过 customEditors 来配置
const vcdViewerProvider = new WaveView.VcdViewerProvider(context);
vscode.window.registerCustomEditorProvider('digital-ide.vcd.viewer', vcdViewerProvider,
{
webviewOptions: {
retainContextWhenHidden: true,