Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added icons/button_disabled.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added icons/button_enabled.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export function activate(context: vscode.ExtensionContext): void {
});

const liveDiagnosticsCommand = vscode.commands.registerCommand('minecraft-debugger.liveDiagnostics', () => {
MinecraftDiagnosticsPanel.render(context.extensionUri, liveStatsProvider, eventEmitter);
MinecraftDiagnosticsPanel.render(context.extensionUri, liveStatsProvider, eventEmitter, context.globalState);
});

const replayDiagnosticsCommand = vscode.commands.registerCommand(
Expand All @@ -98,7 +98,7 @@ export function activate(context: vscode.ExtensionContext): void {
return;
}
const replayStats = new ReplayStatsProvider(fileUri[0].fsPath);
MinecraftDiagnosticsPanel.render(context.extensionUri, replayStats, eventEmitter);
MinecraftDiagnosticsPanel.render(context.extensionUri, replayStats, eventEmitter, context.globalState);
},
);

Expand Down
60 changes: 55 additions & 5 deletions src/panels/minecraft-diagnostics.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
// Copyright (C) Microsoft Corporation. All rights reserved.

import { Disposable, Webview, WebviewPanel, window, workspace, Uri, ViewColumn } from 'vscode';
import { Disposable, Memento, Webview, WebviewPanel, window, workspace, Uri, ViewColumn } from 'vscode';
import { EventEmitter } from 'stream';
import { getUri } from '../utilities/getUri';
import { getNonce } from '../utilities/getNonce';
import { DebuggerRequestHandler } from '../requests/debugger-request-handler';
import { StatData, StatsListener, StatsProvider } from '../stats/stats-provider';
import { DiagnosticsTabDescriptor } from '../diagnostics-schema';

const DIAGNOSTICS_TAB_STATES_KEY = 'minecraftDiagnosticsTabStates';
type DiagnosticsTabStates = Record<string, boolean>;

export class MinecraftDiagnosticsPanel {
private static activeDiagnosticsPanels: MinecraftDiagnosticsPanel[] = [];

Expand All @@ -24,6 +27,7 @@
statsTracker: StatsProvider,
eventEmitter: EventEmitter,
debuggerRequestHandler: DebuggerRequestHandler,
private readonly _globalState: Memento,
) {
this._panel = panel;
this._statsTracker = statsTracker;
Expand All @@ -39,6 +43,7 @@
this._panel.webview,
extensionUri,
statsTracker.manualControl(),
this.getDiagnosticsTabStates(),
);

// Handle events from the webview panel
Expand All @@ -50,6 +55,7 @@
this._panel.webview,
extensionUri,
statsTracker.manualControl(),
this.getDiagnosticsTabStates(),
);
break;
case 'pause':
Expand All @@ -75,6 +81,12 @@
case 'debugger-request':
this._debuggerRequestHandler.handleDebuggerRequest(message.request, message.args);
break;
case 'set-diagnostics-active':
this.handleDiagnosticsActiveMessage(message);
break;
case 'sync-diagnostics-tabs':
this._eventEmitter.emit('sync-diagnostics-tabs', message.states);
break;
case 'export-data':
void this.handleExportDataMessage(message);
break;
Expand Down Expand Up @@ -132,6 +144,33 @@
this._statsTracker.addStatListener(this._statsCallback);
}

private getDiagnosticsTabStates(): DiagnosticsTabStates {
const states = this._globalState.get<DiagnosticsTabStates>(DIAGNOSTICS_TAB_STATES_KEY, {});
if (states === null || typeof states !== 'object' || Array.isArray(states)) {
return {};
}

return Object.fromEntries(
Object.entries(states).filter(([tabName, active]) => tabName.trim() !== '' && typeof active === 'boolean'),
);
}

private handleDiagnosticsActiveMessage(message: any): void {
if (typeof message.tabName !== 'string' || message.tabName.trim() === '' || typeof message.active !== 'boolean') {
return;
}

const states = this.getDiagnosticsTabStates();
states[message.tabName] = message.active;
void this._globalState.update(DIAGNOSTICS_TAB_STATES_KEY, states);
this._eventEmitter.emit('set-diagnostics-active', message.tabName, message.active);
this._panel.webview.postMessage({
type: 'diagnostics-tab-state',
tabName: message.tabName,
active: message.active,
});
}

