// 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;
}
}
}
}