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