v8/tools/turbolizer/code-view.js
Sigurd Schneider 3cd0a367aa [turbolizer] Refactor view management
Change-Id: I6d84e7ef500aecd83a77ed2ce3fed4e15b29b7ac
Reviewed-on: https://chromium-review.googlesource.com/1065881
Commit-Queue: Sigurd Schneider <sigurds@chromium.org>
Reviewed-by: Peter Marshall <petermarshall@chromium.org>
Cr-Commit-Position: refs/heads/master@{#53289}
2018-05-22 18:19:52 +00:00

262 lines
9.2 KiB
JavaScript

// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
"use strict";
class CodeView extends View {
static get MAIN_SOURCE() {
return "main function";
}
static get INLINED_SOURCE() {
return "inlined function";
}
createViewElement() {
const sourceContainer = document.createElement("div");
sourceContainer.classList.add("source-container");
return sourceContainer;
}
constructor(parentId, broker, sourceResolver, sourceFunction, codeMode) {
super(parentId);
let view = this;
view.mouseDown = false;
view.broker = broker;
view.source = null;
view.sourceResolver = sourceResolver;
view.source = sourceFunction;
view.codeMode = codeMode;
this.lineToSourcePositions = new Map();
this.sourcePositionToHtmlElement = new Map();
this.showAdditionalInliningPosition = false;
const selectionHandler = {
clear: function () {
view.selection.clear();
view.updateSelection();
broker.broadcastClear(this)
},
select: function (sourcePositions, selected) {
const locations = [];
for (var sourcePosition of sourcePositions) {
locations.push(sourcePosition);
sourceResolver.addInliningPositions(sourcePosition, locations);
}
if (locations.length == 0) return;
view.selection.select(locations, selected);
view.updateSelection();
broker.broadcastSourcePositionSelect(this, locations, selected);
},
brokeredSourcePositionSelect: function (locations, selected) {
const firstSelect = view.selection.isEmpty();
for (const location of locations) {
const translated = sourceResolver.translateToSourceId(view.source.sourceId, location);
if (!translated) continue;
view.selection.select(translated, selected);
}
view.updateSelection(firstSelect);
},
brokeredClear: function () {
view.selection.clear();
view.updateSelection();
},
};
view.selection = new Selection(sourcePositionToStringKey);
broker.addSourcePositionHandler(selectionHandler);
this.selectionHandler = selectionHandler;
this.initializeCode();
}
addSourcePositionToLine(lineNumber, sourcePosition) {
const lineNumberString = anyToString(lineNumber);
if (!this.lineToSourcePositions.has(lineNumberString)) {
this.lineToSourcePositions.set(lineNumberString, []);
}
this.lineToSourcePositions.get(lineNumberString).push(sourcePosition);
}
addHtmlElementToSourcePosition(sourcePosition, element) {
const key = sourcePositionToStringKey(sourcePosition);
if (this.sourcePositionToHtmlElement.has(key)) {
console.log("Warning: duplicate source position", sourcePosition);
}
this.sourcePositionToHtmlElement.set(key, element);
}
getHtmlElementForSourcePosition(sourcePosition) {
const key = sourcePositionToStringKey(lineNumber);
return this.sourcePositionToHtmlElement.get(key);
}
updateSelection(scrollIntoView) {
const mkVisible = new ViewElements(this.divNode.parentNode);
for (const [sp, el] of this.sourcePositionToHtmlElement.entries()) {
const isSelected = this.selection.isKeySelected(sp);
mkVisible.consider(el, isSelected);
el.classList.toggle("selected", isSelected);
}
mkVisible.apply(scrollIntoView);
}
initializeContent(data, rememberedSelection) {
this.data = data;
}
getCodeHtmlElementName() {
return `source-pre-${this.source.sourceId}`;
}
getCodeHeaderHtmlElementName() {
return `source-pre-${this.source.sourceId}-header`;
}
getHtmlCodeLines() {
const lineListDiv = this.divNode.querySelector(`#${this.getCodeHtmlElementName()} ol`).childNodes;
return lineListDiv;
}
onSelectLine(lineNumber, doClear) {
const sourcePositions = this.lineToSourcePositions.get(anyToString(lineNumber));
if (doClear) {
this.selectionHandler.clear();
}
if (!sourcePositions) return;
this.selectionHandler.select(sourcePositions, undefined);
}
onSelectSourcePosition(sourcePosition, doClear) {
if (doClear) {
this.selectionHandler.clear();
}
this.selectionHandler.select([sourcePosition], undefined);
}
initializeCode() {
var view = this;
const source = this.source;
const sourceText = source.sourceText;
if (!sourceText) return;
const sourceContainer = view.divNode;
if (this.codeMode == CodeView.MAIN_SOURCE) {
sourceContainer.classList.add("main-source");
} else {
sourceContainer.classList.add("inlined-source");
}
var codeHeader = document.createElement("div");
codeHeader.setAttribute("id", this.getCodeHeaderHtmlElementName());
codeHeader.classList.add("code-header");
var codeFileFunction = document.createElement("div");
codeFileFunction.classList.add("code-file-function");
codeFileFunction.innerHTML = `${source.sourceName}:${source.functionName}`;
codeHeader.appendChild(codeFileFunction);
var codeModeDiv = document.createElement("div");
codeModeDiv.classList.add("code-mode");
codeModeDiv.innerHTML = `${this.codeMode}`;
codeHeader.appendChild(codeModeDiv);
var clearDiv = document.createElement("div");
clearDiv.style = "clear:both;"
codeHeader.appendChild(clearDiv);
sourceContainer.appendChild(codeHeader);
var codePre = document.createElement("pre");
codePre.setAttribute("id", this.getCodeHtmlElementName());
codePre.classList.add("prettyprint");
sourceContainer.appendChild(codePre);
codeHeader.onclick = function myFunction() {
if (codePre.style.display === "none") {
codePre.style.display = "block";
} else {
codePre.style.display = "none";
}
}
if (sourceText != "") {
codePre.classList.add("linenums");
codePre.textContent = sourceText;
try {
// Wrap in try to work when offline.
PR.prettyPrint(undefined, sourceContainer);
} catch (e) {
console.log(e);
}
view.divNode.onclick = function (e) {
view.selectionHandler.clear();
}
const base = source.startPosition;
let current = 0;
const lineListDiv = this.getHtmlCodeLines();
let newlineAdjust = 0;
for (let i = 0; i < lineListDiv.length; i++) {
// Line numbers are not zero-based.
const lineNumber = i + 1;
const currentLineElement = lineListDiv[i];
currentLineElement.id = "li" + i;
currentLineElement.dataset.lineNumber = lineNumber;
const spans = currentLineElement.childNodes;
for (let j = 0; j < spans.length; ++j) {
const currentSpan = spans[j];
const pos = base + current;
const end = pos + currentSpan.textContent.length;
current += currentSpan.textContent.length;
this.insertSourcePositions(currentSpan, lineNumber, pos, end, newlineAdjust);
newlineAdjust = 0;
}
this.insertLineNumber(currentLineElement, lineNumber);
while ((current < sourceText.length) &&
(sourceText[current] == '\n' || sourceText[current] == '\r')) {
++current;
++newlineAdjust;
}
}
}
}
insertSourcePositions(currentSpan, lineNumber, pos, end, adjust) {
const view = this;
const sps = this.sourceResolver.sourcePositionsInRange(this.source.sourceId, pos - adjust, end);
for (const sourcePosition of sps) {
view.addSourcePositionToLine(lineNumber, sourcePosition);
const textnode = currentSpan.tagName == 'SPAN' ? currentSpan.firstChild : currentSpan;
const replacementNode = textnode.splitText(Math.max(0, sourcePosition.scriptOffset - pos));
const span = document.createElement('span');
span.setAttribute("scriptOffset", sourcePosition.scriptOffset);
span.classList.add("source-position")
const marker = document.createElement('span');
marker.classList.add("marker")
span.appendChild(marker);
const inlining = this.sourceResolver.getInliningForPosition(sourcePosition);
if (inlining != undefined && view.showAdditionalInliningPosition) {
const sourceName = this.sourceResolver.getSourceName(inlining.sourceId);
const inliningMarker = document.createElement('span');
inliningMarker.classList.add("inlining-marker")
inliningMarker.setAttribute("data-descr", `${sourceName} was inlined here`)
span.appendChild(inliningMarker);
}
span.onclick = function (e) {
e.stopPropagation();
view.onSelectSourcePosition(sourcePosition, !e.shiftKey)
};
view.addHtmlElementToSourcePosition(sourcePosition, span);
textnode.parentNode.insertBefore(span, replacementNode);
}
}
insertLineNumber(lineElement, lineNumber) {
const view = this;
const lineNumberElement = document.createElement("div");
lineNumberElement.classList.add("line-number");
lineNumberElement.innerText = lineNumber;
lineNumberElement.onclick = function (e) {
e.stopPropagation();
view.onSelectLine(lineNumber, !e.shiftKey);
}
lineElement.insertBefore(lineNumberElement, lineElement.firstChild)
}
deleteContent() { }
}