private async handleExportDataMessage(message: any): Promise<void> {
if (typeof message.content !== 'string') {
console.error('Received export-data message without a valid content string.');
Expand All @@ -149,7 +188,7 @@
saveLabel: 'Export',
defaultUri: workspaceFolderUri ? Uri.joinPath(workspaceFolderUri, suggestedFileName) : undefined,
filters: {
'CSV Files': ['csv'],

Check warning on line 191 in src/panels/minecraft-diagnostics.ts

View workflow job for this annotation

GitHub Actions / Build and Test

Object Literal Property name `CSV Files` must match one of the following formats: camelCase, snake_case
},
});

Expand All @@ -161,7 +200,12 @@
window.showInformationMessage(`Exported diagnostics data to ${outputUri.fsPath}.`);
}

public static render(extensionUri: Uri, statsTracker: StatsProvider, eventEmitter: EventEmitter): void {
public static render(
extensionUri: Uri,
statsTracker: StatsProvider,
eventEmitter: EventEmitter,
globalState: Memento,
): void {
const statsTrackerId = statsTracker.uniqueId;
const existingPanel = MinecraftDiagnosticsPanel.activeDiagnosticsPanels.find(
panel => panel._statsTracker.uniqueId === statsTrackerId,
Expand Down Expand Up @@ -189,6 +233,7 @@
statsTracker,
eventEmitter,
new DebuggerRequestHandler(panel.webview),
globalState,
),
);
}
Expand Down Expand Up @@ -217,7 +262,12 @@
}
}

