From 37ad644762e30df7a36261ce8520f66eecaed397 Mon Sep 17 00:00:00 2001 From: Jon Lamb Date: Thu, 11 Apr 2024 03:54:17 -0700 Subject: [PATCH] Support multiple selected timelines in event view --- vscode/package.json | 4 - vscode/src/events.ts | 218 +++++++++++++++++------------ vscode/src/main.ts | 13 +- vscode/src/modalityEventInspect.ts | 2 +- vscode/src/timelines.ts | 10 -- 5 files changed, 143 insertions(+), 104 deletions(-) diff --git a/vscode/package.json b/vscode/package.json index 33525c3..b73c7bb 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -634,10 +634,6 @@ "title": "Refresh Event List", "icon": "$(refresh)" }, - { - "command": "auxon.events.setSelectedTimeline", - "title": "Update events summary for the selected timeline" - }, { "command": "auxon.events.createEventTimingNotebook", "title": "View Event Timing Plots in a Jupyter Notebook" diff --git a/vscode/src/events.ts b/vscode/src/events.ts index b48ffd9..4db04f6 100644 --- a/vscode/src/events.ts +++ b/vscode/src/events.ts @@ -7,13 +7,16 @@ import * as modalityEventInspect from "./modalityEventInspect"; import * as commonNotebookCells from "./notebooks/common.json"; import * as eventTimingNotebookCells from "./notebooks/eventTiming.json"; -import * as eventAttributeValuesNotebookCells from "./notebooks/eventAttributeValues.json"; import * as eventMultiAttributeValuesNotebookCells from "./notebooks/eventMultiAttributeValues.json"; type JupyterNotebookCell = (typeof commonNotebookCells.cells)[0]; +export interface SelectedTimeline { + timelineId: api.TimelineId; + timelineName: string; +} + export class EventsTreeDataProvider implements vscode.TreeDataProvider { - selectedTimelineId?: api.TimelineId = undefined; - selectedTimelineName?: string = undefined; + selectedTimelines: SelectedTimeline[] = []; view: vscode.TreeView; private _onDidChangeTreeData: vscode.EventEmitter = @@ -34,9 +37,6 @@ export class EventsTreeDataProvider implements vscode.TreeDataProvider this.refresh()), vscode.commands.registerCommand("auxon.events.logSelected", () => this.logSelectedCommand()), - vscode.commands.registerCommand("auxon.events.setSelectedTimeline", (timelineId, timelineName) => - this.setSelectedTimeline(timelineId, timelineName) - ), vscode.commands.registerCommand("auxon.events.createEventTimingNotebook", async (item) => this.createEventTimingNotebook(item) ), @@ -72,76 +72,81 @@ export class EventsTreeDataProvider implements vscode.TreeDataProvider { - if (!this.selectedTimelineId) { + if (this.selectedTimelines.length === 0) { return []; } if (!element) { const children = []; - const eventsSummary = await this.apiClient.events().eventsSummaryForTimeline(this.selectedTimelineId); - for (const summary of eventsSummary.events) { - let name = summary.name; - if (name == null) { - name = ""; - } - const attrs = []; - for (const attr of summary.attributes) { - if (attr.startsWith("event.")) { - attrs.push(attr.replace("event.", "")); + for (const selectedTimeline of this.selectedTimelines) { + const eventsSummary = await this.apiClient + .events() + .eventsSummaryForTimeline(selectedTimeline.timelineId); + for (const summary of eventsSummary.events) { + let name = summary.name; + if (name == null) { + name = ""; + } + const attrs = []; + for (const attr of summary.attributes) { + if (attr.startsWith("event.")) { + attrs.push(attr.replace("event.", "")); + } } + children.push( + new EventNameTreeItemData( + name, + summary.n_instances, + attrs, + selectedTimeline.timelineName, + selectedTimeline.timelineId + ) + ); } - children.push(new EventNameTreeItemData(name, summary.n_instances, attrs, this.selectedTimelineId)); } children.sort((a, b) => a.eventName.localeCompare(b.eventName)); return children; } else { if (element instanceof EventNameTreeItemData) { - return element.attributes.map((attrKey) => new EventAttributeTreeItemData(element.eventName, attrKey)); + return element.attributes.map( + (attrKey) => + new EventAttributeTreeItemData( + element.eventName, + element.timelineName, + element.timelineId, + attrKey + ) + ); } else { return []; } } } - setSelectedTimeline(timelineId?: api.TimelineId, timelineName?: string) { - if (timelineId && timelineName) { - this.selectedTimelineId = timelineId; - this.selectedTimelineName = timelineName; - this.refresh(); - } + setSelectedTimelines(timelines: SelectedTimeline[]) { + this.selectedTimelines = timelines; + this.refresh(); } logSelectedCommand() { - if (this.selectedTimelineId == null) { + if (this.selectedTimelines.length === 0) { vscode.window.showWarningMessage(`No timeline is selected`); return; } const thingsToLog = []; - const literalTimelineId = "%" + this.selectedTimelineId.replace(/-/g, ""); - for (const itemData of this.view.selection) { - thingsToLog.push(`${itemData.eventName}@*(_.timeline.id=${literalTimelineId})`); - } - vscode.commands.executeCommand( - modalityLog.MODALITY_LOG_COMMAND, - new modalityLog.ModalityLogCommandArgs({ thingToLog: thingsToLog }) - ); - } - - async createEventTimingNotebook(item: EventNameTreeItemData) { - let selectedEventNames = this.view.selection.map((data) => data.eventName); - // Add the item the command was executed on, it may not be in the selection - selectedEventNames.push(item.eventName); - selectedEventNames = [...new Set(selectedEventNames)]; // dedupe - for (const eventName of selectedEventNames) { - const varMap = this.templateVariableMap(); - if (varMap == null) { - return; + if (itemData instanceof EventNameTreeItemData && itemData.timelineId) { + const literalTimelineId = "%" + itemData.timelineId.replace(/-/g, ""); + thingsToLog.push(`"${itemData.eventName}"@*(_.timeline.id=${literalTimelineId})`); } - varMap["eventName"] = eventName; - await this.createJupyterNotebook(eventTimingNotebookCells.cells, varMap); + } + if (thingsToLog.length !== 0) { + vscode.commands.executeCommand( + modalityLog.MODALITY_LOG_COMMAND, + new modalityLog.ModalityLogCommandArgs({ thingToLog: thingsToLog }) + ); } } @@ -152,39 +157,76 @@ export class EventsTreeDataProvider implements vscode.TreeDataProvider { + return { name: data.eventName, timelineId: data.timelineId, timelineName: data.timelineName }; + }); + // dedupe + selectedEvents = selectedEvents.filter( + (v, i, a) => a.findIndex((v2) => JSON.stringify(v2) === JSON.stringify(v)) === i + ); + for (const event of selectedEvents) { + const varMap = this.templateVariableMap(event.timelineName, event.timelineId); if (varMap == null) { return; } + varMap["eventName"] = event.name; + await this.createJupyterNotebook(eventTimingNotebookCells.cells, varMap); + } + } - varMap.eventName = item.eventName; - varMap.eventAttribute = item.attribute; - await this.createJupyterNotebook(eventAttributeValuesNotebookCells.cells, varMap); - } else { - let selectedEventAttrs = [...this.view.selection, ...[item]]; - selectedEventAttrs = [...new Set(selectedEventAttrs)]; // dedupe + async createEventAttrNotebook(item: EventAttributeTreeItemData) { + type EventName = string; + type EventMeta = { + timelineName: string; + attributes: Set; + }; - const attributesByEvent = new Map(); - for (const ev of selectedEventAttrs) { - if (!(ev instanceof EventAttributeTreeItemData)) { - throw new Error("Internal error: event tree node not of expected type"); + // Group attributes by event@timeline, we'll make a notebook for + // each group + const eventGroups = new Map>(); + for (const data of [...this.view.selection, ...[item]]) { + if (data instanceof EventAttributeTreeItemData) { + let eventToMeta = null; + if (!eventGroups.has(data.timelineId)) { + eventGroups.set(data.timelineId, new Map()); } - if (attributesByEvent.has(ev.eventName)) { - const attributes = attributesByEvent.get(ev.eventName); - if (attributes != null) { - attributes.push(ev.attribute); + eventToMeta = eventGroups.get(data.timelineId); + + if (eventToMeta != null) { + let eventMeta = null; + if (!eventToMeta.has(data.eventName)) { + eventToMeta.set(data.eventName, { + timelineName: data.timelineName, + attributes: new Set(), + }); + } + eventMeta = eventToMeta.get(data.eventName); + + if (eventMeta != null) { + eventMeta.attributes.add(data.attribute); } - } else { - const attributes = []; - attributes.push(ev.attribute); - attributesByEvent.set(ev.eventName, attributes); } } + } - attributesByEvent.forEach(async (attributes: string[], eventName: string) => { - const varMap = this.templateVariableMap(); + for (const [timelineId, eventToMeta] of eventGroups) { + for (const [eventName, eventMeta] of eventToMeta) { + const varMap = this.templateVariableMap(eventMeta.timelineName, timelineId); if (varMap == null) { return; } @@ -193,19 +235,19 @@ export class EventsTreeDataProvider implements vscode.TreeDataProvider { + eventAttributesList.push("'event." + attr + "'"); + varMap["eventAttribute" + i] = attr; const newSrc = srcCell.source[0].replace(/eventAttribute/g, "eventAttribute" + i); cells[1].source.push(newSrc); - } + }); cells[1].source.push(figShowCell.source[0]); varMap.eventAttributes = eventAttributesList.join(", "); await this.createJupyterNotebook(cells, varMap); - }); + } } } @@ -262,12 +304,12 @@ export class EventsTreeDataProvider implements vscode.TreeDataProvider { @@ -277,16 +319,11 @@ export class EventsTreeDataProvider implements vscode.TreeDataProvider { + const selection = event.selection + .filter((item) => item instanceof timelines.TimelineLeafTreeItemData && item.timelineId != undefined) + .map((item) => { + return { timelineId: item.timelineId as api.TimelineId, timelineName: item.name }; + }); + eventsProvider.setSelectedTimelines(selection); + }); new specs.SpecsTreeDataProvider(apiClient, specCoverageProvider, wss, context); diff --git a/vscode/src/modalityEventInspect.ts b/vscode/src/modalityEventInspect.ts index e739f7e..7c1c073 100644 --- a/vscode/src/modalityEventInspect.ts +++ b/vscode/src/modalityEventInspect.ts @@ -13,7 +13,7 @@ function runModalityEventInspectCommand(event: string) { const modality = config.toolPath("modality"); // We're going to send the text of the command line to the terminal. Build up the args list here. - const modalityArgs = ["LESS=R", modality, "event", "inspect", event]; + const modalityArgs = ["LESS=R", modality, "event", "inspect", `"${event}"`]; for (const extra of config.extraCliArgs("modality event inspect")) { modalityArgs.push(extra); diff --git a/vscode/src/timelines.ts b/vscode/src/timelines.ts index 57df2f8..af80f18 100644 --- a/vscode/src/timelines.ts +++ b/vscode/src/timelines.ts @@ -298,16 +298,6 @@ abstract class TimelineTreeItemData { item.tooltip = this.tooltip; item.iconPath = this.iconPath; - // Timeline selection updates the events summary view - if (this.contextValue == "timeline") { - const command = { - title: "Update events summary for the selected timeline", - command: "auxon.events.setSelectedTimeline", - arguments: [this.timelineId, this.name], - }; - item.command = command; - } - return item; }