1// Copyright 2015 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5"use strict";
6
7class TextView extends View {
8  constructor(id, broker, patterns, allowSpanSelection) {
9    super(id, broker);
10    let view = this;
11    view.sortedPositionList = [];
12    view.nodePositionMap = [];
13    view.positionNodeMap = [];
14    view.textListNode = view.divNode.getElementsByTagName('ul')[0];
15    view.fillerSvgElement = view.divElement.append("svg").attr('version','1.1').attr("width", "0");
16    view.patterns = patterns;
17    view.allowSpanSelection = allowSpanSelection;
18    view.nodeToLineMap = [];
19    var selectionHandler = {
20      clear: function() {
21        broker.clear(selectionHandler);
22      },
23      select: function(items, selected) {
24        for (let i of items) {
25          if (selected) {
26            i.classList.add("selected");
27          } else {
28            i.classList.remove("selected");
29          }
30        }
31        broker.select(selectionHandler, view.getRanges(items), selected);
32      },
33      selectionDifference: function(span1, inclusive1, span2, inclusive2) {
34        return null;
35      },
36      brokeredSelect: function(ranges, selected) {
37        let locations = view.rangesToLocations(ranges);
38        view.selectLocations(locations, selected, true);
39      },
40      brokeredClear: function() {
41        view.selection.clear();
42      }
43    };
44    view.selection = new Selection(selectionHandler);
45    broker.addSelectionHandler(selectionHandler);
46  }
47
48  setPatterns(patterns) {
49    let view = this;
50    view.patterns = patterns;
51  }
52
53  clearText() {
54    let view = this;
55    while (view.textListNode.firstChild) {
56      view.textListNode.removeChild(view.textListNode.firstChild);
57    }
58  }
59
60  rangeToLocation(range) {
61    return range;
62  }
63
64  rangesToLocations(ranges) {
65    let view = this;
66    let nodes = new Set();
67    let result = [];
68    for (let range of ranges) {
69      let start = range[0];
70      let end = range[1];
71      let location = { pos_start: start, pos_end: end };
72      if (range[2] !== null && range[2] != -1) {
73        location.node_id = range[2];
74        if (range[0] == -1 && range[1] == -1) {
75          location.pos_start = view.nodePositionMap[location.node_id];
76          location.pos_end = location.pos_start + 1;
77        }
78      } else {
79        if (range[0] != undefined) {
80          location.pos_start = range[0];
81          location.pos_end = range[1];
82        }
83      }
84      result.push(location);
85    }
86    return result;
87  }
88
89  sameLocation(l1, l2) {
90    let view = this;
91    if (l1.block_id != undefined && l2.block_id != undefined &&
92      l1.block_id == l2.block_id && l1.node_id === undefined) {
93      return true;
94    }
95
96    if (l1.address != undefined && l1.address == l2.address) {
97      return true;
98    }
99
100    let node1 = l1.node_id;
101    let node2 = l2.node_id;
102
103    if (node1 === undefined && node2 == undefined) {
104      if (l1.pos_start === undefined || l2.pos_start == undefined) {
105        return false;
106      }
107      if (l1.pos_start == -1 || l2.pos_start == -1) {
108        return false;
109      }
110      if (l1.pos_start < l2.pos_start) {
111        return l1.pos_end > l2.pos_start;
112      } {
113        return l1.pos_start < l2.pos_end;
114      }
115    }
116
117    if (node1 === undefined) {
118      let lower = lowerBound(view.positionNodeMap, l1.pos_start, undefined, function(a, b) {
119        var node = a[b];
120        return view.nodePositionMap[node];
121      } );
122      while (++lower < view.positionNodeMap.length &&
123             view.nodePositionMap[view.positionNodeMap[lower]] < l1.pos_end) {
124        if (view.positionNodeMap[lower] == node2) {
125          return true;
126        }
127      }
128      return false;
129    }
130
131    if (node2 === undefined) {
132      let lower = lowerBound(view.positionNodeMap, l2.pos_start, undefined, function(a, b) {
133        var node = a[b];
134        return view.nodePositionMap[node];
135      } );
136      while (++lower < view.positionNodeMap.length &&
137             view.nodePositionMap[view.positionNodeMap[lower]] < l2.pos_end) {
138        if (view.positionNodeMap[lower] == node1) {
139          return true;
140        }
141      }
142      return false;
143    }
144
145    return l1.node_id == l2.node_id;
146  }
147
148  setNodePositionMap(map) {
149    let view = this;
150    view.nodePositionMap = map;
151    view.positionNodeMap = [];
152    view.sortedPositionList = [];
153    let next = 0;
154    for (let i in view.nodePositionMap) {
155      view.sortedPositionList[next] = Number(view.nodePositionMap[i]);
156      view.positionNodeMap[next++] = i;
157    }
158    view.sortedPositionList = sortUnique(view.sortedPositionList,
159                                         function(a,b) { return a - b; });
160    this.positionNodeMap.sort(function(a,b) {
161      let result = view.nodePositionMap[a] - view.nodePositionMap[b];
162      if (result != 0) return result;
163      return a - b;
164    });
165  }
166
167  selectLocations(locations, selected, makeVisible) {
168    let view = this;
169    for (let l of locations) {
170      for (let i = 0; i < view.textListNode.children.length; ++i) {
171        let child = view.textListNode.children[i];
172        if (child.location != undefined && view.sameLocation(l, child.location)) {
173          view.selectCommon(child, selected, makeVisible);
174        }
175      }
176    }
177  }
178
179  getRanges(items) {
180    let result = [];
181    let lastObject = null;
182    for (let i of items) {
183      if (i.location) {
184        let location = i.location;
185        let start = -1;
186        let end = -1;
187        let node_id = -1;
188        if (location.node_id !== undefined) {
189          node_id = location.node_id;
190        }
191        if (location.pos_start !== undefined) {
192          start = location.pos_start;
193          end = location.pos_end;
194        } else {
195          if (this.nodePositionMap && this.nodePositionMap[node_id]) {
196            start = this.nodePositionMap[node_id];
197            end = start + 1;
198          }
199        }
200        if (lastObject == null ||
201            (lastObject[2] != node_id ||
202             lastObject[0] != start ||
203             lastObject[1] != end)) {
204          lastObject = [start, end, node_id];
205          result.push(lastObject);
206        }
207      }
208    }
209    return result;
210  }
211
212  createFragment(text, style) {
213    let view = this;
214    let span = document.createElement("SPAN");
215    span.onmousedown = function(e) {
216      view.mouseDownSpan(span, e);
217    }
218    if (style != undefined) {
219      span.classList.add(style);
220    }
221    span.innerText = text;
222    return span;
223  }
224
225  appendFragment(li, fragment) {
226    li.appendChild(fragment);
227  }
228
229  processLine(line) {
230    let view = this;
231    let result = [];
232    let patternSet = 0;
233    while (true) {
234      let beforeLine = line;
235      for (let pattern of view.patterns[patternSet]) {
236        let matches = line.match(pattern[0]);
237        if (matches != null) {
238          if (matches[0] != '') {
239            let style = pattern[1] != null ? pattern[1] : {};
240            let text = matches[0];
241            if (text != '') {
242              let fragment = view.createFragment(matches[0], style.css);
243              if (style.link) {
244                fragment.classList.add('linkable-text');
245                fragment.link = style.link;
246              }
247              result.push(fragment);
248              if (style.location != undefined) {
249                let location = style.location(text);
250                if (location != undefined) {
251                  fragment.location = location;
252                }
253              }
254            }
255            line = line.substr(matches[0].length);
256          }
257          let nextPatternSet = patternSet;
258          if (pattern.length > 2) {
259            nextPatternSet = pattern[2];
260          }
261          if (line == "") {
262            if (nextPatternSet != -1) {
263              throw("illegal parsing state in text-view in patternSet" + patternSet);
264            }
265            return result;
266          }
267          patternSet = nextPatternSet;
268          break;
269        }
270      }
271      if (beforeLine == line) {
272        throw("input not consumed in text-view in patternSet" + patternSet);
273      }
274    }
275  }
276
277  select(s, selected, makeVisible) {
278    let view = this;
279    view.selection.clear();
280    view.selectCommon(s, selected, makeVisible);
281  }
282
283  selectCommon(s, selected, makeVisible) {
284    let view = this;
285    let firstSelect = makeVisible && view.selection.isEmpty();
286    if ((typeof s) === 'function') {
287      for (let i = 0; i < view.textListNode.children.length; ++i) {
288        let child = view.textListNode.children[i];
289        if (child.location && s(child.location)) {
290          if (firstSelect) {
291            makeContainerPosVisible(view.parentNode, child.offsetTop);
292            firstSelect = false;
293          }
294          view.selection.select(child, selected);
295        }
296      }
297    } else if (s.length) {
298      for (let i of s) {
299        if (firstSelect) {
300          makeContainerPosVisible(view.parentNode, i.offsetTop);
301          firstSelect = false;
302        }
303        view.selection.select(i, selected);
304      }
305    } else {
306      if (firstSelect) {
307        makeContainerPosVisible(view.parentNode, s.offsetTop);
308        firstSelect = false;
309      }
310      view.selection.select(s, selected);
311    }
312  }
313
314  mouseDownLine(li, e) {
315    let view = this;
316    e.stopPropagation();
317    if (!e.shiftKey) {
318      view.selection.clear();
319    }
320    if (li.location != undefined) {
321      view.selectLocations([li.location], true, false);
322    }
323  }
324
325  mouseDownSpan(span, e) {
326    let view = this;
327    if (view.allowSpanSelection) {
328      e.stopPropagation();
329      if (!e.shiftKey) {
330        view.selection.clear();
331      }
332      select(li, true);
333    } else if (span.link) {
334      span.link(span.textContent);
335      e.stopPropagation();
336    }
337  }
338
339  processText(text) {
340    let view = this;
341    let textLines = text.split(/[\n]/);
342    let lineNo = 0;
343    for (let line of textLines) {
344      let li = document.createElement("LI");
345      li.onmousedown = function(e) {
346        view.mouseDownLine(li, e);
347      }
348      li.className = "nolinenums";
349      li.lineNo = lineNo++;
350      let fragments = view.processLine(line);
351      for (let fragment of fragments) {
352        view.appendFragment(li, fragment);
353      }
354      let lineLocation = view.lineLocation(li);
355      if (lineLocation != undefined) {
356        li.location = lineLocation;
357      }
358      view.textListNode.appendChild(li);
359    }
360  }
361
362  initializeContent(data, rememberedSelection) {
363    let view = this;
364    view.clearText();
365    view.processText(data);
366    var fillerSize = document.documentElement.clientHeight -
367        view.textListNode.clientHeight;
368    if (fillerSize < 0) {
369      fillerSize = 0;
370    }
371    view.fillerSvgElement.attr("height", fillerSize);
372  }
373
374  deleteContent() {
375  }
376
377  isScrollable() {
378    return true;
379  }
380
381  detachSelection() {
382    return null;
383  }
384
385  lineLocation(li) {
386    let view = this;
387    for (let i = 0; i < li.children.length; ++i) {
388      let fragment = li.children[i];
389      if (fragment.location != undefined && !view.allowSpanSelection) {
390        return fragment.location;
391      }
392    }
393  }
394}
395