private _getWebviewContent(webview: Webview, extensionUri: Uri, showReplayControls: boolean) {
private _getWebviewContent(
webview: Webview,
extensionUri: Uri,
showReplayControls: boolean,
diagnosticsTabStates: DiagnosticsTabStates,
) {
// The CSS file from the React build output
const stylesUri = getUri(webview, extensionUri, ['webview-ui', 'build', 'assets', 'diagnosticsPanel.css']);
// The JS file from the React build output
Expand All @@ -231,11 +281,11 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src ${webview.cspSource}; script-src 'nonce-${nonce}';">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src ${webview.cspSource}; style-src ${webview.cspSource}; script-src 'nonce-${nonce}';">
<link rel="stylesheet" type="text/css" href="${stylesUri}">
<title>Minecraft Diagnostics</title>
<script nonce="${nonce}">
window.initialParams = { showReplayControls: ${showReplayControls} };
window.initialParams = ${JSON.stringify({ showReplayControls, diagnosticsTabStates })};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this not receiving the object instead of the string?

</script>
</head>
<body>
Expand Down
14 changes: 12 additions & 2 deletions src/protocol-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { DiagnosticsTabDescriptor } from './diagnostics-schema';
// 8 - New serialization tech (use Cereal)
// 9 - Added support for MC C++/native driven stat descriptors/schemas for UI display
// 10 - Added is_empty_tab to DiagnosticsTabDescriptor
// 11 - Added per-tab diagnostics activation

export enum ProtocolVersion {
_Unknown = 0,
Expand All @@ -29,9 +30,10 @@ export enum ProtocolVersion {
SupportCerealSerialization = 8,
SupportNativeDescriptors = 9,
SupportEmptyTabs = 10,
SupportDiagnosticsSetActive = 11,
}

export const DEBUGGER_PROTOCOL_VERSION = ProtocolVersion.SupportEmptyTabs;
export const DEBUGGER_PROTOCOL_VERSION = ProtocolVersion.SupportDiagnosticsSetActive;

// -------------------------------------------------------------------------
// Interfaces for event message payloads (received from the debugee)
Expand Down Expand Up @@ -131,7 +133,8 @@ export enum OutgoingEventType {
Resume = 'resume',
Request = 'request',
Breakpoints = 'breakpoints',
DebuggerRequest = 'debugger-request'
DebuggerRequest = 'debugger-request',
DiagnosticsSetActive = 'diagnostics-set-active',
}

export interface ProtocolResponse {
Expand Down Expand Up @@ -182,6 +185,12 @@ export interface ResumeMessage {
type: OutgoingEventType.Resume;
}

export interface DiagnosticsSetActiveMessage {
type: OutgoingEventType.DiagnosticsSetActive;
tab_name: string;
active: boolean;
}

export interface RequestMessage {
type: OutgoingEventType.Request;
request: { request_seq: number; command: string; args: unknown };
Expand Down Expand Up @@ -224,6 +233,7 @@ export type OutgoingDebuggeeMessage =
| StopProfilerMessage
| StopOnExceptionMessage
| ResumeMessage
| DiagnosticsSetActiveMessage
| RequestMessage
| BreakpointsLegacyMessage
| BreakpointsMessage
Expand Down
38 changes: 38 additions & 0 deletions src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@ export class Session extends DebugSession implements IDebuggeeMessageSender {
this._eventEmitter.on('start-profiler', this.onStartProfiler.bind(this));
this._eventEmitter.on('stop-profiler', this.onStopProfiler.bind(this));
this._eventEmitter.on('request-debugger-status', this.onRequestDebuggerStatus.bind(this));
this._eventEmitter.on('set-diagnostics-active', this.onSetDiagnosticsActive.bind(this));
this._eventEmitter.on('sync-diagnostics-tabs', this.onSyncDiagnosticsTabs.bind(this));
}

// Use this to register new events that are handled from the debugee (Minecraft)
Expand Down Expand Up @@ -212,6 +214,8 @@ export class Session extends DebugSession implements IDebuggeeMessageSender {
this._eventEmitter.removeAllListeners('start-profiler');
this._eventEmitter.removeAllListeners('stop-profiler');
this._eventEmitter.removeAllListeners('request-debugger-status');
this._eventEmitter.removeAllListeners('set-diagnostics-active');
this._eventEmitter.removeAllListeners('sync-diagnostics-tabs');

if (this._sourceFileWatcher) {
this._sourceFileWatcher.dispose();
Expand Down Expand Up @@ -277,6 +281,40 @@ export class Session extends DebugSession implements IDebuggeeMessageSender {
}
}

private onSetDiagnosticsActive(tabName: unknown, active: unknown): void {
if (typeof tabName !== 'string' || tabName.trim() === '' || typeof active !== 'boolean') {
return;
}

this.sendDiagnosticsSetActive(tabName, active);
}

private onSyncDiagnosticsTabs(tabStates: unknown): void {
if (tabStates === null || typeof tabStates !== 'object' || Array.isArray(tabStates)) {
return;
}

for (const [tabName, active] of Object.entries(tabStates)) {
if (typeof active !== 'boolean' || tabName.trim() === '') {
continue;
}

this.sendDiagnosticsSetActive(tabName, active);
}
}

private sendDiagnosticsSetActive(tabName: string, active: boolean): void {
if (this._clientProtocolVersion < ProtocolVersion.SupportDiagnosticsSetActive) {
return;
}

this.sendDebuggeeMessage({
type: OutgoingEventType.DiagnosticsSetActive,
tab_name: tabName,
active,
});
}

private writeProfilerCaptureToFile(
captureData: string,
capturePath: string,
Expand Down
39 changes: 39 additions & 0 deletions webview-ui/src/diagnostics_panel/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ main {
border-right: 1px solid var(--vscode-editorGroup-border);
}

.vertical-tab-row {
display: flex;
align-items: stretch;
}

.vertical-tab-item {
background: transparent;
border: none;
Expand All @@ -35,13 +40,47 @@ main {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex: 1;
}

.diagnostics-tab-toggle {
border: none;
border-left: 1px solid var(--vscode-editorGroup-border);
background: transparent;
color: var(--vscode-descriptionForeground);
cursor: pointer;
font-size: 10px;
min-width: 34px;
}

.diagnostics-tab-toggle:hover:not(:disabled) {
background-color: var(--vscode-list-hoverBackground);
color: var(--vscode-list-hoverForeground);
}

.diagnostics-tab-toggle:disabled {
cursor: wait;
opacity: 0.7;
}

.diagnostics-tab-disabled {
color: var(--vscode-descriptionForeground);
padding: 24px;
}

.vertical-tab-item:hover {
background-color: var(--vscode-list-hoverBackground);
color: var(--vscode-list-hoverForeground);
}

.vertical-tab-item.disabled {
color: var(--vscode-descriptionForeground);
}

.vertical-tab-item.disabled:hover {
color: var(--vscode-descriptionForeground);
}

.vertical-tab-item.active {
background-color: var(--vscode-list-activeSelectionBackground);
color: var(--vscode-list-activeSelectionForeground);
Expand Down
Loading