Index: tools/turbolizer/text-view.js |
diff --git a/tools/turbolizer/text-view.js b/tools/turbolizer/text-view.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..c41c25b768253e6d9c1b1a3cc744bcf6553bbfc4 |
--- /dev/null |
+++ b/tools/turbolizer/text-view.js |
@@ -0,0 +1,394 @@ |
+// Copyright 2015 the V8 project authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+"use strict"; |
+ |
+class TextView extends View { |
+ constructor(id, broker, patterns, allowSpanSelection) { |
+ super(id, broker); |
+ let view = this; |
+ view.sortedPositionList = []; |
+ view.nodePositionMap = []; |
+ view.positionNodeMap = []; |
+ view.textListNode = view.divNode.getElementsByTagName('ul')[0]; |
+ view.fillerSvgElement = view.divElement.append("svg").attr('version','1.1').attr("width", "0"); |
+ view.patterns = patterns; |
+ view.allowSpanSelection = allowSpanSelection; |
+ view.nodeToLineMap = []; |
+ var selectionHandler = { |
+ clear: function() { |
+ broker.clear(selectionHandler); |
+ }, |
+ select: function(items, selected) { |
+ for (let i of items) { |
+ if (selected) { |
+ i.classList.add("selected"); |
+ } else { |
+ i.classList.remove("selected"); |
+ } |
+ } |
+ broker.select(selectionHandler, view.getRanges(items), selected); |
+ }, |
+ selectionDifference: function(span1, inclusive1, span2, inclusive2) { |
+ return null; |
+ }, |
+ brokeredSelect: function(ranges, selected) { |
+ let locations = view.rangesToLocations(ranges); |
+ view.selectLocations(locations, selected, true); |
+ }, |
+ brokeredClear: function() { |
+ view.selection.clear(); |
+ } |
+ }; |
+ view.selection = new Selection(selectionHandler); |
+ broker.addSelectionHandler(selectionHandler); |
+ } |
+ |
+ setPatterns(patterns) { |
+ let view = this; |
+ view.patterns = patterns; |
+ } |
+ |
+ clearText() { |
+ let view = this; |
+ while (view.textListNode.firstChild) { |
+ view.textListNode.removeChild(view.textListNode.firstChild); |
+ } |
+ } |
+ |
+ rangeToLocation(range) { |
+ return range; |
+ } |
+ |
+ rangesToLocations(ranges) { |
+ let view = this; |
+ let nodes = new Set(); |
+ let result = []; |
+ for (let range of ranges) { |
+ let start = range[0]; |
+ let end = range[1]; |
+ let location = { pos_start: start, pos_end: end }; |
+ if (range[2] !== null && range[2] != -1) { |
+ location.node_id = range[2]; |
+ if (range[0] == -1 && range[1] == -1) { |
+ location.pos_start = view.nodePositionMap[location.node_id]; |
+ location.pos_end = location.pos_start + 1; |
+ } |
+ } else { |
+ if (range[0] != undefined) { |
+ location.pos_start = range[0]; |
+ location.pos_end = range[1]; |
+ } |
+ } |
+ result.push(location); |
+ } |
+ return result; |
+ } |
+ |
+ sameLocation(l1, l2) { |
+ let view = this; |
+ if (l1.block_id != undefined && l2.block_id != undefined && |
+ l1.block_id == l2.block_id && l1.node_id === undefined) { |
+ return true; |
+ } |
+ |
+ if (l1.address != undefined && l1.address == l2.address) { |
+ return true; |
+ } |
+ |
+ let node1 = l1.node_id; |
+ let node2 = l2.node_id; |
+ |
+ if (node1 === undefined && node2 == undefined) { |
+ if (l1.pos_start === undefined || l2.pos_start == undefined) { |
+ return false; |
+ } |
+ if (l1.pos_start == -1 || l2.pos_start == -1) { |
+ return false; |
+ } |
+ if (l1.pos_start < l2.pos_start) { |
+ return l1.pos_end > l2.pos_start; |
+ } { |
+ return l1.pos_start < l2.pos_end; |
+ } |
+ } |
+ |
+ if (node1 === undefined) { |
+ let lower = lowerBound(view.positionNodeMap, l1.pos_start, undefined, function(a, b) { |
+ var node = a[b]; |
+ return view.nodePositionMap[node]; |
+ } ); |
+ while (++lower < view.positionNodeMap.length && |
+ view.nodePositionMap[view.positionNodeMap[lower]] < l1.pos_end) { |
+ if (view.positionNodeMap[lower] == node2) { |
+ return true; |
+ } |
+ } |
+ return false; |
+ } |
+ |
+ if (node2 === undefined) { |
+ let lower = lowerBound(view.positionNodeMap, l2.pos_start, undefined, function(a, b) { |
+ var node = a[b]; |
+ return view.nodePositionMap[node]; |
+ } ); |
+ while (++lower < view.positionNodeMap.length && |
+ view.nodePositionMap[view.positionNodeMap[lower]] < l2.pos_end) { |
+ if (view.positionNodeMap[lower] == node1) { |
+ return true; |
+ } |
+ } |
+ return false; |
+ } |
+ |
+ return l1.node_id == l2.node_id; |
+ } |
+ |
+ setNodePositionMap(map) { |
+ let view = this; |
+ view.nodePositionMap = map; |
+ view.positionNodeMap = []; |
+ view.sortedPositionList = []; |
+ let next = 0; |
+ for (let i in view.nodePositionMap) { |
+ view.sortedPositionList[next] = Number(view.nodePositionMap[i]); |
+ view.positionNodeMap[next++] = i; |
+ } |
+ view.sortedPositionList = sortUnique(view.sortedPositionList, |
+ function(a,b) { return a - b; }); |
+ this.positionNodeMap.sort(function(a,b) { |
+ let result = view.nodePositionMap[a] - view.nodePositionMap[b]; |
+ if (result != 0) return result; |
+ return a - b; |
+ }); |
+ } |
+ |
+ selectLocations(locations, selected, makeVisible) { |
+ let view = this; |
+ for (let l of locations) { |
+ for (let i = 0; i < view.textListNode.children.length; ++i) { |
+ let child = view.textListNode.children[i]; |
+ if (child.location != undefined && view.sameLocation(l, child.location)) { |
+ view.selectCommon(child, selected, makeVisible); |
+ } |
+ } |
+ } |
+ } |
+ |
+ getRanges(items) { |
+ let result = []; |
+ let lastObject = null; |
+ for (let i of items) { |
+ if (i.location) { |
+ let location = i.location; |
+ let start = -1; |
+ let end = -1; |
+ let node_id = -1; |
+ if (location.node_id !== undefined) { |
+ node_id = location.node_id; |
+ } |
+ if (location.pos_start !== undefined) { |
+ start = location.pos_start; |
+ end = location.pos_end; |
+ } else { |
+ if (this.nodePositionMap && this.nodePositionMap[node_id]) { |
+ start = this.nodePositionMap[node_id]; |
+ end = start + 1; |
+ } |
+ } |
+ if (lastObject == null || |
+ (lastObject[2] != node_id || |
+ lastObject[0] != start || |
+ lastObject[1] != end)) { |
+ lastObject = [start, end, node_id]; |
+ result.push(lastObject); |
+ } |
+ } |
+ } |
+ return result; |
+ } |
+ |
+ createFragment(text, style) { |
+ let view = this; |
+ let span = document.createElement("SPAN"); |
+ span.onmousedown = function(e) { |
+ view.mouseDownSpan(span, e); |
+ } |
+ if (style != undefined) { |
+ span.classList.add(style); |
+ } |
+ span.innerText = text; |
+ return span; |
+ } |
+ |
+ appendFragment(li, fragment) { |
+ li.appendChild(fragment); |
+ } |
+ |
+ processLine(line) { |
+ let view = this; |
+ let result = []; |
+ let patternSet = 0; |
+ while (true) { |
+ let beforeLine = line; |
+ for (let pattern of view.patterns[patternSet]) { |
+ let matches = line.match(pattern[0]); |
+ if (matches != null) { |
+ if (matches[0] != '') { |
+ let style = pattern[1] != null ? pattern[1] : {}; |
+ let text = matches[0]; |
+ if (text != '') { |
+ let fragment = view.createFragment(matches[0], style.css); |
+ if (style.link) { |
+ fragment.classList.add('linkable-text'); |
+ fragment.link = style.link; |
+ } |
+ result.push(fragment); |
+ if (style.location != undefined) { |
+ let location = style.location(text); |
+ if (location != undefined) { |
+ fragment.location = location; |
+ } |
+ } |
+ } |
+ line = line.substr(matches[0].length); |
+ } |
+ let nextPatternSet = patternSet; |
+ if (pattern.length > 2) { |
+ nextPatternSet = pattern[2]; |
+ } |
+ if (line == "") { |
+ if (nextPatternSet != -1) { |
+ throw("illegal parsing state in text-view in patternSet" + patternSet); |
+ } |
+ return result; |
+ } |
+ patternSet = nextPatternSet; |
+ break; |
+ } |
+ } |
+ if (beforeLine == line) { |
+ throw("input not consumed in text-view in patternSet" + patternSet); |
+ } |
+ } |
+ } |
+ |
+ select(s, selected, makeVisible) { |
+ let view = this; |
+ view.selection.clear(); |
+ view.selectCommon(s, selected, makeVisible); |
+ } |
+ |
+ selectCommon(s, selected, makeVisible) { |
+ let view = this; |
+ let firstSelect = makeVisible && view.selection.isEmpty(); |
+ if ((typeof s) === 'function') { |
+ for (let i = 0; i < view.textListNode.children.length; ++i) { |
+ let child = view.textListNode.children[i]; |
+ if (child.location && s(child.location)) { |
+ if (firstSelect) { |
+ makeContainerPosVisible(view.parentNode, child.offsetTop); |
+ firstSelect = false; |
+ } |
+ view.selection.select(child, selected); |
+ } |
+ } |
+ } else if (s.length) { |
+ for (let i of s) { |
+ if (firstSelect) { |
+ makeContainerPosVisible(view.parentNode, i.offsetTop); |
+ firstSelect = false; |
+ } |
+ view.selection.select(i, selected); |
+ } |
+ } else { |
+ if (firstSelect) { |
+ makeContainerPosVisible(view.parentNode, s.offsetTop); |
+ firstSelect = false; |
+ } |
+ view.selection.select(s, selected); |
+ } |
+ } |
+ |
+ mouseDownLine(li, e) { |
+ let view = this; |
+ e.stopPropagation(); |
+ if (!e.shiftKey) { |
+ view.selection.clear(); |
+ } |
+ if (li.location != undefined) { |
+ view.selectLocations([li.location], true, false); |
+ } |
+ } |
+ |
+ mouseDownSpan(span, e) { |
+ let view = this; |
+ if (view.allowSpanSelection) { |
+ e.stopPropagation(); |
+ if (!e.shiftKey) { |
+ view.selection.clear(); |
+ } |
+ select(li, true); |
+ } else if (span.link) { |
+ span.link(span.textContent); |
+ e.stopPropagation(); |
+ } |
+ } |
+ |
+ processText(text) { |
+ let view = this; |
+ let textLines = text.split(/[\n]/); |
+ let lineNo = 0; |
+ for (let line of textLines) { |
+ let li = document.createElement("LI"); |
+ li.onmousedown = function(e) { |
+ view.mouseDownLine(li, e); |
+ } |
+ li.className = "nolinenums"; |
+ li.lineNo = lineNo++; |
+ let fragments = view.processLine(line); |
+ for (let fragment of fragments) { |
+ view.appendFragment(li, fragment); |
+ } |
+ let lineLocation = view.lineLocation(li); |
+ if (lineLocation != undefined) { |
+ li.location = lineLocation; |
+ } |
+ view.textListNode.appendChild(li); |
+ } |
+ } |
+ |
+ initializeContent(data, rememberedSelection) { |
+ let view = this; |
+ view.clearText(); |
+ view.processText(data); |
+ var fillerSize = document.documentElement.clientHeight - |
+ view.textListNode.clientHeight; |
+ if (fillerSize < 0) { |
+ fillerSize = 0; |
+ } |
+ view.fillerSvgElement.attr("height", fillerSize); |
+ } |
+ |
+ deleteContent() { |
+ } |
+ |
+ isScrollable() { |
+ return true; |
+ } |
+ |
+ detachSelection() { |
+ return null; |
+ } |
+ |
+ lineLocation(li) { |
+ let view = this; |
+ for (let i = 0; i < li.children.length; ++i) { |
+ let fragment = li.children[i]; |
+ if (fragment.location != undefined && !view.allowSpanSelection) { |
+ return fragment.location; |
+ } |
+ } |
+ } |
+} |