blob: 50db1e1c3a7b080d881e4eacf674c4e32a338238 [file] [log] [blame]
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<K extends keyof EventMap>(
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<vscode.DebugSession, DebugProtocol.Module[]>();
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<vscode.DebugSession | undefined> =
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<vscode.DebugAdapterTracker> {
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);
}
}
}