From d206a3eb712da3d458d274292e7ea7bb4f3ea311 Mon Sep 17 00:00:00 2001 From: thegecko Date: Thu, 2 Jan 2025 15:49:22 +0000 Subject: [PATCH 1/4] Use explicit debug session --- src/plugin/memory-provider.ts | 20 ++++++++++++++------ src/plugin/memory-webview-main.ts | 13 ++++++++----- src/plugin/session-tracker.ts | 20 ++++++++------------ 3 files changed, 30 insertions(+), 23 deletions(-) diff --git a/src/plugin/memory-provider.ts b/src/plugin/memory-provider.ts index 71584a2..e7fbc04 100644 --- a/src/plugin/memory-provider.ts +++ b/src/plugin/memory-provider.ts @@ -31,6 +31,9 @@ export interface LabeledUint8Array extends Uint8Array { export class MemoryProvider { protected scheduledOnDidMemoryWriteEvents: { [sessionidmemoryReference: string]: ((response: WriteMemoryResult) => void) | undefined } = {}; + protected _sessionId: string | undefined; + public get sessionId(): string | undefined { return this._sessionId; } + constructor(protected adapterRegistry: AdapterRegistry, protected sessionTracker: SessionTracker) { this.sessionTracker.onSessionEvent(event => { if (isSessionEvent('memory-written', event)) { @@ -48,12 +51,17 @@ export class MemoryProvider { context.subscriptions.push(vscode.debug.registerDebugAdapterTrackerFactory('*', { createDebugAdapterTracker })); } + public setSessionId(value: string | undefined): void { + this._sessionId = value; + } + public async readMemory(args: DebugProtocol.ReadMemoryArguments): Promise { - return sendRequest(this.sessionTracker.assertDebugCapability(this.sessionTracker.activeSession, 'supportsReadMemoryRequest', 'read memory'), 'readMemory', args); + const session = this.sessionTracker.assertSession(this.sessionId); + return sendRequest(this.sessionTracker.assertDebugCapability(session, 'supportsReadMemoryRequest', 'read memory'), 'readMemory', args); } public async writeMemory(args: DebugProtocol.WriteMemoryArguments): Promise { - const session = this.sessionTracker.assertDebugCapability(this.sessionTracker.activeSession, 'supportsWriteMemoryRequest', 'write memory'); + const session = this.sessionTracker.assertDebugCapability(this.sessionTracker.assertSession(this.sessionId), 'supportsWriteMemoryRequest', 'write memory'); // Schedule a emit in case we don't retrieve a memory event this.scheduledOnDidMemoryWriteEvents[session.id + '_' + args.memoryReference] = response => { // We only send out a custom event if we don't expect the client to handle the memory event @@ -72,26 +80,26 @@ export class MemoryProvider { } public async getVariables(variableArguments: DebugProtocol.ReadMemoryArguments): Promise { - const session = this.sessionTracker.assertActiveSession('get variables'); + const session = this.sessionTracker.assertSession(this.sessionId, 'get variables'); const handler = this.adapterRegistry?.getHandlerForSession(session.type); if (handler?.getResidents) { return handler.getResidents(session, variableArguments); } return handler?.getVariables?.(session) ?? []; } public async getAddressOfVariable(variableName: string): Promise { - const session = this.sessionTracker.assertActiveSession('get address of variable'); + const session = this.sessionTracker.assertSession(this.sessionId, 'get address of variable'); const handler = this.adapterRegistry?.getHandlerForSession(session.type); return handler?.getAddressOfVariable?.(session, variableName); } public async getSizeOfVariable(variableName: string): Promise { - const session = this.sessionTracker.assertActiveSession('get size of variable'); + const session = this.sessionTracker.assertSession(this.sessionId, 'get size of variable'); const handler = this.adapterRegistry?.getHandlerForSession(session.type); return handler?.getSizeOfVariable?.(session, variableName); } public async getMemoryDisplaySettingsContribution(): Promise { - const session = this.sessionTracker.assertActiveSession('get memory display settings contribution'); + const session = this.sessionTracker.assertSession(this.sessionId, 'get memory display settings contribution'); const handler = this.adapterRegistry?.getHandlerForSession(session.type); return handler?.getMemoryDisplaySettings?.(session) ?? {}; } diff --git a/src/plugin/memory-webview-main.ts b/src/plugin/memory-webview-main.ts index d13914a..f11ba0c 100644 --- a/src/plugin/memory-webview-main.ts +++ b/src/plugin/memory-webview-main.ts @@ -168,6 +168,7 @@ export class MemoryWebview implements vscode.CustomReadonlyEditorProvider { // Sets up an event listener to listen for messages passed from the webview view context // and executes code based on the message that is received this.setWebviewMessageListener(panel, initialMemory); + this.memoryProvider.setSessionId(vscode.debug.activeDebugSession?.id); } protected async getWebviewContent(panel: vscode.WebviewPanel): Promise { @@ -204,7 +205,7 @@ export class MemoryWebview implements vscode.CustomReadonlyEditorProvider { const participant = this.messenger.registerWebviewPanel(panel); const disposables = [ this.messenger.onNotification(readyType, async () => { - this.setSessionContext(participant, this.createContext()); + this.setSessionContext(participant, this.createContext(this.sessionTracker.assertSession(this.memoryProvider.sessionId))); await this.setMemoryDisplaySettings(participant, panel.title); this.refresh(participant, options); }, { sender: participant }), @@ -278,7 +279,9 @@ export class MemoryWebview implements vscode.CustomReadonlyEditorProvider { protected handleSessionEvent(participant: WebviewIdMessageParticipant, event: SessionEvent): void { if (isSessionEvent('active', event)) { - this.setSessionContext(participant, this.createContext(event.session?.raw)); + const session = this.sessionTracker.assertSession(event.session?.raw.id); + this.memoryProvider.setSessionId(session.id); + this.setSessionContext(participant, this.createContext(session)); return; } // we are only interested in the events of the active session @@ -292,7 +295,7 @@ export class MemoryWebview implements vscode.CustomReadonlyEditorProvider { } } - protected createContext(session = this.sessionTracker.activeSession): SessionContext { + protected createContext(session: vscode.DebugSession): SessionContext { const sessionId = session?.id; return { sessionId, @@ -346,14 +349,14 @@ export class MemoryWebview implements vscode.CustomReadonlyEditorProvider { protected async storeMemory(storeArguments: StoreMemoryArguments): 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'); + this.sessionTracker.assertDebugCapability(this.sessionTracker.assertSession(this.memoryProvider.sessionId), 'supportsReadMemoryRequest', 'store memory'); return vscode.commands.executeCommand(StoreCommandType, storeArguments); } protected async applyMemory(): 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'); + this.sessionTracker.assertDebugCapability(this.sessionTracker.assertSession(this.memoryProvider.sessionId), 'supportsWriteMemoryRequest', 'apply memory'); return vscode.commands.executeCommand(ApplyCommandType); } diff --git a/src/plugin/session-tracker.ts b/src/plugin/session-tracker.ts index d10529f..f0bc6cb 100644 --- a/src/plugin/session-tracker.ts +++ b/src/plugin/session-tracker.ts @@ -139,30 +139,26 @@ export class SessionTracker implements vscode.DebugAdapterTrackerFactory { } } - get activeSession(): vscode.DebugSession | undefined { - return vscode.debug.activeDebugSession; - } - - assertActiveSession(action: string = 'get session'): vscode.DebugSession { - if (!this.activeSession) { + assertSession(sessionId: string | undefined, action: string = 'get session'): vscode.DebugSession { + if (!sessionId || !this._sessionInfo.has(sessionId)) { throw new Error(`Cannot ${action}. No active debug session.`); } - return this.activeSession; + return this._sessionInfo.get(sessionId)!.raw; } - isActive(session = this.activeSession): boolean { + isActive(session: vscode.DebugSession): boolean { return !!session && vscode.debug.activeDebugSession?.id === session?.id; } - isStopped(session = this.activeSession): boolean { + isStopped(session: vscode.DebugSession): boolean { return !!session && !!this.sessionInfo(session).stopped; } - hasDebugCapability(session = this.activeSession, capability: DebugCapability): boolean { + hasDebugCapability(session: vscode.DebugSession, capability: DebugCapability): boolean { return !!session && !!this.sessionInfo(session).debugCapabilities?.[capability]; } - assertDebugCapability(session = this.assertActiveSession(), capability: DebugCapability, action: string = 'execute action'): vscode.DebugSession { + assertDebugCapability(session: vscode.DebugSession, capability: DebugCapability, action: string = 'execute action'): vscode.DebugSession { if (!this.hasDebugCapability(session, capability)) { throw new Error(`Cannot ${action}. Session does not have capability '${capability}'.`); } @@ -173,7 +169,7 @@ export class SessionTracker implements vscode.DebugAdapterTrackerFactory { return !!session && !!this.sessionInfo(session).clientCapabilities?.[capability]; } - assertClientCapability(session = this.assertActiveSession(), capability: ClientCapability, action: string = 'execute action'): vscode.DebugSession { + assertClientCapability(session: vscode.DebugSession, capability: ClientCapability, action: string = 'execute action'): vscode.DebugSession { if (!this.hasClientCapability(session, capability)) { throw new Error(`Cannot ${action}. Client does not have capability '${capability}'.`); } From 8841a0f2b511cf6c425b5b71828f8f01f93a6384 Mon Sep 17 00:00:00 2001 From: thegecko Date: Thu, 2 Jan 2025 20:27:26 +0000 Subject: [PATCH 2/4] Show disabled session dropdown when more than one exists --- media/options-widget.css | 18 ++++++++++------ src/common/messaging.ts | 7 +++++++ src/plugin/memory-webview-main.ts | 21 ++++++++++++++++--- src/plugin/session-tracker.ts | 18 +++++++++++++++- src/webview/components/memory-widget.tsx | 6 +++++- src/webview/components/options-widget.tsx | 25 ++++++++++++++++++++++- src/webview/memory-webview-view.tsx | 23 +++++++++++++++++++++ 7 files changed, 106 insertions(+), 12 deletions(-) diff --git a/media/options-widget.css b/media/options-widget.css index d77c40d..d2ec083 100644 --- a/media/options-widget.css +++ b/media/options-widget.css @@ -77,19 +77,20 @@ padding-bottom: 16px; } -.form-textfield > input { +.form-textfield>input { width: 150px; } -.form-texfield-long > input { +.form-texfield-long>input { width: 200px; } .form-options button[type="submit"] { /* Match height of inputs */ height: calc(var(--input-minHeight) * 1px); - margin-top: 1rem; + margin-top: 1.1rem; align-self: start; + margin-right: auto; } .form-options-memory-read-argument-hint { @@ -106,7 +107,7 @@ font-size: 11px; text-transform: uppercase; line-height: 22px; - margin:0; + margin: 0; font-weight: normal; } @@ -145,10 +146,14 @@ padding-bottom: 0.5rem; } +.advanced-options-session { + align-self: start; + width: 250px; +} + .advanced-options-toggle { - margin-left: auto; align-self: start; - margin-top: 1.1rem + margin-top: 1.3rem } .p-accordion-content .advanced-options-content { @@ -187,6 +192,7 @@ margin-top: 0px; width: 210px; } + .advanced-options-panel::-webkit-scrollbar-track { background-color: var(--vscode-dropdown-listBackground); } diff --git a/src/common/messaging.ts b/src/common/messaging.ts index fb5954d..df57f02 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 Session { + id: string + name: string; +} + export interface SessionContext { sessionId?: string; canRead: boolean; @@ -50,6 +55,8 @@ export const readyType: NotificationType = { method: 'ready' }; export const setMemoryViewSettingsType: NotificationType> = { method: 'setMemoryViewSettings' }; export const setTitleType: NotificationType = { method: 'setTitle' }; export const memoryWrittenType: NotificationType = { method: 'memoryWritten' }; +export const sessionsChangedType: NotificationType = { method: 'sessionsChanged' }; +export const setSessionType: NotificationType = { method: 'setSession' }; export const sessionContextChangedType: NotificationType = { method: 'sessionContextChanged' }; // Requests diff --git a/src/plugin/memory-webview-main.ts b/src/plugin/memory-webview-main.ts index f11ba0c..ffa4fd6 100644 --- a/src/plugin/memory-webview-main.ts +++ b/src/plugin/memory-webview-main.ts @@ -31,6 +31,9 @@ import { ReadMemoryResult, readMemoryType, readyType, + Session, + sessionsChangedType, + setSessionType, SessionContext, sessionContextChangedType, setMemoryViewSettingsType, @@ -146,6 +149,8 @@ export class MemoryWebview implements vscode.CustomReadonlyEditorProvider { } public async show(initialMemory?: MemoryOptions, panel?: vscode.WebviewPanel): Promise { + this.memoryProvider.setSessionId(vscode.debug.activeDebugSession?.id); + const distPathUri = vscode.Uri.joinPath(this.extensionUri, 'dist', 'views'); const mediaPathUri = vscode.Uri.joinPath(this.extensionUri, 'media'); const codiconPathUri = vscode.Uri.joinPath(this.extensionUri, 'node_modules', '@vscode', 'codicons', 'dist'); @@ -168,7 +173,6 @@ export class MemoryWebview implements vscode.CustomReadonlyEditorProvider { // Sets up an event listener to listen for messages passed from the webview view context // and executes code based on the message that is received this.setWebviewMessageListener(panel, initialMemory); - this.memoryProvider.setSessionId(vscode.debug.activeDebugSession?.id); } protected async getWebviewContent(panel: vscode.WebviewPanel): Promise { @@ -205,6 +209,7 @@ export class MemoryWebview implements vscode.CustomReadonlyEditorProvider { const participant = this.messenger.registerWebviewPanel(panel); const disposables = [ this.messenger.onNotification(readyType, async () => { + this.setSessions(participant, this.sessionTracker.getSessions()); this.setSessionContext(participant, this.createContext(this.sessionTracker.assertSession(this.memoryProvider.sessionId))); await this.setMemoryDisplaySettings(participant, panel.title); this.refresh(participant, options); @@ -215,6 +220,7 @@ export class MemoryWebview implements vscode.CustomReadonlyEditorProvider { this.messenger.onRequest(writeMemoryType, request => this.writeMemory(request), { sender: participant }), this.messenger.onRequest(getVariablesType, request => this.getVariables(request), { sender: participant }), this.messenger.onNotification(setTitleType, title => { panel.title = title; }, { sender: participant }), + this.messenger.onNotification(setSessionType, sessionId => this.memoryProvider.setSessionId(sessionId), { sender: participant }), this.messenger.onRequest(storeMemoryType, args => this.storeMemory(args), { sender: participant }), this.messenger.onRequest(applyMemoryType, () => this.applyMemory(), { sender: participant }), this.sessionTracker.onSessionEvent(event => this.handleSessionEvent(participant, event)) @@ -243,6 +249,10 @@ export class MemoryWebview implements vscode.CustomReadonlyEditorProvider { this.messenger.sendNotification(setMemoryViewSettingsType, webviewParticipant, settings); } + protected setSessions(webviewParticipant: WebviewIdMessageParticipant, sessions: Session[]): void { + this.messenger.sendNotification(sessionsChangedType, webviewParticipant, sessions); + } + protected setSessionContext(webviewParticipant: WebviewIdMessageParticipant, context: SessionContext): void { this.messenger.sendNotification(sessionContextChangedType, webviewParticipant, context); } @@ -284,8 +294,13 @@ export class MemoryWebview implements vscode.CustomReadonlyEditorProvider { this.setSessionContext(participant, this.createContext(session)); return; } - // we are only interested in the events of the active session - if (!event.session?.active) { + if (isSessionEvent('changed', event)) { + this.setSessions(participant, this.sessionTracker.getSessions()); + return; + } + + // we are only interested in the events of the current session + if (!event.session || event.session.raw.id !== this.memoryProvider.sessionId) { return; } if (isSessionEvent('memory-written', event)) { diff --git a/src/plugin/session-tracker.ts b/src/plugin/session-tracker.ts index f0bc6cb..7f3dad2 100644 --- a/src/plugin/session-tracker.ts +++ b/src/plugin/session-tracker.ts @@ -17,6 +17,7 @@ import { DebugProtocol } from '@vscode/debugprotocol'; import * as vscode from 'vscode'; import { isDebugEvent, isDebugRequest, isDebugResponse } from '../common/debug-requests'; import { WrittenMemory } from '../common/memory-range'; +import type { Session } from '../common/messaging'; export interface SessionInfo { raw: vscode.DebugSession; @@ -52,11 +53,16 @@ export interface SessionContinuedEvent extends SessionEvent { session: SessionInfo; } +export interface SessionsChangedEvent extends SessionEvent { + event: 'changed'; +} + export interface SessionEvents { 'active': ActiveSessionChangedEvent, 'memory-written': SessionMemoryWrittenEvent, 'continued': SessionContinuedEvent, 'stopped': SessionStoppedEvent + 'changed': SessionsChangedEvent } export type DebugCapability = keyof DebugProtocol.Capabilities; @@ -95,7 +101,10 @@ export class SessionTracker implements vscode.DebugAdapterTrackerFactory { let info = this._sessionInfo.get(session.id); if (!info) { info = { raw: session }; - this._sessionInfo.set(session.id, info); + if (this._sessionInfo.has(session.id)) { + // Only update session if it is active + this._sessionInfo.set(session.id, info); + } } return info; } @@ -113,10 +122,12 @@ export class SessionTracker implements vscode.DebugAdapterTrackerFactory { protected async sessionWillStart(session: vscode.DebugSession): Promise { this._sessionInfo.set(session.id, { raw: session }); + this.fireSessionEvent(session, 'changed', undefined); } protected sessionWillStop(session: vscode.DebugSession): void { this._sessionInfo.delete(session.id); + this.fireSessionEvent(session, 'changed', undefined); } protected willSendClientMessage(session: vscode.DebugSession, message: unknown): void { @@ -139,6 +150,11 @@ export class SessionTracker implements vscode.DebugAdapterTrackerFactory { } } + public getSessions(): Session[] { + return Array.from(this._sessionInfo.values()) + .map(info => ({ id: info.raw.id, name: info.raw.name })); + } + assertSession(sessionId: string | undefined, action: string = 'get session'): vscode.DebugSession { if (!sessionId || !this._sessionInfo.has(sessionId)) { throw new Error(`Cannot ${action}. No active debug session.`); diff --git a/src/webview/components/memory-widget.tsx b/src/webview/components/memory-widget.tsx index 1c46fbf..952af30 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 { Session, WebviewSelection } from '../../common/messaging'; import { MemoryOptions, ReadMemoryArguments, SessionContext } from '../../common/messaging'; import { MemoryDataDisplaySettings } from '../../common/webview-configuration'; import { ColumnStatus } from '../columns/column-contribution-service'; @@ -30,6 +30,8 @@ import { OptionsWidget } from './options-widget'; interface MemoryWidgetProps extends MemoryDataDisplaySettings { messageParticipant: WebviewIdMessageParticipant; + sessions: Session[]; + updateSession: (sessionId: string) => void; sessionContext: SessionContext; configuredReadArguments: Required; activeReadArguments: Required; @@ -87,6 +89,8 @@ export class MemoryWidget extends React.Component { + sessions: Session[] + updateSession: (sessionId: string) => void; sessionContext: SessionContext; configuredReadArguments: Required; activeReadArguments: Required; @@ -74,6 +76,7 @@ const enum InputId { RefreshOnStop = 'refresh-on-stop', PeriodicRefresh = 'periodic-refresh', PeriodicRefreshInterval = 'periodic-refresh-interval', + Session = 'session' } interface OptionsForm { @@ -165,6 +168,23 @@ export class OptionsWidget extends React.Component + + ({ label: session.name, value: session.id }))} + value={this.props.sessionContext.sessionId} + onChange={this.handleSessionDropdownChange} + /> + ; + return (
@@ -289,6 +309,7 @@ export class OptionsWidget extends React.Component Go + {sessionSelector}