11cb0ef41Sopenharmony_ci// Copyright 2015 the V8 project authors. All rights reserved. 21cb0ef41Sopenharmony_ci// Use of this source code is governed by a BSD-style license that can be 31cb0ef41Sopenharmony_ci// found in the LICENSE file. 41cb0ef41Sopenharmony_ci 51cb0ef41Sopenharmony_ciinterface PR { 61cb0ef41Sopenharmony_ci prettyPrint(_: unknown, el: HTMLElement): void; 71cb0ef41Sopenharmony_ci} 81cb0ef41Sopenharmony_ci 91cb0ef41Sopenharmony_cideclare global { 101cb0ef41Sopenharmony_ci const PR: PR; 111cb0ef41Sopenharmony_ci} 121cb0ef41Sopenharmony_ci 131cb0ef41Sopenharmony_ciimport { Source, SourceResolver, sourcePositionToStringKey } from "../src/source-resolver"; 141cb0ef41Sopenharmony_ciimport { SelectionBroker } from "../src/selection-broker"; 151cb0ef41Sopenharmony_ciimport { View } from "../src/view"; 161cb0ef41Sopenharmony_ciimport { MySelection } from "../src/selection"; 171cb0ef41Sopenharmony_ciimport { ViewElements } from "../src/util"; 181cb0ef41Sopenharmony_ciimport { SelectionHandler } from "./selection-handler"; 191cb0ef41Sopenharmony_ci 201cb0ef41Sopenharmony_ciexport enum CodeMode { 211cb0ef41Sopenharmony_ci MAIN_SOURCE = "main function", 221cb0ef41Sopenharmony_ci INLINED_SOURCE = "inlined function" 231cb0ef41Sopenharmony_ci} 241cb0ef41Sopenharmony_ci 251cb0ef41Sopenharmony_ciexport class CodeView extends View { 261cb0ef41Sopenharmony_ci broker: SelectionBroker; 271cb0ef41Sopenharmony_ci source: Source; 281cb0ef41Sopenharmony_ci sourceResolver: SourceResolver; 291cb0ef41Sopenharmony_ci codeMode: CodeMode; 301cb0ef41Sopenharmony_ci sourcePositionToHtmlElements: Map<string, Array<HTMLElement>>; 311cb0ef41Sopenharmony_ci showAdditionalInliningPosition: boolean; 321cb0ef41Sopenharmony_ci selectionHandler: SelectionHandler; 331cb0ef41Sopenharmony_ci selection: MySelection; 341cb0ef41Sopenharmony_ci 351cb0ef41Sopenharmony_ci createViewElement() { 361cb0ef41Sopenharmony_ci const sourceContainer = document.createElement("div"); 371cb0ef41Sopenharmony_ci sourceContainer.classList.add("source-container"); 381cb0ef41Sopenharmony_ci return sourceContainer; 391cb0ef41Sopenharmony_ci } 401cb0ef41Sopenharmony_ci 411cb0ef41Sopenharmony_ci constructor(parent: HTMLElement, broker: SelectionBroker, sourceResolver: SourceResolver, sourceFunction: Source, codeMode: CodeMode) { 421cb0ef41Sopenharmony_ci super(parent); 431cb0ef41Sopenharmony_ci const view = this; 441cb0ef41Sopenharmony_ci view.broker = broker; 451cb0ef41Sopenharmony_ci view.sourceResolver = sourceResolver; 461cb0ef41Sopenharmony_ci view.source = sourceFunction; 471cb0ef41Sopenharmony_ci view.codeMode = codeMode; 481cb0ef41Sopenharmony_ci this.sourcePositionToHtmlElements = new Map(); 491cb0ef41Sopenharmony_ci this.showAdditionalInliningPosition = false; 501cb0ef41Sopenharmony_ci 511cb0ef41Sopenharmony_ci const selectionHandler = { 521cb0ef41Sopenharmony_ci clear: function () { 531cb0ef41Sopenharmony_ci view.selection.clear(); 541cb0ef41Sopenharmony_ci view.updateSelection(); 551cb0ef41Sopenharmony_ci broker.broadcastClear(this); 561cb0ef41Sopenharmony_ci }, 571cb0ef41Sopenharmony_ci select: function (sourcePositions, selected) { 581cb0ef41Sopenharmony_ci const locations = []; 591cb0ef41Sopenharmony_ci for (const sourcePosition of sourcePositions) { 601cb0ef41Sopenharmony_ci locations.push(sourcePosition); 611cb0ef41Sopenharmony_ci sourceResolver.addInliningPositions(sourcePosition, locations); 621cb0ef41Sopenharmony_ci } 631cb0ef41Sopenharmony_ci if (locations.length == 0) return; 641cb0ef41Sopenharmony_ci view.selection.select(locations, selected); 651cb0ef41Sopenharmony_ci view.updateSelection(); 661cb0ef41Sopenharmony_ci broker.broadcastSourcePositionSelect(this, locations, selected); 671cb0ef41Sopenharmony_ci }, 681cb0ef41Sopenharmony_ci brokeredSourcePositionSelect: function (locations, selected) { 691cb0ef41Sopenharmony_ci const firstSelect = view.selection.isEmpty(); 701cb0ef41Sopenharmony_ci for (const location of locations) { 711cb0ef41Sopenharmony_ci const translated = sourceResolver.translateToSourceId(view.source.sourceId, location); 721cb0ef41Sopenharmony_ci if (!translated) continue; 731cb0ef41Sopenharmony_ci view.selection.select([translated], selected); 741cb0ef41Sopenharmony_ci } 751cb0ef41Sopenharmony_ci view.updateSelection(firstSelect); 761cb0ef41Sopenharmony_ci }, 771cb0ef41Sopenharmony_ci brokeredClear: function () { 781cb0ef41Sopenharmony_ci view.selection.clear(); 791cb0ef41Sopenharmony_ci view.updateSelection(); 801cb0ef41Sopenharmony_ci }, 811cb0ef41Sopenharmony_ci }; 821cb0ef41Sopenharmony_ci view.selection = new MySelection(sourcePositionToStringKey); 831cb0ef41Sopenharmony_ci broker.addSourcePositionHandler(selectionHandler); 841cb0ef41Sopenharmony_ci this.selectionHandler = selectionHandler; 851cb0ef41Sopenharmony_ci this.initializeCode(); 861cb0ef41Sopenharmony_ci } 871cb0ef41Sopenharmony_ci 881cb0ef41Sopenharmony_ci addHtmlElementToSourcePosition(sourcePosition, element) { 891cb0ef41Sopenharmony_ci const key = sourcePositionToStringKey(sourcePosition); 901cb0ef41Sopenharmony_ci if (!this.sourcePositionToHtmlElements.has(key)) { 911cb0ef41Sopenharmony_ci this.sourcePositionToHtmlElements.set(key, []); 921cb0ef41Sopenharmony_ci } 931cb0ef41Sopenharmony_ci this.sourcePositionToHtmlElements.get(key).push(element); 941cb0ef41Sopenharmony_ci } 951cb0ef41Sopenharmony_ci 961cb0ef41Sopenharmony_ci getHtmlElementForSourcePosition(sourcePosition) { 971cb0ef41Sopenharmony_ci const key = sourcePositionToStringKey(sourcePosition); 981cb0ef41Sopenharmony_ci return this.sourcePositionToHtmlElements.get(key); 991cb0ef41Sopenharmony_ci } 1001cb0ef41Sopenharmony_ci 1011cb0ef41Sopenharmony_ci updateSelection(scrollIntoView: boolean = false): void { 1021cb0ef41Sopenharmony_ci const mkVisible = new ViewElements(this.divNode.parentNode as HTMLElement); 1031cb0ef41Sopenharmony_ci for (const [sp, els] of this.sourcePositionToHtmlElements.entries()) { 1041cb0ef41Sopenharmony_ci const isSelected = this.selection.isKeySelected(sp); 1051cb0ef41Sopenharmony_ci for (const el of els) { 1061cb0ef41Sopenharmony_ci mkVisible.consider(el, isSelected); 1071cb0ef41Sopenharmony_ci el.classList.toggle("selected", isSelected); 1081cb0ef41Sopenharmony_ci } 1091cb0ef41Sopenharmony_ci } 1101cb0ef41Sopenharmony_ci mkVisible.apply(scrollIntoView); 1111cb0ef41Sopenharmony_ci } 1121cb0ef41Sopenharmony_ci 1131cb0ef41Sopenharmony_ci getCodeHtmlElementName() { 1141cb0ef41Sopenharmony_ci return `source-pre-${this.source.sourceId}`; 1151cb0ef41Sopenharmony_ci } 1161cb0ef41Sopenharmony_ci 1171cb0ef41Sopenharmony_ci getCodeHeaderHtmlElementName() { 1181cb0ef41Sopenharmony_ci return `source-pre-${this.source.sourceId}-header`; 1191cb0ef41Sopenharmony_ci } 1201cb0ef41Sopenharmony_ci 1211cb0ef41Sopenharmony_ci getHtmlCodeLines(): NodeListOf<HTMLElement> { 1221cb0ef41Sopenharmony_ci const ordereList = this.divNode.querySelector(`#${this.getCodeHtmlElementName()} ol`); 1231cb0ef41Sopenharmony_ci return ordereList.childNodes as NodeListOf<HTMLElement>; 1241cb0ef41Sopenharmony_ci } 1251cb0ef41Sopenharmony_ci 1261cb0ef41Sopenharmony_ci onSelectLine(lineNumber: number, doClear: boolean) { 1271cb0ef41Sopenharmony_ci if (doClear) { 1281cb0ef41Sopenharmony_ci this.selectionHandler.clear(); 1291cb0ef41Sopenharmony_ci } 1301cb0ef41Sopenharmony_ci const positions = this.sourceResolver.lineToSourcePositions(lineNumber - 1); 1311cb0ef41Sopenharmony_ci if (positions !== undefined) { 1321cb0ef41Sopenharmony_ci this.selectionHandler.select(positions, undefined); 1331cb0ef41Sopenharmony_ci } 1341cb0ef41Sopenharmony_ci } 1351cb0ef41Sopenharmony_ci 1361cb0ef41Sopenharmony_ci onSelectSourcePosition(sourcePosition, doClear: boolean) { 1371cb0ef41Sopenharmony_ci if (doClear) { 1381cb0ef41Sopenharmony_ci this.selectionHandler.clear(); 1391cb0ef41Sopenharmony_ci } 1401cb0ef41Sopenharmony_ci this.selectionHandler.select([sourcePosition], undefined); 1411cb0ef41Sopenharmony_ci } 1421cb0ef41Sopenharmony_ci 1431cb0ef41Sopenharmony_ci initializeCode() { 1441cb0ef41Sopenharmony_ci const view = this; 1451cb0ef41Sopenharmony_ci const source = this.source; 1461cb0ef41Sopenharmony_ci const sourceText = source.sourceText; 1471cb0ef41Sopenharmony_ci if (!sourceText) return; 1481cb0ef41Sopenharmony_ci const sourceContainer = view.divNode; 1491cb0ef41Sopenharmony_ci if (this.codeMode == CodeMode.MAIN_SOURCE) { 1501cb0ef41Sopenharmony_ci sourceContainer.classList.add("main-source"); 1511cb0ef41Sopenharmony_ci } else { 1521cb0ef41Sopenharmony_ci sourceContainer.classList.add("inlined-source"); 1531cb0ef41Sopenharmony_ci } 1541cb0ef41Sopenharmony_ci const codeHeader = document.createElement("div"); 1551cb0ef41Sopenharmony_ci codeHeader.setAttribute("id", this.getCodeHeaderHtmlElementName()); 1561cb0ef41Sopenharmony_ci codeHeader.classList.add("code-header"); 1571cb0ef41Sopenharmony_ci const codeFileFunction = document.createElement("div"); 1581cb0ef41Sopenharmony_ci codeFileFunction.classList.add("code-file-function"); 1591cb0ef41Sopenharmony_ci codeFileFunction.innerHTML = `${source.sourceName}:${source.functionName}`; 1601cb0ef41Sopenharmony_ci codeHeader.appendChild(codeFileFunction); 1611cb0ef41Sopenharmony_ci const codeModeDiv = document.createElement("div"); 1621cb0ef41Sopenharmony_ci codeModeDiv.classList.add("code-mode"); 1631cb0ef41Sopenharmony_ci codeModeDiv.innerHTML = `${this.codeMode}`; 1641cb0ef41Sopenharmony_ci codeHeader.appendChild(codeModeDiv); 1651cb0ef41Sopenharmony_ci const clearDiv = document.createElement("div"); 1661cb0ef41Sopenharmony_ci clearDiv.style.clear = "both"; 1671cb0ef41Sopenharmony_ci codeHeader.appendChild(clearDiv); 1681cb0ef41Sopenharmony_ci sourceContainer.appendChild(codeHeader); 1691cb0ef41Sopenharmony_ci const codePre = document.createElement("pre"); 1701cb0ef41Sopenharmony_ci codePre.setAttribute("id", this.getCodeHtmlElementName()); 1711cb0ef41Sopenharmony_ci codePre.classList.add("prettyprint"); 1721cb0ef41Sopenharmony_ci sourceContainer.appendChild(codePre); 1731cb0ef41Sopenharmony_ci 1741cb0ef41Sopenharmony_ci codeHeader.onclick = function myFunction() { 1751cb0ef41Sopenharmony_ci if (codePre.style.display === "none") { 1761cb0ef41Sopenharmony_ci codePre.style.display = "block"; 1771cb0ef41Sopenharmony_ci } else { 1781cb0ef41Sopenharmony_ci codePre.style.display = "none"; 1791cb0ef41Sopenharmony_ci } 1801cb0ef41Sopenharmony_ci }; 1811cb0ef41Sopenharmony_ci if (sourceText != "") { 1821cb0ef41Sopenharmony_ci codePre.classList.add("linenums"); 1831cb0ef41Sopenharmony_ci codePre.textContent = sourceText; 1841cb0ef41Sopenharmony_ci try { 1851cb0ef41Sopenharmony_ci // Wrap in try to work when offline. 1861cb0ef41Sopenharmony_ci PR.prettyPrint(undefined, sourceContainer); 1871cb0ef41Sopenharmony_ci } catch (e) { 1881cb0ef41Sopenharmony_ci console.log(e); 1891cb0ef41Sopenharmony_ci } 1901cb0ef41Sopenharmony_ci 1911cb0ef41Sopenharmony_ci view.divNode.onclick = function (e: MouseEvent) { 1921cb0ef41Sopenharmony_ci if (e.target instanceof Element && e.target.tagName == "DIV") { 1931cb0ef41Sopenharmony_ci const targetDiv = e.target as HTMLDivElement; 1941cb0ef41Sopenharmony_ci if (targetDiv.classList.contains("line-number")) { 1951cb0ef41Sopenharmony_ci e.stopPropagation(); 1961cb0ef41Sopenharmony_ci view.onSelectLine(Number(targetDiv.dataset.lineNumber), !e.shiftKey); 1971cb0ef41Sopenharmony_ci } 1981cb0ef41Sopenharmony_ci } else { 1991cb0ef41Sopenharmony_ci view.selectionHandler.clear(); 2001cb0ef41Sopenharmony_ci } 2011cb0ef41Sopenharmony_ci }; 2021cb0ef41Sopenharmony_ci 2031cb0ef41Sopenharmony_ci const base: number = source.startPosition; 2041cb0ef41Sopenharmony_ci let current = 0; 2051cb0ef41Sopenharmony_ci const lineListDiv = this.getHtmlCodeLines(); 2061cb0ef41Sopenharmony_ci let newlineAdjust = 0; 2071cb0ef41Sopenharmony_ci for (let i = 0; i < lineListDiv.length; i++) { 2081cb0ef41Sopenharmony_ci // Line numbers are not zero-based. 2091cb0ef41Sopenharmony_ci const lineNumber = i + 1; 2101cb0ef41Sopenharmony_ci const currentLineElement = lineListDiv[i]; 2111cb0ef41Sopenharmony_ci currentLineElement.id = "li" + i; 2121cb0ef41Sopenharmony_ci currentLineElement.dataset.lineNumber = "" + lineNumber; 2131cb0ef41Sopenharmony_ci const spans = currentLineElement.childNodes; 2141cb0ef41Sopenharmony_ci for (const currentSpan of spans) { 2151cb0ef41Sopenharmony_ci if (currentSpan instanceof HTMLSpanElement) { 2161cb0ef41Sopenharmony_ci const pos = base + current; 2171cb0ef41Sopenharmony_ci const end = pos + currentSpan.textContent.length; 2181cb0ef41Sopenharmony_ci current += currentSpan.textContent.length; 2191cb0ef41Sopenharmony_ci this.insertSourcePositions(currentSpan, lineNumber, pos, end, newlineAdjust); 2201cb0ef41Sopenharmony_ci newlineAdjust = 0; 2211cb0ef41Sopenharmony_ci } 2221cb0ef41Sopenharmony_ci } 2231cb0ef41Sopenharmony_ci 2241cb0ef41Sopenharmony_ci this.insertLineNumber(currentLineElement, lineNumber); 2251cb0ef41Sopenharmony_ci 2261cb0ef41Sopenharmony_ci while ((current < sourceText.length) && 2271cb0ef41Sopenharmony_ci (sourceText[current] == '\n' || sourceText[current] == '\r')) { 2281cb0ef41Sopenharmony_ci ++current; 2291cb0ef41Sopenharmony_ci ++newlineAdjust; 2301cb0ef41Sopenharmony_ci } 2311cb0ef41Sopenharmony_ci } 2321cb0ef41Sopenharmony_ci } 2331cb0ef41Sopenharmony_ci } 2341cb0ef41Sopenharmony_ci 2351cb0ef41Sopenharmony_ci insertSourcePositions(currentSpan, lineNumber, pos, end, adjust) { 2361cb0ef41Sopenharmony_ci const view = this; 2371cb0ef41Sopenharmony_ci const sps = this.sourceResolver.sourcePositionsInRange(this.source.sourceId, pos - adjust, end); 2381cb0ef41Sopenharmony_ci let offset = 0; 2391cb0ef41Sopenharmony_ci for (const sourcePosition of sps) { 2401cb0ef41Sopenharmony_ci // Internally, line numbers are 0-based so we have to substract 1 from the line number. This 2411cb0ef41Sopenharmony_ci // path in only taken by non-Wasm code. Wasm code relies on setSourceLineToBytecodePosition. 2421cb0ef41Sopenharmony_ci this.sourceResolver.addAnyPositionToLine(lineNumber - 1, sourcePosition); 2431cb0ef41Sopenharmony_ci const textnode = currentSpan.tagName == 'SPAN' ? currentSpan.lastChild : currentSpan; 2441cb0ef41Sopenharmony_ci if (!(textnode instanceof Text)) continue; 2451cb0ef41Sopenharmony_ci const splitLength = Math.max(0, sourcePosition.scriptOffset - pos - offset); 2461cb0ef41Sopenharmony_ci offset += splitLength; 2471cb0ef41Sopenharmony_ci const replacementNode = textnode.splitText(splitLength); 2481cb0ef41Sopenharmony_ci const span = document.createElement('span'); 2491cb0ef41Sopenharmony_ci span.setAttribute("scriptOffset", sourcePosition.scriptOffset); 2501cb0ef41Sopenharmony_ci span.classList.add("source-position"); 2511cb0ef41Sopenharmony_ci const marker = document.createElement('span'); 2521cb0ef41Sopenharmony_ci marker.classList.add("marker"); 2531cb0ef41Sopenharmony_ci span.appendChild(marker); 2541cb0ef41Sopenharmony_ci const inlining = this.sourceResolver.getInliningForPosition(sourcePosition); 2551cb0ef41Sopenharmony_ci if (inlining != undefined && view.showAdditionalInliningPosition) { 2561cb0ef41Sopenharmony_ci const sourceName = this.sourceResolver.getSourceName(inlining.sourceId); 2571cb0ef41Sopenharmony_ci const inliningMarker = document.createElement('span'); 2581cb0ef41Sopenharmony_ci inliningMarker.classList.add("inlining-marker"); 2591cb0ef41Sopenharmony_ci inliningMarker.setAttribute("data-descr", `${sourceName} was inlined here`); 2601cb0ef41Sopenharmony_ci span.appendChild(inliningMarker); 2611cb0ef41Sopenharmony_ci } 2621cb0ef41Sopenharmony_ci span.onclick = function (e) { 2631cb0ef41Sopenharmony_ci e.stopPropagation(); 2641cb0ef41Sopenharmony_ci view.onSelectSourcePosition(sourcePosition, !e.shiftKey); 2651cb0ef41Sopenharmony_ci }; 2661cb0ef41Sopenharmony_ci view.addHtmlElementToSourcePosition(sourcePosition, span); 2671cb0ef41Sopenharmony_ci textnode.parentNode.insertBefore(span, replacementNode); 2681cb0ef41Sopenharmony_ci } 2691cb0ef41Sopenharmony_ci } 2701cb0ef41Sopenharmony_ci 2711cb0ef41Sopenharmony_ci insertLineNumber(lineElement: HTMLElement, lineNumber: number) { 2721cb0ef41Sopenharmony_ci const view = this; 2731cb0ef41Sopenharmony_ci const lineNumberElement = document.createElement("div"); 2741cb0ef41Sopenharmony_ci lineNumberElement.classList.add("line-number"); 2751cb0ef41Sopenharmony_ci lineNumberElement.dataset.lineNumber = `${lineNumber}`; 2761cb0ef41Sopenharmony_ci lineNumberElement.innerText = `${lineNumber}`; 2771cb0ef41Sopenharmony_ci lineElement.insertBefore(lineNumberElement, lineElement.firstChild); 2781cb0ef41Sopenharmony_ci for (const sourcePosition of this.sourceResolver.lineToSourcePositions(lineNumber - 1)) { 2791cb0ef41Sopenharmony_ci view.addHtmlElementToSourcePosition(sourcePosition, lineElement); 2801cb0ef41Sopenharmony_ci } 2811cb0ef41Sopenharmony_ci } 2821cb0ef41Sopenharmony_ci 2831cb0ef41Sopenharmony_ci} 284