blob: c41c25b768253e6d9c1b1a3cc744bcf6553bbfc4 [file] [log] [blame]
Ben Murdoch61f157c2016-09-16 13:49:30 +01001// 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}