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/entry-points/desktop/extension.ts b/src/entry-points/desktop/extension.ts index d8c6905..2bd1d79 100644 --- a/src/entry-points/desktop/extension.ts +++ b/src/entry-points/desktop/extension.ts @@ -16,8 +16,10 @@ import * as vscode from 'vscode'; import { AdapterRegistry } from '../../plugin/adapter-registry/adapter-registry'; +import { AmalgamatorGdbVariableTransformer, AmalgamatorSessionManager } from '../../plugin/adapter-registry/amalgamator-gdb-tracker'; import { CAdapter } from '../../plugin/adapter-registry/c-adapter'; import { ContextTracker } from '../../plugin/context-tracker'; +import { outputChannelLogger } from '../../plugin/logger'; import { MemoryProvider } from '../../plugin/memory-provider'; import { MemoryStorage } from '../../plugin/memory-storage'; import { MemoryWebview } from '../../plugin/memory-webview-main'; @@ -32,6 +34,9 @@ export const activate = async (context: vscode.ExtensionContext): 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, 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> }; @@ -105,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); @@ -126,15 +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, context?: ConnectionContext): 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 { @@ -156,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..e21d45f --- /dev/null +++ b/src/plugin/adapter-registry/amalgamator-gdb-tracker.ts @@ -0,0 +1,114 @@ +/******************************************************************************** + * 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 { + const curConnectionContext = this.connectionContexts?.length ? + (this.connectionContexts?.filter(context => context.id === this.currentConnectionContext?.id).shift() ?? + this.currentConnectionContext) : + this.currentConnectionContext; + return Promise.resolve(curConnectionContext); + } + + 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 71584a2..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,11 +48,14 @@ export class MemoryProvider { context.subscriptions.push(vscode.debug.registerDebugAdapterTrackerFactory('*', { createDebugAdapterTracker })); } - public async readMemory(args: DebugProtocol.ReadMemoryArguments): Promise { - return sendRequest(this.sessionTracker.assertDebugCapability(this.sessionTracker.activeSession, 'supportsReadMemoryRequest', 'read memory'), 'readMemory', args); + 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, 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 => { @@ -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, 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; + }); + }; 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 @@ -71,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()}