Skip to content

Commit

Permalink
Extend Auto Refresh capabilities with 'After Delay' and 'On Focus'
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
martin-fleck-at committed Mar 27, 2024
1 parent e92b909 commit dc631e7
Show file tree
Hide file tree
Showing 23 changed files with 548 additions and 261 deletions.
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
34 changes: 26 additions & 8 deletions package.json
Expand Up @@ -26,7 +26,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 @@ -79,6 +79,7 @@
{
"command": "memory-inspector.show",
"title": "Show Memory Inspector",
"icon": "$(file-binary)",
"category": "Memory"
},
{
Expand Down Expand Up @@ -201,6 +202,13 @@
"group": "display@6",
"when": "webviewId === memory-inspector.memory"
}
],
"editor/title": [
{
"command": "memory-inspector.show",
"group": "navigation",
"when": "memory-inspector.canRead && (resourceLangId === c || resourceLangId === cpp)"
}
]
},
"customEditors": [
Expand Down Expand Up @@ -242,18 +250,28 @@
],
"description": "C-based debuggers to activate (requires debug session restart)"
},
"memory-inspector.refreshOnStop": {
"memory-inspector.autoRefresh": {
"type": "string",
"enum": [
"on",
"off"
"On Stop",
"On Focus",
"After Delay",
"Off"
],
"enumDescriptions": [
"Refresh memory views when when debugger stops (e.g. a breakpoint is hit)",
"Memory view data is manually refreshed by user"
"Refresh when the debugger stops (e.g. a breakpoint is hit)",
"Refresh automatically when the view is newly focussed.",
"Refresh automatically after the configured `#memory-inspector.autoRefreshDelay#`",
"Memory Inspector needs to be manually refreshed by the user"
],
"default": "on",
"description": "Refresh memory views when debugger stops"
"default": "On Stop",
"description": "Controls when the Memory Inspector is refreshed."
},
"memory-inspector.autoRefreshDelay": {
"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.autoRefresh#` is set to `afterDelay`."
},
"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
16 changes: 11 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,17 @@ 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';

// Auto Refresh
export const CONFIG_AUTO_REFRESH = 'autoRefresh';
export const AUTO_REFRESH_CHOICES = ['On Stop', 'On Focus', 'After Delay', 'Off'] as const;
export type AutoRefresh = (typeof AUTO_REFRESH_CHOICES)[number];
export const DEFAULT_AUTO_REFRESH: AutoRefresh = 'On Stop';
export const CONFIG_AUTO_REFRESH_DELAY = 'autoRefreshDelay';
export const DEFAULT_AUTO_REFRESH_DELAY = 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'
}
7 changes: 7 additions & 0 deletions src/common/messaging.ts
Expand Up @@ -42,6 +42,12 @@ export interface SessionContext {
sessionId?: string;
canRead: boolean;
canWrite: boolean;
stopped?: boolean;
}

export interface ViewState {
active: boolean;
visible: boolean;
}

// 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;
}
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

0 comments on commit dc631e7

Please sign in to comment.