2018-07-06 00:13:07 +00:00
|
|
|
"use strict";
|
|
|
|
|
2018-07-07 14:55:27 +00:00
|
|
|
import $ = require("jquery");
|
2018-08-27 13:28:31 +00:00
|
|
|
//import CodeMirror = require("codemirror");
|
2018-07-08 14:07:19 +00:00
|
|
|
import { CodeProject } from "./project";
|
2019-03-18 18:39:02 +00:00
|
|
|
import { SourceFile, WorkerError, Segment, FileData } from "./workertypes";
|
2019-03-03 16:32:25 +00:00
|
|
|
import { Platform, EmuState, ProfilerOutput, lookupSymbol } from "./baseplatform";
|
2019-03-18 18:39:02 +00:00
|
|
|
import { hex, lpad, rpad, safeident } from "./util";
|
2018-08-17 19:13:58 +00:00
|
|
|
import { CodeAnalyzer } from "./analysis";
|
2019-02-26 15:56:51 +00:00
|
|
|
import { platform, platform_id, compparams, current_project, lastDebugState, projectWindows } from "./ui";
|
2019-03-18 18:39:02 +00:00
|
|
|
import { PixelViewer, PixelMapper, PixelPalettizer, PixelFileDataNode, PixelTextDataNode, parseHexWords } from "./pixed/pixeleditor";
|
2018-07-07 14:55:27 +00:00
|
|
|
|
|
|
|
export interface ProjectView {
|
2018-07-08 03:10:51 +00:00
|
|
|
createDiv(parent:HTMLElement, text:string) : HTMLElement;
|
2019-03-08 01:00:12 +00:00
|
|
|
setVisible?(showing : boolean) : void;
|
2018-08-02 17:08:37 +00:00
|
|
|
refresh(moveCursor:boolean) : void;
|
2018-07-08 03:10:51 +00:00
|
|
|
tick?() : void;
|
2018-12-08 00:28:11 +00:00
|
|
|
getPath?() : string;
|
2018-07-08 03:10:51 +00:00
|
|
|
getValue?() : string;
|
2018-08-21 14:16:47 +00:00
|
|
|
setText?(text : string) : void;
|
2018-11-24 20:43:08 +00:00
|
|
|
insertText?(text : string) : void;
|
2018-07-07 14:55:27 +00:00
|
|
|
getCursorPC?() : number;
|
|
|
|
getSourceFile?() : SourceFile;
|
2018-07-08 03:10:51 +00:00
|
|
|
setGutterBytes?(line:number, s:string) : void;
|
|
|
|
openBitmapEditorAtCursor?() : void;
|
2018-07-26 19:47:09 +00:00
|
|
|
markErrors?(errors:WorkerError[]) : void;
|
2018-07-08 03:10:51 +00:00
|
|
|
clearErrors?() : void;
|
2018-08-17 19:13:58 +00:00
|
|
|
setTimingResult?(result:CodeAnalyzer) : void;
|
2019-03-03 20:37:22 +00:00
|
|
|
recreateOnResize? : boolean;
|
2018-07-07 14:55:27 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
declare var CodeMirror;
|
|
|
|
declare var VirtualList;
|
2018-07-06 00:13:07 +00:00
|
|
|
|
2018-07-07 14:55:27 +00:00
|
|
|
// helper function for editor
|
2018-07-06 02:37:19 +00:00
|
|
|
function jumpToLine(ed, i:number) {
|
|
|
|
var t = ed.charCoords({line: i, ch: 0}, "local").top;
|
|
|
|
var middleHeight = ed.getScrollerElement().offsetHeight / 2;
|
|
|
|
ed.scrollTo(null, t - middleHeight - 5);
|
|
|
|
}
|
2018-07-06 00:13:07 +00:00
|
|
|
|
2019-03-03 20:37:22 +00:00
|
|
|
function createTextSpan(text:string, className:string) : HTMLElement {
|
|
|
|
var span = document.createElement("span");
|
|
|
|
span.setAttribute("class", className);
|
|
|
|
span.appendChild(document.createTextNode(text));
|
|
|
|
return span;
|
|
|
|
}
|
|
|
|
|
2018-12-01 11:48:33 +00:00
|
|
|
// TODO: https://stackoverflow.com/questions/10463518/converting-em-to-px-in-javascript-and-getting-default-font-size
|
2018-07-07 14:55:27 +00:00
|
|
|
function getVisibleEditorLineHeight() : number{
|
2018-12-01 11:48:33 +00:00
|
|
|
return $("#booksMenuButton").first().height();
|
2018-07-07 14:55:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/////
|
|
|
|
|
2018-10-05 13:36:10 +00:00
|
|
|
const MAX_ERRORS = 200;
|
|
|
|
|
2018-07-08 03:10:51 +00:00
|
|
|
export class SourceEditor implements ProjectView {
|
2018-07-07 14:55:27 +00:00
|
|
|
constructor(path:string, mode:string) {
|
|
|
|
this.path = path;
|
|
|
|
this.mode = mode;
|
|
|
|
}
|
|
|
|
path : string;
|
|
|
|
mode : string;
|
|
|
|
editor;
|
|
|
|
dirtylisting = true;
|
|
|
|
sourcefile : SourceFile;
|
|
|
|
currentDebugLine : number;
|
2018-07-26 19:47:09 +00:00
|
|
|
errormsgs = [];
|
2018-07-20 21:25:52 +00:00
|
|
|
errorwidgets = [];
|
2018-08-27 13:28:31 +00:00
|
|
|
inspectWidget;
|
2018-11-21 12:21:07 +00:00
|
|
|
|
2018-07-07 14:55:27 +00:00
|
|
|
createDiv(parent:HTMLElement, text:string) {
|
2018-07-06 00:13:07 +00:00
|
|
|
var div = document.createElement('div');
|
|
|
|
div.setAttribute("class", "editor");
|
|
|
|
parent.appendChild(div);
|
2018-11-28 18:54:42 +00:00
|
|
|
var asmOverride = text && this.mode=='verilog' && /__asm\b([\s\S]+?)\b__endasm\b/.test(text);
|
|
|
|
this.newEditor(div, asmOverride);
|
2018-07-06 00:13:07 +00:00
|
|
|
if (text)
|
2018-07-07 14:55:27 +00:00
|
|
|
this.setText(text); // TODO: this calls setCode() and builds... it shouldn't
|
2018-08-04 14:21:50 +00:00
|
|
|
this.setupEditor();
|
2018-07-06 00:13:07 +00:00
|
|
|
return div;
|
|
|
|
}
|
|
|
|
|
2018-11-28 18:54:42 +00:00
|
|
|
newEditor(parent:HTMLElement, isAsmOverride?:boolean) {
|
|
|
|
var isAsm = isAsmOverride || this.mode=='6502' || this.mode =='z80' || this.mode=='jsasm' || this.mode=='gas'; // TODO
|
2018-11-21 16:53:33 +00:00
|
|
|
var lineWrap = this.mode=='markdown';
|
2018-07-07 14:55:27 +00:00
|
|
|
this.editor = CodeMirror(parent, {
|
2018-07-06 00:13:07 +00:00
|
|
|
theme: 'mbo',
|
|
|
|
lineNumbers: true,
|
|
|
|
matchBrackets: true,
|
|
|
|
tabSize: 8,
|
|
|
|
indentAuto: true,
|
2018-11-21 16:53:33 +00:00
|
|
|
lineWrapping: lineWrap,
|
2018-07-06 00:13:07 +00:00
|
|
|
gutters: isAsm ? ["CodeMirror-linenumbers", "gutter-offset", "gutter-bytes", "gutter-clock", "gutter-info"]
|
|
|
|
: ["CodeMirror-linenumbers", "gutter-offset", "gutter-info"],
|
|
|
|
});
|
2018-08-04 14:21:50 +00:00
|
|
|
}
|
2018-11-21 12:21:07 +00:00
|
|
|
|
2018-08-04 14:21:50 +00:00
|
|
|
setupEditor() {
|
2018-07-06 00:13:07 +00:00
|
|
|
var timer;
|
2018-07-07 14:55:27 +00:00
|
|
|
this.editor.on('changes', (ed, changeobj) => {
|
2018-07-06 00:13:07 +00:00
|
|
|
clearTimeout(timer);
|
2018-07-07 14:55:27 +00:00
|
|
|
timer = setTimeout( () => {
|
|
|
|
current_project.updateFile(this.path, this.editor.getValue());
|
2018-07-20 21:25:52 +00:00
|
|
|
}, 300);
|
2018-07-06 00:13:07 +00:00
|
|
|
});
|
2018-07-07 14:55:27 +00:00
|
|
|
this.editor.on('cursorActivity', (ed) => {
|
|
|
|
var start = this.editor.getCursor(true);
|
|
|
|
var end = this.editor.getCursor(false);
|
2018-08-27 13:28:31 +00:00
|
|
|
if (start.line == end.line && start.ch < end.ch && end.ch-start.ch < 80) {
|
2018-07-07 14:55:27 +00:00
|
|
|
var name = this.editor.getSelection();
|
2018-08-27 13:28:31 +00:00
|
|
|
this.inspect(name);
|
2018-07-06 00:13:07 +00:00
|
|
|
} else {
|
2018-08-27 13:28:31 +00:00
|
|
|
this.inspect(null);
|
2018-07-06 00:13:07 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
//scrollProfileView(editor);
|
2018-07-07 14:55:27 +00:00
|
|
|
this.editor.setOption("mode", this.mode);
|
2018-07-06 00:13:07 +00:00
|
|
|
}
|
2018-11-21 12:21:07 +00:00
|
|
|
|
2018-08-27 13:28:31 +00:00
|
|
|
inspect(ident : string) : void {
|
|
|
|
var result;
|
|
|
|
if (platform.inspect) {
|
|
|
|
result = platform.inspect(ident);
|
|
|
|
}
|
|
|
|
if (this.inspectWidget) {
|
|
|
|
this.inspectWidget.clear();
|
|
|
|
this.inspectWidget = null;
|
|
|
|
}
|
|
|
|
if (result) {
|
2019-03-03 20:37:22 +00:00
|
|
|
var infospan = createTextSpan(result, "tooltipinfoline");
|
2018-08-27 13:28:31 +00:00
|
|
|
var line = this.editor.getCursor().line;
|
|
|
|
this.inspectWidget = this.editor.addLineWidget(line, infospan, {above:false});
|
|
|
|
}
|
|
|
|
}
|
2018-07-06 00:13:07 +00:00
|
|
|
|
2018-07-07 14:55:27 +00:00
|
|
|
setText(text:string) {
|
|
|
|
this.editor.setValue(text); // calls setCode()
|
|
|
|
this.editor.clearHistory();
|
2018-07-06 00:13:07 +00:00
|
|
|
}
|
2018-11-21 12:21:07 +00:00
|
|
|
|
2018-11-24 20:43:08 +00:00
|
|
|
insertText(text:string) {
|
|
|
|
var cur = this.editor.getCursor();
|
|
|
|
this.editor.replaceRange(text, cur, cur);
|
|
|
|
}
|
|
|
|
|
2018-07-07 14:55:27 +00:00
|
|
|
getValue() : string {
|
|
|
|
return this.editor.getValue();
|
2018-07-06 00:13:07 +00:00
|
|
|
}
|
2018-11-21 12:21:07 +00:00
|
|
|
|
2018-07-07 14:55:27 +00:00
|
|
|
getPath() : string { return this.path; }
|
2018-07-06 00:13:07 +00:00
|
|
|
|
2018-07-26 19:47:09 +00:00
|
|
|
addErrorMarker(line:number, msg:string) {
|
2018-07-06 00:13:07 +00:00
|
|
|
var div = document.createElement("div");
|
|
|
|
div.setAttribute("class", "tooltipbox tooltiperror");
|
|
|
|
div.appendChild(document.createTextNode("\u24cd"));
|
2018-07-25 17:29:09 +00:00
|
|
|
this.editor.setGutterMarker(line, "gutter-info", div);
|
2018-07-26 19:47:09 +00:00
|
|
|
this.errormsgs.push({line:line, msg:msg});
|
|
|
|
// expand line widgets when mousing over errors
|
|
|
|
$(div).mouseover((e) => {
|
|
|
|
this.expandErrors();
|
|
|
|
});
|
|
|
|
}
|
2018-11-21 12:21:07 +00:00
|
|
|
|
2018-07-26 19:47:09 +00:00
|
|
|
addErrorLine(line:number, msg:string) {
|
2019-03-03 20:37:22 +00:00
|
|
|
var errspan = createTextSpan(msg, "tooltiperrorline");
|
2018-07-26 19:47:09 +00:00
|
|
|
this.errorwidgets.push(this.editor.addLineWidget(line, errspan));
|
|
|
|
}
|
2018-11-21 12:21:07 +00:00
|
|
|
|
2018-07-26 19:47:09 +00:00
|
|
|
expandErrors() {
|
|
|
|
var e;
|
|
|
|
while (e = this.errormsgs.shift()) {
|
|
|
|
this.addErrorLine(e.line, e.msg);
|
|
|
|
}
|
2018-07-06 00:13:07 +00:00
|
|
|
}
|
2018-11-21 12:21:07 +00:00
|
|
|
|
2018-07-26 19:47:09 +00:00
|
|
|
markErrors(errors:WorkerError[]) {
|
2018-07-06 00:13:07 +00:00
|
|
|
// TODO: move cursor to error line if offscreen?
|
2018-07-07 14:55:27 +00:00
|
|
|
this.clearErrors();
|
|
|
|
var numLines = this.editor.lineCount();
|
2018-10-05 13:36:10 +00:00
|
|
|
errors = errors.slice(0, MAX_ERRORS);
|
2018-07-06 00:13:07 +00:00
|
|
|
for (var info of errors) {
|
|
|
|
// only mark errors with this filename, or without any filename
|
2018-07-07 14:55:27 +00:00
|
|
|
if (!info.path || this.path.endsWith(info.path)) {
|
2018-07-06 00:13:07 +00:00
|
|
|
var line = info.line-1;
|
|
|
|
if (line < 0 || line >= numLines) line = 0;
|
2018-07-26 19:47:09 +00:00
|
|
|
this.addErrorMarker(line, info.msg);
|
2018-07-06 00:13:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-11-21 12:21:07 +00:00
|
|
|
|
2018-07-07 14:55:27 +00:00
|
|
|
clearErrors() {
|
|
|
|
this.editor.clearGutter("gutter-info");
|
2018-08-02 17:08:37 +00:00
|
|
|
this.refreshDebugState(false);
|
2018-07-07 14:55:27 +00:00
|
|
|
this.dirtylisting = true;
|
2018-07-20 21:25:52 +00:00
|
|
|
// clear line widgets
|
2018-07-26 19:47:09 +00:00
|
|
|
this.errormsgs = [];
|
2018-07-20 21:25:52 +00:00
|
|
|
while (this.errorwidgets.length)
|
|
|
|
this.errorwidgets.shift().clear();
|
2018-07-06 00:13:07 +00:00
|
|
|
}
|
2018-11-21 12:21:07 +00:00
|
|
|
|
2018-07-07 14:55:27 +00:00
|
|
|
getSourceFile() : SourceFile { return this.sourcefile; }
|
2018-11-21 12:21:07 +00:00
|
|
|
|
2018-08-18 14:21:18 +00:00
|
|
|
updateListing() {
|
2018-07-06 00:13:07 +00:00
|
|
|
// update editor annotations
|
2018-11-28 17:39:10 +00:00
|
|
|
// TODO: recreate editor if gutter-bytes is used (verilog)
|
2018-07-07 14:55:27 +00:00
|
|
|
this.editor.clearGutter("gutter-info");
|
|
|
|
this.editor.clearGutter("gutter-bytes");
|
|
|
|
this.editor.clearGutter("gutter-offset");
|
|
|
|
this.editor.clearGutter("gutter-clock");
|
|
|
|
var lstlines = this.sourcefile.lines || [];
|
2018-07-06 00:13:07 +00:00
|
|
|
for (var info of lstlines) {
|
|
|
|
if (info.offset >= 0) {
|
2018-08-02 19:49:30 +00:00
|
|
|
this.setGutter("gutter-offset", info.line-1, hex(info.offset&0xffff,4));
|
2018-07-06 00:13:07 +00:00
|
|
|
}
|
|
|
|
if (info.insns) {
|
|
|
|
var insnstr = info.insns.length > 9 ? ("...") : info.insns;
|
2018-08-02 19:49:30 +00:00
|
|
|
this.setGutter("gutter-bytes", info.line-1, insnstr);
|
2018-07-06 00:13:07 +00:00
|
|
|
if (info.iscode) {
|
2018-08-14 20:28:29 +00:00
|
|
|
// TODO: labels trick this part?
|
2018-07-07 14:55:27 +00:00
|
|
|
var opcode = parseInt(info.insns.split(" ")[0], 16);
|
2018-07-06 00:13:07 +00:00
|
|
|
if (platform.getOpcodeMetadata) {
|
|
|
|
var meta = platform.getOpcodeMetadata(opcode, info.offset);
|
|
|
|
var clockstr = meta.minCycles+"";
|
2018-08-02 19:49:30 +00:00
|
|
|
this.setGutter("gutter-clock", info.line-1, clockstr);
|
2018-07-06 00:13:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-11-21 12:21:07 +00:00
|
|
|
|
2018-08-02 19:49:30 +00:00
|
|
|
setGutter(type:string, line:number, text:string) {
|
|
|
|
var lineinfo = this.editor.lineInfo(line);
|
2018-08-05 14:00:53 +00:00
|
|
|
if (lineinfo && lineinfo.gutterMarkers && lineinfo.gutterMarkers[type]) {
|
2018-08-02 19:49:30 +00:00
|
|
|
// do not replace existing marker
|
|
|
|
} else {
|
|
|
|
var textel = document.createTextNode(text);
|
|
|
|
this.editor.setGutterMarker(line, type, textel);
|
|
|
|
}
|
|
|
|
}
|
2018-11-21 12:21:07 +00:00
|
|
|
|
2018-07-07 14:55:27 +00:00
|
|
|
setGutterBytes(line:number, s:string) {
|
2018-08-02 19:49:30 +00:00
|
|
|
this.setGutter("gutter-bytes", line-1, s);
|
2018-07-06 00:13:07 +00:00
|
|
|
}
|
2018-11-21 12:21:07 +00:00
|
|
|
|
2018-08-17 19:13:58 +00:00
|
|
|
setTimingResult(result:CodeAnalyzer) : void {
|
|
|
|
this.editor.clearGutter("gutter-bytes");
|
|
|
|
// show the lines
|
2018-08-18 00:46:55 +00:00
|
|
|
for (const line of Object.keys(this.sourcefile.line2offset)) {
|
2018-08-17 19:13:58 +00:00
|
|
|
var pc = this.sourcefile.line2offset[line];
|
|
|
|
var minclocks = result.pc2minclocks[pc];
|
|
|
|
var maxclocks = result.pc2maxclocks[pc];
|
|
|
|
if (minclocks>=0 && maxclocks>=0) {
|
|
|
|
var s;
|
|
|
|
if (maxclocks == minclocks)
|
|
|
|
s = minclocks + "";
|
|
|
|
else
|
|
|
|
s = minclocks + "-" + maxclocks;
|
|
|
|
if (maxclocks == result.MAX_CLOCKS)
|
|
|
|
s += "+";
|
|
|
|
this.setGutterBytes(parseInt(line), s);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-11-21 12:21:07 +00:00
|
|
|
|
2018-08-02 17:08:37 +00:00
|
|
|
setCurrentLine(line:number, moveCursor:boolean) {
|
2018-07-07 14:55:27 +00:00
|
|
|
|
|
|
|
var addCurrentMarker = (line:number) => {
|
2018-07-06 00:13:07 +00:00
|
|
|
var div = document.createElement("div");
|
|
|
|
div.style.color = '#66ffff';
|
|
|
|
div.appendChild(document.createTextNode("\u25b6"));
|
2018-07-07 14:55:27 +00:00
|
|
|
this.editor.setGutterMarker(line, "gutter-info", div);
|
2018-07-06 00:13:07 +00:00
|
|
|
}
|
2018-07-07 14:55:27 +00:00
|
|
|
|
|
|
|
this.clearCurrentLine();
|
2018-07-06 00:13:07 +00:00
|
|
|
if (line>0) {
|
|
|
|
addCurrentMarker(line-1);
|
2018-08-02 17:08:37 +00:00
|
|
|
if (moveCursor)
|
|
|
|
this.editor.setSelection({line:line,ch:0}, {line:line-1,ch:0}, {scroll:true});
|
2018-07-07 14:55:27 +00:00
|
|
|
this.currentDebugLine = line;
|
2018-07-06 00:13:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-07 14:55:27 +00:00
|
|
|
clearCurrentLine() {
|
|
|
|
if (this.currentDebugLine) {
|
|
|
|
this.editor.clearGutter("gutter-info");
|
|
|
|
this.editor.setSelection(this.editor.getCursor());
|
|
|
|
this.currentDebugLine = 0;
|
2018-07-06 00:13:07 +00:00
|
|
|
}
|
|
|
|
}
|
2018-11-21 12:21:07 +00:00
|
|
|
|
2018-08-04 14:21:50 +00:00
|
|
|
getActiveLine() {
|
2018-07-06 00:13:07 +00:00
|
|
|
var state = lastDebugState;
|
2018-08-04 14:21:50 +00:00
|
|
|
if (state && state.c && this.sourcefile) {
|
2018-09-13 00:54:25 +00:00
|
|
|
var EPC = state.c.EPC || state.c.PC;
|
|
|
|
var line = this.sourcefile.findLineForOffset(EPC, 15);
|
2018-08-04 14:21:50 +00:00
|
|
|
return line;
|
|
|
|
} else
|
|
|
|
return -1;
|
|
|
|
}
|
2018-11-21 12:21:07 +00:00
|
|
|
|
2018-08-04 14:21:50 +00:00
|
|
|
refreshDebugState(moveCursor:boolean) {
|
|
|
|
var line = this.getActiveLine();
|
|
|
|
if (line >= 0) {
|
2018-09-11 00:44:53 +00:00
|
|
|
this.clearCurrentLine();
|
2018-08-04 14:21:50 +00:00
|
|
|
this.setCurrentLine(line, moveCursor);
|
|
|
|
// TODO: switch to disasm?
|
2018-07-06 00:13:07 +00:00
|
|
|
}
|
|
|
|
}
|
2018-11-21 12:21:07 +00:00
|
|
|
|
2018-07-07 14:55:27 +00:00
|
|
|
refreshListing() {
|
2018-08-18 14:21:18 +00:00
|
|
|
// lookup corresponding sourcefile for this file, using listing
|
2018-07-07 14:55:27 +00:00
|
|
|
var lst = current_project.getListingForFile(this.path);
|
2018-08-19 23:25:42 +00:00
|
|
|
if (lst && lst.sourcefile && lst.sourcefile !== this.sourcefile) {
|
2018-08-18 14:21:18 +00:00
|
|
|
this.sourcefile = lst.sourcefile;
|
|
|
|
this.dirtylisting = true;
|
2018-07-06 00:13:07 +00:00
|
|
|
}
|
2018-08-18 14:21:18 +00:00
|
|
|
if (!this.sourcefile || !this.dirtylisting) return;
|
|
|
|
this.dirtylisting = false;
|
|
|
|
this.updateListing();
|
2018-07-06 00:13:07 +00:00
|
|
|
}
|
|
|
|
|
2018-08-02 17:08:37 +00:00
|
|
|
refresh(moveCursor: boolean) {
|
2018-07-07 14:55:27 +00:00
|
|
|
this.refreshListing();
|
2018-08-02 17:08:37 +00:00
|
|
|
this.refreshDebugState(moveCursor);
|
2018-07-06 00:13:07 +00:00
|
|
|
}
|
2018-11-21 12:21:07 +00:00
|
|
|
|
2018-07-07 14:55:27 +00:00
|
|
|
getLine(line : number) {
|
|
|
|
return this.editor.getLine(line-1);
|
2018-07-06 00:13:07 +00:00
|
|
|
}
|
2018-11-21 12:21:07 +00:00
|
|
|
|
2018-07-07 14:55:27 +00:00
|
|
|
getCurrentLine() : number {
|
|
|
|
return this.editor.getCursor().line+1;
|
2018-07-06 00:13:07 +00:00
|
|
|
}
|
2018-11-21 12:21:07 +00:00
|
|
|
|
2018-07-07 14:55:27 +00:00
|
|
|
getCursorPC() : number {
|
|
|
|
var line = this.getCurrentLine();
|
|
|
|
while (this.sourcefile && line >= 0) {
|
|
|
|
var pc = this.sourcefile.line2offset[line];
|
2018-07-06 00:13:07 +00:00
|
|
|
if (pc >= 0) return pc;
|
|
|
|
line--;
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// bitmap editor (TODO: refactor)
|
|
|
|
|
2018-07-07 14:55:27 +00:00
|
|
|
openBitmapEditorWithParams(fmt, bytestr, palfmt, palstr) {
|
|
|
|
|
2018-11-21 12:21:07 +00:00
|
|
|
var handleWindowMessage = (e) => {
|
2018-07-07 14:55:27 +00:00
|
|
|
//console.log("window message", e.data);
|
|
|
|
if (e.data.bytes) {
|
|
|
|
this.editor.replaceSelection(e.data.bytestr);
|
|
|
|
}
|
|
|
|
if (e.data.close) {
|
|
|
|
$("#pixeditback").hide();
|
|
|
|
}
|
2018-08-13 03:29:05 +00:00
|
|
|
e.target.removeEventListener("message", handleWindowMessage);
|
2018-07-06 00:13:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
$("#pixeditback").show();
|
|
|
|
window.addEventListener("message", handleWindowMessage, false); // TODO: remove listener
|
2018-07-06 23:12:58 +00:00
|
|
|
window['pixeditframe'].contentWindow.postMessage({fmt:fmt, bytestr:bytestr, palfmt:palfmt, palstr:palstr}, '*');
|
2018-07-06 00:13:07 +00:00
|
|
|
}
|
|
|
|
|
2018-07-07 14:55:27 +00:00
|
|
|
lookBackwardsForJSONComment(line, req) {
|
2018-07-06 00:13:07 +00:00
|
|
|
var re = /[/;][*;]([{].+[}])[*;][/;]/;
|
|
|
|
while (--line >= 0) {
|
2018-07-07 14:55:27 +00:00
|
|
|
var s = this.editor.getLine(line);
|
2018-07-06 00:13:07 +00:00
|
|
|
var m = re.exec(s);
|
|
|
|
if (m) {
|
|
|
|
var jsontxt = m[1].replace(/([A-Za-z]+):/g, '"$1":'); // fix lenient JSON
|
|
|
|
var obj = JSON.parse(jsontxt);
|
|
|
|
if (obj[req]) {
|
|
|
|
var start = {obj:obj, line:line, ch:s.indexOf(m[0])+m[0].length};
|
|
|
|
var line0 = line;
|
|
|
|
var pos0 = start.ch;
|
|
|
|
line--;
|
2018-07-07 14:55:27 +00:00
|
|
|
while (++line < this.editor.lineCount()) {
|
|
|
|
var l = this.editor.getLine(line);
|
2018-07-06 00:13:07 +00:00
|
|
|
var endsection;
|
|
|
|
if (platform_id == 'verilog')
|
|
|
|
endsection = l.indexOf('end') >= pos0;
|
2018-11-28 22:47:39 +00:00
|
|
|
else if (s.startsWith(';;'))
|
|
|
|
endsection = l.indexOf(';;') >= pos0;
|
2018-07-06 00:13:07 +00:00
|
|
|
else
|
|
|
|
endsection = l.indexOf(';') >= pos0;
|
|
|
|
if (endsection) {
|
2018-07-07 14:55:27 +00:00
|
|
|
var end = {line:line, ch:this.editor.getLine(line).length};
|
2018-07-06 00:13:07 +00:00
|
|
|
return {obj:obj, start:start, end:end};
|
|
|
|
}
|
|
|
|
pos0 = 0;
|
|
|
|
}
|
|
|
|
line = line0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-07 14:55:27 +00:00
|
|
|
openBitmapEditorAtCursor() {
|
2018-07-06 00:13:07 +00:00
|
|
|
if ($("#pixeditback").is(":visible")) {
|
|
|
|
$("#pixeditback").hide(250);
|
|
|
|
return;
|
|
|
|
}
|
2018-07-07 14:55:27 +00:00
|
|
|
var line = this.editor.getCursor().line + 1;
|
|
|
|
var data = this.lookBackwardsForJSONComment(this.getCurrentLine(), 'w');
|
2018-07-06 00:13:07 +00:00
|
|
|
if (data && data.obj && data.obj.w>0 && data.obj.h>0) {
|
2018-07-07 14:55:27 +00:00
|
|
|
var paldata = this.lookBackwardsForJSONComment(data.start.line-1, 'pal');
|
2018-07-06 00:13:07 +00:00
|
|
|
var palbytestr;
|
|
|
|
if (paldata) {
|
2018-07-07 14:55:27 +00:00
|
|
|
palbytestr = this.editor.getRange(paldata.start, paldata.end);
|
2018-07-06 00:13:07 +00:00
|
|
|
paldata = paldata.obj;
|
|
|
|
}
|
2018-07-07 14:55:27 +00:00
|
|
|
this.editor.setSelection(data.end, data.start);
|
|
|
|
this.openBitmapEditorWithParams(data.obj, this.editor.getSelection(), paldata, palbytestr);
|
2018-07-06 00:13:07 +00:00
|
|
|
} else {
|
|
|
|
alert("To edit graphics, move cursor to a constant array preceded by a comment in the format:\n\n/*{w:,h:,bpp:,count:...}*/\n\n(See code examples)");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
///
|
|
|
|
|
2018-07-08 03:10:51 +00:00
|
|
|
export class DisassemblerView implements ProjectView {
|
2018-07-07 14:55:27 +00:00
|
|
|
disasmview;
|
2018-11-21 12:21:07 +00:00
|
|
|
|
2018-07-07 14:55:27 +00:00
|
|
|
getDisasmView() { return this.disasmview; }
|
2018-11-21 12:21:07 +00:00
|
|
|
|
2018-07-07 14:55:27 +00:00
|
|
|
createDiv(parent : HTMLElement) {
|
2018-07-06 00:13:07 +00:00
|
|
|
var div = document.createElement('div');
|
|
|
|
div.setAttribute("class", "editor");
|
|
|
|
parent.appendChild(div);
|
2018-07-07 14:55:27 +00:00
|
|
|
this.newEditor(div);
|
2018-07-06 00:13:07 +00:00
|
|
|
return div;
|
|
|
|
}
|
2018-11-21 12:21:07 +00:00
|
|
|
|
2018-07-07 14:55:27 +00:00
|
|
|
newEditor(parent : HTMLElement) {
|
|
|
|
this.disasmview = CodeMirror(parent, {
|
2018-07-06 00:13:07 +00:00
|
|
|
mode: 'z80', // TODO: pick correct one
|
|
|
|
theme: 'cobalt',
|
|
|
|
tabSize: 8,
|
|
|
|
readOnly: true,
|
|
|
|
styleActiveLine: true
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: too many globals
|
2018-08-02 17:08:37 +00:00
|
|
|
refresh(moveCursor: boolean) {
|
2018-07-06 00:13:07 +00:00
|
|
|
var state = lastDebugState || platform.saveState();
|
|
|
|
var pc = state.c ? state.c.PC : 0;
|
|
|
|
var curline = 0;
|
|
|
|
var selline = 0;
|
2018-09-17 20:09:09 +00:00
|
|
|
var addr2symbol = (platform.debugSymbols && platform.debugSymbols.addr2symbol) || {};
|
2018-07-06 00:13:07 +00:00
|
|
|
// TODO: not perfect disassembler
|
2018-07-07 14:55:27 +00:00
|
|
|
var disassemble = (start, end) => {
|
2018-07-06 00:13:07 +00:00
|
|
|
if (start < 0) start = 0;
|
|
|
|
if (end > 0xffff) end = 0xffff;
|
|
|
|
// TODO: use pc2visits
|
|
|
|
var a = start;
|
|
|
|
var s = "";
|
|
|
|
while (a < end) {
|
2018-09-11 00:44:53 +00:00
|
|
|
var disasm = platform.disassemble(a, platform.readAddress.bind(platform));
|
2018-07-06 00:13:07 +00:00
|
|
|
/* TODO: look thru all source files
|
2018-07-07 14:55:27 +00:00
|
|
|
var srclinenum = sourcefile && this.sourcefile.offset2line[a];
|
2018-07-06 00:13:07 +00:00
|
|
|
if (srclinenum) {
|
|
|
|
var srcline = getActiveEditor().getLine(srclinenum);
|
|
|
|
if (srcline && srcline.trim().length) {
|
|
|
|
s += "; " + srclinenum + ":\t" + srcline + "\n";
|
|
|
|
curline++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
var bytes = "";
|
2018-08-28 12:28:53 +00:00
|
|
|
var comment = "";
|
2018-07-06 00:13:07 +00:00
|
|
|
for (var i=0; i<disasm.nbytes; i++)
|
|
|
|
bytes += hex(platform.readAddress(a+i));
|
|
|
|
while (bytes.length < 14)
|
|
|
|
bytes += ' ';
|
2018-08-14 20:28:29 +00:00
|
|
|
var dstr = disasm.line;
|
2018-08-28 12:28:53 +00:00
|
|
|
if (addr2symbol && disasm.isaddr) {
|
2018-08-14 20:28:29 +00:00
|
|
|
dstr = dstr.replace(/([^#])[$]([0-9A-F]+)/, (substr:string, ...args:any[]):string => {
|
|
|
|
var addr = parseInt(args[1], 16);
|
|
|
|
var sym = addr2symbol[addr];
|
|
|
|
if (sym) return (args[0] + sym);
|
2018-08-15 04:43:52 +00:00
|
|
|
sym = addr2symbol[addr-1];
|
2018-08-14 20:28:29 +00:00
|
|
|
if (sym) return (args[0] + sym + "+1");
|
|
|
|
return substr;
|
|
|
|
});
|
|
|
|
}
|
2018-08-28 12:28:53 +00:00
|
|
|
if (addr2symbol) {
|
|
|
|
var sym = addr2symbol[a];
|
|
|
|
if (sym) {
|
|
|
|
comment = "; " + sym;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var dline = hex(parseInt(a), 4) + "\t" + rpad(bytes,14) + "\t" + rpad(dstr,30) + comment + "\n";
|
2018-07-06 00:13:07 +00:00
|
|
|
s += dline;
|
|
|
|
if (a == pc) selline = curline;
|
|
|
|
curline++;
|
|
|
|
a += disasm.nbytes || 1;
|
|
|
|
}
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
var text = disassemble(pc-96, pc) + disassemble(pc, pc+96);
|
2018-07-07 14:55:27 +00:00
|
|
|
this.disasmview.setValue(text);
|
2018-08-02 17:08:37 +00:00
|
|
|
if (moveCursor) {
|
|
|
|
this.disasmview.setCursor(selline, 0);
|
|
|
|
jumpToLine(this.disasmview, selline);
|
|
|
|
}
|
2018-07-06 00:13:07 +00:00
|
|
|
}
|
|
|
|
|
2018-07-07 14:55:27 +00:00
|
|
|
getCursorPC() : number {
|
|
|
|
var line = this.disasmview.getCursor().line;
|
2018-07-06 00:13:07 +00:00
|
|
|
if (line >= 0) {
|
2018-09-05 02:28:12 +00:00
|
|
|
var toks = this.disasmview.getLine(line).trim().split(/\s+/);
|
2018-07-06 00:13:07 +00:00
|
|
|
if (toks && toks.length >= 1) {
|
|
|
|
var pc = parseInt(toks[0], 16);
|
|
|
|
if (pc >= 0) return pc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
///
|
|
|
|
|
2018-07-08 03:10:51 +00:00
|
|
|
export class ListingView extends DisassemblerView implements ProjectView {
|
2018-07-07 14:55:27 +00:00
|
|
|
assemblyfile : SourceFile;
|
2018-08-19 23:25:42 +00:00
|
|
|
path : string;
|
2018-07-06 00:13:07 +00:00
|
|
|
|
2018-08-19 23:25:42 +00:00
|
|
|
constructor(lstfn : string) {
|
2018-07-07 14:55:27 +00:00
|
|
|
super();
|
2018-08-19 23:25:42 +00:00
|
|
|
this.path = lstfn;
|
|
|
|
}
|
|
|
|
|
|
|
|
refreshListing() {
|
|
|
|
// lookup corresponding assemblyfile for this file, using listing
|
|
|
|
var lst = current_project.getListingForFile(this.path);
|
|
|
|
if (lst && lst.assemblyfile && lst.assemblyfile !== this.assemblyfile) {
|
|
|
|
this.assemblyfile = lst.assemblyfile;
|
|
|
|
}
|
2019-02-21 00:38:30 +00:00
|
|
|
else if (lst && lst.sourcefile && lst.sourcefile !== this.assemblyfile) {
|
|
|
|
this.assemblyfile = lst.sourcefile;
|
|
|
|
}
|
2018-07-07 14:55:27 +00:00
|
|
|
}
|
|
|
|
|
2018-08-02 17:08:37 +00:00
|
|
|
refresh(moveCursor: boolean) {
|
2018-08-19 23:25:42 +00:00
|
|
|
this.refreshListing();
|
|
|
|
if (!this.assemblyfile) return; // TODO?
|
2018-07-06 00:13:07 +00:00
|
|
|
var state = lastDebugState || platform.saveState();
|
2018-11-22 12:39:06 +00:00
|
|
|
var pc = state.c ? (state.c.EPC || state.c.PC) : 0;
|
2018-07-07 14:55:27 +00:00
|
|
|
var asmtext = this.assemblyfile.text;
|
|
|
|
var disasmview = this.getDisasmView();
|
2018-07-06 00:13:07 +00:00
|
|
|
disasmview.setValue(asmtext);
|
2019-03-03 20:37:22 +00:00
|
|
|
var debugging = true; // TODO: platform.isDebugging && platform.isDebugging();
|
2018-08-27 00:22:50 +00:00
|
|
|
var findPC = debugging ? pc : -1;
|
2018-08-04 14:21:50 +00:00
|
|
|
if (findPC >= 0 && this.assemblyfile) {
|
2018-08-18 14:21:18 +00:00
|
|
|
var lineno = this.assemblyfile.findLineForOffset(findPC, 15);
|
2018-08-02 17:08:37 +00:00
|
|
|
if (lineno && moveCursor) {
|
2018-07-06 00:13:07 +00:00
|
|
|
// set cursor while debugging
|
2018-08-27 00:22:50 +00:00
|
|
|
if (debugging)
|
2018-07-06 00:13:07 +00:00
|
|
|
disasmview.setCursor(lineno-1, 0);
|
|
|
|
jumpToLine(disasmview, lineno-1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
///
|
|
|
|
|
2018-08-19 23:25:42 +00:00
|
|
|
// TODO: make it use debug state
|
2018-08-24 00:53:37 +00:00
|
|
|
// TODO: make it safe (load/restore state?)
|
2018-07-08 03:10:51 +00:00
|
|
|
export class MemoryView implements ProjectView {
|
2018-07-07 14:55:27 +00:00
|
|
|
memorylist;
|
|
|
|
dumplines;
|
|
|
|
maindiv : HTMLElement;
|
|
|
|
static IGNORE_SYMS = {s__INITIALIZER:true, /* s__GSINIT:true, */ _color_prom:true};
|
2019-03-03 20:37:22 +00:00
|
|
|
recreateOnResize = true;
|
2018-08-19 23:25:42 +00:00
|
|
|
/*
|
|
|
|
read(addr:number) {
|
|
|
|
// TODO: b offset ?
|
|
|
|
if (lastDebugState && lastDebugState.b && addr < lastDebugState.b.length)
|
|
|
|
return lastDebugState.b[addr];
|
|
|
|
else
|
|
|
|
return this.platform.readMemory(addr);
|
|
|
|
}
|
|
|
|
*/
|
2018-07-06 00:13:07 +00:00
|
|
|
|
2018-07-07 14:55:27 +00:00
|
|
|
createDiv(parent : HTMLElement) {
|
|
|
|
var div = document.createElement('div');
|
2018-07-06 00:13:07 +00:00
|
|
|
div.setAttribute("class", "memdump");
|
|
|
|
parent.appendChild(div);
|
2018-09-11 00:44:53 +00:00
|
|
|
this.showMemoryWindow(parent, div);
|
2018-07-07 14:55:27 +00:00
|
|
|
return this.maindiv = div;
|
2018-07-06 00:13:07 +00:00
|
|
|
}
|
2018-07-07 14:55:27 +00:00
|
|
|
|
2018-09-11 00:44:53 +00:00
|
|
|
showMemoryWindow(workspace:HTMLElement, parent:HTMLElement) {
|
2018-07-07 14:55:27 +00:00
|
|
|
this.memorylist = new VirtualList({
|
2018-09-11 00:44:53 +00:00
|
|
|
w: $(workspace).width(),
|
|
|
|
h: $(workspace).height(),
|
2018-07-06 00:13:07 +00:00
|
|
|
itemHeight: getVisibleEditorLineHeight(),
|
2018-09-18 18:09:51 +00:00
|
|
|
totalRows: 0x2000,
|
2018-07-07 14:55:27 +00:00
|
|
|
generatorFn: (row : number) => {
|
|
|
|
var s = this.getMemoryLineAt(row);
|
|
|
|
var linediv = document.createElement("div");
|
|
|
|
if (this.dumplines) {
|
|
|
|
var dlr = this.dumplines[row];
|
|
|
|
if (dlr) linediv.classList.add('seg_' + this.getMemorySegment(this.dumplines[row].a));
|
2018-07-06 00:13:07 +00:00
|
|
|
}
|
2018-07-07 14:55:27 +00:00
|
|
|
linediv.appendChild(document.createTextNode(s));
|
|
|
|
return linediv;
|
2018-07-06 00:13:07 +00:00
|
|
|
}
|
|
|
|
});
|
2018-07-07 14:55:27 +00:00
|
|
|
$(parent).append(this.memorylist.container);
|
|
|
|
this.tick();
|
|
|
|
if (compparams && this.dumplines)
|
2019-02-26 15:56:51 +00:00
|
|
|
this.scrollToAddress(compparams.data_start);
|
|
|
|
}
|
2019-03-03 16:32:25 +00:00
|
|
|
|
2019-02-26 15:56:51 +00:00
|
|
|
scrollToAddress(addr : number) {
|
|
|
|
this.memorylist.scrollToItem(this.findMemoryWindowLine(addr));
|
2018-07-06 00:13:07 +00:00
|
|
|
}
|
2018-11-21 12:21:07 +00:00
|
|
|
|
2018-07-07 14:55:27 +00:00
|
|
|
refresh() {
|
2018-09-15 22:47:40 +00:00
|
|
|
this.dumplines = null;
|
2018-09-18 18:09:51 +00:00
|
|
|
this.tick();
|
2018-07-07 14:55:27 +00:00
|
|
|
}
|
2018-11-21 12:21:07 +00:00
|
|
|
|
2018-07-07 14:55:27 +00:00
|
|
|
tick() {
|
|
|
|
if (this.memorylist) {
|
|
|
|
$(this.maindiv).find('[data-index]').each( (i,e) => {
|
2018-07-06 00:13:07 +00:00
|
|
|
var div = $(e);
|
2018-07-07 14:55:27 +00:00
|
|
|
var row = parseInt(div.attr('data-index'));
|
2018-07-06 00:13:07 +00:00
|
|
|
var oldtext = div.text();
|
2018-07-07 14:55:27 +00:00
|
|
|
var newtext = this.getMemoryLineAt(row);
|
2018-07-06 00:13:07 +00:00
|
|
|
if (oldtext != newtext)
|
|
|
|
div.text(newtext);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-07 14:55:27 +00:00
|
|
|
getMemoryLineAt(row : number) : string {
|
2018-07-06 00:13:07 +00:00
|
|
|
var offset = row * 16;
|
|
|
|
var n1 = 0;
|
|
|
|
var n2 = 16;
|
|
|
|
var sym;
|
2018-07-07 14:55:27 +00:00
|
|
|
if (this.getDumpLines()) {
|
|
|
|
var dl = this.dumplines[row];
|
2018-07-06 00:13:07 +00:00
|
|
|
if (dl) {
|
|
|
|
offset = dl.a & 0xfff0;
|
|
|
|
n1 = dl.a - offset;
|
|
|
|
n2 = n1 + dl.l;
|
|
|
|
sym = dl.s;
|
|
|
|
} else {
|
|
|
|
return '.';
|
|
|
|
}
|
|
|
|
}
|
2018-09-18 18:09:51 +00:00
|
|
|
var s = hex(offset+n1,4) + ' ';
|
2018-07-06 00:13:07 +00:00
|
|
|
for (var i=0; i<n1; i++) s += ' ';
|
|
|
|
if (n1 > 8) s += ' ';
|
|
|
|
for (var i=n1; i<n2; i++) {
|
2019-03-15 01:55:16 +00:00
|
|
|
var read = this.readAddress(offset+i);
|
2018-07-06 00:13:07 +00:00
|
|
|
if (i==8) s += ' ';
|
2018-08-24 00:53:37 +00:00
|
|
|
s += ' ' + (read!==null?hex(read,2):'??');
|
2018-07-06 00:13:07 +00:00
|
|
|
}
|
|
|
|
for (var i=n2; i<16; i++) s += ' ';
|
|
|
|
if (sym) s += ' ' + sym;
|
|
|
|
return s;
|
|
|
|
}
|
2019-03-15 01:55:16 +00:00
|
|
|
|
|
|
|
readAddress(n : number) {
|
|
|
|
return platform.readAddress(n);
|
|
|
|
}
|
2018-07-06 00:13:07 +00:00
|
|
|
|
2018-07-07 14:55:27 +00:00
|
|
|
getDumpLineAt(line : number) {
|
|
|
|
var d = this.dumplines[line];
|
2018-07-06 00:13:07 +00:00
|
|
|
if (d) {
|
|
|
|
return d.a + " " + d.s;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: addr2symbol for ca65; and make it work without symbols
|
2018-07-07 14:55:27 +00:00
|
|
|
getDumpLines() {
|
2018-09-17 20:09:09 +00:00
|
|
|
var addr2sym = (platform.debugSymbols && platform.debugSymbols.addr2symbol) || {};
|
2018-09-15 22:47:40 +00:00
|
|
|
if (!this.dumplines) {
|
2018-07-07 14:55:27 +00:00
|
|
|
this.dumplines = [];
|
2018-07-06 00:13:07 +00:00
|
|
|
var ofs = 0;
|
|
|
|
var sym;
|
2018-11-21 12:21:07 +00:00
|
|
|
for (const _nextofs of Object.keys(addr2sym)) {
|
2018-07-06 00:13:07 +00:00
|
|
|
var nextofs = parseInt(_nextofs); // convert from string (stupid JS)
|
2018-09-15 22:47:40 +00:00
|
|
|
var nextsym = addr2sym[nextofs];
|
2018-07-06 00:13:07 +00:00
|
|
|
if (sym) {
|
2018-07-07 14:55:27 +00:00
|
|
|
if (MemoryView.IGNORE_SYMS[sym]) {
|
2018-07-06 00:13:07 +00:00
|
|
|
ofs = nextofs;
|
|
|
|
} else {
|
|
|
|
while (ofs < nextofs) {
|
|
|
|
var ofs2 = (ofs + 16) & 0xffff0;
|
|
|
|
if (ofs2 > nextofs) ofs2 = nextofs;
|
|
|
|
//if (ofs < 1000) console.log(ofs, ofs2, nextofs, sym);
|
2018-07-07 14:55:27 +00:00
|
|
|
this.dumplines.push({a:ofs, l:ofs2-ofs, s:sym});
|
2018-07-06 00:13:07 +00:00
|
|
|
ofs = ofs2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sym = nextsym;
|
|
|
|
}
|
|
|
|
}
|
2018-07-07 14:55:27 +00:00
|
|
|
return this.dumplines;
|
2018-07-06 00:13:07 +00:00
|
|
|
}
|
|
|
|
|
2019-02-26 15:56:51 +00:00
|
|
|
// TODO: use segments list?
|
2018-07-07 14:55:27 +00:00
|
|
|
getMemorySegment(a:number) : string {
|
2019-03-16 00:34:17 +00:00
|
|
|
if (compparams) {
|
|
|
|
if (a >= compparams.data_start && a < compparams.data_start+compparams.data_size) {
|
|
|
|
if (platform.getSP && a >= platform.getSP() - 15)
|
|
|
|
return 'stack';
|
|
|
|
else
|
|
|
|
return 'data';
|
|
|
|
}
|
|
|
|
else if (a >= compparams.code_start && a < compparams.code_start+(compparams.code_size||compparams.rom_size))
|
|
|
|
return 'code';
|
2018-07-06 00:13:07 +00:00
|
|
|
}
|
2019-03-16 00:34:17 +00:00
|
|
|
var segments = current_project.segments;
|
|
|
|
if (segments) {
|
|
|
|
for (var seg of segments) {
|
|
|
|
if (a >= seg.start && a < seg.start+seg.size) {
|
|
|
|
if (seg.type == 'rom') return 'code';
|
|
|
|
if (seg.type == 'ram') return 'data';
|
|
|
|
if (seg.type == 'io') return 'io';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 'unknown';
|
2018-07-06 00:13:07 +00:00
|
|
|
}
|
|
|
|
|
2018-07-07 14:55:27 +00:00
|
|
|
findMemoryWindowLine(a:number) : number {
|
|
|
|
for (var i=0; i<this.dumplines.length; i++)
|
|
|
|
if (this.dumplines[i].a >= a)
|
2018-07-06 00:13:07 +00:00
|
|
|
return i;
|
|
|
|
}
|
|
|
|
}
|
2018-12-01 09:45:55 +00:00
|
|
|
|
2019-03-15 01:55:16 +00:00
|
|
|
export class VRAMMemoryView extends MemoryView {
|
|
|
|
readAddress(n : number) {
|
|
|
|
return platform.readVRAMAddress(n);
|
|
|
|
}
|
|
|
|
getMemorySegment(a:number) : string {
|
|
|
|
return 'video';
|
|
|
|
}
|
|
|
|
getDumpLines() {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-01 09:45:55 +00:00
|
|
|
///
|
|
|
|
|
|
|
|
export class BinaryFileView implements ProjectView {
|
|
|
|
memorylist;
|
|
|
|
maindiv : HTMLElement;
|
2018-12-08 00:28:11 +00:00
|
|
|
path:string;
|
|
|
|
data:Uint8Array;
|
2019-03-03 20:37:22 +00:00
|
|
|
recreateOnResize = true;
|
2018-12-01 09:45:55 +00:00
|
|
|
|
2018-12-08 00:28:11 +00:00
|
|
|
constructor(path:string, data:Uint8Array) {
|
|
|
|
this.path = path;
|
2018-12-01 09:45:55 +00:00
|
|
|
this.data = data;
|
|
|
|
}
|
|
|
|
|
|
|
|
createDiv(parent : HTMLElement) {
|
|
|
|
var div = document.createElement('div');
|
|
|
|
div.setAttribute("class", "memdump");
|
|
|
|
parent.appendChild(div);
|
|
|
|
this.showMemoryWindow(parent, div);
|
|
|
|
return this.maindiv = div;
|
|
|
|
}
|
|
|
|
|
|
|
|
showMemoryWindow(workspace:HTMLElement, parent:HTMLElement) {
|
|
|
|
this.memorylist = new VirtualList({
|
|
|
|
w: $(workspace).width(),
|
|
|
|
h: $(workspace).height(),
|
|
|
|
itemHeight: getVisibleEditorLineHeight(),
|
|
|
|
totalRows: ((this.data.length+15) >> 4),
|
|
|
|
generatorFn: (row : number) => {
|
|
|
|
var s = this.getMemoryLineAt(row);
|
|
|
|
var linediv = document.createElement("div");
|
|
|
|
linediv.appendChild(document.createTextNode(s));
|
|
|
|
return linediv;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
$(parent).append(this.memorylist.container);
|
|
|
|
}
|
|
|
|
|
|
|
|
getMemoryLineAt(row : number) : string {
|
|
|
|
var offset = row * 16;
|
|
|
|
var n1 = 0;
|
|
|
|
var n2 = 16;
|
|
|
|
var s = hex(offset+n1,4) + ' ';
|
|
|
|
for (var i=0; i<n1; i++) s += ' ';
|
|
|
|
if (n1 > 8) s += ' ';
|
|
|
|
for (var i=n1; i<n2; i++) {
|
|
|
|
var read = this.data[offset+i];
|
|
|
|
if (i==8) s += ' ';
|
|
|
|
s += ' ' + (read>=0?hex(read,2):' ');
|
|
|
|
}
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
refresh() {
|
|
|
|
}
|
2019-03-03 16:32:25 +00:00
|
|
|
|
2018-12-08 00:28:11 +00:00
|
|
|
getPath() { return this.path; }
|
2018-12-01 09:45:55 +00:00
|
|
|
}
|
2019-02-21 21:47:25 +00:00
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
export class MemoryMapView implements ProjectView {
|
|
|
|
maindiv : JQuery;
|
|
|
|
|
|
|
|
createDiv(parent : HTMLElement) {
|
2019-02-21 22:41:59 +00:00
|
|
|
this.maindiv = $('<div class="vertical-scroll"/>');
|
2019-02-21 21:47:25 +00:00
|
|
|
$(parent).append(this.maindiv);
|
|
|
|
return this.maindiv[0];
|
|
|
|
}
|
2019-03-03 16:32:25 +00:00
|
|
|
|
2019-02-21 22:41:59 +00:00
|
|
|
// TODO: overlapping segments (e.g. ROM + LC)
|
2019-02-21 21:47:25 +00:00
|
|
|
addSegment(seg : Segment) {
|
|
|
|
var offset = $('<div class="col-md-1 segment-offset"/>');
|
|
|
|
offset.text('$'+hex(seg.start,4));
|
|
|
|
var segdiv = $('<div class="col-md-4 segment"/>');
|
|
|
|
if (seg.last)
|
|
|
|
segdiv.text(seg.name+" ("+(seg.last-seg.start)+" / "+seg.size+" bytes used)");
|
|
|
|
else
|
|
|
|
segdiv.text(seg.name+" ("+seg.size+" bytes)");
|
|
|
|
if (seg.size >= 256) {
|
|
|
|
var pad = (Math.log(seg.size) - Math.log(256)) * 0.5;
|
|
|
|
segdiv.css('padding-top', pad+'em');
|
|
|
|
segdiv.css('padding-bottom', pad+'em');
|
|
|
|
}
|
|
|
|
if (seg.type) {
|
|
|
|
segdiv.addClass('segment-'+seg.type);
|
|
|
|
}
|
|
|
|
var row = $('<div class="row"/>').append(offset, segdiv);
|
|
|
|
var container = $('<div class="container"/>').append(row);
|
|
|
|
this.maindiv.append(container);
|
2019-02-26 15:56:51 +00:00
|
|
|
segdiv.click(() => {
|
|
|
|
var memview = projectWindows.createOrShow('#memory') as MemoryView;
|
|
|
|
memview.scrollToAddress(seg.start);
|
|
|
|
// TODO: this doesn't update nav bar
|
|
|
|
});
|
2019-02-21 21:47:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
refresh() {
|
|
|
|
this.maindiv.empty();
|
|
|
|
var segments = current_project.segments;
|
|
|
|
if (segments) {
|
|
|
|
var curofs = 0;
|
|
|
|
for (var seg of segments) {
|
2019-02-21 22:41:59 +00:00
|
|
|
//var used = seg.last ? (seg.last-seg.start) : seg.size;
|
2019-03-13 18:52:30 +00:00
|
|
|
if (seg.start > curofs)
|
2019-02-21 21:47:25 +00:00
|
|
|
this.addSegment({name:'',start:curofs, size:seg.start-curofs});
|
|
|
|
this.addSegment(seg);
|
2019-02-21 22:41:59 +00:00
|
|
|
curofs = seg.start + seg.size;
|
2019-02-21 21:47:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-03-03 16:32:25 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
export class ProfileView implements ProjectView {
|
|
|
|
profilelist;
|
|
|
|
prof : ProfilerOutput;
|
|
|
|
maindiv : HTMLElement;
|
|
|
|
symcache : {};
|
2019-03-03 20:37:22 +00:00
|
|
|
recreateOnResize = true;
|
2019-03-03 16:32:25 +00:00
|
|
|
|
|
|
|
createDiv(parent : HTMLElement) {
|
|
|
|
var div = document.createElement('div');
|
2019-03-03 20:37:22 +00:00
|
|
|
div.setAttribute("class", "profiler");
|
2019-03-03 16:32:25 +00:00
|
|
|
parent.appendChild(div);
|
|
|
|
this.showMemoryWindow(parent, div);
|
|
|
|
return this.maindiv = div;
|
|
|
|
}
|
|
|
|
|
|
|
|
showMemoryWindow(workspace:HTMLElement, parent:HTMLElement) {
|
|
|
|
this.profilelist = new VirtualList({
|
|
|
|
w: $(workspace).width(),
|
|
|
|
h: $(workspace).height(),
|
|
|
|
itemHeight: getVisibleEditorLineHeight(),
|
|
|
|
totalRows: 262,
|
|
|
|
generatorFn: (row : number) => {
|
|
|
|
var linediv = document.createElement("div");
|
2019-03-03 20:37:22 +00:00
|
|
|
this.addProfileLine(linediv, row);
|
2019-03-03 16:32:25 +00:00
|
|
|
return linediv;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
$(parent).append(this.profilelist.container);
|
|
|
|
this.symcache = {};
|
|
|
|
this.tick();
|
|
|
|
}
|
|
|
|
|
2019-03-03 20:37:22 +00:00
|
|
|
addProfileLine(div : HTMLElement, row : number) : void {
|
|
|
|
div.appendChild(createTextSpan(lpad(row+':',4), "profiler-lineno"));
|
|
|
|
if (!this.prof) return;
|
2019-03-03 16:32:25 +00:00
|
|
|
var f = this.prof.frame;
|
2019-03-03 20:37:22 +00:00
|
|
|
if (!f) return;
|
2019-03-03 16:32:25 +00:00
|
|
|
var l = f.lines[row];
|
2019-03-03 20:37:22 +00:00
|
|
|
if (!l) return;
|
2019-03-03 16:32:25 +00:00
|
|
|
var lastsym = '';
|
|
|
|
for (var i=l.start; i<=l.end; i++) {
|
|
|
|
var pc = f.iptab[i];
|
|
|
|
var sym = this.symcache[pc];
|
|
|
|
if (!sym) {
|
|
|
|
sym = lookupSymbol(platform, pc, false);
|
|
|
|
this.symcache[pc] = sym;
|
|
|
|
}
|
|
|
|
if (sym != lastsym) {
|
2019-03-03 20:37:22 +00:00
|
|
|
var cls = "profiler";
|
|
|
|
if (sym.startsWith('_')) cls = "profiler-cident";
|
|
|
|
else if (sym.startsWith('@')) cls = "profiler-local";
|
|
|
|
else if (/^\d*[.]/.exec(sym)) cls = "profiler-local";
|
|
|
|
div.appendChild(createTextSpan(' '+sym, cls));
|
2019-03-03 16:32:25 +00:00
|
|
|
lastsym = sym;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
refresh() {
|
|
|
|
this.tick();
|
|
|
|
}
|
|
|
|
|
|
|
|
tick() {
|
|
|
|
if (this.profilelist) {
|
|
|
|
$(this.maindiv).find('[data-index]').each( (i,e) => {
|
|
|
|
var div = $(e);
|
|
|
|
var row = parseInt(div.attr('data-index'));
|
2019-03-03 20:37:22 +00:00
|
|
|
div.empty();
|
|
|
|
this.addProfileLine(div[0], row);
|
2019-03-03 16:32:25 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2019-03-03 20:37:22 +00:00
|
|
|
|
2019-03-08 01:00:12 +00:00
|
|
|
setVisible(showing : boolean) : void {
|
|
|
|
if (showing)
|
|
|
|
this.prof = platform.startProfiling();
|
|
|
|
else
|
|
|
|
platform.stopProfiling();
|
2019-03-03 20:37:22 +00:00
|
|
|
}
|
2019-02-21 21:47:25 +00:00
|
|
|
}
|
2019-03-18 18:39:02 +00:00
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
export class AssetEditorView implements ProjectView {
|
|
|
|
maindiv : JQuery;
|
|
|
|
|
|
|
|
createDiv(parent : HTMLElement) {
|
|
|
|
this.maindiv = $('<div class="vertical-scroll"/>');
|
|
|
|
$(parent).append(this.maindiv);
|
|
|
|
return this.maindiv[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
scanFileTextForAssets(id : string, data : string) {
|
|
|
|
// scan file for assets
|
|
|
|
// /*{json}*/ or ;;{json};;
|
|
|
|
var result = [];
|
|
|
|
var re1 = /[/;][*;]([{].+[}])[*;][/;]/g;
|
|
|
|
var m;
|
|
|
|
while (m = re1.exec(data)) {
|
|
|
|
var start = m.index + m[0].length;
|
|
|
|
var end;
|
|
|
|
// TODO: verilog end
|
|
|
|
if (m[0].startsWith(';;')) {
|
|
|
|
end = data.indexOf(';;', start); // asm
|
|
|
|
} else {
|
|
|
|
end = data.indexOf(';', start); // C
|
|
|
|
}
|
|
|
|
console.log(id, start, end, m[1]);
|
|
|
|
if (end > start) {
|
|
|
|
try {
|
|
|
|
var jsontxt = m[1].replace(/([A-Za-z]+):/g, '"$1":'); // fix lenient JSON
|
|
|
|
var json = JSON.parse(jsontxt);
|
|
|
|
// TODO: name?
|
|
|
|
result.push({fileid:id,fmt:json,start:start,end:end});
|
|
|
|
} catch (e) {
|
|
|
|
console.log(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
addPixelEditor(filediv:JQuery, firstnode:PixelFileDataNode|PixelTextDataNode, fmt?) {
|
|
|
|
var adual = $('<div class="asset_dual"/>');
|
|
|
|
// create tile set
|
|
|
|
var aedit = $('<div class="asset_grid"/>');
|
|
|
|
adual.append(aedit);
|
|
|
|
filediv.append(adual);
|
|
|
|
// data -> pixels
|
|
|
|
var mapper = new PixelMapper();
|
|
|
|
fmt = fmt || {w:8,h:8,bpp:1,count:256/*(data.length>>4)*/,brev:true,np:2,pofs:8,remap:[0,1,2,4,5,6,7,8,9,10,11,12]}; // TODO
|
|
|
|
fmt.xform = 'scale(2)';
|
|
|
|
mapper.fmt = fmt;
|
|
|
|
var imgsperline = fmt.w == 8 ? 16 : 8; // TODO
|
|
|
|
// TODO: rotate node?
|
|
|
|
firstnode.addRight(mapper);
|
|
|
|
// pixels -> RGBA
|
|
|
|
var palizer = new PixelPalettizer();
|
|
|
|
if (fmt.bpp*(fmt.np|1) == 1)
|
|
|
|
palizer.palette = new Uint32Array([0xff000000, 0xffffffff]);
|
|
|
|
else
|
|
|
|
palizer.palette = new Uint32Array([0xff000000, 0xffff00ff, 0xffffff00, 0xffffffff]); // TODO
|
|
|
|
mapper.addRight(palizer);
|
|
|
|
// refresh
|
|
|
|
firstnode.refreshRight();
|
|
|
|
// add view objects (TODO)
|
|
|
|
// TODO: they need to update when refreshed from right
|
|
|
|
var i = 0;
|
|
|
|
var span = null;
|
|
|
|
for (var imdata of palizer.output) {
|
|
|
|
var viewer = new PixelViewer();
|
|
|
|
viewer.width = mapper.fmt.w | 0;
|
|
|
|
viewer.height = mapper.fmt.h | 0;
|
|
|
|
viewer.recreate();
|
|
|
|
viewer.canvas.style.width = (viewer.width*2)+'px'; // TODO
|
|
|
|
viewer.updateImage(imdata); // TODO
|
|
|
|
$(viewer.canvas).click((e) => {
|
|
|
|
console.log(e); // TODO
|
|
|
|
});
|
|
|
|
if (!span) {
|
|
|
|
span = $('<span/>');
|
|
|
|
aedit.append(span);
|
|
|
|
}
|
|
|
|
span.append(viewer.canvas);
|
|
|
|
if (++i == imgsperline) {
|
|
|
|
aedit.append($("<br/>"));
|
|
|
|
span = null;
|
|
|
|
i = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
refreshAssetsInFile(fileid : string, data : FileData) {
|
|
|
|
let filediv = $('#'+this.getFileDivId(fileid)).empty();
|
|
|
|
// TODO
|
|
|
|
// TODO: check if open
|
|
|
|
if (fileid.endsWith('.chr') && data instanceof Uint8Array) {
|
|
|
|
let node = new PixelFileDataNode(fileid, data);
|
|
|
|
this.addPixelEditor(filediv, node);
|
|
|
|
} else if (typeof data === 'string') {
|
|
|
|
let textfrags = this.scanFileTextForAssets(fileid, data);
|
|
|
|
for (let frag of textfrags) {
|
|
|
|
// is this a bitmap?
|
|
|
|
if (frag.fmt && frag.fmt.w > 0 && frag.fmt.h > 0) {
|
|
|
|
let node = new PixelTextDataNode(fileid, data, frag.start, frag.end);
|
|
|
|
this.addPixelEditor(filediv, node, frag.fmt);
|
|
|
|
} else {
|
|
|
|
// TODO: other kinds of resources?
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
getFileDivId(id : string) {
|
|
|
|
return '__asset__' + safeident(id);
|
|
|
|
}
|
|
|
|
|
|
|
|
refresh() {
|
|
|
|
this.maindiv.empty();
|
|
|
|
current_project.iterateFiles((id, data) => {
|
|
|
|
var divid = this.getFileDivId(id);
|
|
|
|
var header = $('<div class="asset_file_header"/>').text(id);
|
|
|
|
var body = $('<div/>').attr('id',divid);
|
|
|
|
var filediv = $('<div class="asset_file"/>').append(header, body).appendTo(this.maindiv);
|
|
|
|
this.refreshAssetsInFile(id, data);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|