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