From 963161df841e98a1afa71933a4712efcbee2d01f Mon Sep 17 00:00:00 2001 From: Catherine Date: Mon, 2 Dec 2024 20:14:36 +0000 Subject: [PATCH 1/2] Add boilerplate for Surfer webview integration. --- build.mjs | 41 ++++++++++++++++++++++---------- package.json | 16 +++++++++---- src/extension.ts | 14 +++++++++++ src/surfer/embed.html | 34 +++++++++++++++++++++++++++ src/surfer/embed.ts | 29 +++++++++++++++++++++++ src/ui/waveform.ts | 54 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 172 insertions(+), 16 deletions(-) create mode 100644 src/surfer/embed.html create mode 100644 src/surfer/embed.ts create mode 100644 src/ui/waveform.ts diff --git a/build.mjs b/build.mjs index 46f32e3..5c893be 100644 --- a/build.mjs +++ b/build.mjs @@ -5,26 +5,43 @@ const mode = (process.argv[2] ?? 'build'); const commonOptions = { logLevel: 'info', bundle: true, + loader: { + '.html': 'text', + }, target: 'es2021', sourcemap: 'linked', outdir: 'out/', }; -const extensionContext = await esbuild.context({ - external: ['vscode'], - format: 'cjs', - platform: 'node', - entryPoints: { - 'extension': './src/extension.ts', - }, - ...commonOptions -}); +const contexts = [ + await esbuild.context({ + external: ['vscode'], + format: 'cjs', + platform: 'node', + entryPoints: { + 'extension': './src/extension.ts', + }, + ...commonOptions + }), + await esbuild.context({ + format: 'esm', + platform: 'browser', + entryPoints: { + 'surferEmbed': './src/surfer/embed.ts', + }, + ...commonOptions + }), +]; if (mode === 'build') { - await extensionContext.rebuild(); - await extensionContext.dispose(); + for (const context of contexts) { + await context.rebuild(); + await context.dispose(); + } } else if (mode === 'watch') { - await extensionContext.watch(); + for (const context of contexts) { + await context.watch(); + } } else { console.error(`Usage: ${process.argv0} [build|watch]`); } diff --git a/package.json b/package.json index f0c0acd..458df94 100644 --- a/package.json +++ b/package.json @@ -169,6 +169,11 @@ "category": "RTL Debugger", "title": "Stop Watching", "icon": "$(remove)" + }, + { + "command": "rtlDebugger.browseWaveforms", + "category": "RTL Debugger", + "title": "Browse Waveforms" } ], "viewsContainers": { @@ -261,6 +266,10 @@ { "command": "rtlDebugger.unWatchVariable", "when": "rtlDebugger.sessionStatus == running" + }, + { + "command": "rtlDebugger.browseWaveforms", + "when": "rtlDebugger.sessionStatus == running" } ], "view/title": [ @@ -349,14 +358,13 @@ "devDependencies": { "@types/node": "20.x", "@types/vscode": "^1.94", + "@types/vscode-webview": "^1.57", + "@stylistic/eslint-plugin": "^2.9.0", "@typescript-eslint/eslint-plugin": "^8", "@typescript-eslint/parser": "^8", "@vscode/vsce": "^3.x", "esbuild": "^0.24", "eslint": "^9.12", - "typescript": "5.5.x" - }, - "dependencies": { - "@stylistic/eslint-plugin": "^2.9.0" + "typescript": "5.5.x", } } diff --git a/src/extension.ts b/src/extension.ts index 3d3589b..22b8e36 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -7,6 +7,7 @@ import { globalVariableOptions } from './debug/options'; import { HoverProvider } from './ui/hover'; import { DiagnosticProvider } from './ui/diagnostic'; import { inputTime } from './ui/input'; +import { WaveformProvider } from './ui/waveform'; export function activate(context: vscode.ExtensionContext) { const rtlDebugger = new CXXRTLDebugger(); @@ -88,6 +89,19 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions.push(vscode.commands.registerCommand('rtlDebugger.unWatchVariable', (treeItem) => globalWatchList.remove(treeItem.metadata.index))); + context.subscriptions.push(vscode.commands.registerCommand('rtlDebugger.browseWaveforms', () => { + const webviewPanel = vscode.window.createWebviewPanel( + 'rtlDebugger.waveforms', + 'Waveforms', { + viewColumn: vscode.ViewColumn.Beside, + }, { + enableScripts: true, + retainContextWhenHidden: true, + }); + const bundleRoot = vscode.Uri.joinPath(context.extensionUri, 'out/'); + context.subscriptions.push(new WaveformProvider(rtlDebugger, webviewPanel, bundleRoot)); + })); + // For an unknown reason, the `vscode.open` command (which does the exact same thing) ignores the options. context.subscriptions.push(vscode.commands.registerCommand('rtlDebugger.openDocument', (uri: vscode.Uri, options: vscode.TextDocumentShowOptions) => { diff --git a/src/surfer/embed.html b/src/surfer/embed.html new file mode 100644 index 0000000..669245d --- /dev/null +++ b/src/surfer/embed.html @@ -0,0 +1,34 @@ + + + + + + + + + + + +
Could not start Surfer waveform viewer.
+ + diff --git a/src/surfer/embed.ts b/src/surfer/embed.ts new file mode 100644 index 0000000..afe64ee --- /dev/null +++ b/src/surfer/embed.ts @@ -0,0 +1,29 @@ +import type { ExtensionToWebviewMessage, WebviewToExtensionMessage } from '../ui/waveform'; + +document.addEventListener('DOMContentLoaded', () => { + const vscode = acquireVsCodeApi(); + const canvas = document.getElementById('canvas'); + const overlay = document.getElementById('overlay'); + + canvas.height = canvas.clientHeight; + canvas.width = canvas.clientWidth; + canvas.addEventListener('resize', () => { + canvas.height = canvas.clientHeight; + canvas.width = canvas.clientWidth; + }); + + const postMessage = <(message: WebviewToExtensionMessage) => void>vscode.postMessage; + window.addEventListener('message', (event: MessageEvent) => { + const message = event.data; + if (message.type === 'drawRect') { + const ctx = canvas.getContext('2d')!; + ctx.fillStyle = 'red'; + ctx.fillRect(...message.bounds); + } else { + console.error('[RTL Debugger] [surferEmbed] Unhandled extension to webview message', message); + } + }); + + overlay.style.display = 'none'; + postMessage({ type: 'ready' }); +}); diff --git a/src/ui/waveform.ts b/src/ui/waveform.ts new file mode 100644 index 0000000..02bd2e6 --- /dev/null +++ b/src/ui/waveform.ts @@ -0,0 +1,54 @@ +import * as vscode from 'vscode'; + +import { CXXRTLDebugger } from '../debugger'; +// @ts-ignore +import embedHtml from '../surfer/embed.html'; + +export type ExtensionToWebviewMessage = +| { type: 'restore', state: any } +| { type: 'drawRect', bounds: [number, number, number, number] } +; + +export type WebviewToExtensionMessage = +| { type: 'ready' } +| { type: 'crash', error: any } +; + +export class WaveformProvider { + constructor( + private rtlDebugger: CXXRTLDebugger, + private webviewPanel: vscode.WebviewPanel, + bundleRoot: vscode.Uri, + ) { + const webviewHtml = embedHtml.replace(/__base_href__/, + this.webview.asWebviewUri(bundleRoot).toString()); + this.webview.onDidReceiveMessage(this.processMessage.bind(this)); + this.webview.html = webviewHtml; + } + + dispose() { + this.webviewPanel.dispose(); + } + + get webview() { + return this.webviewPanel.webview; + } + + private async sendMessage(message: ExtensionToWebviewMessage) { + const messagePosted = await this.webview.postMessage(message); + if (!messagePosted) { + console.warn('[RTL Debugger] [WaveformProvider] Dropping extension to webview message:', message); + } + } + + private async processMessage(message: WebviewToExtensionMessage) { + if (message.type === 'ready') { + console.log('[RTL Debugger] [WaveformProvider] Ready'); + this.sendMessage({ type: 'drawRect', bounds: [0, 0, 100, 100]}); + } else if (message.type === 'crash') { + console.log('[RTL Debugger] [WaveformProvider] Crash:', message.error); + } else { + console.error('[RTL Debugger] [WaveformProvider] Unhandled webview to extension message:', message); + } + } +} From edd0b11c698a6df6f4e3af537d72d0005360ca07 Mon Sep 17 00:00:00 2001 From: Catherine Date: Mon, 2 Dec 2024 23:31:23 +0000 Subject: [PATCH 2/2] [WIP] Embed Surfer waveform viewer in a webview. This doesn't yet do anything useful since there is no way to load waveform data. To build the extension, run: git submodule update --init --recursive cd vendor/surfer/libsurfer wasm-pack build --target web Co-authored-by: TheZoq2 --- .gitmodules | 3 +++ build.mjs | 3 +++ package.json | 3 +++ src/surfer/embed.html | 2 +- src/surfer/embed.ts | 33 +++++++++++++++++++++++---------- src/ui/waveform.ts | 2 -- vendor/surfer | 1 + 7 files changed, 34 insertions(+), 13 deletions(-) create mode 100644 .gitmodules create mode 160000 vendor/surfer diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..1dfef15 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "vendor/surfer"] + path = vendor/surfer + url = https://gitlab.com/surfer-project/surfer diff --git a/build.mjs b/build.mjs index 5c893be..4bc35ec 100644 --- a/build.mjs +++ b/build.mjs @@ -1,12 +1,15 @@ import * as esbuild from 'esbuild'; +import metaUrlPlugin from '@chialab/esbuild-plugin-meta-url'; const mode = (process.argv[2] ?? 'build'); const commonOptions = { logLevel: 'info', + plugins: [metaUrlPlugin()], bundle: true, loader: { '.html': 'text', + '.wasm': 'file', }, target: 'es2021', sourcemap: 'linked', diff --git a/package.json b/package.json index 458df94..7c2e11e 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "name": "rtl-debugger", "displayName": "RTL Debugger", "description": "Debugger for HDLs supported by the open-source toolchain: Amaranth, Verilog, VHDL, ...", + "license": "(ISC AND EUPL-1.2)", "version": "0.0.0", "repository": { "type": "git", @@ -366,5 +367,7 @@ "esbuild": "^0.24", "eslint": "^9.12", "typescript": "5.5.x", + "libsurfer": "file:vendor/surfer/libsurfer/pkg", + "@chialab/esbuild-plugin-meta-url": "^0.18" } } diff --git a/src/surfer/embed.html b/src/surfer/embed.html index 669245d..03cc871 100644 --- a/src/surfer/embed.html +++ b/src/surfer/embed.html @@ -29,6 +29,6 @@ -
Could not start Surfer waveform viewer.
+
Starting waveform viewer...
diff --git a/src/surfer/embed.ts b/src/surfer/embed.ts index afe64ee..7f1fd3e 100644 --- a/src/surfer/embed.ts +++ b/src/surfer/embed.ts @@ -1,6 +1,12 @@ +import libsurferInit, * as libsurfer from 'libsurfer'; + import type { ExtensionToWebviewMessage, WebviewToExtensionMessage } from '../ui/waveform'; -document.addEventListener('DOMContentLoaded', () => { +function libsurferInjectMessage(message: any) { + libsurfer.inject_message(JSON.stringify(message)); +} + +document.addEventListener('DOMContentLoaded', async () => { const vscode = acquireVsCodeApi(); const canvas = document.getElementById('canvas'); const overlay = document.getElementById('overlay'); @@ -15,15 +21,22 @@ document.addEventListener('DOMContentLoaded', () => { const postMessage = <(message: WebviewToExtensionMessage) => void>vscode.postMessage; window.addEventListener('message', (event: MessageEvent) => { const message = event.data; - if (message.type === 'drawRect') { - const ctx = canvas.getContext('2d')!; - ctx.fillStyle = 'red'; - ctx.fillRect(...message.bounds); - } else { - console.error('[RTL Debugger] [surferEmbed] Unhandled extension to webview message', message); - } + console.error('[RTL Debugger] [surferEmbed] Unhandled extension to webview message', message); }); - overlay.style.display = 'none'; - postMessage({ type: 'ready' }); + try { + await libsurferInit(); + await new libsurfer.WebHandle().start(canvas); + + libsurferInjectMessage('ToggleMenu'); // turn off menu + libsurferInjectMessage('ToggleStatusBar'); // turn off status bar + libsurferInjectMessage('ToggleSidePanel'); + libsurferInjectMessage({ SelectTheme: 'dark+' }); // pick VS Code like theme + + overlay.style.display = 'none'; + postMessage({ type: 'ready' }); + } catch (error) { + overlay.innerHTML = `Could not start Surfer waveform viewer.

Cause: ${error}`; + postMessage({ type: 'crash', error }); + } }); diff --git a/src/ui/waveform.ts b/src/ui/waveform.ts index 02bd2e6..24a3746 100644 --- a/src/ui/waveform.ts +++ b/src/ui/waveform.ts @@ -6,7 +6,6 @@ import embedHtml from '../surfer/embed.html'; export type ExtensionToWebviewMessage = | { type: 'restore', state: any } -| { type: 'drawRect', bounds: [number, number, number, number] } ; export type WebviewToExtensionMessage = @@ -44,7 +43,6 @@ export class WaveformProvider { private async processMessage(message: WebviewToExtensionMessage) { if (message.type === 'ready') { console.log('[RTL Debugger] [WaveformProvider] Ready'); - this.sendMessage({ type: 'drawRect', bounds: [0, 0, 100, 100]}); } else if (message.type === 'crash') { console.log('[RTL Debugger] [WaveformProvider] Crash:', message.error); } else { diff --git a/vendor/surfer b/vendor/surfer new file mode 160000 index 0000000..d799765 --- /dev/null +++ b/vendor/surfer @@ -0,0 +1 @@ +Subproject commit d799765996895b10c6ab6b70dbb5ae6f5501c579