Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add auto-refresh capabilities for memory inspector windows #115

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion media/options-widget.css
Expand Up @@ -120,7 +120,8 @@
gap: 8px;
}

.advanced-options-dropdown {
.advanced-options-dropdown,
.advanced-options-input {
width: 100%;
}

Expand Down
30 changes: 26 additions & 4 deletions package.json
Expand Up @@ -25,7 +25,7 @@
"prepare": "yarn build",
"clean": "git clean -f -x ./node_modules ./dist",
"build": "webpack --mode production && yarn lint",
"watch": "webpack -w",
"watch": "webpack -w --mode development ",
"lint": "eslint . --ext .ts,.tsx",
"package": "vsce package --yarn",
"serve": "serve --cors -p 3333"
Expand Down Expand Up @@ -78,6 +78,7 @@
{
"command": "memory-inspector.show",
"title": "Show Memory Inspector",
"icon": "$(file-binary)",
"category": "Memory"
},
{
Expand Down Expand Up @@ -248,11 +249,32 @@
"off"
],
"enumDescriptions": [
"Refresh memory views when when debugger stops (e.g. a breakpoint is hit)",
"Memory view data is manually refreshed by user"
"Refresh Memory Inspector when the debugger stops (e.g. a breakpoint is hit)",
"Do not automatically refresh when the debugger stops"
],
"default": "on",
"description": "Refresh memory views when debugger stops"
"description": "Refresh Memory Inspectors when the debugger stops"
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
"description": "Refresh Memory Inspectors when the debugger stops"
"description": "Refresh Memory Inspector windows when the debugger stops"

},
"memory-inspector.periodicRefresh": {
"type": "string",
"enum": [
"always",
"while running",
"off"
],
"markdownEnumDescriptions": [
"Always refresh automatically after the configured `#memory-inspector.periodicRefreshInterval#`",
"Refresh automatically after the configured `#memory-inspector.periodicRefreshInterval#` while the CPU is running",
"Do not automatically refresh after the configured delay"
],
"default": "off",
"description": "Refresh Memory Inspectors after the configured delay"
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's include the link to the other preference here as well. That'll be easier for users to click on and make the connection more obvious.

},
"memory-inspector.periodicRefreshInterval": {
"type": "number",
"default": 500,
"minimum": 500,
"markdownDescription": "Controls the delay in milliseconds after which a Memory Inspector is refrehsed automatically. Only applies when `#memory-inspector.periodicRefresh#` is set to `on`."
},
"memory-inspector.groupings.bytesPerMAU": {
"type": "number",
Expand Down
1 change: 1 addition & 0 deletions src/common/debug-requests.ts
Expand Up @@ -29,6 +29,7 @@ export interface DebugRequestTypes {

export interface DebugEvents {
'memory': DebugProtocol.MemoryEvent,
'continued': DebugProtocol.ContinuedEvent,
'stopped': DebugProtocol.StoppedEvent
}

Expand Down
22 changes: 17 additions & 5 deletions src/plugin/manifest.ts → src/common/manifest.ts
Expand Up @@ -14,8 +14,6 @@
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import { Endianness } from '../common/memory-range';

// Common
export const PACKAGE_NAME = 'memory-inspector';
export const DISPLAY_NAME = 'Memory Inspector';
Expand All @@ -26,8 +24,6 @@ export const CONFIG_LOGGING_VERBOSITY = 'loggingVerbosity';
export const DEFAULT_LOGGING_VERBOSITY = 'warn';
export const CONFIG_DEBUG_TYPES = 'debugTypes';
export const DEFAULT_DEBUG_TYPES = ['gdb', 'embedded-debug', 'arm-debugger'];
export const CONFIG_REFRESH_ON_STOP = 'refreshOnStop';
export const DEFAULT_REFRESH_ON_STOP = 'on';

// MAUs (Minimum Addressable Units)
// - Bytes per MAU
Expand All @@ -49,7 +45,23 @@ export const DEFAULT_GROUPS_PER_ROW: GroupsPerRowOption = 4;

// - Group Endianness
export const CONFIG_ENDIANNESS = 'endianness';
export const DEFAULT_ENDIANNESS = Endianness.Little;
export const ENDIANNESS_CHOICES = ['Little Endian', 'Big Endian'] as const;
export type Endianness = (typeof ENDIANNESS_CHOICES)[number];
export const DEFAULT_ENDIANNESS: Endianness = 'Little Endian';

// Refresh: On Stop
export const CONFIG_REFRESH_ON_STOP = 'refreshOnStop';
export const REFRESH_ON_STOP = ['on', 'off'] as const;
export type RefreshOnStop = (typeof REFRESH_ON_STOP)[number];
export const DEFAULT_REFRESH_ON_STOP = 'on';

// Refresh: Periodic
export const CONFIG_PERIODIC_REFRESH = 'periodicRefresh';
export const PERIODIC_REFRESH_CHOICES = ['always', 'while running', 'off'] as const;
export type PeriodicRefresh = (typeof PERIODIC_REFRESH_CHOICES)[number];
export const DEFAULT_PERIODIC_REFRESH: PeriodicRefresh = 'always';
export const CONFIG_PERIODIC_REFRESH_INTERVAL = 'periodicRefreshInterval';
export const DEFAULT_PERIODIC_REFRESH_INTERVAL = 500;

// Scroll
export const CONFIG_SCROLLING_BEHAVIOR = 'scrollingBehavior';
Expand Down
5 changes: 0 additions & 5 deletions src/common/memory-range.ts
Expand Up @@ -125,8 +125,3 @@ export function areVariablesEqual(one: BigIntVariableRange, other: BigIntVariabl
export function toOffset(startAddress: bigint, targetAddress: bigint, mauSize: number): number {
return Number(targetAddress - startAddress) * (mauSize / 8);
}

export enum Endianness {
Little = 'Little Endian',
Big = 'Big Endian'
}
9 changes: 8 additions & 1 deletion src/common/messaging.ts
Expand Up @@ -18,9 +18,9 @@ import type { DebugProtocol } from '@vscode/debugprotocol';
import type { NotificationType, RequestType } from 'vscode-messenger-common';
import { URI } from 'vscode-uri';
import { VariablesView } from '../plugin/external-views';
import { MemoryViewSettings } from '../webview/utils/view-types';
import { DebugRequestTypes } from './debug-requests';
import type { VariableRange, WrittenMemory } from './memory-range';
import { MemoryViewSettings } from './webview-configuration';
import { WebviewContext } from './webview-context';

// convenience types for easier readability and better semantics
Expand All @@ -42,6 +42,12 @@ export interface SessionContext {
sessionId?: string;
canRead: boolean;
canWrite: boolean;
stopped?: boolean;
}

export interface ViewState {
active: boolean;
visible: boolean;
Comment on lines +48 to +50
Copy link
Contributor

Choose a reason for hiding this comment

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

This interface is passed around, but I don't see that its values are ever used?

}

// Notifications
Expand All @@ -51,6 +57,7 @@ export const resetMemoryViewSettingsType: NotificationType<void> = { method: 're
export const setTitleType: NotificationType<string> = { method: 'setTitle' };
export const memoryWrittenType: NotificationType<WrittenMemory> = { method: 'memoryWritten' };
export const sessionContextChangedType: NotificationType<SessionContext> = { method: 'sessionContextChanged' };
export const viewStateChangedType: NotificationType<ViewState> = { method: 'viewStateChanged' };

// Requests
export const setOptionsType: RequestType<MemoryOptions, void> = { method: 'setOptions' };
Expand Down
13 changes: 13 additions & 0 deletions src/common/typescript.ts
Expand Up @@ -24,3 +24,16 @@ export function tryToNumber(value?: string | number): number | undefined {
export function stringifyWithBigInts(object: any, space?: string | number): any {
return JSON.stringify(object, (_key, value) => typeof value === 'bigint' ? value.toString() : value, space);
}

export interface Change<T> {
from: T;
to: T;
}

export function hasChanged<T, P extends keyof T>(change: Change<T>, prop: P): boolean {
return change.from[prop] !== change.to[prop];
}

export function hasChangedTo<T, P extends keyof T>(change: Change<T>, prop: P, value: T[P]): boolean {
return change.from[prop] !== change.to[prop] && change.to[prop] === value;
}
47 changes: 47 additions & 0 deletions src/common/webview-configuration.ts
@@ -0,0 +1,47 @@
/********************************************************************************
* Copyright (C) 2024 EclipseSource and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
import { WebviewIdMessageParticipant } from 'vscode-messenger-common';
import { Endianness, GroupsPerRowOption, PeriodicRefresh, RefreshOnStop } from './manifest';
import { Radix } from './memory-range';

/** The memory display configuration that can be specified for the memory widget. */
export interface MemoryDisplayConfiguration {
bytesPerMau: number;
mausPerGroup: number;
groupsPerRow: GroupsPerRowOption;
endianness: Endianness;
scrollingBehavior: ScrollingBehavior;
addressPadding: AddressPadding;
addressRadix: Radix;
showRadixPrefix: boolean;
refreshOnStop: RefreshOnStop;
periodicRefresh: PeriodicRefresh;
periodicRefreshInterval: number;
}

export type ScrollingBehavior = 'Paginate' | 'Grow' | 'Auto-Append';

export type AddressPadding = 'Minimal' | number;

export interface ColumnVisibilityStatus {
visibleColumns: string[];
}

/** All settings related to memory view that can be specified for the webview from the extension "main". */
export interface MemoryViewSettings extends ColumnVisibilityStatus, MemoryDisplayConfiguration {
title: string
messageParticipant: WebviewIdMessageParticipant;
}
9 changes: 7 additions & 2 deletions src/entry-points/browser/extension.ts
Expand Up @@ -17,18 +17,23 @@
import * as vscode from 'vscode';
import { AdapterRegistry } from '../../plugin/adapter-registry/adapter-registry';
import { CAdapter } from '../../plugin/adapter-registry/c-adapter';
import { ContextTracker } from '../../plugin/context-tracker';
import { MemoryProvider } from '../../plugin/memory-provider';
import { MemoryStorage } from '../../plugin/memory-storage';
import { MemoryWebview } from '../../plugin/memory-webview-main';
import { SessionTracker } from '../../plugin/session-tracker';

export const activate = async (context: vscode.ExtensionContext): Promise<AdapterRegistry> => {
const registry = new AdapterRegistry();
const memoryProvider = new MemoryProvider(registry);
const memoryView = new MemoryWebview(context.extensionUri, memoryProvider);
const sessionTracker = new SessionTracker();
new ContextTracker(sessionTracker);
const memoryProvider = new MemoryProvider(registry, sessionTracker);
const memoryView = new MemoryWebview(context.extensionUri, memoryProvider, sessionTracker);
const memoryStorage = new MemoryStorage(memoryProvider);
const cAdapter = new CAdapter(registry);

registry.activate(context);
sessionTracker.activate(context);
memoryProvider.activate(context);
memoryView.activate(context);
memoryStorage.activate(context);
Expand Down
11 changes: 8 additions & 3 deletions src/entry-points/desktop/extension.ts
Expand Up @@ -17,19 +17,24 @@
import * as vscode from 'vscode';
import { AdapterRegistry } from '../../plugin/adapter-registry/adapter-registry';
import { CAdapter } from '../../plugin/adapter-registry/c-adapter';
import { ContextTracker } from '../../plugin/context-tracker';
import { MemoryProvider } from '../../plugin/memory-provider';
import { MemoryStorage } from '../../plugin/memory-storage';
import { MemoryWebview } from '../../plugin/memory-webview-main';
import { SessionTracker } from '../../plugin/session-tracker';

export const activate = async (context: vscode.ExtensionContext): Promise<AdapterRegistry> => {
const registry = new AdapterRegistry();
const memoryProvider = new MemoryProvider(registry);
const memoryView = new MemoryWebview(context.extensionUri, memoryProvider);
const sessionTracker = new SessionTracker();
new ContextTracker(sessionTracker);
const memoryProvider = new MemoryProvider(registry, sessionTracker);
const memoryView = new MemoryWebview(context.extensionUri, memoryProvider, sessionTracker);
const memoryStorage = new MemoryStorage(memoryProvider);
const cAdapter = new CAdapter(registry);

memoryProvider.activate(context);
registry.activate(context);
sessionTracker.activate(context);
memoryProvider.activate(context);
memoryView.activate(context);
memoryStorage.activate(context);
cAdapter.activate(context);
Expand Down
4 changes: 2 additions & 2 deletions src/plugin/adapter-registry/adapter-capabilities.ts
Expand Up @@ -74,7 +74,7 @@ export class AdapterVariableTracker implements vscode.DebugAdapterTracker {
onDidSendMessage(message: unknown): void {
if (isDebugResponse('scopes', message)) {
this.variablesTree = {}; // Scopes request implies that all scopes will be queried again.
for (const scope of message.body.scopes) {
for (const scope of message.body?.scopes) {
if (this.isDesiredScope(scope)) {
if (!this.variablesTree[scope.variablesReference] || this.variablesTree[scope.variablesReference].name !== scope.name) {
this.variablesTree[scope.variablesReference] = { ...scope };
Expand All @@ -86,7 +86,7 @@ export class AdapterVariableTracker implements vscode.DebugAdapterTracker {
const parentReference = this.pendingMessages.get(message.request_seq)!;
this.pendingMessages.delete(message.request_seq);
if (parentReference in this.variablesTree) {
this.variablesTree[parentReference].children = message.body.variables;
this.variablesTree[parentReference].children = message.body?.variables;
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/plugin/adapter-registry/c-adapter.ts
Expand Up @@ -15,8 +15,8 @@
********************************************************************************/

import * as vscode from 'vscode';
import * as manifest from '../../common/manifest';
import { outputChannelLogger } from '../logger';
import * as manifest from '../manifest';
import { VariableTracker } from './adapter-capabilities';
import { AdapterRegistry } from './adapter-registry';
import { CTracker } from './c-tracker';
Expand Down
35 changes: 35 additions & 0 deletions src/plugin/context-tracker.ts
@@ -0,0 +1,35 @@
/********************************************************************************
* Copyright (C) 2022 Ericsson, Arm and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import * as vscode from 'vscode';
import * as manifest from '../common/manifest';
import { isSessionEvent, SessionEvent, SessionTracker } from './session-tracker';

export class ContextTracker {
public static ReadKey = `${manifest.PACKAGE_NAME}.canRead`;
public static WriteKey = `${manifest.PACKAGE_NAME}.canWrite`;

constructor(protected sessionTracker: SessionTracker) {
this.sessionTracker.onSessionEvent(event => this.onSessionEvent(event));
}

onSessionEvent(event: SessionEvent): void {
if (isSessionEvent('active', event)) {
vscode.commands.executeCommand('setContext', ContextTracker.ReadKey, !!event.session?.debugCapabilities?.supportsReadMemoryRequest);
vscode.commands.executeCommand('setContext', ContextTracker.WriteKey, !!event.session?.debugCapabilities?.supportsWriteMemoryRequest);
}
}
}
2 changes: 1 addition & 1 deletion src/plugin/logger.ts
Expand Up @@ -15,8 +15,8 @@
********************************************************************************/

import * as vscode from 'vscode';
import * as manifest from '../common/manifest';
import { stringifyWithBigInts } from '../common/typescript';
import * as manifest from './manifest';

export enum Verbosity {
off = 0,
Expand Down