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
5import { PhaseView } from "../src/view";
6import { anyToString, ViewElements, isIterable } from "../src/util";
7import { MySelection } from "../src/selection";
8import { SourceResolver } from "./source-resolver";
9import { SelectionBroker } from "./selection-broker";
10import { NodeSelectionHandler, BlockSelectionHandler, RegisterAllocationSelectionHandler } from "./selection-handler";
11
12export abstract class TextView extends PhaseView {
13  selectionHandler: NodeSelectionHandler;
14  blockSelectionHandler: BlockSelectionHandler;
15  registerAllocationSelectionHandler: RegisterAllocationSelectionHandler;
16  selection: MySelection;
17  blockSelection: MySelection;
18  registerAllocationSelection: MySelection;
19  textListNode: HTMLUListElement;
20  instructionIdToHtmlElementsMap: Map<string, Array<HTMLElement>>;
21  nodeIdToHtmlElementsMap: Map<string, Array<HTMLElement>>;
22  blockIdToHtmlElementsMap: Map<string, Array<HTMLElement>>;
23  blockIdtoNodeIds: Map<string, Array<string>>;
24  nodeIdToBlockId: Array<string>;
25  patterns: any;
26  sourceResolver: SourceResolver;
27  broker: SelectionBroker;
28
29  constructor(id, broker) {
30    super(id);
31    const view = this;
32    view.textListNode = view.divNode.getElementsByTagName('ul')[0];
33    view.patterns = null;
34    view.instructionIdToHtmlElementsMap = new Map();
35    view.nodeIdToHtmlElementsMap = new Map();
36    view.blockIdToHtmlElementsMap = new Map();
37    view.blockIdtoNodeIds = new Map();
38    view.nodeIdToBlockId = [];
39    view.selection = new MySelection(anyToString);
40    view.blockSelection = new MySelection(anyToString);
41    view.broker = broker;
42    view.sourceResolver = broker.sourceResolver;
43    const selectionHandler = {
44      clear: function () {
45        view.selection.clear();
46        view.updateSelection();
47        broker.broadcastClear(selectionHandler);
48      },
49      select: function (nodeIds, selected) {
50        view.selection.select(nodeIds, selected);
51        view.updateSelection();
52        broker.broadcastNodeSelect(selectionHandler, view.selection.selectedKeys(), selected);
53      },
54      brokeredNodeSelect: function (nodeIds, selected) {
55        const firstSelect = view.blockSelection.isEmpty();
56        view.selection.select(nodeIds, selected);
57        view.updateSelection(firstSelect);
58      },
59      brokeredClear: function () {
60        view.selection.clear();
61        view.updateSelection();
62      }
63    };
64    this.selectionHandler = selectionHandler;
65    broker.addNodeHandler(selectionHandler);
66    view.divNode.addEventListener('click', e => {
67      if (!e.shiftKey) {
68        view.selectionHandler.clear();
69      }
70      e.stopPropagation();
71    });
72
73    const blockSelectionHandler = {
74      clear: function () {
75        view.blockSelection.clear();
76        view.updateSelection();
77        broker.broadcastClear(blockSelectionHandler);
78      },
79      select: function (blockIds, selected) {
80        view.blockSelection.select(blockIds, selected);
81        view.updateSelection();
82        broker.broadcastBlockSelect(blockSelectionHandler, blockIds, selected);
83      },
84      brokeredBlockSelect: function (blockIds, selected) {
85        const firstSelect = view.blockSelection.isEmpty();
86        view.blockSelection.select(blockIds, selected);
87        view.updateSelection(firstSelect);
88      },
89      brokeredClear: function () {
90        view.blockSelection.clear();
91        view.updateSelection();
92      }
93    };
94    this.blockSelectionHandler = blockSelectionHandler;
95    broker.addBlockHandler(blockSelectionHandler);
96
97    view.registerAllocationSelection = new MySelection(anyToString);
98    const registerAllocationSelectionHandler = {
99      clear: function () {
100        view.registerAllocationSelection.clear();
101        view.updateSelection();
102        broker.broadcastClear(registerAllocationSelectionHandler);
103      },
104      select: function (instructionIds, selected) {
105        view.registerAllocationSelection.select(instructionIds, selected);
106        view.updateSelection();
107        broker.broadcastInstructionSelect(null, [instructionIds], selected);
108      },
109      brokeredRegisterAllocationSelect: function (instructionIds, selected) {
110        const firstSelect = view.blockSelection.isEmpty();
111        view.registerAllocationSelection.select(instructionIds, selected);
112        view.updateSelection(firstSelect);
113      },
114      brokeredClear: function () {
115        view.registerAllocationSelection.clear();
116        view.updateSelection();
117      }
118    };
119    broker.addRegisterAllocatorHandler(registerAllocationSelectionHandler);
120    view.registerAllocationSelectionHandler = registerAllocationSelectionHandler;
121  }
122
123  // instruction-id are the divs for the register allocator phase
124  addHtmlElementForInstructionId(anyInstructionId: any, htmlElement: HTMLElement) {
125    const instructionId = anyToString(anyInstructionId);
126    if (!this.instructionIdToHtmlElementsMap.has(instructionId)) {
127      this.instructionIdToHtmlElementsMap.set(instructionId, []);
128    }
129    this.instructionIdToHtmlElementsMap.get(instructionId).push(htmlElement);
130  }
131
132  addHtmlElementForNodeId(anyNodeId: any, htmlElement: HTMLElement) {
133    const nodeId = anyToString(anyNodeId);
134    if (!this.nodeIdToHtmlElementsMap.has(nodeId)) {
135      this.nodeIdToHtmlElementsMap.set(nodeId, []);
136    }
137    this.nodeIdToHtmlElementsMap.get(nodeId).push(htmlElement);
138  }
139
140  addHtmlElementForBlockId(anyBlockId, htmlElement) {
141    const blockId = anyToString(anyBlockId);
142    if (!this.blockIdToHtmlElementsMap.has(blockId)) {
143      this.blockIdToHtmlElementsMap.set(blockId, []);
144    }
145    this.blockIdToHtmlElementsMap.get(blockId).push(htmlElement);
146  }
147
148  addNodeIdToBlockId(anyNodeId, anyBlockId) {
149    const blockId = anyToString(anyBlockId);
150    if (!this.blockIdtoNodeIds.has(blockId)) {
151      this.blockIdtoNodeIds.set(blockId, []);
152    }
153    this.blockIdtoNodeIds.get(blockId).push(anyToString(anyNodeId));
154    this.nodeIdToBlockId[anyNodeId] = blockId;
155  }
156
157  blockIdsForNodeIds(nodeIds) {
158    const blockIds = [];
159    for (const nodeId of nodeIds) {
160      const blockId = this.nodeIdToBlockId[nodeId];
161      if (blockId == undefined) continue;
162      blockIds.push(blockId);
163    }
164    return blockIds;
165  }
166
167  updateSelection(scrollIntoView: boolean = false) {
168    if (this.divNode.parentNode == null) return;
169    const mkVisible = new ViewElements(this.divNode.parentNode as HTMLElement);
170    const view = this;
171    const elementsToSelect = view.divNode.querySelectorAll(`[data-pc-offset]`);
172    for (const el of elementsToSelect) {
173      el.classList.toggle("selected", false);
174    }
175    for (const [blockId, elements] of this.blockIdToHtmlElementsMap.entries()) {
176      const isSelected = view.blockSelection.isSelected(blockId);
177      for (const element of elements) {
178        mkVisible.consider(element, isSelected);
179        element.classList.toggle("selected", isSelected);
180      }
181    }
182
183    for (const key of this.instructionIdToHtmlElementsMap.keys()) {
184      for (const element of this.instructionIdToHtmlElementsMap.get(key)) {
185        element.classList.toggle("selected", false);
186      }
187    }
188    for (const instrId of view.registerAllocationSelection.selectedKeys()) {
189      const elements = this.instructionIdToHtmlElementsMap.get(instrId);
190      if (!elements) continue;
191      for (const element of elements) {
192        mkVisible.consider(element, true);
193        element.classList.toggle("selected", true);
194      }
195    }
196
197    for (const key of this.nodeIdToHtmlElementsMap.keys()) {
198      for (const element of this.nodeIdToHtmlElementsMap.get(key)) {
199        element.classList.toggle("selected", false);
200      }
201    }
202    for (const nodeId of view.selection.selectedKeys()) {
203      const elements = this.nodeIdToHtmlElementsMap.get(nodeId);
204      if (!elements) continue;
205      for (const element of elements) {
206        mkVisible.consider(element, true);
207        element.classList.toggle("selected", true);
208      }
209    }
210    mkVisible.apply(scrollIntoView);
211  }
212
213  setPatterns(patterns) {
214    this.patterns = patterns;
215  }
216
217  clearText() {
218    while (this.textListNode.firstChild) {
219      this.textListNode.removeChild(this.textListNode.firstChild);
220    }
221  }
222
223  createFragment(text, style) {
224    const fragment = document.createElement("SPAN");
225
226    if (typeof style.associateData == 'function') {
227      if (style.associateData(text, fragment) === false) {
228         return null;
229      }
230    } else {
231      if (style.css != undefined) {
232        const css = isIterable(style.css) ? style.css : [style.css];
233        for (const cls of css) {
234          fragment.classList.add(cls);
235        }
236      }
237      fragment.innerText = text;
238    }
239
240    return fragment;
241  }
242
243  processLine(line) {
244    const view = this;
245    const result = [];
246    let patternSet = 0;
247    while (true) {
248      const beforeLine = line;
249      for (const pattern of view.patterns[patternSet]) {
250        const matches = line.match(pattern[0]);
251        if (matches != null) {
252          if (matches[0] != '') {
253            const style = pattern[1] != null ? pattern[1] : {};
254            const text = matches[0];
255            if (text != '') {
256              const fragment = view.createFragment(matches[0], style);
257              if (fragment !== null) result.push(fragment);
258            }
259            line = line.substr(matches[0].length);
260          }
261          let nextPatternSet = patternSet;
262          if (pattern.length > 2) {
263            nextPatternSet = pattern[2];
264          }
265          if (line == "") {
266            if (nextPatternSet != -1) {
267              throw ("illegal parsing state in text-view in patternSet" + patternSet);
268            }
269            return result;
270          }
271          patternSet = nextPatternSet;
272          break;
273        }
274      }
275      if (beforeLine == line) {
276        throw ("input not consumed in text-view in patternSet" + patternSet);
277      }
278    }
279  }
280
281  processText(text) {
282    const view = this;
283    const textLines = text.split(/[\n]/);
284    let lineNo = 0;
285    for (const line of textLines) {
286      const li = document.createElement("LI");
287      li.className = "nolinenums";
288      li.dataset.lineNo = "" + lineNo++;
289      const fragments = view.processLine(line);
290      for (const fragment of fragments) {
291        li.appendChild(fragment);
292      }
293      view.textListNode.appendChild(li);
294    }
295  }
296
297  initializeContent(data, rememberedSelection) {
298    this.clearText();
299    this.processText(data);
300    this.show();
301  }
302
303  public onresize(): void {}
304}
305