From e52907f807dd64147deac064973325d2409ba144 Mon Sep 17 00:00:00 2001 From: danno Date: Mon, 13 Jun 2016 06:21:23 -0700 Subject: [PATCH] [turbolizer] Features and bug-fixes Fix bugs and add a few small useful features: * Fix Schedule view to properly parse schedule output and respond to switching back to graph views. * Add shorcuts for showing edges of selected nodes. - 'i' shows all inputs - 'o' shows all outputs - '1'-'9' shows all nodes nth input where 1 <= n <= 9 - 'c' shows all control inputs - 'e' shows all effect inputs * Holding the control key down when using a edge-showing shortcut toggles edge state rather the just showing. * 'a' selects all nodes in graph view. * Node selection is preserved between graph views and Schedule views. * Holding control key down when using regular expression search shows currently hidden nodes that match the regex search. * Pane expansion buttons now respond to clicks in entire button area. * Default text in regex search box makes searching more discoverable. Review-Url: https://codereview.chromium.org/2059193002 Cr-Commit-Position: refs/heads/master@{#36932} --- tools/turbolizer/graph-view.js | 175 ++++++++++++++++++++++---- tools/turbolizer/schedule-view.js | 23 +++- tools/turbolizer/turbo-visualizer.css | 3 +- tools/turbolizer/turbo-visualizer.js | 28 ++--- 4 files changed, 183 insertions(+), 46 deletions(-) diff --git a/tools/turbolizer/graph-view.js b/tools/turbolizer/graph-view.js index aa1b638f57..efddc90a7d 100644 --- a/tools/turbolizer/graph-view.js +++ b/tools/turbolizer/graph-view.js @@ -173,16 +173,30 @@ class GraphView extends View { if (d3.event.keyCode == 13) { graph.state.selection.clear(); var reg = new RegExp(this.value); + var filterFunction = function(n) { + return (reg.exec(n.getDisplayLabel()) != null || + (graph.state.showTypes && reg.exec(n.getDisplayType())) || + reg.exec(n.opcode) != null); + }; + if (d3.event.ctrlKey) { + graph.nodes.forEach(function(n, i) { + if (filterFunction(n)) { + n.visible = true; + } + }); + graph.updateGraphVisibility(); + } var selected = graph.visibleNodes.each(function(n) { - if (reg.exec(n.getDisplayLabel()) != null || - (graph.state.showTypes && reg.exec(n.getDisplayType())) || - reg.exec(n.opcode) != null) { + if (filterFunction(n)) { graph.state.selection.select(this, true); } }); + graph.connectVisibleSelectedNodes(); + graph.updateGraphVisibility(); this.blur(); graph.viewSelection(); } + d3.event.stopPropagation(); }); // listen for key events @@ -243,9 +257,10 @@ class GraphView extends View { } initializeContent(data, rememberedSelection) { - this.createGraph(data); + this.createGraph(data, rememberedSelection); if (rememberedSelection != null) { this.attachSelection(rememberedSelection); + this.connectVisibleSelectedNodes(); } this.updateGraphVisibility(); } @@ -259,7 +274,7 @@ class GraphView extends View { } }; - createGraph(data) { + createGraph(data, initiallyVisibileIds) { var g = this; g.nodes = data.nodes; g.nodeMap = []; @@ -298,6 +313,11 @@ class GraphView extends View { }); g.nodes.forEach(function(n, i) { n.visible = isNodeInitiallyVisible(n); + if (initiallyVisibileIds != undefined) { + if (initiallyVisibileIds.has(n.id)) { + n.visible = true; + } + } }); g.fitGraphViewToWindow(); g.updateGraphVisibility(); @@ -306,6 +326,23 @@ class GraphView extends View { g.viewWholeGraph(); } + connectVisibleSelectedNodes() { + var graph = this; + graph.state.selection.selection.forEach(function(element) { + var edgeNumber = 0; + element.__data__.inputs.forEach(function(edge) { + if (edge.source.visible && edge.target.visible) { + edge.visible = true; + } + }); + element.__data__.outputs.forEach(function(edge) { + if (edge.source.visible && edge.target.visible) { + edge.visible = true; + } + }); + }); + } + updateInputAndOutputBubbles() { var g = this; var s = g.visibleBubbles; @@ -359,11 +396,11 @@ class GraphView extends View { detachSelection() { var selection = this.state.selection.detachSelection(); - var result = new Set(); + var s = new Set(); for (var i of selection) { - result.add(i.__data__.id); + s.add(i.__data__.id); }; - return result; + return s; } pathMouseDown(path, d) { @@ -436,6 +473,17 @@ class GraphView extends View { }); } + selectAllNodes(inEdges, filter) { + var graph = this; + if (!d3.event.shiftKey) { + graph.state.selection.clear(); + } + graph.visibleNodes.each(function(n) { + graph.state.selection.select(this, true); + }); + graph.updateGraphVisibility(); + } + svgMouseDown() { this.state.graphMouseDown = true; } @@ -463,36 +511,109 @@ class GraphView extends View { // Don't handle key press repetition if(state.lastKeyDown !== -1) return; - var getEdgeFrontier = function(inEdges) { + var getEdgeFrontier = function(inEdges, edgeFilter) { var frontierSet = new Set(); + var newState = true; + if (d3.event.ctrlKey) { + state.selection.selection.forEach(function(element) { + var edges = inEdges ? element.__data__.inputs : element.__data__.outputs; + var edgeNumber = 0; + // Control key toggles edges rather than just turning them on + edges.forEach(function(i) { + if (edgeFilter == undefined || edgeFilter(i, edgeNumber)) { + if (i.visible) { + newState = false; + } + } + ++edgeNumber; + }); + }); + } state.selection.selection.forEach(function(element) { - var nodes = inEdges ? element.__data__.inputs : element.__data__.outputs; - nodes.forEach(function(i) { - i.visible = true; - var candidate = inEdges ? i.source : i.target; - candidate.visible = true; - frontierSet.add(candidate); + var edges = inEdges ? element.__data__.inputs : element.__data__.outputs; + var edgeNumber = 0; + edges.forEach(function(i) { + if (edgeFilter == undefined || edgeFilter(i, edgeNumber)) { + i.visible = newState; + if (newState) { + var candidate = inEdges ? i.source : i.target; + candidate.visible = true; + frontierSet.add(candidate); + } + } + ++edgeNumber; }); }); graph.updateGraphVisibility(); - return graph.visibleNodes.filter(function(n) { - return frontierSet.has(n); - }); + if (newState) { + return graph.visibleNodes.filter(function(n) { + return frontierSet.has(n); + }); + } else { + return undefined; + } + } + + var selectNodesThroughEdges = function(inEdges, filter, reselect) { + var frontier = getEdgeFrontier(inEdges, filter); + if (frontier != undefined) { + if (reselect) { + if (!d3.event.shiftKey) { + state.selection.clear(); + } + frontier.each(function(n) { + state.selection.select(this, true); + }); + } + graph.updateGraphVisibility(); + } + allowRepetition = false; } var allowRepetition = true; switch(d3.event.keyCode) { + case 49: + case 50: + case 51: + case 52: + case 53: + case 54: + case 55: + case 56: + case 57: + // '1'-'9' + selectNodesThroughEdges(true, + (edge, index) => { return index == (d3.event.keyCode - 49); }, + false); + break; + case 67: + // 'c' + selectNodesThroughEdges(true, + (edge, index) => { return edge.type == 'control'; }, + false); + break; + case 69: + // 'e' + selectNodesThroughEdges(true, + (edge, index) => { return edge.type == 'effect'; }, + false); + break; + case 79: + // 'o' + selectNodesThroughEdges(false, undefined, false); + break; + case 73: + // 'i' + selectNodesThroughEdges(true, undefined, false); + break; + case 65: + // 'a' + graph.selectAllNodes(); + allowRepetition = false; + break; case 38: case 40: { - var frontier = getEdgeFrontier(d3.event.keyCode == 38); - if (!d3.event.shiftKey) { - state.selection.clear(); - } - frontier.each(function(n) { - state.selection.select(this, true); - }); - graph.updateGraphVisibility(); - allowRepetition = false; + selectNodesThroughEdges(d3.event.keyCode == 38, undefined, true); break; } } diff --git a/tools/turbolizer/schedule-view.js b/tools/turbolizer/schedule-view.js index ca1cf8ae8e..58d0af7ec5 100644 --- a/tools/turbolizer/schedule-view.js +++ b/tools/turbolizer/schedule-view.js @@ -76,8 +76,8 @@ class ScheduleView extends TextView { ], // Parse opcode including [] [ - [/^[A-Za-z0-9_]+(\[[^\]]+\])?$/, NODE_STYLE, -1], - [/^[A-Za-z0-9_]+(\[[^\]]+\])?/, NODE_STYLE, 3] + [/^[A-Za-z0-9_]+(\[.+])?$/, NODE_STYLE, -1], + [/^[A-Za-z0-9_]+(\[.+])?/, NODE_STYLE, 3] ], // Parse optional parameters [ @@ -105,4 +105,23 @@ class ScheduleView extends TextView { this.setPatterns(patterns); this.setNodePositionMap(nodePositionMap); } + + initializeContent(data, rememberedSelection) { + super.initializeContent(data, rememberedSelection); + var graph = this; + var locations = []; + for (var id of rememberedSelection) { + locations.push({ node_id : id }); + } + this.selectLocations(locations, true, false); + } + + detachSelection() { + var selection = this.selection.detachSelection(); + var s = new Set(); + for (var i of selection) { + s.add(i.location.node_id); + }; + return s; + } } diff --git a/tools/turbolizer/turbo-visualizer.css b/tools/turbolizer/turbo-visualizer.css index 4f7190c23b..3c5c9d682a 100644 --- a/tools/turbolizer/turbo-visualizer.css +++ b/tools/turbolizer/turbo-visualizer.css @@ -16,6 +16,7 @@ padding: 0.5em; z-index: 5; opacity: 0.7; + cursor: pointer; } .search-input { @@ -227,7 +228,7 @@ ul.noindent { -webkit-margin-after: 0px; } -input:hover { +input:hover, .collapse-pane:hover input { opacity: 1; cursor: pointer; } diff --git a/tools/turbolizer/turbo-visualizer.js b/tools/turbolizer/turbo-visualizer.js index 7c989e1632..d85be6b863 100644 --- a/tools/turbolizer/turbo-visualizer.js +++ b/tools/turbolizer/turbo-visualizer.js @@ -44,6 +44,10 @@ document.onload = (function(d3){ } } + function toggleSourceExpanded() { + setSourceExpanded(!sourceExpanded); + } + function setSourceExpanded(newState) { sourceExpanded = newState; updatePanes(); @@ -60,6 +64,10 @@ document.onload = (function(d3){ } } + function toggleDisassemblyExpanded() { + setDisassemblyExpanded(!disassemblyExpanded); + } + function setDisassemblyExpanded(newState) { disassemblyExpanded = newState; updatePanes(); @@ -118,26 +126,14 @@ document.onload = (function(d3){ selectionBroker = new SelectionBroker(); function initializeHandlers(g) { - d3.select("#source-expand").on("click", function(){ - setSourceExpanded(true); + d3.select("#source-collapse").on("click", function(){ + toggleSourceExpanded(true); setTimeout(function(){ g.fitGraphViewToWindow(); }, 1000); }); - d3.select("#source-shrink").on("click", function(){ - setSourceExpanded(false); - setTimeout(function(){ - g.fitGraphViewToWindow(); - }, 1000); - }); - d3.select("#disassembly-expand").on("click", function(){ - setDisassemblyExpanded(true); - setTimeout(function(){ - g.fitGraphViewToWindow(); - }, 1000); - }); - d3.select("#disassembly-shrink").on("click", function(){ - setDisassemblyExpanded(false); + d3.select("#disassembly-collapse").on("click", function(){ + toggleDisassemblyExpanded(); setTimeout(function(){ g.fitGraphViewToWindow(); }, 1000);