diff --git a/.vscodeignore b/.vscodeignore
index 262a135..feb3459 100644
--- a/.vscodeignore
+++ b/.vscodeignore
@@ -16,4 +16,5 @@ resources/**/*.wasm
resources/dide-lsp/server
tsconfig.json
design
-lib
\ No newline at end of file
+lib
+*.vcd
\ No newline at end of file
diff --git a/images/svg/dark/view.svg b/images/svg/dark/view.svg
new file mode 100644
index 0000000..7ccda0f
--- /dev/null
+++ b/images/svg/dark/view.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/images/svg/light/view.svg b/images/svg/light/view.svg
new file mode 100644
index 0000000..7ccda0f
--- /dev/null
+++ b/images/svg/light/view.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/l10n/bundle.l10n.en.json b/l10n/bundle.l10n.en.json
index 3a21a12..76ee35d 100644
--- a/l10n/bundle.l10n.en.json
+++ b/l10n/bundle.l10n.en.json
@@ -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"
}
\ No newline at end of file
diff --git a/l10n/bundle.l10n.zh-cn.json b/l10n/bundle.l10n.zh-cn.json
index 8b910d9..38200f3 100644
--- a/l10n/bundle.l10n.zh-cn.json
+++ b/l10n/bundle.l10n.zh-cn.json
@@ -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群一起讨论"
}
\ No newline at end of file
diff --git a/l10n/bundle.l10n.zh-tw.json b/l10n/bundle.l10n.zh-tw.json
index 88f1610..0735aa0 100644
--- a/l10n/bundle.l10n.zh-tw.json
+++ b/l10n/bundle.l10n.zh-tw.json
@@ -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群一起讨论"
}
\ No newline at end of file
diff --git a/package.json b/package.json
index a3ba997..885b1e1 100644
--- a/package.json
+++ b/package.json
@@ -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": [
diff --git a/src/function/dide-viewer/api.ts b/src/function/dide-viewer/api.ts
index 781fd4d..e4fb3af 100644
--- a/src/function/dide-viewer/api.ts
+++ b/src/function/dide-viewer/api.ts
@@ -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();
+
+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 = {};
+ 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 = {};
+ 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);
+ }
}
\ No newline at end of file
diff --git a/src/function/dide-viewer/index.ts b/src/function/dide-viewer/index.ts
index b1a999c..71d9190 100644
--- a/src/function/dide-viewer/index.ts
+++ b/src/function/dide-viewer/index.ts
@@ -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(/( {
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('', workerRootUri.toString());
+ .replace('test.vcd', vcd)
+ .replace('test.view', view)
+ .replace('worker.js', worker)
+ .replace('', 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 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>();
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('', workerRootUri.toString());
+ .replace('test.vcd', vcd)
+ .replace('test.view', view)
+ .replace('worker.js', worker)
+ .replace('', 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 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
};
\ No newline at end of file
diff --git a/src/function/index.ts b/src/function/index.ts
index 3b987d8..305398b 100644
--- a/src/function/index.ts
+++ b/src/function/index.ts
@@ -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,