diff --git a/package.json b/package.json index 7c2e11e..591dafc 100644 --- a/package.json +++ b/package.json @@ -175,6 +175,12 @@ "command": "rtlDebugger.browseWaveforms", "category": "RTL Debugger", "title": "Browse Waveforms" + }, + { + "command": "rtlDebugger.addToWaveform", + "category": "RTL Debugger", + "title": "Add to Waveform", + "icon": "$(keybindings-add)" } ], "viewsContainers": { @@ -319,6 +325,11 @@ "command": "rtlDebugger.unWatchVariable", "when": "view == rtlDebugger.sidebar && viewItem =~ /inWatchList/", "group": "inline" + }, + { + "command": "rtlDebugger.addToWaveform", + "when": "view == rtlDebugger.sidebar && viewItem =~ /variable/", + "group": "inline" } ], "rtlDebugger.setRadix": [ diff --git a/src/debug/session.ts b/src/debug/session.ts index 1156019..a8a413b 100644 --- a/src/debug/session.ts +++ b/src/debug/session.ts @@ -99,6 +99,7 @@ export class Session { onRecv: async (serverPacket) => {}, onDone: async () => {}, }; + this.secondaryLinks.push(secondaryLink); return secondaryLink; } diff --git a/src/debugger.ts b/src/debugger.ts index 216c80d..6963c66 100644 --- a/src/debugger.ts +++ b/src/debugger.ts @@ -4,6 +4,7 @@ import * as vscode from 'vscode'; import { NodeStreamLink } from './cxxrtl/link'; import { StatusBarItem } from './ui/status'; import { Session } from './debug/session'; +import { WaveformProvider } from './ui/waveform'; export enum CXXRTLSimulationStatus { Paused = 'paused', @@ -21,6 +22,9 @@ export class CXXRTLDebugger { private statusBarItem: StatusBarItem; private terminal: vscode.Terminal | null = null; session: Session | null = null; + nextWaveformviewId: number = 0; + waveformProviders: Map = new Map(); + lastActiveWaveformTab: string | null = null; // Session properties. diff --git a/src/extension.ts b/src/extension.ts index 22b8e36..55c5291 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -75,6 +75,16 @@ export function activate(context: vscode.ExtensionContext) { } })); + context.subscriptions.push(vscode.commands.registerCommand('rtlDebugger.addToWaveform', (treeItem) => { + if (rtlDebugger.lastActiveWaveformTab) { + console.log(rtlDebugger.lastActiveWaveformTab); + const target = rtlDebugger.waveformProviders.get(rtlDebugger.lastActiveWaveformTab); + if (target) { + target.addVariable(treeItem.designation.variable); + } + } + })); + context.subscriptions.push(vscode.commands.registerCommand('rtlDebugger.setRadix.2', (treeItem) => globalVariableOptions.update(treeItem.designation.variable.cxxrtlIdentifier, { radix: 2 }))); context.subscriptions.push(vscode.commands.registerCommand('rtlDebugger.setRadix.8', (treeItem) => @@ -90,18 +100,48 @@ export function activate(context: vscode.ExtensionContext) { globalWatchList.remove(treeItem.metadata.index))); context.subscriptions.push(vscode.commands.registerCommand('rtlDebugger.browseWaveforms', () => { + const viewKey = `rtlDebugger.waveforms.${rtlDebugger.nextWaveformviewId++}`; const webviewPanel = vscode.window.createWebviewPanel( - 'rtlDebugger.waveforms', - 'Waveforms', { + viewKey, + 'Waveforms', + { viewColumn: vscode.ViewColumn.Beside, - }, { + }, + { enableScripts: true, retainContextWhenHidden: true, - }); + } + ); + console.log(`Creating web view panel with viewType ${webviewPanel.viewType}`); const bundleRoot = vscode.Uri.joinPath(context.extensionUri, 'out/'); - context.subscriptions.push(new WaveformProvider(rtlDebugger, webviewPanel, bundleRoot)); + const waveformProvider = new WaveformProvider(rtlDebugger, webviewPanel, bundleRoot); + rtlDebugger.waveformProviders.set(viewKey, waveformProvider); + rtlDebugger.lastActiveWaveformTab = viewKey; + context.subscriptions.push(waveformProvider); })); + vscode.window.tabGroups.onDidChangeTabs(event => { + const activeWaveformTab = event.changed.map((tab, _) => { + if (tab.input instanceof vscode.TabInputWebview) { + console.log(`${tab.input.viewType}`); + + const key = tab.input.viewType.match(/.*(rtlDebugger.waveforms\.\d+)/); + + if (key) { + return key[1]; + } else { + return null; + } + } else { + return null; + } + }).find((id) => id !== null); + + if (activeWaveformTab) { + rtlDebugger.lastActiveWaveformTab = activeWaveformTab; + } + }); + // 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/model/variable.ts b/src/model/variable.ts index c873887..0baf3d1 100644 --- a/src/model/variable.ts +++ b/src/model/variable.ts @@ -44,6 +44,10 @@ export abstract class Variable { get cxxrtlIdentifier(): string { return this.fullName.join(' '); } + + get wcpIdentifier(): string { + return this.fullName.join(' '); + } } export class ScalarVariable extends Variable { diff --git a/src/surfer/embed.ts b/src/surfer/embed.ts index 7f1fd3e..8ea4546 100644 --- a/src/surfer/embed.ts +++ b/src/surfer/embed.ts @@ -1,11 +1,12 @@ import libsurferInit, * as libsurfer from 'libsurfer'; -import type { ExtensionToWebviewMessage, WebviewToExtensionMessage } from '../ui/waveform'; +import { ClientPacketString, type ExtensionToWebviewMessage, type WebviewToExtensionMessage } from '../ui/waveform'; function libsurferInjectMessage(message: any) { libsurfer.inject_message(JSON.stringify(message)); } + document.addEventListener('DOMContentLoaded', async () => { const vscode = acquireVsCodeApi(); const canvas = document.getElementById('canvas'); @@ -19,17 +20,51 @@ document.addEventListener('DOMContentLoaded', async () => { }); const postMessage = <(message: WebviewToExtensionMessage) => void>vscode.postMessage; - window.addEventListener('message', (event: MessageEvent) => { + window.addEventListener('message', async (event: MessageEvent) => { const message = event.data; - console.error('[RTL Debugger] [surferEmbed] Unhandled extension to webview message', message); + if (message.type === 'cxxrtl_sc_message') { + await libsurfer.on_cxxrtl_sc_message(message.message.inner); + } else if (message.type === 'wcp_cs_message') { + await libsurfer.handle_wcp_cs_message(message.message); + } else { + console.error('[RTL Debugger] [surferEmbed] Unhandled extension to webview message', message); + } }); + const handle_cxxrtl_cs_messages = async () => { + while (true) { + const message = await libsurfer.cxxrtl_cs_message(); + if (message) { + postMessage({type: 'cxxrtl_cs_message', message: new ClientPacketString(message)}); + } else { + throw Error('Got an undefined message from Surfer. Its client probably disconnected'); + } + } + }; + + const handle_wcp_sc_messages = async () => { + while (true) { + const message = await libsurfer.next_wcp_sc_message(); + if (message) { + postMessage({type: 'wcp_sc_message', message: message}); + } else { + throw Error('Got an undefined message from Surfer. Its client probably disconnected'); + } + } + }; + try { await libsurferInit(); await new libsurfer.WebHandle().start(canvas); + handle_cxxrtl_cs_messages(); + handle_wcp_sc_messages(); + + await libsurfer.start_cxxrtl(); + await libsurfer.start_wcp(); + libsurferInjectMessage('ToggleMenu'); // turn off menu - libsurferInjectMessage('ToggleStatusBar'); // turn off status bar + libsurferInjectMessage('ToggleStatusbar'); // turn off status bar libsurferInjectMessage('ToggleSidePanel'); libsurferInjectMessage({ SelectTheme: 'dark+' }); // pick VS Code like theme diff --git a/src/ui/sidebar.ts b/src/ui/sidebar.ts index 6d8b73e..1c206d9 100644 --- a/src/ui/sidebar.ts +++ b/src/ui/sidebar.ts @@ -203,10 +203,10 @@ class ScopeTreeItem extends TreeItem { for (const variable of variables) { if (variable instanceof ScalarVariable) { children.push(new ScalarTreeItem(this.provider, variable.designation(), - variable.width > 1 ? 'canWatch|canSetRadix' : 'canWatch')); + variable.width > 1 ? 'canWatch|canSetRadix|variable' : 'canWatch|variable')); } if (variable instanceof MemoryVariable) { - children.push(new ArrayTreeItem(this.provider, variable.designation(), 'canWatch|canSetRadix')); + children.push(new ArrayTreeItem(this.provider, variable.designation(), 'canWatch|canSetRadix|variable')); } } return children; diff --git a/src/ui/waveform.ts b/src/ui/waveform.ts index 24a3746..03e0ac9 100644 --- a/src/ui/waveform.ts +++ b/src/ui/waveform.ts @@ -3,14 +3,31 @@ import * as vscode from 'vscode'; import { CXXRTLDebugger } from '../debugger'; // @ts-ignore import embedHtml from '../surfer/embed.html'; +import { ILink, Packet } from '../cxxrtl/link'; +import { ClientPacket } from '../cxxrtl/proto'; +import { Variable } from '../model/variable'; + +export class ClientPacketString { + constructor(public inner: string) { } +} +export class ServerPacketString { + constructor(public inner: string) { } +} + export type ExtensionToWebviewMessage = | { type: 'restore', state: any } +// TODO: Proper type here +| { type: 'cxxrtl_sc_message', message: ServerPacketString } +| { type: 'wcp_cs_message', message: string } ; export type WebviewToExtensionMessage = | { type: 'ready' } | { type: 'crash', error: any } +// TODO: Proper type here +| { type: 'cxxrtl_cs_message', message: ClientPacketString } +| { type: 'wcp_sc_message', message: string } ; export class WaveformProvider { @@ -23,6 +40,18 @@ export class WaveformProvider { this.webview.asWebviewUri(bundleRoot).toString()); this.webview.onDidReceiveMessage(this.processMessage.bind(this)); this.webview.html = webviewHtml; + const debuggerLink = rtlDebugger.session?.createSecondaryLink(); + + // TODO: Correct way to handle errors? + if (debuggerLink) { + this.debuggerLink = debuggerLink; + this.debuggerLink.onRecv = async (message) => { + // console.log("Running on recv for ", message) + await this.sendMessage({ type: 'cxxrtl_sc_message', message: new ServerPacketString(message.asString()) }); + }; + } else { + throw new Error('Failed to create secondary debugger link'); + } } dispose() { @@ -45,8 +74,26 @@ export class WaveformProvider { console.log('[RTL Debugger] [WaveformProvider] Ready'); } else if (message.type === 'crash') { console.log('[RTL Debugger] [WaveformProvider] Crash:', message.error); + } else if (message.type === 'cxxrtl_cs_message') { + console.log('[RTL Debugger] [WaveformProvider] Got CSMessage', message.message); + const packet: Packet = Packet.fromString(message.message.inner); + await this.debuggerLink.send(packet); + } else if (message.type === 'wcp_sc_message') { + console.log('[RTL Debugger] [WaveformProvider] Got WCP SC message', message.message); } else { console.error('[RTL Debugger] [WaveformProvider] Unhandled webview to extension message:', message); } } + + async addVariable(variable: Variable) { + // TODO: How should we handle the callbacks here? + const message = JSON.stringify({ + type: 'command', + command: 'add_variables', + names: [variable.wcpIdentifier] + }); + this.sendMessage({type: 'wcp_cs_message', message}); + } + + private debuggerLink: ILink; } diff --git a/vendor/surfer b/vendor/surfer index d799765..3dd32c7 160000 --- a/vendor/surfer +++ b/vendor/surfer @@ -1 +1 @@ -Subproject commit d799765996895b10c6ab6b70dbb5ae6f5501c579 +Subproject commit 3dd32c79133009642042a2c06bb3a96f24e43644