[tools] Add markers to system-analyzer source panel
- Create SourcePosition objects for Map and IC log entries - Display source code with markers for SourcePositions - Avoid some try-catches for a better debugging experience Bug: v8:10644 Change-Id: I559b0eaeaa1442986a00d2ef720d19ba85178509 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2424258 Reviewed-by: Sathya Gunasekaran <gsathya@chromium.org> Commit-Queue: Camillo Bruni <cbruni@chromium.org> Auto-Submit: Camillo Bruni <cbruni@chromium.org> Cr-Commit-Position: refs/heads/master@{#70091}
This commit is contained in:
parent
607414e91c
commit
89e0d45c66
@ -25,6 +25,52 @@
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// TODO: move to separate modules
|
||||
class SourcePosition {
|
||||
constructor(script, line, column) {
|
||||
this.script = script;
|
||||
this.line = line;
|
||||
this.column = column;
|
||||
this.entries = [];
|
||||
}
|
||||
addEntry(entry) {
|
||||
this.entries.push(entry);
|
||||
}
|
||||
}
|
||||
|
||||
class Script {
|
||||
|
||||
constructor(id, name, source) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.source = source;
|
||||
this.sourcePositions = [];
|
||||
// Map<line, Map<column, SourcePosition>>
|
||||
this.lineToColumn = new Map();
|
||||
}
|
||||
|
||||
addSourcePosition(line, column, entry) {
|
||||
let sourcePosition = this.lineToColumn.get(line)?.get(column);
|
||||
if (sourcePosition === undefined) {
|
||||
sourcePosition = new SourcePosition(this, line, column, )
|
||||
this.#addSourcePosition(line, column, sourcePosition);
|
||||
}
|
||||
sourcePosition.addEntry(entry);
|
||||
return sourcePosition;
|
||||
}
|
||||
|
||||
#addSourcePosition(line, column, sourcePosition) {
|
||||
let columnToSourcePosition;
|
||||
if (this.lineToColumn.has(line)) {
|
||||
columnToSourcePosition = this.lineToColumn.get(line);
|
||||
} else {
|
||||
columnToSourcePosition = new Map();
|
||||
this.lineToColumn.set(line, columnToSourcePosition);
|
||||
}
|
||||
this.sourcePositions.push(sourcePosition);
|
||||
columnToSourcePosition.set(column, sourcePosition);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a profile object for processing profiling-related events
|
||||
@ -228,13 +274,10 @@ Profile.prototype.addSourcePositions = function (
|
||||
/**
|
||||
* Adds script source code.
|
||||
*/
|
||||
Profile.prototype.addScriptSource = function (scriptId, url, source) {
|
||||
this.scripts_[scriptId] = {
|
||||
scriptId: scriptId,
|
||||
name: url,
|
||||
source: source
|
||||
};
|
||||
this.urlToScript_.set(url, source);
|
||||
Profile.prototype.addScriptSource = function (id, url, source) {
|
||||
const script = new Script(id, url, source);
|
||||
this.scripts_[id] = script;
|
||||
this.urlToScript_.set(url, script);
|
||||
};
|
||||
|
||||
|
||||
@ -1033,12 +1076,8 @@ JsonProfile.prototype.addSourcePositions = function (
|
||||
};
|
||||
};
|
||||
|
||||
JsonProfile.prototype.addScriptSource = function (scriptId, url, source) {
|
||||
this.scripts_[scriptId] = {
|
||||
scriptId: scriptId,
|
||||
name: url,
|
||||
source: source
|
||||
};
|
||||
JsonProfile.prototype.addScriptSource = function (id, url, source) {
|
||||
this.scripts_[id] = new Script(id, url, source);
|
||||
};
|
||||
|
||||
|
||||
|
@ -4,7 +4,6 @@
|
||||
|
||||
import { Group } from './ic-model.mjs';
|
||||
import Processor from "./processor.mjs";
|
||||
import { SourcePositionLogEvent } from "./log/sourcePosition.mjs";
|
||||
import { MapLogEvent } from "./log/map.mjs";
|
||||
import { FocusEvent, SelectTimeEvent, SelectionEvent } from './events.mjs';
|
||||
import { defineCustomElement, V8CustomElement } from './helper.mjs';
|
||||
@ -122,18 +121,7 @@ defineCustomElement('ic-panel', (templateText) =>
|
||||
//TODO(zcankara) Handle in the processor for events with source positions.
|
||||
handleFilePositionClick(e) {
|
||||
const entry = e.target.parentNode.entry;
|
||||
const filePosition =
|
||||
this.createSourcePositionLogEvent(
|
||||
entry.entries[0].type, entry.entries[0].time, entry.key,
|
||||
entry.entries[0].script);
|
||||
this.dispatchEvent(new FocusEvent(filePosition));
|
||||
}
|
||||
|
||||
createSourcePositionLogEvent(type, time, filePositionLine, script) {
|
||||
const [file, line, col] = filePositionLine.split(':');
|
||||
const filePosition = new SourcePositionLogEvent(type, time,
|
||||
file, line, col, script);
|
||||
return filePosition
|
||||
this.dispatchEvent(new FocusEvent(entry.filePosition));
|
||||
}
|
||||
|
||||
render(entries, parent) {
|
||||
|
@ -140,7 +140,9 @@ button {
|
||||
color: var(--on-primary-color);
|
||||
}
|
||||
.clickable:hover,
|
||||
.clickable:active {
|
||||
.mark:hover,
|
||||
.clickable:active,
|
||||
.mark:active {
|
||||
background-color: var(--primary-color);
|
||||
color: var(--on-primary-color);
|
||||
cursor: pointer;
|
||||
|
@ -4,7 +4,6 @@
|
||||
|
||||
import { SelectionEvent, FocusEvent, SelectTimeEvent } from "./events.mjs";
|
||||
import { State } from "./app-model.mjs";
|
||||
import { SourcePositionLogEvent } from "./log/sourcePosition.mjs";
|
||||
import { MapLogEvent } from "./log/map.mjs";
|
||||
import { IcLogEvent } from "./log/ic.mjs";
|
||||
import Processor from "./processor.mjs";
|
||||
@ -55,10 +54,10 @@ class App {
|
||||
this.showMapEntries(e.entries);
|
||||
} else if (e.entries[0] instanceof IcLogEvent) {
|
||||
this.showIcEntries(e.entries);
|
||||
} else if (e.entries[0] instanceof SourcePositionLogEvent) {
|
||||
} else if (e.entries[0] instanceof SourcePosition) {
|
||||
this.showSourcePositionEntries(e.entries);
|
||||
} else {
|
||||
console.error("Undefined selection!");
|
||||
throw new Error("Unknown selection type!");
|
||||
}
|
||||
}
|
||||
showMapEntries(entries) {
|
||||
@ -82,10 +81,10 @@ class App {
|
||||
this.selectMapLogEvent(e.entry);
|
||||
} else if (e.entry instanceof IcLogEvent) {
|
||||
this.selectICLogEvent(e.entry);
|
||||
} else if (e.entry instanceof SourcePositionLogEvent) {
|
||||
} else if (e.entry instanceof SourcePosition) {
|
||||
this.selectSourcePositionEvent(e.entry);
|
||||
} else {
|
||||
console.log("undefined");
|
||||
throw new Error("Unknown selection type!");
|
||||
}
|
||||
}
|
||||
selectTimeRange(start, end) {
|
||||
@ -108,7 +107,6 @@ class App {
|
||||
}
|
||||
selectSourcePositionEvent(sourcePositions) {
|
||||
if (!sourcePositions.script) return;
|
||||
console.log("source positions: ", sourcePositions);
|
||||
this.#view.sourcePanel.selectedSourcePositions = [sourcePositions];
|
||||
}
|
||||
|
||||
@ -133,26 +131,22 @@ class App {
|
||||
$("#container").className = "loaded";
|
||||
// instantiate the app logic
|
||||
let fileData = e.detail;
|
||||
try {
|
||||
const processor = this.handleLoadTextProcessor(fileData.chunk);
|
||||
const mapTimeline = processor.mapTimeline;
|
||||
const icTimeline = processor.icTimeline;
|
||||
//TODO(zcankara) Make sure only one instance of src event map ic id match
|
||||
// Load map log events timeline.
|
||||
this.#state.mapTimeline = mapTimeline;
|
||||
// Transitions must be set before timeline for stats panel.
|
||||
this.#view.mapPanel.transitions = this.#state.mapTimeline.transitions;
|
||||
this.#view.mapTrack.data = mapTimeline;
|
||||
this.#state.chunks = this.#view.mapTrack.chunks;
|
||||
this.#view.mapPanel.timeline = mapTimeline;
|
||||
// Load ic log events timeline.
|
||||
this.#state.icTimeline = icTimeline;
|
||||
this.#view.icPanel.timeline = icTimeline;
|
||||
this.#view.icTrack.data = icTimeline;
|
||||
// TODO(zcankara) Load source position log events timeline.
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
const processor = this.handleLoadTextProcessor(fileData.chunk);
|
||||
const mapTimeline = processor.mapTimeline;
|
||||
const icTimeline = processor.icTimeline;
|
||||
//TODO(zcankara) Make sure only one instance of src event map ic id match
|
||||
// Load map log events timeline.
|
||||
this.#state.mapTimeline = mapTimeline;
|
||||
// Transitions must be set before timeline for stats panel.
|
||||
this.#view.mapPanel.transitions = this.#state.mapTimeline.transitions;
|
||||
this.#view.mapTrack.data = mapTimeline;
|
||||
this.#state.chunks = this.#view.mapTrack.chunks;
|
||||
this.#view.mapPanel.timeline = mapTimeline;
|
||||
// Load ic log events timeline.
|
||||
this.#state.icTimeline = icTimeline;
|
||||
this.#view.icPanel.timeline = icTimeline;
|
||||
this.#view.icTrack.data = icTimeline;
|
||||
this.#view.sourcePanel.data = processor.scripts
|
||||
this.fileLoaded = true;
|
||||
}
|
||||
|
||||
|
@ -1,16 +0,0 @@
|
||||
// Copyright 2020 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.
|
||||
import { Event } from './log.mjs';
|
||||
|
||||
class SourcePositionLogEvent extends Event {
|
||||
constructor(type, time, file, line, col, script) {
|
||||
super(type, time);
|
||||
this.file = file;
|
||||
this.line = line;
|
||||
this.col = col;
|
||||
this.script = script;
|
||||
}
|
||||
}
|
||||
|
||||
export { SourcePositionLogEvent };
|
@ -3,7 +3,6 @@
|
||||
// found in the LICENSE file.
|
||||
import { V8CustomElement, defineCustomElement } from "../helper.mjs";
|
||||
import { FocusEvent } from "../events.mjs";
|
||||
import { SourcePositionLogEvent } from "../log/sourcePosition.mjs";
|
||||
|
||||
defineCustomElement(
|
||||
"./map-panel/map-details",
|
||||
@ -43,21 +42,7 @@ defineCustomElement(
|
||||
}
|
||||
|
||||
handleFilePositionClick() {
|
||||
let filePosition =
|
||||
this.createSourcePositionLogEvent(
|
||||
this.selectedMap.type, this.selectedMap.time,
|
||||
this.selectedMap.filePosition, this.selectedMap.script);
|
||||
this.dispatchEvent(new FocusEvent(filePosition));
|
||||
}
|
||||
|
||||
createSourcePositionLogEvent(type, time, filePositionLine, script) {
|
||||
// remove token
|
||||
if (!(/\s/.test(filePositionLine))) return;
|
||||
filePositionLine = filePositionLine.split(' ');
|
||||
let [file, line, col] = filePositionLine[1].split(':');
|
||||
let filePosition = new SourcePositionLogEvent(type, time,
|
||||
file, line, col, script);
|
||||
return filePosition
|
||||
this.dispatchEvent(new FocusEvent(this.selectedMap.sourcePosition));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -52,7 +52,7 @@ class Processor extends LogReader {
|
||||
'map': {
|
||||
parsers: [
|
||||
parseString, parseInt, parseString, parseString, parseInt, parseInt,
|
||||
parseString, parseString, parseString
|
||||
parseInt, parseString, parseString
|
||||
],
|
||||
processor: this.processMap
|
||||
},
|
||||
@ -154,10 +154,6 @@ class Processor extends LogReader {
|
||||
});
|
||||
}
|
||||
|
||||
addEntry(entry) {
|
||||
this.entries.push(entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parser for dynamic code optimization state.
|
||||
*/
|
||||
@ -227,10 +223,13 @@ class Processor extends LogReader {
|
||||
let parts = fnName.split(' ');
|
||||
let fileName = parts[1];
|
||||
let script = this.getScript(fileName);
|
||||
// TODO: Use SourcePosition here directly
|
||||
let entry = new IcLogEvent(
|
||||
type, fnName, time, line, column, key, old_state, new_state, map,
|
||||
slow_reason, script);
|
||||
//TODO(zcankara) Process sourcePosition
|
||||
if (script) {
|
||||
entry.sourcePosition = script.addSourcePosition(line, column, entry);
|
||||
}
|
||||
this.#icTimeline.push(entry);
|
||||
}
|
||||
|
||||
@ -266,11 +265,14 @@ class Processor extends LogReader {
|
||||
if (type === 'Deprecate') return this.deprecateMap(type, time_, from);
|
||||
let from_ = this.getExistingMap(from, time_);
|
||||
let to_ = this.getExistingMap(to, time_);
|
||||
//TODO(zcankara) Process sourcePosition
|
||||
// TODO: use SourcePosition directly.
|
||||
let edge = new Edge(type, name, reason, time, from_, to_);
|
||||
to_.filePosition = this.formatPC(pc, line, column);
|
||||
let fileName = this.processFileName(to_.filePosition);
|
||||
to_.script = this.getScript(fileName);
|
||||
if (to_.script) {
|
||||
to_.sourcePosition = to_.script.addSourcePosition(line, column, to_)
|
||||
}
|
||||
edge.finishSetup();
|
||||
}
|
||||
|
||||
@ -292,7 +294,6 @@ class Processor extends LogReader {
|
||||
|
||||
createMap(id, time) {
|
||||
let map = new MapLogEvent(id, time);
|
||||
//TODO(zcankara) Process sourcePosition
|
||||
this.#mapTimeline.push(map);
|
||||
return map;
|
||||
}
|
||||
@ -309,7 +310,12 @@ class Processor extends LogReader {
|
||||
}
|
||||
|
||||
getScript(url) {
|
||||
return this.#profile.getScript(url);
|
||||
const script = this.#profile.getScript(url);
|
||||
// TODO create placeholder script for empty urls.
|
||||
if (script === undefined) {
|
||||
console.error(`Could not find script for url: '${url}'`)
|
||||
}
|
||||
return script;
|
||||
}
|
||||
|
||||
get icTimeline() {
|
||||
@ -320,7 +326,9 @@ class Processor extends LogReader {
|
||||
return this.#mapTimeline;
|
||||
}
|
||||
|
||||
|
||||
get scripts() {
|
||||
return this.#profile.scripts_.filter(script => script !== undefined);
|
||||
}
|
||||
}
|
||||
|
||||
Processor.kProperties = [
|
||||
|
@ -2,32 +2,50 @@
|
||||
Use of this source code is governed by a BSD-style license that can be
|
||||
found in the LICENSE file. -->
|
||||
|
||||
<head>
|
||||
<link href="./index.css" rel="stylesheet">
|
||||
</head>
|
||||
<style>
|
||||
@import "./index.css";
|
||||
|
||||
pre.scriptNode {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
pre.scriptNode:before {
|
||||
counter-reset: listing;
|
||||
counter-reset: sourceLineCounter;
|
||||
}
|
||||
|
||||
pre.scriptNode code {
|
||||
counter-increment: listing;
|
||||
pre.scriptNode span {
|
||||
counter-increment: sourceLineCounter;
|
||||
}
|
||||
|
||||
pre.scriptNode code::before {
|
||||
content: counter(listing) ". ";
|
||||
pre.scriptNode span::before {
|
||||
content: counter(sourceLineCounter) " ";
|
||||
display: inline-block;
|
||||
width: 4em;
|
||||
padding-left: auto;
|
||||
margin-left: auto;
|
||||
text-align: left;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
mark {
|
||||
width: 1ch;
|
||||
height: 1lh;
|
||||
border-radius: 0.1lh;
|
||||
border: 0.5px var(--background-color) solid;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.marked {
|
||||
background-color: var(--primary-color);
|
||||
color: var(--on-primary-color);
|
||||
}
|
||||
</style>
|
||||
<div class="panel">
|
||||
<h2>Source Panel</h2>
|
||||
<div class="script-dropdown">
|
||||
<label for="scripts-label">Scripts:</label>
|
||||
<select id="script-dropdown"></select>
|
||||
</div>
|
||||
<div id="script">
|
||||
<pre class="scripNode"></pre>
|
||||
</div>
|
||||
|
@ -2,14 +2,21 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
import { V8CustomElement, defineCustomElement } from "./helper.mjs";
|
||||
import { SelectionEvent, FocusEvent } from "./events.mjs";
|
||||
import { MapLogEvent } from "./log/map.mjs";
|
||||
import { IcLogEvent } from "./log/ic.mjs";
|
||||
|
||||
defineCustomElement(
|
||||
"source-panel",
|
||||
(templateText) =>
|
||||
class SourcePanel extends V8CustomElement {
|
||||
#selectedSourcePositions;
|
||||
#scripts = [];
|
||||
#script;
|
||||
constructor() {
|
||||
super(templateText);
|
||||
this.scriptDropdown.addEventListener(
|
||||
'change', e => this.handleSelectScript(e));
|
||||
}
|
||||
get script() {
|
||||
return this.$('#script');
|
||||
@ -18,56 +25,165 @@ defineCustomElement(
|
||||
return this.$('.scriptNode');
|
||||
}
|
||||
set script(script) {
|
||||
this.renderSourcePanel(script);
|
||||
this.#script = script;
|
||||
this.renderSourcePanel();
|
||||
}
|
||||
set selectedSourcePositions(sourcePositions) {
|
||||
this.#selectedSourcePositions = sourcePositions;
|
||||
this.renderSourcePanelSelectedHighlight();
|
||||
}
|
||||
get selectedSourcePositions() {
|
||||
return this.#selectedSourcePositions;
|
||||
}
|
||||
highlightSourcePosition(line, col, script) {
|
||||
//TODO(zcankara) change setting source to support multiple files
|
||||
this.script = script;
|
||||
let codeNodes = this.scriptNode.children;
|
||||
for (let index = 1; index <= codeNodes.length; index++) {
|
||||
if (index != line) continue;
|
||||
let lineText = codeNodes[index - 1].innerHTML;
|
||||
for (let char = 1; char <= lineText.length; char++) {
|
||||
if (char != col) continue;
|
||||
let target = char - 1;
|
||||
codeNodes[line - 1].innerHTML = lineText.slice(0, target) +
|
||||
"<span class='highlight'> </span>" +
|
||||
lineText.slice(target, lineText.length);
|
||||
}
|
||||
set data(value) {
|
||||
this.#scripts = value;
|
||||
this.initializeScriptDropdown();
|
||||
this.script = this.#scripts[0];
|
||||
}
|
||||
get scriptDropdown() {
|
||||
return this.$("#script-dropdown");
|
||||
}
|
||||
initializeScriptDropdown() {
|
||||
this.#scripts.sort((a, b) => a.name.localeCompare(b.name));
|
||||
let select = this.scriptDropdown;
|
||||
select.options.length = 0;
|
||||
for (const script of this.#scripts) {
|
||||
const option = document.createElement("option");
|
||||
option.text = `${script.name} (id=${script.id})`;
|
||||
option.script = script;
|
||||
select.add(option);
|
||||
}
|
||||
}
|
||||
createScriptNode() {
|
||||
let scriptNode = document.createElement("pre");
|
||||
scriptNode.classList.add('scriptNode');
|
||||
return scriptNode;
|
||||
}
|
||||
renderSourcePanel(source) {
|
||||
let scriptNode = this.createScriptNode();
|
||||
let sourceLines = source.split("\n");
|
||||
for (let line = 1; line <= sourceLines.length; line++) {
|
||||
let codeNode = document.createElement("code");
|
||||
codeNode.classList.add("line" + line);
|
||||
codeNode.innerHTML = sourceLines[line - 1] + "\n";
|
||||
scriptNode.appendChild(codeNode);
|
||||
}
|
||||
let oldScriptNode = this.script.childNodes[1];
|
||||
|
||||
renderSourcePanel() {
|
||||
const builder = new LineBuilder(this, this.#script);
|
||||
const scriptNode = builder.createScriptNode();
|
||||
const oldScriptNode = this.script.childNodes[1];
|
||||
this.script.replaceChild(scriptNode, oldScriptNode);
|
||||
}
|
||||
renderSourcePanelSelectedHighlight() {
|
||||
for (const sourcePosition of this.selectedSourcePositions) {
|
||||
let line = sourcePosition.line;
|
||||
let col = sourcePosition.col;
|
||||
let script = sourcePosition.script;
|
||||
if (!(line && col && script)) continue;
|
||||
this.highlightSourcePosition(line, col, script);
|
||||
|
||||
handleSelectScript(e) {
|
||||
const option = this.scriptDropdown.options[this.scriptDropdown.selectedIndex];
|
||||
this.script = option.script;
|
||||
}
|
||||
|
||||
handleSourcePositionClick(e) {
|
||||
let icLogEvents = [];
|
||||
let mapLogEvents = [];
|
||||
for (const entry of e.target.sourcePosition.entries) {
|
||||
if (entry instanceof MapLogEvent) {
|
||||
mapLogEvents.push(entry);
|
||||
} else if (entry instanceof IcLogEvent) {
|
||||
icLogEvents.push(entry);
|
||||
}
|
||||
}
|
||||
if (icLogEvents.length > 0 ) {
|
||||
this.dispatchEvent(new SelectionEvent(icLogEvents));
|
||||
this.dispatchEvent(new FocusEvent(icLogEvents[0]));
|
||||
}
|
||||
if (mapLogEvents.length > 0) {
|
||||
this.dispatchEvent(new SelectionEvent(mapLogEvents));
|
||||
this.dispatchEvent(new FocusEvent(mapLogEvents[0]));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
class SourcePositionIterator {
|
||||
#entries;
|
||||
#index = 0;
|
||||
constructor(sourcePositions) {
|
||||
this.#entries = sourcePositions;
|
||||
}
|
||||
|
||||
*forLine(lineIndex) {
|
||||
while(!this.#done() && this.#current().line === lineIndex) {
|
||||
yield this.#current();
|
||||
this.#next();
|
||||
}
|
||||
}
|
||||
|
||||
#current() {
|
||||
return this.#entries[this.#index];
|
||||
}
|
||||
|
||||
#done() {
|
||||
return this.#index + 1 >= this.#entries.length;
|
||||
}
|
||||
|
||||
#next() {
|
||||
this.#index++;
|
||||
}
|
||||
}
|
||||
|
||||
function * lineIterator(source) {
|
||||
let current = 0;
|
||||
let line = 1;
|
||||
while(current < source.length) {
|
||||
const next = source.indexOf("\n", current);
|
||||
if (next === -1) break;
|
||||
yield [line, source.substring(current, next)];
|
||||
line++;
|
||||
current = next + 1;
|
||||
}
|
||||
if (current < source.length) yield [line, source.substring(current)];
|
||||
}
|
||||
|
||||
class LineBuilder {
|
||||
#script
|
||||
#clickHandler
|
||||
#sourcePositions
|
||||
|
||||
constructor(panel, script) {
|
||||
this.#script = script;
|
||||
this.#clickHandler = panel.handleSourcePositionClick.bind(panel);
|
||||
// TODO: sort on script finalization.
|
||||
script.sourcePositions.sort((a, b) => {
|
||||
if (a.line === b.line) return a.column - b.column;
|
||||
return a.line - b.line;
|
||||
})
|
||||
this.#sourcePositions
|
||||
= new SourcePositionIterator(script.sourcePositions);
|
||||
|
||||
}
|
||||
|
||||
createScriptNode() {
|
||||
const scriptNode = document.createElement("pre");
|
||||
scriptNode.classList.add('scriptNode');
|
||||
for (let [lineIndex, line] of lineIterator(this.#script.source)) {
|
||||
scriptNode.appendChild(this.#createLineNode(lineIndex, line));
|
||||
}
|
||||
return scriptNode;
|
||||
}
|
||||
|
||||
#createLineNode(lineIndex, line) {
|
||||
const lineNode = document.createElement("span");
|
||||
let columnIndex = 0;
|
||||
for (const sourcePosition of this.#sourcePositions.forLine(lineIndex)) {
|
||||
const nextColumnIndex = sourcePosition.column - 1;
|
||||
lineNode.appendChild(
|
||||
document.createTextNode(
|
||||
line.substring(columnIndex, nextColumnIndex)));
|
||||
columnIndex = nextColumnIndex;
|
||||
|
||||
lineNode.appendChild(
|
||||
this.#createMarkerNode(line[columnIndex], sourcePosition));
|
||||
columnIndex++;
|
||||
}
|
||||
lineNode.appendChild(
|
||||
document.createTextNode(line.substring(columnIndex) + "\n"));
|
||||
return lineNode;
|
||||
}
|
||||
|
||||
#createMarkerNode(text, sourcePosition) {
|
||||
const marker = document.createElement("mark");
|
||||
marker.classList.add('marked');
|
||||
marker.textContent = text;
|
||||
marker.sourcePosition = sourcePosition;
|
||||
marker.onclick = this.#clickHandler;
|
||||
return marker;
|
||||
}
|
||||
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user