import { DebugProtocol } from "@vscode/debugprotocol"; import * as vscode from "vscode"; /** A helper type for mapping event types to their corresponding data type. */ // prettier-ignore interface EventMap { "module": DebugProtocol.ModuleEvent; } /** A type assertion to check if a ProtocolMessage is an event or if it is a specific event. */ function isEvent( message: DebugProtocol.ProtocolMessage, ): message is DebugProtocol.Event; function isEvent( message: DebugProtocol.ProtocolMessage, event: K, ): message is EventMap[K]; function isEvent( message: DebugProtocol.ProtocolMessage, event?: string, ): boolean { return ( message.type === "event" && (!event || (message as DebugProtocol.Event).event === event) ); } /** Tracks lldb-dap sessions for data visualizers. */ export class DebugSessionTracker implements vscode.DebugAdapterTrackerFactory, vscode.Disposable { /** * Tracks active modules for each debug sessions. * * The modules are kept in an array to maintain the load order of the modules. */ private modules = new Map(); private modulesChanged = new vscode.EventEmitter< vscode.DebugSession | undefined >(); /** * Fired when modules are changed for any active debug session. * * Use `debugSessionModules` to retieve the active modules for a given debug session. */ onDidChangeModules: vscode.Event = this.modulesChanged.event; constructor() { this.onDidChangeModules(this.moduleChangedListener, this); vscode.debug.onDidChangeActiveDebugSession((session) => this.modulesChanged.fire(session), ); } dispose() { this.modules.clear(); this.modulesChanged.dispose(); } createDebugAdapterTracker( session: vscode.DebugSession, ): vscode.ProviderResult { return { onDidSendMessage: (message) => this.onDidSendMessage(session, message), onExit: () => this.onExit(session), }; } /** * Retrieves the modules for the given debug session. * * Modules are returned in load order. */ debugSessionModules(session: vscode.DebugSession): DebugProtocol.Module[] { return this.modules.get(session) ?? []; } /** Clear information from the active session. */ private onExit(session: vscode.DebugSession) { this.modules.delete(session); this.modulesChanged.fire(undefined); } private showModulesTreeView(showModules: boolean) { vscode.commands.executeCommand( "setContext", "lldb-dap.showModules", showModules, ); } private moduleChangedListener(session: vscode.DebugSession | undefined) { if (!session) { this.showModulesTreeView(false); return; } if (session == vscode.debug.activeDebugSession) { const sessionHasModules = this.modules.get(session) != undefined; this.showModulesTreeView(sessionHasModules); } } private onDidSendMessage( session: vscode.DebugSession, message: DebugProtocol.ProtocolMessage, ) { if (isEvent(message, "module")) { const { module, reason } = message.body; const modules = this.modules.get(session) ?? []; switch (reason) { case "new": case "changed": { const index = modules.findIndex((m) => m.id === module.id); if (index !== -1) { modules[index] = module; } else { modules.push(module); } break; } case "removed": { const index = modules.findIndex((m) => m.id === module.id); if (index !== -1) { modules.splice(index, 1); } break; } default: console.error("unexpected module event reason"); break; } this.modules.set(session, modules); this.modulesChanged.fire(session); } } }