From 99302f5a8c92e5c2793f444cd52f97590e730d6a Mon Sep 17 00:00:00 2001 From: WyoTwT Date: Tue, 30 Apr 2024 23:16:41 +0200 Subject: [PATCH 1/4] Move ReadMemory/WriteMemory into adapter-capabilities Since the ReadMemory/WriteMemory may be dependent upon the DAPs, move into the adapter-capabilities.ts file. Signed-off-by: Thor Thayer --- src/plugin/adapter-registry/adapter-capabilities.ts | 7 +++++++ src/plugin/memory-provider.ts | 13 ++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/plugin/adapter-registry/adapter-capabilities.ts b/src/plugin/adapter-registry/adapter-capabilities.ts index 67886d4..adca974 100644 --- a/src/plugin/adapter-registry/adapter-capabilities.ts +++ b/src/plugin/adapter-registry/adapter-capabilities.ts @@ -18,6 +18,7 @@ import { DebugProtocol } from '@vscode/debugprotocol'; import * as vscode from 'vscode'; import { isDebugRequest, isDebugResponse } from '../../common/debug-requests'; import { VariableRange } from '../../common/memory-range'; +import { ReadMemoryArguments, ReadMemoryResult, WriteMemoryArguments, WriteMemoryResult } from '../../common/messaging'; import { MemoryDisplaySettingsContribution } from '../../common/webview-configuration'; import { Logger } from '../logger'; @@ -35,6 +36,8 @@ export interface AdapterCapabilities { getMemoryDisplaySettings?(session: vscode.DebugSession): Promise>; /** Initialize the trackers of this adapter's for the debug session. */ initializeAdapterTracker?(session: vscode.DebugSession): vscode.DebugAdapterTracker | undefined; + readMemory?(session: vscode.DebugSession, params: ReadMemoryArguments): Promise; + writeMemory?(session: vscode.DebugSession, params: WriteMemoryArguments): Promise; } export type WithChildren = Original & { children?: Array> }; @@ -135,6 +138,10 @@ export class AdapterVariableTracker implements vscode.DebugAdapterTracker { /** Resolves the size of a given variable in bytes within the current context. */ getSizeOfVariable?(variableName: string, session: vscode.DebugSession): Promise; + + readMemory?(session: vscode.DebugSession, params: ReadMemoryArguments): Promise; + + writeMemory?(session: vscode.DebugSession, params: WriteMemoryArguments): Promise; } export class VariableTracker implements AdapterCapabilities { diff --git a/src/plugin/memory-provider.ts b/src/plugin/memory-provider.ts index 71584a2..28d2845 100644 --- a/src/plugin/memory-provider.ts +++ b/src/plugin/memory-provider.ts @@ -49,7 +49,10 @@ export class MemoryProvider { } public async readMemory(args: DebugProtocol.ReadMemoryArguments): Promise { - return sendRequest(this.sessionTracker.assertDebugCapability(this.sessionTracker.activeSession, 'supportsReadMemoryRequest', 'read memory'), 'readMemory', args); + const session = this.sessionTracker.assertDebugCapability(this.sessionTracker.activeSession, 'supportsReadMemoryRequest', 'read memory'); + const handler = this.adapterRegistry?.getHandlerForSession(session.type); + if (handler?.readMemory) { return handler.readMemory(session, args); } + return sendRequest(session, 'readMemory', args); } public async writeMemory(args: DebugProtocol.WriteMemoryArguments): Promise { @@ -63,6 +66,14 @@ export class MemoryProvider { // if our custom handler is active, let's fire the event ourselves this.sessionTracker.fireSessionEvent(session, 'memory-written', { memoryReference: args.memoryReference, offset, count }); }; + const handler = this.adapterRegistry?.getHandlerForSession(session.type); + if (handler?.writeMemory) { + return handler.writeMemory(session, args).then(response => { + // The memory event is handled before we got here, if the scheduled event still exists, we need to handle it + this.scheduledOnDidMemoryWriteEvents[args.memoryReference]?.(response); + return response; + }); + }; return sendRequest(session, 'writeMemory', args).then(response => { // The memory event is handled before we got here, if the scheduled event still exists, we need to handle it From efc0ab000c55526163404d6670af1577c7bb1d57 Mon Sep 17 00:00:00 2001 From: WyoTwT Date: Tue, 30 Apr 2024 23:18:11 +0200 Subject: [PATCH 2/4] Adding Context for cdt-amalgamator and other DAPs Populate the Context Dropdown from the different debug Contexts if available so different DAPs can be addressed. Add an optional Context to queries so the query can be directed to the appropriate handler. Add support for cdt-amalgamator. Signed-off-by: Thor Thayer --- src/common/messaging.ts | 17 ++- .../adapter-registry/adapter-capabilities.ts | 47 ++++---- .../amalgamator-gdb-tracker.ts | 110 ++++++++++++++++++ src/plugin/memory-provider.ts | 36 ++++-- src/plugin/memory-storage.ts | 18 +-- src/plugin/memory-webview-main.ts | 62 ++++++++-- src/webview/columns/data-column.tsx | 11 +- src/webview/components/memory-widget.tsx | 8 +- src/webview/components/options-widget.tsx | 31 ++++- src/webview/memory-webview-view.tsx | 39 ++++++- src/webview/utils/view-types.ts | 4 +- src/webview/variables/variable-decorations.ts | 6 +- 12 files changed, 319 insertions(+), 70 deletions(-) create mode 100644 src/plugin/adapter-registry/amalgamator-gdb-tracker.ts diff --git a/src/common/messaging.ts b/src/common/messaging.ts index fb5954d..5b4d2ce 100644 --- a/src/common/messaging.ts +++ b/src/common/messaging.ts @@ -38,6 +38,11 @@ export type StoreMemoryResult = void; export type ApplyMemoryArguments = URI | undefined; export type ApplyMemoryResult = MemoryOptions; +export interface ConnectionContext { + name: string; + id: number; +} + export interface SessionContext { sessionId?: string; canRead: boolean; @@ -51,15 +56,17 @@ export const setMemoryViewSettingsType: NotificationType = { method: 'setTitle' }; export const memoryWrittenType: NotificationType = { method: 'memoryWritten' }; export const sessionContextChangedType: NotificationType = { method: 'sessionContextChanged' }; +export const connectionContextChangedType: NotificationType<[ConnectionContext?, + ConnectionContext[]?]> = { method: 'connectionContextChanged' }; // Requests export const setOptionsType: RequestType = { method: 'setOptions' }; export const logMessageType: RequestType = { method: 'logMessage' }; -export const readMemoryType: RequestType = { method: 'readMemory' }; -export const writeMemoryType: RequestType = { method: 'writeMemory' }; -export const getVariablesType: RequestType = { method: 'getVariables' }; -export const storeMemoryType: RequestType = { method: 'storeMemory' }; -export const applyMemoryType: RequestType = { method: 'applyMemory' }; +export const readMemoryType: RequestType<[ReadMemoryArguments, ConnectionContext?], ReadMemoryResult> = { method: 'readMemory' }; +export const writeMemoryType: RequestType<[WriteMemoryArguments, ConnectionContext?], WriteMemoryResult> = { method: 'writeMemory' }; +export const getVariablesType: RequestType<[ReadMemoryArguments, ConnectionContext?], VariableRange[]> = { method: 'getVariables' }; +export const storeMemoryType: RequestType<[StoreMemoryArguments, ConnectionContext?], void> = { method: 'storeMemory' }; +export const applyMemoryType: RequestType<[ApplyMemoryArguments, ConnectionContext?], ApplyMemoryResult> = { method: 'applyMemory' }; export const showAdvancedOptionsType: NotificationType = { method: 'showAdvancedOptions' }; export const getWebviewSelectionType: RequestType = { method: 'getWebviewSelection' }; diff --git a/src/plugin/adapter-registry/adapter-capabilities.ts b/src/plugin/adapter-registry/adapter-capabilities.ts index adca974..d5cfd6b 100644 --- a/src/plugin/adapter-registry/adapter-capabilities.ts +++ b/src/plugin/adapter-registry/adapter-capabilities.ts @@ -18,26 +18,29 @@ import { DebugProtocol } from '@vscode/debugprotocol'; import * as vscode from 'vscode'; import { isDebugRequest, isDebugResponse } from '../../common/debug-requests'; import { VariableRange } from '../../common/memory-range'; -import { ReadMemoryArguments, ReadMemoryResult, WriteMemoryArguments, WriteMemoryResult } from '../../common/messaging'; +import { ConnectionContext, ReadMemoryArguments, ReadMemoryResult, + WriteMemoryArguments, WriteMemoryResult } from '../../common/messaging'; import { MemoryDisplaySettingsContribution } from '../../common/webview-configuration'; import { Logger } from '../logger'; /** Represents capabilities that may be achieved with particular debug adapters but are not part of the DAP */ export interface AdapterCapabilities { /** Resolve variables known to the adapter to their locations. Fallback if {@link getResidents} is not present */ - getVariables?(session: vscode.DebugSession): Promise; + getVariables?(session: vscode.DebugSession, context?: ConnectionContext): Promise; /** Resolve symbols resident in the memory at the specified range. Will be preferred to {@link getVariables} if present. */ - getResidents?(session: vscode.DebugSession, params: DebugProtocol.ReadMemoryArguments): Promise; + getResidents?(session: vscode.DebugSession, params: DebugProtocol.ReadMemoryArguments, context?: ConnectionContext): Promise; /** Resolves the address of a given variable in bytes with the current context. */ - getAddressOfVariable?(session: vscode.DebugSession, variableName: string): Promise; + getAddressOfVariable?(session: vscode.DebugSession, variableName: string, context?: ConnectionContext): Promise; /** Resolves the size of a given variable in bytes within the current context. */ - getSizeOfVariable?(session: vscode.DebugSession, variableName: string): Promise; + getSizeOfVariable?(session: vscode.DebugSession, variableName: string, context?: ConnectionContext): Promise; /** Retrieve the suggested default display settings for the memory view. */ getMemoryDisplaySettings?(session: vscode.DebugSession): Promise>; /** Initialize the trackers of this adapter's for the debug session. */ initializeAdapterTracker?(session: vscode.DebugSession): vscode.DebugAdapterTracker | undefined; - readMemory?(session: vscode.DebugSession, params: ReadMemoryArguments): Promise; - writeMemory?(session: vscode.DebugSession, params: WriteMemoryArguments): Promise; + readMemory?(session: vscode.DebugSession, params: ReadMemoryArguments, context?: ConnectionContext): Promise; + writeMemory?(session: vscode.DebugSession, params: WriteMemoryArguments, context?: ConnectionContext): Promise; + getConnectionContexts?(session: vscode.DebugSession): Promise; + getCurrentConnectionContext?(session: vscode.DebugSession): Promise; } export type WithChildren = Original & { children?: Array> }; @@ -108,14 +111,14 @@ export class AdapterVariableTracker implements vscode.DebugAdapterTracker { this.pendingMessages.clear(); } - async getLocals(session: vscode.DebugSession): Promise { + async getLocals(session: vscode.DebugSession, context?: ConnectionContext): Promise { this.logger.debug('Retrieving local variables in', session.name + ' Current variables:\n', this.variablesTree); if (this.currentFrame === undefined) { return []; } const maybeRanges = await Promise.all(Object.values(this.variablesTree).reduce>>((previous, parent) => { if (this.isDesiredVariable(parent) && parent.children?.length) { this.logger.debug('Resolving children of', parent.name); parent.children.forEach(child => { - previous.push(this.variableToVariableRange(child, session)); + previous.push(this.variableToVariableRange(child, session, context)); }); } else { this.logger.debug('Ignoring', parent.name); @@ -129,19 +132,23 @@ export class AdapterVariableTracker implements vscode.DebugAdapterTracker { return candidate.presentationHint !== 'registers' && candidate.name !== 'Registers'; } - protected variableToVariableRange(_variable: DebugProtocol.Variable, _session: vscode.DebugSession): Promise { + protected variableToVariableRange(_variable: DebugProtocol.Variable, _session: vscode.DebugSession, + _context?: ConnectionContext): Promise { throw new Error('To be implemented by derived classes!'); } /** Resolves the address of a given variable in bytes within the current context. */ - getAddressOfVariable?(variableName: string, session: vscode.DebugSession): Promise; + getAddressOfVariable?(variableName: string, session: vscode.DebugSession, context?: ConnectionContext): Promise; /** Resolves the size of a given variable in bytes within the current context. */ - getSizeOfVariable?(variableName: string, session: vscode.DebugSession): Promise; + getSizeOfVariable?(variableName: string, session: vscode.DebugSession, context?: ConnectionContext): Promise; - readMemory?(session: vscode.DebugSession, params: ReadMemoryArguments): Promise; + readMemory?(session: vscode.DebugSession, params: ReadMemoryArguments, context?: ConnectionContext): Promise; - writeMemory?(session: vscode.DebugSession, params: WriteMemoryArguments): Promise; + writeMemory?(session: vscode.DebugSession, params: WriteMemoryArguments, context?: ConnectionContext): Promise; + + getConnectionContexts?(session: vscode.DebugSession): Promise; + getCurrentConnectionContext?(session: vscode.DebugSession): Promise; } export class VariableTracker implements AdapterCapabilities { @@ -163,15 +170,15 @@ export class VariableTracker implements AdapterCapabilities { } } - async getVariables(session: vscode.DebugSession): Promise { - return this.sessions.get(session.id)?.getLocals(session) ?? []; + async getVariables(session: vscode.DebugSession, context?: ConnectionContext): Promise { + return this.sessions.get(session.id)?.getLocals(session, context) ?? []; } - async getAddressOfVariable(session: vscode.DebugSession, variableName: string): Promise { - return this.sessions.get(session.id)?.getAddressOfVariable?.(variableName, session); + async getAddressOfVariable(session: vscode.DebugSession, variableName: string, context?: ConnectionContext): Promise { + return this.sessions.get(session.id)?.getAddressOfVariable?.(variableName, session, context); } - async getSizeOfVariable(session: vscode.DebugSession, variableName: string): Promise { - return this.sessions.get(session.id)?.getSizeOfVariable?.(variableName, session); + async getSizeOfVariable(session: vscode.DebugSession, variableName: string, context?: ConnectionContext): Promise { + return this.sessions.get(session.id)?.getSizeOfVariable?.(variableName, session, context); } } diff --git a/src/plugin/adapter-registry/amalgamator-gdb-tracker.ts b/src/plugin/adapter-registry/amalgamator-gdb-tracker.ts new file mode 100644 index 0000000..7af62ce --- /dev/null +++ b/src/plugin/adapter-registry/amalgamator-gdb-tracker.ts @@ -0,0 +1,110 @@ +/******************************************************************************** + * Copyright (C) 2024 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 { DebugProtocol } from '@vscode/debugprotocol'; +import * as vscode from 'vscode'; +import { ConnectionContext, ReadMemoryArguments, ReadMemoryResult, WriteMemoryArguments, WriteMemoryResult } from '../../common/messaging'; +import { AdapterCapabilities, AdapterVariableTracker, VariableTracker } from './adapter-capabilities'; + +// Copied from cdt-amalgamator [AmalgamatorSession.d.ts] file +/** + * Response for our custom 'cdt-amalgamator/getChildDaps' request. + */ +export interface ConnectionContexts { + children?: ConnectionContext[]; +} +export interface GetContextsResponse extends DebugProtocol.Response { + body: ConnectionContexts; +} +export type GetContextsResult = GetContextsResponse['body']; + +export interface AmalgamatorReadArgs extends ReadMemoryArguments { + child: ConnectionContext; +} + +export class AmalgamatorSessionManager extends VariableTracker implements AdapterCapabilities { + async getConnectionContexts(session: vscode.DebugSession): Promise { + return this.sessions.get(session.id)?.getConnectionContexts?.(session) || []; + } + + async readMemory(session: vscode.DebugSession, args: ReadMemoryArguments, context: ConnectionContext): Promise { + if (!context) { + vscode.window.showErrorMessage('Invalid context for Amalgamator. Select Context in Dropdown'); + return { + address: args.memoryReference + }; + } + return this.sessions.get(session.id)?.readMemory?.(session, args, context); + } + + async writeMemory(session: vscode.DebugSession, args: WriteMemoryArguments, context: ConnectionContext): Promise { + return this.sessions.get(session.id)?.writeMemory?.(session, args, context); + } + + async getCurrentConnectionContext(session: vscode.DebugSession): Promise { + return this.sessions.get(session.id)?.getCurrentConnectionContext?.(session); + } +} + +export class AmalgamatorGdbVariableTransformer extends AdapterVariableTracker { + protected connectionContexts?: ConnectionContext[]; + protected currentConnectionContext?: ConnectionContext; + + onWillReceiveMessage(message: unknown): void { + if (isStacktraceRequest(message)) { + if (typeof (message.arguments.threadId) !== 'undefined') { + this.currentConnectionContext = { + id: message.arguments.threadId, + name: message.arguments.threadId.toString() + }; + } else { + this.logger.warn('Invalid ThreadID in stackTrace'); + this.currentConnectionContext = undefined; + } + } else { + super.onWillReceiveMessage(message); + } + } + + get frame(): number | undefined { return this.currentFrame; } + + async getConnectionContexts(session: vscode.DebugSession): Promise { + if (!this.connectionContexts) { + const contexts: GetContextsResult = (await session.customRequest('cdt-amalgamator/getChildDaps')); + this.connectionContexts = contexts.children?.map(({ name, id }) => ({ name, id })) ?? []; + } + return Promise.resolve(this.connectionContexts); + } + + async getCurrentConnectionContext(_session: vscode.DebugSession): Promise { + return Promise.resolve(this.currentConnectionContext); + } + + readMemory(session: vscode.DebugSession, args: ReadMemoryArguments, context: ConnectionContext): Promise { + const amalReadArgs: AmalgamatorReadArgs = { ...args, child: context }; + return Promise.resolve(session.customRequest('cdt-amalgamator/readMemory', amalReadArgs)); + } +} + +export function isStacktraceRequest(message: unknown): message is DebugProtocol.StackTraceRequest { + const candidate = message as DebugProtocol.StackTraceRequest; + return !!candidate && candidate.command === 'stackTrace'; +} + +export function isStacktraceResponse(message: unknown): message is DebugProtocol.StackTraceResponse { + const candidate = message as DebugProtocol.StackTraceResponse; + return !!candidate && candidate.command === 'stackTrace' && Array.isArray(candidate.body.stackFrames); +} diff --git a/src/plugin/memory-provider.ts b/src/plugin/memory-provider.ts index 28d2845..f5f5e0a 100644 --- a/src/plugin/memory-provider.ts +++ b/src/plugin/memory-provider.ts @@ -19,7 +19,7 @@ import * as vscode from 'vscode'; import { sendRequest } from '../common/debug-requests'; import { stringToBytesMemory } from '../common/memory'; import { VariableRange } from '../common/memory-range'; -import { ReadMemoryResult, WriteMemoryResult } from '../common/messaging'; +import { ConnectionContext, ReadMemoryResult, WriteMemoryResult } from '../common/messaging'; import { MemoryDisplaySettingsContribution } from '../common/webview-configuration'; import { AdapterRegistry } from './adapter-registry/adapter-registry'; import { isSessionEvent, SessionTracker } from './session-tracker'; @@ -48,14 +48,14 @@ export class MemoryProvider { context.subscriptions.push(vscode.debug.registerDebugAdapterTrackerFactory('*', { createDebugAdapterTracker })); } - public async readMemory(args: DebugProtocol.ReadMemoryArguments): Promise { + public async readMemory(args: DebugProtocol.ReadMemoryArguments, context?: ConnectionContext): Promise { const session = this.sessionTracker.assertDebugCapability(this.sessionTracker.activeSession, 'supportsReadMemoryRequest', 'read memory'); const handler = this.adapterRegistry?.getHandlerForSession(session.type); - if (handler?.readMemory) { return handler.readMemory(session, args); } + if (handler?.readMemory) { return handler.readMemory(session, args, context); } return sendRequest(session, 'readMemory', args); } - public async writeMemory(args: DebugProtocol.WriteMemoryArguments): Promise { + public async writeMemory(args: DebugProtocol.WriteMemoryArguments, context?: ConnectionContext): Promise { const session = this.sessionTracker.assertDebugCapability(this.sessionTracker.activeSession, 'supportsWriteMemoryRequest', 'write memory'); // Schedule a emit in case we don't retrieve a memory event this.scheduledOnDidMemoryWriteEvents[session.id + '_' + args.memoryReference] = response => { @@ -68,7 +68,7 @@ export class MemoryProvider { }; const handler = this.adapterRegistry?.getHandlerForSession(session.type); if (handler?.writeMemory) { - return handler.writeMemory(session, args).then(response => { + return handler.writeMemory(session, args, context).then(response => { // The memory event is handled before we got here, if the scheduled event still exists, we need to handle it this.scheduledOnDidMemoryWriteEvents[args.memoryReference]?.(response); return response; @@ -82,23 +82,35 @@ export class MemoryProvider { }); } - public async getVariables(variableArguments: DebugProtocol.ReadMemoryArguments): Promise { + public async getVariables(variableArguments: DebugProtocol.ReadMemoryArguments, context?: ConnectionContext): Promise { const session = this.sessionTracker.assertActiveSession('get variables'); const handler = this.adapterRegistry?.getHandlerForSession(session.type); - if (handler?.getResidents) { return handler.getResidents(session, variableArguments); } - return handler?.getVariables?.(session) ?? []; + if (handler?.getResidents) { return handler.getResidents(session, variableArguments, context); } + return handler?.getVariables?.(session, context) ?? []; } - public async getAddressOfVariable(variableName: string): Promise { + public async getAddressOfVariable(variableName: string, context?: ConnectionContext): Promise { const session = this.sessionTracker.assertActiveSession('get address of variable'); const handler = this.adapterRegistry?.getHandlerForSession(session.type); - return handler?.getAddressOfVariable?.(session, variableName); + return handler?.getAddressOfVariable?.(session, variableName, context); } - public async getSizeOfVariable(variableName: string): Promise { + public async getSizeOfVariable(variableName: string, context?: ConnectionContext): Promise { const session = this.sessionTracker.assertActiveSession('get size of variable'); const handler = this.adapterRegistry?.getHandlerForSession(session.type); - return handler?.getSizeOfVariable?.(session, variableName); + return handler?.getSizeOfVariable?.(session, variableName, context); + } + + public async getConnectionContexts(): Promise { + const session = this.sessionTracker.assertActiveSession('get list of connection Contexts'); + const handler = this.adapterRegistry?.getHandlerForSession(session.type); + return handler?.getConnectionContexts?.(session) ?? []; + } + + public async getCurrentConnectionContext(): Promise { + const session = this.sessionTracker.assertActiveSession('get current connection Context'); + const handler = this.adapterRegistry?.getHandlerForSession(session.type); + return handler?.getCurrentConnectionContext?.(session); } public async getMemoryDisplaySettingsContribution(): Promise { diff --git a/src/plugin/memory-storage.ts b/src/plugin/memory-storage.ts index a19036b..e9cfd3b 100644 --- a/src/plugin/memory-storage.ts +++ b/src/plugin/memory-storage.ts @@ -25,7 +25,7 @@ import { validateCount, validateMemoryReference, validateOffset } from '../common/memory'; import { toHexStringWithRadixMarker } from '../common/memory-range'; -import { ApplyMemoryArguments, ApplyMemoryResult, MemoryOptions, StoreMemoryArguments } from '../common/messaging'; +import { ApplyMemoryArguments, ApplyMemoryResult, ConnectionContext, MemoryOptions, StoreMemoryArguments } from '../common/messaging'; import { isWebviewContext } from '../common/webview-context'; import { MemoryProvider } from './memory-provider'; @@ -60,7 +60,8 @@ export class MemoryStorage { ); } - public async storeMemory(args?: StoreMemoryArguments): Promise { + public async storeMemory(storeMemArgs?: [StoreMemoryArguments, ConnectionContext?]): Promise { + const [args, context] = storeMemArgs ?? []; const providedDefaultOptions = await this.storeArgsToOptions(args); const options = await this.getStoreMemoryOptions(providedDefaultOptions); if (!options) { @@ -70,7 +71,7 @@ export class MemoryStorage { const { outputFile, ...readArgs } = options; try { - const memoryResponse = await this.memoryProvider.readMemory(readArgs); + const memoryResponse = await this.memoryProvider.readMemory(readArgs, context); const memory = createMemoryFromRead(memoryResponse); const memoryMap = new MemoryMap({ [Number(memory.address)]: memory.bytes }); await vscode.workspace.fs.writeFile(outputFile, new TextEncoder().encode(memoryMap.asHexString())); @@ -89,7 +90,7 @@ export class MemoryStorage { } } - protected async storeArgsToOptions(args?: StoreMemoryArguments): Promise> { + protected async storeArgsToOptions(args?: StoreMemoryArguments, context?: ConnectionContext): Promise> { if (!args) { return {}; } @@ -99,8 +100,8 @@ export class MemoryStorage { if (isVariablesContext(args)) { try { const variableName = args.variable.evaluateName ?? args.variable.name; - const count = await this.memoryProvider.getSizeOfVariable(variableName); - const memoryReference = args.variable.memoryReference ?? await this.memoryProvider.getAddressOfVariable(variableName); + const count = await this.memoryProvider.getSizeOfVariable(variableName, context); + const memoryReference = args.variable.memoryReference ?? await this.memoryProvider.getAddressOfVariable(variableName, context); return { count: Number(count), memoryReference, offset: 0, proposedOutputName: variableName }; } catch (error) { // ignore, we are just using them as default values @@ -153,7 +154,8 @@ export class MemoryStorage { return { memoryReference, offset: Number(offset), count: Number(count), outputFile }; } - public async applyMemory(args?: ApplyMemoryArguments): Promise { + public async applyMemory(applyMemArgs?: [ApplyMemoryArguments, ConnectionContext]): Promise { + const [args, context] = applyMemArgs || []; const providedDefaultOptions = await this.applyArgsToOptions(args); const options = await this.getApplyMemoryOptions(providedDefaultOptions); if (!options) { @@ -169,7 +171,7 @@ export class MemoryStorage { memoryReference = toHexStringWithRadixMarker(address); count = memory.length; const data = bytesToStringMemory(memory); - await this.memoryProvider.writeMemory({ memoryReference, data }); + await this.memoryProvider.writeMemory({ memoryReference, data }, context); } await vscode.window.showInformationMessage(`Memory from '${vscode.workspace.asRelativePath(options.uri)}' applied.`); return { memoryReference, count, offset: 0 }; diff --git a/src/plugin/memory-webview-main.ts b/src/plugin/memory-webview-main.ts index d13914a..c0c5360 100644 --- a/src/plugin/memory-webview-main.ts +++ b/src/plugin/memory-webview-main.ts @@ -21,7 +21,10 @@ import { isVariablesContext } from '../common/external-views'; import * as manifest from '../common/manifest'; import { VariableRange } from '../common/memory-range'; import { + ApplyMemoryArguments, applyMemoryType, + ConnectionContext, + connectionContextChangedType, getVariablesType, getWebviewSelectionType, logMessageType, @@ -57,6 +60,9 @@ const CONFIGURABLE_COLUMNS = [ manifest.CONFIG_SHOW_VARIABLES_COLUMN, ]; +type ReadMemoryWithContext = [ReadMemoryArguments, ConnectionContext?]; +type WriteMemoryWithContext = [WriteMemoryArguments, ConnectionContext?]; + export class MemoryWebview implements vscode.CustomReadonlyEditorProvider { public static ViewType = `${manifest.PACKAGE_NAME}.memory`; public static ShowCommandType = `${manifest.PACKAGE_NAME}.show`; @@ -206,6 +212,7 @@ export class MemoryWebview implements vscode.CustomReadonlyEditorProvider { this.messenger.onNotification(readyType, async () => { this.setSessionContext(participant, this.createContext()); await this.setMemoryDisplaySettings(participant, panel.title); + await this.setConnectionContext(participant); this.refresh(participant, options); }, { sender: participant }), this.messenger.onRequest(setOptionsType, newOptions => { options = { ...options, ...newOptions }; }, { sender: participant }), @@ -215,9 +222,17 @@ export class MemoryWebview implements vscode.CustomReadonlyEditorProvider { this.messenger.onRequest(getVariablesType, request => this.getVariables(request), { sender: participant }), this.messenger.onNotification(setTitleType, title => { panel.title = title; }, { sender: participant }), this.messenger.onRequest(storeMemoryType, args => this.storeMemory(args), { sender: participant }), - this.messenger.onRequest(applyMemoryType, () => this.applyMemory(), { sender: participant }), + this.messenger.onRequest(applyMemoryType, args => this.applyMemory(args), { sender: participant }), this.sessionTracker.onSessionEvent(event => this.handleSessionEvent(participant, event)) ]; + let panelHasBeenVisible = false; + panel.onDidChangeViewState(newState => { + // Skip on first appearance. + if (panelHasBeenVisible && newState.webviewPanel.visible) { + this.refresh(participant, options); + } + panelHasBeenVisible ||= newState.webviewPanel.visible; + }); panel.onDidDispose(() => disposables.forEach(disposable => disposable.dispose())); } @@ -246,6 +261,12 @@ export class MemoryWebview implements vscode.CustomReadonlyEditorProvider { this.messenger.sendNotification(sessionContextChangedType, webviewParticipant, context); } + protected async setConnectionContext(webviewParticipant: WebviewIdMessageParticipant): Promise { + const connectionContexts = await this.getConnectionContexts(); // Read available Connection Contexts first. + await this.messenger.sendRequest(connectionContextChangedType, webviewParticipant, + [await this.getCurrentConnectionContext(), connectionContexts]); + } + protected getDefaultMemoryDisplaySettings(): MemoryDisplaySettings { const memoryInspectorSettings = vscode.workspace.getConfiguration(manifest.PACKAGE_NAME); const bytesPerMau = memoryInspectorSettings.get(manifest.CONFIG_BYTES_PER_MAU, manifest.DEFAULT_BYTES_PER_MAU); @@ -302,25 +323,28 @@ export class MemoryWebview implements vscode.CustomReadonlyEditorProvider { }; } - protected async readMemory(request: ReadMemoryArguments): Promise { + protected async readMemory(request: ReadMemoryWithContext): Promise { try { - return await this.memoryProvider.readMemory(request); + const [readMemoryArgs, context] = request; + return await this.memoryProvider.readMemory(readMemoryArgs, context); } catch (err) { this.logError('Error fetching memory', err); } } - protected async writeMemory(request: WriteMemoryArguments): Promise { + protected async writeMemory(request: WriteMemoryWithContext): Promise { try { - return await this.memoryProvider.writeMemory(request); + const [writeMemoryArgs, context] = request; + return await this.memoryProvider.writeMemory(writeMemoryArgs, context); } catch (err) { this.logError('Error writing memory', err); } } - protected async getVariables(request: ReadMemoryArguments): Promise { + protected async getVariables(request: ReadMemoryWithContext): Promise { try { - return await this.memoryProvider.getVariables(request); + const [readMemoryArgs, context] = request; + return await this.memoryProvider.getVariables(readMemoryArgs, context); } catch (err) { this.logError('Error fetching variables', err); return []; @@ -343,21 +367,39 @@ export class MemoryWebview implements vscode.CustomReadonlyEditorProvider { this.setMemoryViewSettings(ctx.messageParticipant, { visibleColumns }); } - protected async storeMemory(storeArguments: StoreMemoryArguments): Promise { + protected async storeMemory(storeArguments: [StoreMemoryArguments, ConnectionContext?]): Promise { // Even if we disable the command in VS Code through enablement or when condition, programmatic execution is still possible. // However, we want to fail early in case the user tries to execute a disabled command this.sessionTracker.assertDebugCapability(this.sessionTracker.activeSession, 'supportsReadMemoryRequest', 'store memory'); return vscode.commands.executeCommand(StoreCommandType, storeArguments); } - protected async applyMemory(): Promise { + protected async applyMemory(applyArguments: [ApplyMemoryArguments, ConnectionContext?]): Promise { // Even if we disable the command in VS Code through enablement or when condition, programmatic execution is still possible. // However, we want to fail early in case the user tries to execute a disabled command this.sessionTracker.assertDebugCapability(this.sessionTracker.activeSession, 'supportsWriteMemoryRequest', 'apply memory'); - return vscode.commands.executeCommand(ApplyCommandType); + return vscode.commands.executeCommand(ApplyCommandType, applyArguments); } protected logError(msg: string, err: unknown): void { outputChannelLogger.error(msg, err instanceof Error ? `: ${err.message}\n${err.stack}` : ''); } + + protected async getConnectionContexts(): Promise { + try { + return await this.memoryProvider.getConnectionContexts(); + } catch (err) { + this.logError('Error getting Connection Contexts', err); + } + return []; + } + + protected async getCurrentConnectionContext(): Promise { + try { + return await this.memoryProvider.getCurrentConnectionContext(); + } catch (err) { + this.logError('Error getting Current Connection Context', err); + } + return undefined; + } } diff --git a/src/webview/columns/data-column.tsx b/src/webview/columns/data-column.tsx index 5ee3d15..c6261cc 100644 --- a/src/webview/columns/data-column.tsx +++ b/src/webview/columns/data-column.tsx @@ -21,7 +21,7 @@ import * as React from 'react'; import { HOST_EXTENSION } from 'vscode-messenger-common'; import { Memory } from '../../common/memory'; import { BigIntMemoryRange, isWithin, toHexStringWithRadixMarker, toOffset } from '../../common/memory-range'; -import { writeMemoryType } from '../../common/messaging'; +import { ConnectionContext, writeMemoryType } from '../../common/messaging'; import type { MemoryRowData, MemorySizeOptions, MemoryTableSelection, MemoryTableState } from '../components/memory-table'; import { decorationService } from '../decorations/decoration-service'; import { Disposable, FullNodeAttributes } from '../utils/view-types'; @@ -100,6 +100,7 @@ export interface EditableDataColumnRowProps { export interface EditableDataColumnRowState { position?: GroupPosition; + context?: ConnectionContext; } export class EditableDataColumnRow extends React.Component { @@ -125,6 +126,10 @@ export class EditableDataColumnRow extends React.Component ({ ...prev, context: currentContext})); + } + protected renderGroups(): React.ReactNode { const { row, config } = this.props; const groups = []; @@ -343,10 +348,10 @@ export class EditableDataColumnRow extends React.Component { }); + }, this.state.context]).catch(() => { }); } this.disableEdit(); diff --git a/src/webview/components/memory-widget.tsx b/src/webview/components/memory-widget.tsx index 1c46fbf..de2f9f2 100644 --- a/src/webview/components/memory-widget.tsx +++ b/src/webview/components/memory-widget.tsx @@ -18,7 +18,7 @@ import React from 'react'; import { WebviewIdMessageParticipant } from 'vscode-messenger-common'; import * as manifest from '../../common/manifest'; import { Memory } from '../../common/memory'; -import { WebviewSelection } from '../../common/messaging'; +import { ConnectionContext, WebviewSelection } from '../../common/messaging'; import { MemoryOptions, ReadMemoryArguments, SessionContext } from '../../common/messaging'; import { MemoryDataDisplaySettings } from '../../common/webview-configuration'; import { ColumnStatus } from '../columns/column-contribution-service'; @@ -51,6 +51,9 @@ interface MemoryWidgetProps extends MemoryDataDisplaySettings { fetchMemory(partialOptions?: MemoryOptions): Promise; storeMemory(): void; applyMemory(): void; + connectionContexts: ConnectionContext[]; + connectionContext?: ConnectionContext; + setConnectionContext: (context: ConnectionContext) => void; } interface MemoryWidgetState { @@ -112,6 +115,9 @@ export class MemoryWidget extends React.Component void; } interface OptionsWidgetState { @@ -74,6 +77,7 @@ const enum InputId { RefreshOnStop = 'refresh-on-stop', PeriodicRefresh = 'periodic-refresh', PeriodicRefreshInterval = 'periodic-refresh-interval', + Contexts = 'connection-contexts' } interface OptionsForm { @@ -151,6 +155,30 @@ export class OptionsWidget extends React.Component { + const { setConnectionContext, connectionContexts } = this.props; + setConnectionContext(connectionContexts.filter(context => context.id === Number(e.value))[0]); + }; + + protected showContexts(): React.ReactNode { + if (this.props.connectionContexts.length === 0) { + return undefined; + } + return ( + + + + ); + }; + override render(): React.ReactNode { this.formConfig.initialValues = this.optionsFormValues; const isLabelEditing = this.state.isTitleEditing; @@ -229,6 +257,7 @@ export class OptionsWidget extends React.Component {formik => (
+ {this.showContexts()}