[turbolizer] Views refactoring
Refactored views: - code-view - disassembly-view Bug: v8:7327 Change-Id: I2020e288ace5b2706546b825620c147686dd310c Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3757899 Reviewed-by: Nico Hartmann <nicohartmann@chromium.org> Commit-Queue: Danylo Boiko <danielboyko02@gmail.com> Cr-Commit-Position: refs/heads/main@{#81820}
This commit is contained in:
parent
398f33fe7a
commit
7e93531493
@ -68,14 +68,14 @@ window.onload = function () {
|
||||
sourceContainer.classList.add("viewpane", "scrollable");
|
||||
sourceTabs.activateTab(sourceTab);
|
||||
|
||||
const sourceView = new CodeView(sourceContainer, selectionBroker, sourceResolver,
|
||||
mainFunction, CodeMode.MainSource);
|
||||
const sourceView = new CodeView(sourceContainer, selectionBroker, mainFunction,
|
||||
sourceResolver, CodeMode.MainSource);
|
||||
sourceView.show();
|
||||
sourceViews.push(sourceView);
|
||||
|
||||
for (const source of sourceResolver.sources) {
|
||||
const sourceView = new CodeView(sourceContainer, selectionBroker, sourceResolver,
|
||||
source, CodeMode.InlinedSource);
|
||||
const sourceView = new CodeView(sourceContainer, selectionBroker, source,
|
||||
sourceResolver, CodeMode.InlinedSource);
|
||||
sourceView.show();
|
||||
sourceViews.push(sourceView);
|
||||
}
|
||||
@ -87,7 +87,7 @@ window.onload = function () {
|
||||
disassemblyView.initializeCode(mainFunction.sourceText);
|
||||
if (sourceResolver.disassemblyPhase) {
|
||||
disassemblyView.initializePerfProfile(jsonObj.eventCounts);
|
||||
disassemblyView.showContent(sourceResolver.disassemblyPhase.data);
|
||||
disassemblyView.showContent(sourceResolver.disassemblyPhase);
|
||||
disassemblyView.show();
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,8 @@ import { SelectionBroker } from "../selection/selection-broker";
|
||||
import { View } from "./view";
|
||||
import { SelectionMap } from "../selection/selection-map";
|
||||
import { ViewElements } from "../common/view-elements";
|
||||
import { SelectionHandler } from "../selection/selection-handler";
|
||||
import { ClearableHandler, SelectionHandler } from "../selection/selection-handler";
|
||||
import { SourcePosition } from "../position";
|
||||
|
||||
interface PR {
|
||||
prettyPrint(_: unknown, el: HTMLElement): void;
|
||||
@ -30,117 +31,32 @@ export class CodeView extends View {
|
||||
codeMode: CodeMode;
|
||||
sourcePositionToHtmlElements: Map<string, Array<HTMLElement>>;
|
||||
showAdditionalInliningPosition: boolean;
|
||||
selectionHandler: SelectionHandler;
|
||||
selection: SelectionMap;
|
||||
selectionHandler: SelectionHandler & ClearableHandler;
|
||||
|
||||
createViewElement() {
|
||||
constructor(parent: HTMLElement, broker: SelectionBroker, sourceFunction: Source,
|
||||
sourceResolver: SourceResolver, codeMode: CodeMode) {
|
||||
super(parent);
|
||||
this.broker = broker;
|
||||
this.source = sourceFunction;
|
||||
this.sourceResolver = sourceResolver;
|
||||
this.codeMode = codeMode;
|
||||
this.sourcePositionToHtmlElements = new Map<string, Array<HTMLElement>>();
|
||||
this.showAdditionalInliningPosition = false;
|
||||
|
||||
this.selection = new SelectionMap((gp: GenericPosition) => gp.toString());
|
||||
this.selectionHandler = this.initializeSourcePositionHandler();
|
||||
broker.addSourcePositionHandler(this.selectionHandler);
|
||||
this.initializeCode();
|
||||
}
|
||||
|
||||
public createViewElement(): HTMLDivElement {
|
||||
const sourceContainer = document.createElement("div");
|
||||
sourceContainer.classList.add("source-container");
|
||||
return sourceContainer;
|
||||
}
|
||||
|
||||
constructor(parent: HTMLElement, broker: SelectionBroker, sourceResolver: SourceResolver, sourceFunction: Source, codeMode: CodeMode) {
|
||||
super(parent);
|
||||
const view = this;
|
||||
view.broker = broker;
|
||||
view.sourceResolver = sourceResolver;
|
||||
view.source = sourceFunction;
|
||||
view.codeMode = codeMode;
|
||||
this.sourcePositionToHtmlElements = new Map();
|
||||
this.showAdditionalInliningPosition = false;
|
||||
|
||||
const selectionHandler = {
|
||||
clear: function () {
|
||||
view.selection.clear();
|
||||
view.updateSelection();
|
||||
broker.broadcastClear(this);
|
||||
},
|
||||
select: function (sourcePositions, selected) {
|
||||
const locations = [];
|
||||
for (const 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 SelectionMap((gp: GenericPosition) => gp.toString());
|
||||
broker.addSourcePositionHandler(selectionHandler);
|
||||
this.selectionHandler = selectionHandler;
|
||||
this.initializeCode();
|
||||
}
|
||||
|
||||
addHtmlElementToSourcePosition(sourcePosition, element) {
|
||||
const key = sourcePosition.toString();
|
||||
if (!this.sourcePositionToHtmlElements.has(key)) {
|
||||
this.sourcePositionToHtmlElements.set(key, []);
|
||||
}
|
||||
this.sourcePositionToHtmlElements.get(key).push(element);
|
||||
}
|
||||
|
||||
getHtmlElementForSourcePosition(sourcePosition) {
|
||||
return this.sourcePositionToHtmlElements.get(sourcePosition.toString());
|
||||
}
|
||||
|
||||
updateSelection(scrollIntoView: boolean = false): void {
|
||||
const mkVisible = new ViewElements(this.divNode.parentNode as HTMLElement);
|
||||
for (const [sp, els] of this.sourcePositionToHtmlElements.entries()) {
|
||||
const isSelected = this.selection.isKeySelected(sp);
|
||||
for (const el of els) {
|
||||
mkVisible.consider(el, isSelected);
|
||||
el.classList.toggle("selected", isSelected);
|
||||
}
|
||||
}
|
||||
mkVisible.apply(scrollIntoView);
|
||||
}
|
||||
|
||||
getCodeHtmlElementName() {
|
||||
return `source-pre-${this.source.sourceId}`;
|
||||
}
|
||||
|
||||
getCodeHeaderHtmlElementName() {
|
||||
return `source-pre-${this.source.sourceId}-header`;
|
||||
}
|
||||
|
||||
getHtmlCodeLines(): NodeListOf<HTMLElement> {
|
||||
const ordereList = this.divNode.querySelector(`#${this.getCodeHtmlElementName()} ol`);
|
||||
return ordereList.childNodes as NodeListOf<HTMLElement>;
|
||||
}
|
||||
|
||||
onSelectLine(lineNumber: number, doClear: boolean) {
|
||||
if (doClear) {
|
||||
this.selectionHandler.clear();
|
||||
}
|
||||
const positions = this.sourceResolver.lineToSourcePositions(lineNumber - 1);
|
||||
if (positions !== undefined) {
|
||||
this.selectionHandler.select(positions, undefined);
|
||||
}
|
||||
}
|
||||
|
||||
onSelectSourcePosition(sourcePosition, doClear: boolean) {
|
||||
if (doClear) {
|
||||
this.selectionHandler.clear();
|
||||
}
|
||||
this.selectionHandler.select([sourcePosition], undefined);
|
||||
}
|
||||
|
||||
initializeCode() {
|
||||
private initializeCode(): void {
|
||||
const view = this;
|
||||
const source = this.source;
|
||||
const sourceText = source.sourceText;
|
||||
@ -160,7 +76,7 @@ export class CodeView extends View {
|
||||
codeHeader.appendChild(codeFileFunction);
|
||||
const codeModeDiv = document.createElement("div");
|
||||
codeModeDiv.classList.add("code-mode");
|
||||
codeModeDiv.innerHTML = `${this.codeMode}`;
|
||||
codeModeDiv.innerHTML = this.codeMode;
|
||||
codeHeader.appendChild(codeModeDiv);
|
||||
const clearDiv = document.createElement("div");
|
||||
clearDiv.style.clear = "both";
|
||||
@ -178,7 +94,7 @@ export class CodeView extends View {
|
||||
codePre.style.display = "none";
|
||||
}
|
||||
};
|
||||
if (sourceText != "") {
|
||||
if (sourceText !== "") {
|
||||
codePre.classList.add("linenums");
|
||||
codePre.textContent = sourceText;
|
||||
try {
|
||||
@ -189,7 +105,7 @@ export class CodeView extends View {
|
||||
}
|
||||
|
||||
view.divNode.onclick = function (e: MouseEvent) {
|
||||
if (e.target instanceof Element && e.target.tagName == "DIV") {
|
||||
if (e.target instanceof Element && e.target.tagName === "DIV") {
|
||||
const targetDiv = e.target as HTMLDivElement;
|
||||
if (targetDiv.classList.contains("line-number")) {
|
||||
e.stopPropagation();
|
||||
@ -208,8 +124,8 @@ export class CodeView extends View {
|
||||
// Line numbers are not zero-based.
|
||||
const lineNumber = i + 1;
|
||||
const currentLineElement = lineListDiv[i];
|
||||
currentLineElement.id = "li" + i;
|
||||
currentLineElement.dataset.lineNumber = "" + lineNumber;
|
||||
currentLineElement.id = `li${i}`;
|
||||
currentLineElement.dataset.lineNumber = String(lineNumber);
|
||||
const spans = currentLineElement.childNodes;
|
||||
for (const currentSpan of spans) {
|
||||
if (currentSpan instanceof HTMLSpanElement) {
|
||||
@ -224,7 +140,7 @@ export class CodeView extends View {
|
||||
this.insertLineNumber(currentLineElement, lineNumber);
|
||||
|
||||
while ((current < sourceText.length) &&
|
||||
(sourceText[current] == '\n' || sourceText[current] == '\r')) {
|
||||
(sourceText[current] === "\n" || sourceText[current] === "\r")) {
|
||||
++current;
|
||||
++newlineAdjust;
|
||||
}
|
||||
@ -232,7 +148,95 @@ export class CodeView extends View {
|
||||
}
|
||||
}
|
||||
|
||||
insertSourcePositions(currentSpan, lineNumber, pos, end, adjust) {
|
||||
private initializeSourcePositionHandler(): SelectionHandler & ClearableHandler {
|
||||
const view = this;
|
||||
const broker = this.broker;
|
||||
const sourceResolver = this.sourceResolver;
|
||||
return {
|
||||
select: function (sourcePositions: Array<SourcePosition>, selected: boolean) {
|
||||
const locations = new Array<SourcePosition>();
|
||||
for (const 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);
|
||||
},
|
||||
clear: function () {
|
||||
view.selection.clear();
|
||||
view.updateSelection();
|
||||
broker.broadcastClear(this);
|
||||
},
|
||||
brokeredSourcePositionSelect: function (locations: Array<SourcePosition>, selected: boolean) {
|
||||
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();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private addHtmlElementToSourcePosition(sourcePosition, element): void {
|
||||
const key = sourcePosition.toString();
|
||||
if (!this.sourcePositionToHtmlElements.has(key)) {
|
||||
this.sourcePositionToHtmlElements.set(key, new Array<HTMLElement>());
|
||||
}
|
||||
this.sourcePositionToHtmlElements.get(key).push(element);
|
||||
}
|
||||
|
||||
private updateSelection(scrollIntoView: boolean = false): void {
|
||||
const mkVisible = new ViewElements(this.divNode.parentNode as HTMLElement);
|
||||
for (const [sp, els] of this.sourcePositionToHtmlElements.entries()) {
|
||||
const isSelected = this.selection.isKeySelected(sp);
|
||||
for (const el of els) {
|
||||
mkVisible.consider(el, isSelected);
|
||||
el.classList.toggle("selected", isSelected);
|
||||
}
|
||||
}
|
||||
mkVisible.apply(scrollIntoView);
|
||||
}
|
||||
|
||||
private getCodeHtmlElementName(): string {
|
||||
return `source-pre-${this.source.sourceId}`;
|
||||
}
|
||||
|
||||
private getCodeHeaderHtmlElementName(): string {
|
||||
return `source-pre-${this.source.sourceId}-header`;
|
||||
}
|
||||
|
||||
private getHtmlCodeLines(): NodeListOf<HTMLElement> {
|
||||
const ordereList = this.divNode.querySelector(`#${this.getCodeHtmlElementName()} ol`);
|
||||
return ordereList.childNodes as NodeListOf<HTMLElement>;
|
||||
}
|
||||
|
||||
private onSelectLine(lineNumber: number, doClear: boolean) {
|
||||
if (doClear) {
|
||||
this.selectionHandler.clear();
|
||||
}
|
||||
const positions = this.sourceResolver.lineToSourcePositions(lineNumber - 1);
|
||||
if (positions !== undefined) {
|
||||
this.selectionHandler.select(positions, undefined);
|
||||
}
|
||||
}
|
||||
|
||||
private onSelectSourcePosition(sourcePosition: SourcePosition, doClear: boolean) {
|
||||
if (doClear) {
|
||||
this.selectionHandler.clear();
|
||||
}
|
||||
this.selectionHandler.select([sourcePosition], undefined);
|
||||
}
|
||||
|
||||
private insertSourcePositions(currentSpan: HTMLSpanElement, lineNumber: number,
|
||||
pos: number, end: number, adjust: number): void {
|
||||
const view = this;
|
||||
const sps = this.sourceResolver.sourcePositionsInRange(this.source.sourceId, pos - adjust, end);
|
||||
let offset = 0;
|
||||
@ -240,43 +244,42 @@ export class CodeView extends View {
|
||||
// Internally, line numbers are 0-based so we have to substract 1 from the line number. This
|
||||
// path in only taken by non-Wasm code. Wasm code relies on setSourceLineToBytecodePosition.
|
||||
this.sourceResolver.addAnyPositionToLine(lineNumber - 1, sourcePosition);
|
||||
const textnode = currentSpan.tagName == 'SPAN' ? currentSpan.lastChild : currentSpan;
|
||||
if (!(textnode instanceof Text)) continue;
|
||||
const textNode = currentSpan.tagName === "SPAN" ? currentSpan.lastChild : currentSpan;
|
||||
if (!(textNode instanceof Text)) continue;
|
||||
const splitLength = Math.max(0, sourcePosition.scriptOffset - pos - offset);
|
||||
offset += splitLength;
|
||||
const replacementNode = textnode.splitText(splitLength);
|
||||
const span = document.createElement('span');
|
||||
const replacementNode = textNode.splitText(splitLength);
|
||||
const span = document.createElement("span");
|
||||
span.setAttribute("scriptOffset", sourcePosition.scriptOffset.toString());
|
||||
span.classList.add("source-position");
|
||||
const marker = document.createElement('span');
|
||||
const marker = document.createElement("span");
|
||||
marker.classList.add("marker");
|
||||
span.appendChild(marker);
|
||||
const inlining = this.sourceResolver.getInliningForPosition(sourcePosition);
|
||||
if (inlining != undefined && view.showAdditionalInliningPosition) {
|
||||
if (inlining && view.showAdditionalInliningPosition) {
|
||||
const sourceName = this.sourceResolver.getSourceName(inlining.sourceId);
|
||||
const inliningMarker = document.createElement('span');
|
||||
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) {
|
||||
span.onclick = function (e: MouseEvent) {
|
||||
e.stopPropagation();
|
||||
view.onSelectSourcePosition(sourcePosition, !e.shiftKey);
|
||||
};
|
||||
view.addHtmlElementToSourcePosition(sourcePosition, span);
|
||||
textnode.parentNode.insertBefore(span, replacementNode);
|
||||
textNode.parentNode.insertBefore(span, replacementNode);
|
||||
}
|
||||
}
|
||||
|
||||
insertLineNumber(lineElement: HTMLElement, lineNumber: number) {
|
||||
const view = this;
|
||||
private insertLineNumber(lineElement: HTMLElement, lineNumber: number): void {
|
||||
const lineNumberElement = document.createElement("div");
|
||||
lineNumberElement.classList.add("line-number");
|
||||
lineNumberElement.dataset.lineNumber = `${lineNumber}`;
|
||||
lineNumberElement.innerText = `${lineNumber}`;
|
||||
lineNumberElement.dataset.lineNumber = String(lineNumber);
|
||||
lineNumberElement.innerText = String(lineNumber);
|
||||
lineElement.insertBefore(lineNumberElement, lineElement.firstChild);
|
||||
for (const sourcePosition of this.sourceResolver.lineToSourcePositions(lineNumber - 1)) {
|
||||
view.addHtmlElementToSourcePosition(sourcePosition, lineElement);
|
||||
this.addHtmlElementToSourcePosition(sourcePosition, lineElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,13 +3,14 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import * as C from "../common/constants";
|
||||
import { interpolate } from "../common/util";
|
||||
import { interpolate, storageGetItem, storageSetItem } from "../common/util";
|
||||
import { SelectionBroker } from "../selection/selection-broker";
|
||||
import { TextView } from "./text-view";
|
||||
import { SelectionMap } from "../selection/selection-map";
|
||||
import { InstructionSelectionHandler } from "../selection/selection-handler";
|
||||
import { ClearableHandler, InstructionSelectionHandler } from "../selection/selection-handler";
|
||||
import { TurbolizerInstructionStartInfo } from "../phases/instructions-phase";
|
||||
import { SelectionStorage } from "../selection/selection-storage";
|
||||
import { DisassemblyPhase } from "../phases/disassembly-phase";
|
||||
|
||||
const toolboxHTML =
|
||||
`<div id="disassembly-toolbox">
|
||||
@ -21,237 +22,27 @@ const toolboxHTML =
|
||||
</div>`;
|
||||
|
||||
export class DisassemblyView extends TextView {
|
||||
SOURCE_POSITION_HEADER_REGEX: any;
|
||||
addrEventCounts: any;
|
||||
totalEventCounts: any;
|
||||
maxEventCounts: any;
|
||||
posLines: Array<any>;
|
||||
instructionSelectionHandler: InstructionSelectionHandler;
|
||||
posLines: Array<number>;
|
||||
instructionSelectionHandler: InstructionSelectionHandler & ClearableHandler;
|
||||
offsetSelection: SelectionMap;
|
||||
showInstructionAddressHandler: () => void;
|
||||
showInstructionBinaryHandler: () => void;
|
||||
highlightGapInstructionsHandler: () => void;
|
||||
|
||||
constructor(parentId, broker: SelectionBroker) {
|
||||
super(parentId, broker);
|
||||
const view = this;
|
||||
const ADDRESS_STYLE = {
|
||||
associateData: (text, fragment: HTMLElement) => {
|
||||
const matches = text.match(/(?<address>0?x?[0-9a-fA-F]{8,16})(?<addressSpace>\s+)(?<offset>[0-9a-f]+)(?<offsetSpace>\s*)/);
|
||||
const offset = Number.parseInt(matches.groups["offset"], 16);
|
||||
const instructionKind = view.sourceResolver.instructionsPhase
|
||||
.getInstructionKindForPCOffset(offset);
|
||||
fragment.dataset.instructionKind = instructionKind;
|
||||
fragment.title = view.sourceResolver.instructionsPhase
|
||||
.instructionKindToReadableName(instructionKind);
|
||||
const blockIds = view.sourceResolver.disassemblyPhase.getBlockIdsForOffset(offset);
|
||||
const blockIdElement = document.createElement("SPAN");
|
||||
blockIdElement.className = "block-id com linkable-text";
|
||||
blockIdElement.innerText = "";
|
||||
if (blockIds && blockIds.length > 0) {
|
||||
blockIds.forEach(blockId => view.addHtmlElementForBlockId(blockId, fragment));
|
||||
blockIdElement.innerText = `B${blockIds.join(",")}:`;
|
||||
blockIdElement.dataset.blockId = `${blockIds.join(",")}`;
|
||||
}
|
||||
fragment.appendChild(blockIdElement);
|
||||
const addressElement = document.createElement("SPAN");
|
||||
addressElement.className = "instruction-address";
|
||||
addressElement.innerText = matches.groups["address"];
|
||||
const offsetElement = document.createElement("SPAN");
|
||||
offsetElement.innerText = matches.groups["offset"];
|
||||
fragment.appendChild(addressElement);
|
||||
fragment.appendChild(document.createTextNode(matches.groups["addressSpace"]));
|
||||
fragment.appendChild(offsetElement);
|
||||
fragment.appendChild(document.createTextNode(matches.groups["offsetSpace"]));
|
||||
fragment.classList.add('tag');
|
||||
constructor(parent: HTMLDivElement, broker: SelectionBroker) {
|
||||
super(parent, broker);
|
||||
this.patterns = this.initializePatterns();
|
||||
|
||||
if (!Number.isNaN(offset)) {
|
||||
let pcOffset = view.sourceResolver.instructionsPhase.getKeyPcOffset(offset);
|
||||
if (pcOffset == -1) pcOffset = Number(offset);
|
||||
fragment.dataset.pcOffset = `${pcOffset}`;
|
||||
addressElement.classList.add('linkable-text');
|
||||
offsetElement.classList.add('linkable-text');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
const UNCLASSIFIED_STYLE = {
|
||||
css: 'com'
|
||||
};
|
||||
const NUMBER_STYLE = {
|
||||
css: ['instruction-binary', 'lit']
|
||||
};
|
||||
const COMMENT_STYLE = {
|
||||
css: 'com'
|
||||
};
|
||||
const OPCODE_ARGS = {
|
||||
associateData: function (text, fragment) {
|
||||
fragment.innerHTML = text;
|
||||
const replacer = (match, hexOffset) => {
|
||||
const offset = Number.parseInt(hexOffset, 16);
|
||||
let keyOffset = view.sourceResolver.instructionsPhase.getKeyPcOffset(offset);
|
||||
if (keyOffset == -1) keyOffset = Number(offset);
|
||||
const blockIds = view.sourceResolver.disassemblyPhase.getBlockIdsForOffset(offset);
|
||||
let block = "";
|
||||
let blockIdData = "";
|
||||
if (blockIds && blockIds.length > 0) {
|
||||
block = `B${blockIds.join(",")} `;
|
||||
blockIdData = `data-block-id="${blockIds.join(",")}"`;
|
||||
}
|
||||
return `<span class="tag linkable-text" data-pc-offset="${keyOffset}" ${blockIdData}>${block}${match}</span>`;
|
||||
};
|
||||
const html = text.replace(/<.0?x?([0-9a-fA-F]+)>/g, replacer);
|
||||
fragment.innerHTML = html;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
const OPCODE_STYLE = {
|
||||
css: 'kwd'
|
||||
};
|
||||
const BLOCK_HEADER_STYLE = {
|
||||
associateData: function (text, fragment) {
|
||||
if (view.sourceResolver.disassemblyPhase.hasBlockStartInfo()) return false;
|
||||
const matches = /\d+/.exec(text);
|
||||
if (!matches) return true;
|
||||
const blockId = matches[0];
|
||||
fragment.dataset.blockId = blockId;
|
||||
fragment.innerHTML = text;
|
||||
fragment.className = "com block";
|
||||
return true;
|
||||
}
|
||||
};
|
||||
const SOURCE_POSITION_HEADER_STYLE = {
|
||||
css: 'com'
|
||||
};
|
||||
view.SOURCE_POSITION_HEADER_REGEX = /^\s*--[^<]*<.*(not inlined|inlined\((\d+)\)):(\d+)>\s*--/;
|
||||
const patterns = [
|
||||
[
|
||||
[/^0?x?[0-9a-fA-F]{8,16}\s+[0-9a-f]+\s+/, ADDRESS_STYLE, 1],
|
||||
[view.SOURCE_POSITION_HEADER_REGEX, SOURCE_POSITION_HEADER_STYLE, -1],
|
||||
[/^\s+-- B\d+ start.*/, BLOCK_HEADER_STYLE, -1],
|
||||
[/^.*/, UNCLASSIFIED_STYLE, -1]
|
||||
],
|
||||
[
|
||||
[/^\s*[0-9a-f]+\s+/, NUMBER_STYLE, 2],
|
||||
[/^\s*[0-9a-f]+\s+[0-9a-f]+\s+/, NUMBER_STYLE, 2],
|
||||
[/^.*/, null, -1]
|
||||
],
|
||||
[
|
||||
[/^REX.W \S+\s+/, OPCODE_STYLE, 3],
|
||||
[/^\S+\s+/, OPCODE_STYLE, 3],
|
||||
[/^\S+$/, OPCODE_STYLE, -1],
|
||||
[/^.*/, null, -1]
|
||||
],
|
||||
[
|
||||
[/^\s+/, null],
|
||||
[/^[^;]+$/, OPCODE_ARGS, -1],
|
||||
[/^[^;]+/, OPCODE_ARGS, 4],
|
||||
[/^;/, COMMENT_STYLE, 5]
|
||||
],
|
||||
[
|
||||
[/^.+$/, COMMENT_STYLE, -1]
|
||||
]
|
||||
];
|
||||
view.setPatterns(patterns);
|
||||
|
||||
const linkHandler = (e: MouseEvent) => {
|
||||
if (!(e.target instanceof HTMLElement)) return;
|
||||
const offsetAsString = typeof e.target.dataset.pcOffset != "undefined" ? e.target.dataset.pcOffset : e.target.parentElement.dataset.pcOffset;
|
||||
const offset = Number.parseInt(offsetAsString, 10);
|
||||
if ((typeof offsetAsString) != "undefined" && !Number.isNaN(offset)) {
|
||||
view.offsetSelection.select([offset], true);
|
||||
const nodes = view.sourceResolver.instructionsPhase.nodesForPCOffset(offset);
|
||||
if (nodes.length > 0) {
|
||||
e.stopPropagation();
|
||||
if (!e.shiftKey) {
|
||||
view.selectionHandler.clear();
|
||||
}
|
||||
view.selectionHandler.select(nodes, true);
|
||||
} else {
|
||||
view.updateSelection();
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
view.divNode.addEventListener('click', linkHandler);
|
||||
|
||||
const linkHandlerBlock = e => {
|
||||
const blockId = e.target.dataset.blockId;
|
||||
if (typeof blockId != "undefined") {
|
||||
const blockIds = blockId.split(",");
|
||||
if (!e.shiftKey) {
|
||||
view.selectionHandler.clear();
|
||||
}
|
||||
view.blockSelectionHandler.select(blockIds, true);
|
||||
}
|
||||
};
|
||||
view.divNode.addEventListener('click', linkHandlerBlock);
|
||||
this.divNode.addEventListener("click", e => this.linkClickHandler(e));
|
||||
this.divNode.addEventListener("click", e => this.linkBlockClickHandler(e));
|
||||
|
||||
this.offsetSelection = new SelectionMap(offset => String(offset));
|
||||
const instructionSelectionHandler = {
|
||||
clear: function () {
|
||||
view.offsetSelection.clear();
|
||||
view.updateSelection();
|
||||
broker.broadcastClear(instructionSelectionHandler);
|
||||
},
|
||||
select: function (instructionIds, selected) {
|
||||
view.offsetSelection.select(instructionIds, selected);
|
||||
view.updateSelection();
|
||||
broker.broadcastBlockSelect(instructionSelectionHandler, instructionIds, selected);
|
||||
},
|
||||
brokeredInstructionSelect: function (instructionIds, selected) {
|
||||
const firstSelect = view.offsetSelection.isEmpty();
|
||||
const keyPcOffsets = view.sourceResolver.instructionsPhase
|
||||
.instructionsToKeyPcOffsets(instructionIds);
|
||||
view.offsetSelection.select(keyPcOffsets, selected);
|
||||
view.updateSelection(firstSelect);
|
||||
},
|
||||
brokeredClear: function () {
|
||||
view.offsetSelection.clear();
|
||||
view.updateSelection();
|
||||
}
|
||||
};
|
||||
this.instructionSelectionHandler = instructionSelectionHandler;
|
||||
broker.addInstructionHandler(instructionSelectionHandler);
|
||||
|
||||
const toolbox = document.createElement("div");
|
||||
toolbox.id = "toolbox-anchor";
|
||||
toolbox.innerHTML = toolboxHTML;
|
||||
view.divNode.insertBefore(toolbox, view.divNode.firstChild);
|
||||
const instructionAddressInput: HTMLInputElement = view.divNode.querySelector("#show-instruction-address");
|
||||
const lastShowInstructionAddress = window.sessionStorage.getItem("show-instruction-address");
|
||||
instructionAddressInput.checked = lastShowInstructionAddress == 'true';
|
||||
const showInstructionAddressHandler = () => {
|
||||
window.sessionStorage.setItem("show-instruction-address", `${instructionAddressInput.checked}`);
|
||||
for (const el of view.divNode.querySelectorAll(".instruction-address")) {
|
||||
el.classList.toggle("invisible", !instructionAddressInput.checked);
|
||||
}
|
||||
};
|
||||
instructionAddressInput.addEventListener("change", showInstructionAddressHandler);
|
||||
this.showInstructionAddressHandler = showInstructionAddressHandler;
|
||||
|
||||
const instructionBinaryInput: HTMLInputElement = view.divNode.querySelector("#show-instruction-binary");
|
||||
const lastShowInstructionBinary = window.sessionStorage.getItem("show-instruction-binary");
|
||||
instructionBinaryInput.checked = lastShowInstructionBinary == 'true';
|
||||
const showInstructionBinaryHandler = () => {
|
||||
window.sessionStorage.setItem("show-instruction-binary", `${instructionBinaryInput.checked}`);
|
||||
for (const el of view.divNode.querySelectorAll(".instruction-binary")) {
|
||||
el.classList.toggle("invisible", !instructionBinaryInput.checked);
|
||||
}
|
||||
};
|
||||
instructionBinaryInput.addEventListener("change", showInstructionBinaryHandler);
|
||||
this.showInstructionBinaryHandler = showInstructionBinaryHandler;
|
||||
|
||||
const highlightGapInstructionsInput: HTMLInputElement = view.divNode.querySelector("#highlight-gap-instructions");
|
||||
const lastHighlightGapInstructions = window.sessionStorage.getItem("highlight-gap-instructions");
|
||||
highlightGapInstructionsInput.checked = lastHighlightGapInstructions == 'true';
|
||||
const highlightGapInstructionsHandler = () => {
|
||||
window.sessionStorage.setItem("highlight-gap-instructions", `${highlightGapInstructionsInput.checked}`);
|
||||
view.divNode.classList.toggle("highlight-gap-instructions", highlightGapInstructionsInput.checked);
|
||||
};
|
||||
|
||||
highlightGapInstructionsInput.addEventListener("change", highlightGapInstructionsHandler);
|
||||
this.highlightGapInstructionsHandler = highlightGapInstructionsHandler;
|
||||
this.instructionSelectionHandler = this.initializeInstructionSelectionHandler();
|
||||
this.broker.addInstructionHandler(this.instructionSelectionHandler);
|
||||
this.addDisassemblyToolbox();
|
||||
}
|
||||
|
||||
public createViewElement(): HTMLDivElement {
|
||||
@ -283,93 +74,22 @@ export class DisassemblyView extends TextView {
|
||||
}
|
||||
}
|
||||
|
||||
public searchInputAction(searchInput: HTMLInputElement, e: Event, onlyVisible: boolean): void {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
||||
public detachSelection(): SelectionStorage {
|
||||
return null;
|
||||
}
|
||||
|
||||
public adaptSelection(selection: SelectionStorage): SelectionStorage {
|
||||
return selection;
|
||||
}
|
||||
|
||||
initializeCode(sourceText, sourcePosition: number = 0) {
|
||||
const view = this;
|
||||
view.addrEventCounts = null;
|
||||
view.totalEventCounts = null;
|
||||
view.maxEventCounts = null;
|
||||
view.posLines = new Array();
|
||||
// Comment lines for line 0 include sourcePosition already, only need to
|
||||
// add sourcePosition for lines > 0.
|
||||
view.posLines[0] = sourcePosition;
|
||||
if (sourceText && sourceText != "") {
|
||||
const base = sourcePosition;
|
||||
let current = 0;
|
||||
const sourceLines = sourceText.split("\n");
|
||||
for (let i = 1; i < sourceLines.length; i++) {
|
||||
// Add 1 for newline character that is split off.
|
||||
current += sourceLines[i - 1].length + 1;
|
||||
view.posLines[i] = base + current;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
initializePerfProfile(eventCounts) {
|
||||
const view = this;
|
||||
if (eventCounts !== undefined) {
|
||||
view.addrEventCounts = eventCounts;
|
||||
|
||||
view.totalEventCounts = {};
|
||||
view.maxEventCounts = {};
|
||||
for (const evName in view.addrEventCounts) {
|
||||
if (view.addrEventCounts.hasOwnProperty(evName)) {
|
||||
const keys = Object.keys(view.addrEventCounts[evName]);
|
||||
const values = keys.map(key => view.addrEventCounts[evName][key]);
|
||||
view.totalEventCounts[evName] = values.reduce((a, b) => a + b);
|
||||
view.maxEventCounts[evName] = values.reduce((a, b) => Math.max(a, b));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
view.addrEventCounts = null;
|
||||
view.totalEventCounts = null;
|
||||
view.maxEventCounts = null;
|
||||
}
|
||||
}
|
||||
|
||||
public showContent(data): void {
|
||||
console.time("disassembly-view");
|
||||
super.initializeContent(data, null);
|
||||
this.showInstructionAddressHandler();
|
||||
this.showInstructionBinaryHandler();
|
||||
this.highlightGapInstructionsHandler();
|
||||
console.timeEnd("disassembly-view");
|
||||
}
|
||||
|
||||
// Shorten decimals and remove trailing zeroes for readability.
|
||||
humanize(num) {
|
||||
return num.toFixed(3).replace(/\.?0+$/, "") + "%";
|
||||
}
|
||||
|
||||
processLine(line) {
|
||||
const view = this;
|
||||
public processLine(line: string): Array<HTMLSpanElement> {
|
||||
let fragments = super.processLine(line);
|
||||
const cssCls = "prof";
|
||||
|
||||
// Add profiling data per instruction if available.
|
||||
if (view.totalEventCounts) {
|
||||
if (this.totalEventCounts) {
|
||||
const matches = /^(0x[0-9a-fA-F]+)\s+\d+\s+[0-9a-fA-F]+/.exec(line);
|
||||
if (matches) {
|
||||
const newFragments = [];
|
||||
for (const event in view.addrEventCounts) {
|
||||
if (!view.addrEventCounts.hasOwnProperty(event)) continue;
|
||||
const count = view.addrEventCounts[event][matches[1]];
|
||||
let str = " ";
|
||||
const cssCls = "prof";
|
||||
const newFragments = new Array<HTMLSpanElement>();
|
||||
for (const event in this.addrEventCounts) {
|
||||
if (!this.addrEventCounts.hasOwnProperty(event)) continue;
|
||||
const count = this.addrEventCounts[event][matches[1]];
|
||||
if (count !== undefined) {
|
||||
const perc = count / view.totalEventCounts[event] * 100;
|
||||
|
||||
const perc = count / this.totalEventCounts[event] * 100;
|
||||
let col = { r: 255, g: 255, b: 255 };
|
||||
|
||||
for (let i = 0; i < C.PROF_COLS.length; i++) {
|
||||
if (perc === C.PROF_COLS[i].perc) {
|
||||
col = C.PROF_COLS[i].col;
|
||||
@ -377,7 +97,6 @@ export class DisassemblyView extends TextView {
|
||||
} else if (perc > C.PROF_COLS[i].perc && perc < C.PROF_COLS[i + 1].perc) {
|
||||
const col1 = C.PROF_COLS[i].col;
|
||||
const col2 = C.PROF_COLS[i + 1].col;
|
||||
|
||||
const val = perc - C.PROF_COLS[i].perc;
|
||||
const max = C.PROF_COLS[i + 1].perc - C.PROF_COLS[i].perc;
|
||||
|
||||
@ -388,15 +107,12 @@ export class DisassemblyView extends TextView {
|
||||
}
|
||||
}
|
||||
|
||||
str = C.UNICODE_BLOCK;
|
||||
|
||||
const fragment = view.createFragment(str, cssCls);
|
||||
fragment.title = event + ": " + view.humanize(perc) + " (" + count + ")";
|
||||
fragment.style.color = "rgb(" + col.r + ", " + col.g + ", " + col.b + ")";
|
||||
|
||||
const fragment = this.createFragment(C.UNICODE_BLOCK, cssCls);
|
||||
fragment.title = `${event}: ${this.makeReadable(perc)} (${count})`;
|
||||
fragment.style.color = `rgb(${col.r}, ${col.g}, ${col.b})`;
|
||||
newFragments.push(fragment);
|
||||
} else {
|
||||
newFragments.push(view.createFragment(str, cssCls));
|
||||
newFragments.push(this.createFragment(" ", cssCls));
|
||||
}
|
||||
}
|
||||
fragments = newFragments.concat(fragments);
|
||||
@ -404,4 +120,321 @@ export class DisassemblyView extends TextView {
|
||||
}
|
||||
return fragments;
|
||||
}
|
||||
|
||||
public detachSelection(): SelectionStorage {
|
||||
return null;
|
||||
}
|
||||
|
||||
public adaptSelection(selection: SelectionStorage): SelectionStorage {
|
||||
return selection;
|
||||
}
|
||||
|
||||
public searchInputAction(searchInput: HTMLInputElement, e: Event, onlyVisible: boolean): void {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
||||
public showContent(disassemblyPhase: DisassemblyPhase): void {
|
||||
console.time("disassembly-view");
|
||||
super.initializeContent(disassemblyPhase, null);
|
||||
this.showInstructionAddressHandler();
|
||||
this.showInstructionBinaryHandler();
|
||||
this.highlightGapInstructionsHandler();
|
||||
console.timeEnd("disassembly-view");
|
||||
}
|
||||
|
||||
public initializeCode(sourceText: string, sourcePosition: number = 0): void {
|
||||
this.addrEventCounts = null;
|
||||
this.totalEventCounts = null;
|
||||
this.maxEventCounts = null;
|
||||
this.posLines = new Array<number>();
|
||||
// Comment lines for line 0 include sourcePosition already, only need to
|
||||
// add sourcePosition for lines > 0.
|
||||
this.posLines[0] = sourcePosition;
|
||||
if (sourceText && sourceText !== "") {
|
||||
const base = sourcePosition;
|
||||
let current = 0;
|
||||
const sourceLines = sourceText.split("\n");
|
||||
for (let i = 1; i < sourceLines.length; i++) {
|
||||
// Add 1 for newline character that is split off.
|
||||
current += sourceLines[i - 1].length + 1;
|
||||
this.posLines[i] = base + current;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public initializePerfProfile(eventCounts): void {
|
||||
if (eventCounts !== undefined) {
|
||||
this.addrEventCounts = eventCounts;
|
||||
this.totalEventCounts = {};
|
||||
this.maxEventCounts = {};
|
||||
for (const evName in this.addrEventCounts) {
|
||||
if (this.addrEventCounts.hasOwnProperty(evName)) {
|
||||
const keys = Object.keys(this.addrEventCounts[evName]);
|
||||
const values = keys.map(key => this.addrEventCounts[evName][key]);
|
||||
this.totalEventCounts[evName] = values.reduce((a, b) => a + b);
|
||||
this.maxEventCounts[evName] = values.reduce((a, b) => Math.max(a, b));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.addrEventCounts = null;
|
||||
this.totalEventCounts = null;
|
||||
this.maxEventCounts = null;
|
||||
}
|
||||
}
|
||||
|
||||
private initializeInstructionSelectionHandler(): InstructionSelectionHandler & ClearableHandler {
|
||||
const view = this;
|
||||
const broker = this.broker;
|
||||
return {
|
||||
select: function (instructionIds: Array<number>, selected: boolean) {
|
||||
view.offsetSelection.select(instructionIds, selected);
|
||||
view.updateSelection();
|
||||
broker.broadcastBlockSelect(this, instructionIds, selected);
|
||||
},
|
||||
clear: function () {
|
||||
view.offsetSelection.clear();
|
||||
view.updateSelection();
|
||||
broker.broadcastClear(this);
|
||||
},
|
||||
brokeredInstructionSelect: function (instructionIds: Array<number>, selected: boolean) {
|
||||
const firstSelect = view.offsetSelection.isEmpty();
|
||||
const keyPcOffsets = view.sourceResolver.instructionsPhase
|
||||
.instructionsToKeyPcOffsets(instructionIds);
|
||||
view.offsetSelection.select(keyPcOffsets, selected);
|
||||
view.updateSelection(firstSelect);
|
||||
},
|
||||
brokeredClear: function () {
|
||||
view.offsetSelection.clear();
|
||||
view.updateSelection();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private linkClickHandler(e: MouseEvent): void {
|
||||
if (!(e.target instanceof HTMLElement)) return;
|
||||
const offsetAsString = e.target.dataset.pcOffset !== undefined
|
||||
? e.target.dataset.pcOffset
|
||||
: e.target.parentElement.dataset.pcOffset;
|
||||
const offset = Number.parseInt(offsetAsString, 10);
|
||||
if (offsetAsString !== undefined && !Number.isNaN(offset)) {
|
||||
this.offsetSelection.select([offset], true);
|
||||
const nodes = this.sourceResolver.instructionsPhase.nodesForPCOffset(offset);
|
||||
if (nodes.length > 0) {
|
||||
e.stopPropagation();
|
||||
if (!e.shiftKey) this.selectionHandler.clear();
|
||||
this.selectionHandler.select(nodes, true);
|
||||
} else {
|
||||
this.updateSelection();
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private linkBlockClickHandler(e: MouseEvent): void {
|
||||
const spanBlockElement = e.target as HTMLSpanElement;
|
||||
const blockId = spanBlockElement.dataset.blockId;
|
||||
if (blockId !== undefined) {
|
||||
const blockIds = blockId.split(",");
|
||||
if (!e.shiftKey) this.selectionHandler.clear();
|
||||
this.blockSelectionHandler.select(blockIds, true);
|
||||
}
|
||||
}
|
||||
|
||||
private addDisassemblyToolbox(): void {
|
||||
const view = this;
|
||||
const toolbox = document.createElement("div");
|
||||
toolbox.id = "toolbox-anchor";
|
||||
toolbox.innerHTML = toolboxHTML;
|
||||
view.divNode.insertBefore(toolbox, view.divNode.firstChild);
|
||||
|
||||
const instructionAddressInput: HTMLInputElement =
|
||||
view.divNode.querySelector("#show-instruction-address");
|
||||
|
||||
instructionAddressInput.checked = storageGetItem("show-instruction-address");
|
||||
const showInstructionAddressHandler = () => {
|
||||
storageSetItem("show-instruction-address", instructionAddressInput.checked);
|
||||
for (const el of view.divNode.querySelectorAll(".instruction-address")) {
|
||||
el.classList.toggle("invisible", !instructionAddressInput.checked);
|
||||
}
|
||||
};
|
||||
instructionAddressInput.addEventListener("change", showInstructionAddressHandler);
|
||||
this.showInstructionAddressHandler = showInstructionAddressHandler;
|
||||
|
||||
const instructionBinaryInput: HTMLInputElement =
|
||||
view.divNode.querySelector("#show-instruction-binary");
|
||||
|
||||
instructionBinaryInput.checked = storageGetItem("show-instruction-binary");
|
||||
const showInstructionBinaryHandler = () => {
|
||||
storageSetItem("show-instruction-binary", instructionBinaryInput.checked);
|
||||
for (const el of view.divNode.querySelectorAll(".instruction-binary")) {
|
||||
el.classList.toggle("invisible", !instructionBinaryInput.checked);
|
||||
}
|
||||
};
|
||||
instructionBinaryInput.addEventListener("change", showInstructionBinaryHandler);
|
||||
this.showInstructionBinaryHandler = showInstructionBinaryHandler;
|
||||
|
||||
const highlightGapInstructionsInput: HTMLInputElement =
|
||||
view.divNode.querySelector("#highlight-gap-instructions");
|
||||
|
||||
highlightGapInstructionsInput.checked = storageGetItem("highlight-gap-instructions");
|
||||
const highlightGapInstructionsHandler = () => {
|
||||
storageSetItem("highlight-gap-instructions", highlightGapInstructionsInput.checked);
|
||||
view.divNode.classList.toggle("highlight-gap-instructions",
|
||||
highlightGapInstructionsInput.checked);
|
||||
};
|
||||
highlightGapInstructionsInput.addEventListener("change", highlightGapInstructionsHandler);
|
||||
this.highlightGapInstructionsHandler = highlightGapInstructionsHandler;
|
||||
}
|
||||
|
||||
private makeReadable(num: number): string {
|
||||
// Shorten decimals and remove trailing zeroes for readability.
|
||||
return `${num.toFixed(3).replace(/\.?0+$/, "")}%`;
|
||||
}
|
||||
|
||||
private initializePatterns(): Array<Array<any>> {
|
||||
return [
|
||||
[
|
||||
[/^0?x?[0-9a-fA-F]{8,16}\s+[0-9a-f]+\s+/, this.addressStyle, 1],
|
||||
[/^\s*--[^<]*<.*(not inlined|inlined\((\d+)\)):(\d+)>\s*--/, this.sourcePositionHeaderStyle, -1],
|
||||
[/^\s+-- B\d+ start.*/, this.blockHeaderStyle, -1],
|
||||
[/^.*/, this.unclassifiedStyle, -1]
|
||||
],
|
||||
[
|
||||
[/^\s*[0-9a-f]+\s+/, this.numberStyle, 2],
|
||||
[/^\s*[0-9a-f]+\s+[0-9a-f]+\s+/, this.numberStyle, 2],
|
||||
[/^.*/, null, -1]
|
||||
],
|
||||
[
|
||||
[/^REX.W \S+\s+/, this.opcodeStyle, 3],
|
||||
[/^\S+\s+/, this.opcodeStyle, 3],
|
||||
[/^\S+$/, this.opcodeStyle, -1],
|
||||
[/^.*/, null, -1]
|
||||
],
|
||||
[
|
||||
[/^\s+/, null],
|
||||
[/^[^;]+$/, this.opcodeArgs, -1],
|
||||
[/^[^;]+/, this.opcodeArgs, 4],
|
||||
[/^;/, this.commentStyle, 5]
|
||||
],
|
||||
[
|
||||
[/^.+$/, this.commentStyle, -1]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
private get numberStyle(): { css: Array<string> } {
|
||||
return {
|
||||
css: ['instruction-binary', 'lit']
|
||||
};
|
||||
}
|
||||
|
||||
private get opcodeStyle(): { css: string } {
|
||||
return {
|
||||
css: 'kwd'
|
||||
};
|
||||
}
|
||||
|
||||
private get unclassifiedStyle(): { css: string } {
|
||||
return {
|
||||
css: 'com'
|
||||
};
|
||||
}
|
||||
|
||||
private get commentStyle(): { css: string } {
|
||||
return {
|
||||
css: 'com'
|
||||
};
|
||||
}
|
||||
|
||||
private get sourcePositionHeaderStyle(): { css: string } {
|
||||
return {
|
||||
css: 'com'
|
||||
};
|
||||
}
|
||||
|
||||
private get blockHeaderStyle(): { associateData(text: string, fragment: HTMLSpanElement) } {
|
||||
const view = this;
|
||||
return {
|
||||
associateData: (text: string, fragment: HTMLSpanElement) => {
|
||||
if (view.sourceResolver.disassemblyPhase.hasBlockStartInfo()) return false;
|
||||
const matches = /\d+/.exec(text);
|
||||
if (!matches) return true;
|
||||
const blockId = matches[0];
|
||||
fragment.dataset.blockId = blockId;
|
||||
fragment.innerHTML = text;
|
||||
fragment.className = "com block";
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private get opcodeArgs(): { associateData(text: string, fragment: HTMLSpanElement) } {
|
||||
const view = this;
|
||||
return {
|
||||
associateData: (text: string, fragment: HTMLSpanElement) => {
|
||||
fragment.innerHTML = text;
|
||||
const replacer = (match, hexOffset) => {
|
||||
const offset = Number.parseInt(hexOffset, 16);
|
||||
let keyOffset = view.sourceResolver.instructionsPhase.getKeyPcOffset(offset);
|
||||
if (keyOffset == -1) keyOffset = Number(offset);
|
||||
const blockIds = view.sourceResolver.disassemblyPhase.getBlockIdsForOffset(offset);
|
||||
let block = "";
|
||||
let blockIdData = "";
|
||||
if (blockIds && blockIds.length > 0) {
|
||||
block = `B${blockIds.join(",")} `;
|
||||
blockIdData = `data-block-id="${blockIds.join(",")}"`;
|
||||
}
|
||||
return `<span class="tag linkable-text" data-pc-offset="${keyOffset}" ${blockIdData}>${block}${match}</span>`;
|
||||
};
|
||||
fragment.innerHTML = text.replace(/<.0?x?([0-9a-fA-F]+)>/g, replacer);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private get addressStyle(): { associateData(text: string, fragment: HTMLSpanElement) } {
|
||||
const view = this;
|
||||
return {
|
||||
associateData: (text: string, fragment: HTMLSpanElement) => {
|
||||
const matches = text.match(/(?<address>0?x?[0-9a-fA-F]{8,16})(?<addressSpace>\s+)(?<offset>[0-9a-f]+)(?<offsetSpace>\s*)/);
|
||||
const offset = Number.parseInt(matches.groups["offset"], 16);
|
||||
const instructionKind = view.sourceResolver.instructionsPhase
|
||||
.getInstructionKindForPCOffset(offset);
|
||||
fragment.dataset.instructionKind = instructionKind;
|
||||
fragment.title = view.sourceResolver.instructionsPhase
|
||||
.instructionKindToReadableName(instructionKind);
|
||||
|
||||
const blockIds = view.sourceResolver.disassemblyPhase.getBlockIdsForOffset(offset);
|
||||
const blockIdElement = document.createElement("span");
|
||||
blockIdElement.className = "block-id com linkable-text";
|
||||
blockIdElement.innerText = "";
|
||||
if (blockIds && blockIds.length > 0) {
|
||||
blockIds.forEach(blockId => view.addHtmlElementForBlockId(blockId, fragment));
|
||||
blockIdElement.innerText = `B${blockIds.join(",")}:`;
|
||||
blockIdElement.dataset.blockId = `${blockIds.join(",")}`;
|
||||
}
|
||||
fragment.appendChild(blockIdElement);
|
||||
|
||||
const addressElement = document.createElement("span");
|
||||
addressElement.className = "instruction-address";
|
||||
addressElement.innerText = matches.groups["address"];
|
||||
const offsetElement = document.createElement("span");
|
||||
offsetElement.innerText = matches.groups["offset"];
|
||||
fragment.appendChild(addressElement);
|
||||
fragment.appendChild(document.createTextNode(matches.groups["addressSpace"]));
|
||||
fragment.appendChild(offsetElement);
|
||||
fragment.appendChild(document.createTextNode(matches.groups["offsetSpace"]));
|
||||
fragment.classList.add("tag");
|
||||
|
||||
if (!Number.isNaN(offset)) {
|
||||
let pcOffset = view.sourceResolver.instructionsPhase.getKeyPcOffset(offset);
|
||||
if (pcOffset == -1) pcOffset = Number(offset);
|
||||
fragment.dataset.pcOffset = String(pcOffset);
|
||||
addressElement.classList.add("linkable-text");
|
||||
offsetElement.classList.add("linkable-text");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ export class SequenceView extends TextView {
|
||||
if (this.showRangeView) this.rangeView.onresize();
|
||||
}
|
||||
|
||||
initializeContent(sequence, rememberedSelection: SelectionStorage) {
|
||||
initializeContent(sequence: SequencePhase, rememberedSelection: SelectionStorage) {
|
||||
this.divNode.innerHTML = '';
|
||||
this.sequence = sequence;
|
||||
this.searchInfo = [];
|
||||
|
@ -2,12 +2,15 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import { PhaseView } from "./view";
|
||||
import { isIterable } from "../common/util";
|
||||
import { PhaseView } from "./view";
|
||||
import { SelectionMap } from "../selection/selection-map";
|
||||
import { SourceResolver } from "../source-resolver";
|
||||
import { SelectionBroker } from "../selection/selection-broker";
|
||||
import { ViewElements } from "../common/view-elements";
|
||||
import { DisassemblyPhase } from "../phases/disassembly-phase";
|
||||
import { SchedulePhase } from "../phases/schedule-phase";
|
||||
import { SequencePhase } from "../phases/sequence-phase";
|
||||
import {
|
||||
NodeSelectionHandler,
|
||||
BlockSelectionHandler,
|
||||
@ -15,6 +18,7 @@ import {
|
||||
ClearableHandler
|
||||
} from "../selection/selection-handler";
|
||||
|
||||
type GenericTextPhase = DisassemblyPhase | SchedulePhase | SequencePhase;
|
||||
export abstract class TextView extends PhaseView {
|
||||
selectionHandler: NodeSelectionHandler & ClearableHandler;
|
||||
blockSelectionHandler: BlockSelectionHandler & ClearableHandler;
|
||||
@ -32,43 +36,42 @@ export abstract class TextView extends PhaseView {
|
||||
sourceResolver: SourceResolver;
|
||||
broker: SelectionBroker;
|
||||
|
||||
constructor(id, broker) {
|
||||
super(id);
|
||||
const view = this;
|
||||
view.broker = broker;
|
||||
view.sourceResolver = broker.sourceResolver;
|
||||
view.textListNode = view.divNode.getElementsByTagName("ul")[0];
|
||||
view.patterns = null;
|
||||
view.instructionIdToHtmlElementsMap = new Map<string, Array<HTMLElement>>();
|
||||
view.nodeIdToHtmlElementsMap = new Map<string, Array<HTMLElement>>();
|
||||
view.blockIdToHtmlElementsMap = new Map<string, Array<HTMLElement>>();
|
||||
view.blockIdToNodeIds = new Map<string, Array<string>>();
|
||||
view.nodeIdToBlockId = new Array<string>();
|
||||
constructor(parent: HTMLDivElement, broker: SelectionBroker) {
|
||||
super(parent);
|
||||
this.broker = broker;
|
||||
this.sourceResolver = broker.sourceResolver;
|
||||
this.textListNode = this.divNode.getElementsByTagName("ul")[0];
|
||||
this.instructionIdToHtmlElementsMap = new Map<string, Array<HTMLElement>>();
|
||||
this.nodeIdToHtmlElementsMap = new Map<string, Array<HTMLElement>>();
|
||||
this.blockIdToHtmlElementsMap = new Map<string, Array<HTMLElement>>();
|
||||
this.blockIdToNodeIds = new Map<string, Array<string>>();
|
||||
this.nodeIdToBlockId = new Array<string>();
|
||||
|
||||
view.selection = new SelectionMap(node => String(node));
|
||||
view.blockSelection = new SelectionMap(block => String(block));
|
||||
view.registerAllocationSelection = new SelectionMap(register => String(register));
|
||||
this.selection = new SelectionMap(node => String(node));
|
||||
this.blockSelection = new SelectionMap(block => String(block));
|
||||
this.registerAllocationSelection = new SelectionMap(register => String(register));
|
||||
|
||||
view.selectionHandler = this.initializeNodeSelectionHandler();
|
||||
view.blockSelectionHandler = this.initializeBlockSelectionHandler();
|
||||
view.registerAllocationSelectionHandler = this.initializeRegisterAllocationSelectionHandler();
|
||||
this.selectionHandler = this.initializeNodeSelectionHandler();
|
||||
this.blockSelectionHandler = this.initializeBlockSelectionHandler();
|
||||
this.registerAllocationSelectionHandler = this.initializeRegisterAllocationSelectionHandler();
|
||||
|
||||
broker.addNodeHandler(view.selectionHandler);
|
||||
broker.addBlockHandler(view.blockSelectionHandler);
|
||||
broker.addRegisterAllocatorHandler(view.registerAllocationSelectionHandler);
|
||||
broker.addNodeHandler(this.selectionHandler);
|
||||
broker.addBlockHandler(this.blockSelectionHandler);
|
||||
broker.addRegisterAllocatorHandler(this.registerAllocationSelectionHandler);
|
||||
|
||||
view.divNode.addEventListener("click", e => {
|
||||
this.divNode.addEventListener("click", e => {
|
||||
if (!e.shiftKey) {
|
||||
view.selectionHandler.clear();
|
||||
this.selectionHandler.clear();
|
||||
}
|
||||
e.stopPropagation();
|
||||
});
|
||||
}
|
||||
|
||||
// TODO (danylo boiko) Change data type
|
||||
public initializeContent(data: any, _): void {
|
||||
public initializeContent(genericPhase: GenericTextPhase, _): void {
|
||||
this.clearText();
|
||||
this.processText(data);
|
||||
if (!(genericPhase instanceof SequencePhase)) {
|
||||
this.processText(genericPhase.data);
|
||||
}
|
||||
this.show();
|
||||
}
|
||||
|
||||
@ -120,7 +123,7 @@ export abstract class TextView extends PhaseView {
|
||||
}
|
||||
|
||||
public processLine(line: string): Array<HTMLSpanElement> {
|
||||
const result = new Array<HTMLSpanElement>();
|
||||
const fragments = new Array<HTMLSpanElement>();
|
||||
let patternSet = 0;
|
||||
while (true) {
|
||||
const beforeLine = line;
|
||||
@ -132,7 +135,7 @@ export abstract class TextView extends PhaseView {
|
||||
const text = matches[0];
|
||||
if (text.length > 0) {
|
||||
const fragment = this.createFragment(matches[0], style);
|
||||
if (fragment !== null) result.push(fragment);
|
||||
if (fragment !== null) fragments.push(fragment);
|
||||
}
|
||||
line = line.substr(matches[0].length);
|
||||
}
|
||||
@ -144,7 +147,7 @@ export abstract class TextView extends PhaseView {
|
||||
if (nextPatternSet != -1) {
|
||||
throw (`illegal parsing state in text-view in patternSet: ${patternSet}`);
|
||||
}
|
||||
return result;
|
||||
return fragments;
|
||||
}
|
||||
patternSet = nextPatternSet;
|
||||
break;
|
||||
@ -201,10 +204,6 @@ export abstract class TextView extends PhaseView {
|
||||
return fragment;
|
||||
}
|
||||
|
||||
protected setPatterns(patterns: Array<Array<any>>): void {
|
||||
this.patterns = patterns;
|
||||
}
|
||||
|
||||
private initializeNodeSelectionHandler(): NodeSelectionHandler & ClearableHandler {
|
||||
const view = this;
|
||||
return {
|
||||
|
Loading…
Reference in New Issue
Block a user