Skip to content

Commit

Permalink
Add auto-refresh capabilities for memory inspector windows (#115)
Browse files Browse the repository at this point in the history
* Extend Auto Refresh capabilities with 'After Delay' and 'On Focus'

- Rework 'refreshOnStop' boolean setting to 'autoRefresh' enumerable
-- On Stop (previously: 'on' for 'refreshOnStop'
-- On Focus (previously: always implicit on view state change)
-- After Delay (new)
-- Off (previously: 'off' for 'refreshOnStop')

- On Stop
-- Rework global setting to be local for each Memory Inspector
-- Listen to debug session stopped event and propagate to view

- On Focus
-- Rework implicit refresh update to option in setting
-- Listen to view state changes and propagate to view

- After Delay
-- New option to explicitly define a delay when re-fetching the memory
-- Minimum: 500ms, default: 500, input step size: 250ms

Refactoring:
- Split debug session tracking into dedicated class with session events
-- Convert debug events into session events with additional data
- Split context tracking from memory provider into dedicated class
- Move manifest to common for default values and avoid duplication
-- Align 'Min' with 'Minimal' value from manifest

Minor:
- Add title toolbar item for C/C++ file to access open memory inspector
- Improve debugging experience by using inline source maps
- Align creation of option enums to use const objects
- Additionally guard 'body' on debug responses for safety
- Avoid functional React state update where unnecessary

Fixes #91

* PR Feedback

- Improve wording in setting descriptions
- Move shared types into common area instead of using type imports
- Fix 'fetchMemory' not returning the correct promise

* PR Feedback: Separate refresh options and remove 'On Focus'

Also remove the show memory inspector toolbar item for C/C++ files

* Apply further PR Feedback

- Improve settings descriptions in package.json
- Get rid of obsolete ViewState and view state-related code
- Properly return provider result for contributed trackers
- Make 'fireSessionEvent' public as we access it from other classes
- Avoid auto-focusing the delay input field if we change options
  • Loading branch information
martin-fleck-at authored May 14, 2024
1 parent 78f313c commit e44ca7f
Show file tree
Hide file tree
Showing 26 changed files with 570 additions and 290 deletions.
3 changes: 2 additions & 1 deletion media/options-widget.css
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@
flex-direction: column;
}

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

Expand Down
30 changes: 26 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
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 @@ -262,11 +263,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 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",
"markdownDescription": "Refresh Memory Inspectors after the configured `#memory-inspector.periodicRefreshInterval#`."
},
"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 enabled."
},
"memory-inspector.groupings.bytesPerMAU": {
"type": "number",
Expand Down
3 changes: 2 additions & 1 deletion src/common/debug-requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ export interface DebugRequestTypes {

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

export type DebugRequest<C, A> = Omit<DebugProtocol.Request, 'command' | 'arguments'> & { command: C, arguments: A };
Expand Down
22 changes: 17 additions & 5 deletions src/plugin/manifest.ts → src/common/manifest.ts
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,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'
}
3 changes: 2 additions & 1 deletion src/common/messaging.ts
Original file line number Diff line number Diff line change
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,7 @@ export interface SessionContext {
sessionId?: string;
canRead: boolean;
canWrite: boolean;
stopped?: boolean;
}

// Notifications
Expand Down
13 changes: 13 additions & 0 deletions src/common/typescript.ts
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Loading

0 comments on commit e44ca7f

Please sign in to comment.