mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2024-11-21 23:30:58 +00:00
converting some stuff to TypeScript (make tsweb)
This commit is contained in:
parent
4bb460a79b
commit
62f5303107
@ -8,3 +8,7 @@ before_install:
|
||||
language: node_js
|
||||
node_js:
|
||||
- "6.5.0"
|
||||
script:
|
||||
- npm run build
|
||||
- npm test
|
||||
|
4
Makefile
4
Makefile
@ -27,4 +27,6 @@ web:
|
||||
|
||||
tsweb:
|
||||
ifconfig | grep inet
|
||||
node ../nodejs-typescript-webserver/bin/FileServer.js .
|
||||
tsc -w &
|
||||
python2 -m SimpleHTTPServer 2>> http.out
|
||||
#node ../nodejs-typescript-webserver/bin/FileServer.js .
|
||||
|
@ -43,6 +43,7 @@ TODO:
|
||||
(https://developers.google.com/web/updates/2018/04/loading-wasm)
|
||||
- unify versioning
|
||||
- more UI tests
|
||||
- base-36 encoding for LZG
|
||||
|
||||
|
||||
WEB WORKER FORMAT
|
||||
|
13
index.html
13
index.html
@ -207,6 +207,13 @@ ga('send', 'pageview');
|
||||
<script src="codemirror/addon/selection/active-line.js"></script>
|
||||
<link rel="stylesheet" href="codemirror/addon/dialog/dialog.css">
|
||||
|
||||
<script>
|
||||
var exports = {};
|
||||
function require(modname) {
|
||||
if (modname == 'jquery') return $;
|
||||
}
|
||||
</script>
|
||||
|
||||
<script src="javatari.js/release/javatari/javatari.js"></script>
|
||||
<script src="src/cpu/z80fast.js"></script>
|
||||
<!--<script src="src/cpu/6809.js"></script>-->
|
||||
@ -232,9 +239,11 @@ ga('send', 'pageview');
|
||||
<script src="src/vlist.js"></script>
|
||||
<script src="src/emu.js"></script>
|
||||
<script src="src/audio.js"></script>
|
||||
<script src="src/util.js"></script>
|
||||
<script src="gen/util.js"></script>
|
||||
<script src="src/cpu/disasm6502.js"></script>
|
||||
<script src="src/project.js"></script>
|
||||
<script src="gen/project.js"></script>
|
||||
<script src="gen/windows.js"></script>
|
||||
<script src="gen/views.js"></script>
|
||||
<script src="src/ui.js"></script>
|
||||
<!-- <script src="src/audio/votrax.js"></script> -->
|
||||
|
||||
|
292
package-lock.json
generated
Normal file
292
package-lock.json
generated
Normal file
@ -0,0 +1,292 @@
|
||||
{
|
||||
"name": "8bitworkshop",
|
||||
"version": "2.2.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@types/jquery": {
|
||||
"version": "3.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.4.tgz",
|
||||
"integrity": "sha512-KqgLNDh8oTl43/2B78S7tjwPnOxPtP9wQsLptbuQCmMwqH5QuPZOk36RsNgbs3mnq/3SCyG1l/GJmhKk5Dg68g==",
|
||||
"dev": true
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
|
||||
"dev": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"balanced-match": "1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"browser-stdout": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz",
|
||||
"integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=",
|
||||
"dev": true
|
||||
},
|
||||
"commander": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz",
|
||||
"integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-readlink": "1.0.1"
|
||||
}
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
|
||||
"dev": true
|
||||
},
|
||||
"debug": {
|
||||
"version": "2.6.8",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz",
|
||||
"integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"diff": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz",
|
||||
"integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=",
|
||||
"dev": true
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
|
||||
"dev": true
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
|
||||
"dev": true
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz",
|
||||
"integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fs.realpath": "1.0.0",
|
||||
"inflight": "1.0.6",
|
||||
"inherits": "2.0.3",
|
||||
"minimatch": "3.0.4",
|
||||
"once": "1.4.0",
|
||||
"path-is-absolute": "1.0.1"
|
||||
}
|
||||
},
|
||||
"graceful-readlink": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
|
||||
"integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=",
|
||||
"dev": true
|
||||
},
|
||||
"growl": {
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz",
|
||||
"integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=",
|
||||
"dev": true
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz",
|
||||
"integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=",
|
||||
"dev": true
|
||||
},
|
||||
"he": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz",
|
||||
"integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=",
|
||||
"dev": true
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"once": "1.4.0",
|
||||
"wrappy": "1.0.2"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
|
||||
"dev": true
|
||||
},
|
||||
"json3": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz",
|
||||
"integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash._baseassign": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz",
|
||||
"integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash._basecopy": "3.0.1",
|
||||
"lodash.keys": "3.1.2"
|
||||
}
|
||||
},
|
||||
"lodash._basecopy": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz",
|
||||
"integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash._basecreate": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz",
|
||||
"integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash._getnative": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz",
|
||||
"integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash._isiterateecall": {
|
||||
"version": "3.0.9",
|
||||
"resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz",
|
||||
"integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.create": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz",
|
||||
"integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash._baseassign": "3.2.0",
|
||||
"lodash._basecreate": "3.0.3",
|
||||
"lodash._isiterateecall": "3.0.9"
|
||||
}
|
||||
},
|
||||
"lodash.isarguments": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
|
||||
"integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.isarray": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz",
|
||||
"integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.keys": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz",
|
||||
"integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash._getnative": "3.9.1",
|
||||
"lodash.isarguments": "3.1.0",
|
||||
"lodash.isarray": "3.0.4"
|
||||
}
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "1.1.11"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
|
||||
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
|
||||
"dev": true
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
|
||||
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
}
|
||||
},
|
||||
"mocha": {
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.3.tgz",
|
||||
"integrity": "sha512-/6na001MJWEtYxHOV1WLfsmR4YIynkUEhBwzsb+fk2qmQ3iqsi258l/Q2MWHJMImAcNpZ8DEdYAK72NHoIQ9Eg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"browser-stdout": "1.3.0",
|
||||
"commander": "2.9.0",
|
||||
"debug": "2.6.8",
|
||||
"diff": "3.2.0",
|
||||
"escape-string-regexp": "1.0.5",
|
||||
"glob": "7.1.1",
|
||||
"growl": "1.9.2",
|
||||
"he": "1.1.1",
|
||||
"json3": "3.3.2",
|
||||
"lodash.create": "3.1.1",
|
||||
"mkdirp": "0.5.1",
|
||||
"supports-color": "3.1.2"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
|
||||
"dev": true
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"wrappy": "1.0.2"
|
||||
}
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
|
||||
"dev": true
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz",
|
||||
"integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "1.0.0"
|
||||
}
|
||||
},
|
||||
"wavedrom-cli": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/wavedrom-cli/-/wavedrom-cli-0.5.0.tgz",
|
||||
"integrity": "sha1-h+lb+naRpbaTmPJzL13QycKkiCE=",
|
||||
"dev": true
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@
|
||||
"author": "Steven Hugg",
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@types/jquery": "^3.3.4",
|
||||
"mocha": "^3.2.0",
|
||||
"mocha-phantomjs": "^4.1.0",
|
||||
"wavedrom-cli": "^0.5.0"
|
||||
@ -15,6 +16,7 @@
|
||||
"test": "tests"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "npm run test-node && npm run test-browser",
|
||||
"test-one": "mocha --recursive --timeout 20000",
|
||||
"test-node": "mocha --recursive --timeout 20000 test/cli",
|
||||
|
237
src/project.js
237
src/project.js
@ -1,237 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
function SourceFile(lines, text) {
|
||||
lines = lines || [];
|
||||
this.lines = lines;
|
||||
this.text = text;
|
||||
this.offset2line = {};
|
||||
this.line2offset = {};
|
||||
for (var info of lines) {
|
||||
if (info.offset >= 0) {
|
||||
this.offset2line[info.offset] = info.line;
|
||||
this.line2offset[info.line] = info.offset;
|
||||
}
|
||||
}
|
||||
this.findLineForOffset = function(PC) {
|
||||
if (this.offset2line) {
|
||||
for (var i=0; i<16; i++) {
|
||||
var line = this.offset2line[PC];
|
||||
if (line >= 0) {
|
||||
return line;
|
||||
}
|
||||
PC--;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
this.lineCount = function() { return lines.length; }
|
||||
}
|
||||
|
||||
function CodeProject(worker, platform_id, platform, store, mainpath) {
|
||||
var self = this;
|
||||
|
||||
var filedata = {};
|
||||
var listings;
|
||||
|
||||
self.callbackBuildResult = function(result) { };
|
||||
self.callbackBuildStatus = function(busy) { };
|
||||
|
||||
var pendingWorkerMessages = 0;
|
||||
|
||||
var tools_preloaded = {};
|
||||
function preloadWorker(path) {
|
||||
var tool = platform.getToolForFilename(path);
|
||||
if (tool && !tools_preloaded[tool]) {
|
||||
worker.postMessage({preload:tool, platform:platform_id});
|
||||
tools_preloaded[tool] = true;
|
||||
}
|
||||
}
|
||||
|
||||
self.setMainPath = function(path) {
|
||||
mainpath = path;
|
||||
}
|
||||
|
||||
// TODO: get local file as well as presets?
|
||||
self.loadFiles = function(paths, callback) {
|
||||
var result = [];
|
||||
function loadNext() {
|
||||
var path = paths.shift();
|
||||
if (!path) {
|
||||
callback(null, result); // TODO?
|
||||
} else {
|
||||
store.getItem(path, function(err, value) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else if (value) {
|
||||
result.push({
|
||||
path:path,
|
||||
filename:getFilenameForPath(path),
|
||||
data:value
|
||||
});
|
||||
filedata[path] = value;
|
||||
loadNext();
|
||||
} else {
|
||||
var webpath = "presets/" + platform_id + "/" + path;
|
||||
if (platform_id == 'vcs' && path.indexOf('.') <= 0)
|
||||
webpath += ".a"; // legacy stuff
|
||||
// TODO: cache files
|
||||
$.get( webpath, function( text ) {
|
||||
console.log("GET",webpath,text.length,'bytes');
|
||||
result.push({
|
||||
path:path,
|
||||
filename:getFilenameForPath(path),
|
||||
data:text
|
||||
});
|
||||
filedata[path] = text;
|
||||
loadNext();
|
||||
}, 'text')
|
||||
.fail(function() {
|
||||
callback("Could not load preset " + path);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
loadNext(); // load first file
|
||||
}
|
||||
|
||||
function parseFileDependencies(text) {
|
||||
var files = [];
|
||||
if (platform_id == 'verilog') {
|
||||
var re = /^(`include|[.]include)\s+"(.+?)"/gm;
|
||||
var m;
|
||||
while (m = re.exec(text)) {
|
||||
files.push(m[2]);
|
||||
}
|
||||
} else {
|
||||
var re = /^([;#]|[/][/][#])link\s+"(.+?)"/gm;
|
||||
var m;
|
||||
while (m = re.exec(text)) {
|
||||
files.push(m[2]);
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
function loadFileDependencies(text, callback) {
|
||||
var paths = parseFileDependencies(text);
|
||||
self.loadFiles(paths, callback);
|
||||
}
|
||||
|
||||
function okToSend() {
|
||||
return pendingWorkerMessages++ == 0;
|
||||
}
|
||||
|
||||
function updateFileInStore(path, text) {
|
||||
// protect against accidential whole-file deletion
|
||||
if (text.trim().length) {
|
||||
// TODO? (originalFileID != path || text != originalText)) {
|
||||
store.setItem(path, text);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: test duplicate files, local paths mixed with presets
|
||||
function buildWorkerMessage(depends) {
|
||||
preloadWorker(mainpath);
|
||||
var msg = {updates:[], buildsteps:[]};
|
||||
// TODO: add preproc directive for __MAINFILE__
|
||||
var mainfilename = getFilenameForPath(mainpath);
|
||||
var maintext = self.getFile(mainpath);
|
||||
msg.updates.push({path:mainfilename, data:maintext});
|
||||
msg.buildsteps.push({path:mainfilename, platform:platform_id, tool:platform.getToolForFilename(mainpath), mainfile:true});
|
||||
for (var i=0; i<depends.length; i++) {
|
||||
var dep = depends[i];
|
||||
if (dep.data) {
|
||||
preloadWorker(dep.filename);
|
||||
msg.updates.push({path:dep.filename, data:dep.data});
|
||||
msg.buildsteps.push({path:dep.filename, platform:platform_id, tool:platform.getToolForFilename(dep.path)});
|
||||
}
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
self.getFile = function(path) {
|
||||
return filedata[path];
|
||||
}
|
||||
|
||||
self.iterateFiles = function(callback) {
|
||||
for (var path in filedata) {
|
||||
callback(path, self.getFile(path));
|
||||
}
|
||||
}
|
||||
|
||||
self.sendBuild = function() {
|
||||
var text = self.getFile(mainpath);
|
||||
loadFileDependencies(text, function(err, depends) {
|
||||
if (err) {
|
||||
console.log(err); // TODO?
|
||||
}
|
||||
if (platform_id == 'verilog') {
|
||||
// TODO: should get rid of this msg format
|
||||
worker.postMessage({
|
||||
code:text,
|
||||
dependencies:depends,
|
||||
platform:platform_id,
|
||||
tool:platform.getToolForFilename(mainpath)
|
||||
});
|
||||
} else {
|
||||
var workermsg = buildWorkerMessage(depends);
|
||||
worker.postMessage(workermsg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
self.updateFile = function(path, text, isBinary) {
|
||||
updateFileInStore(path, text); // TODO: isBinary
|
||||
filedata[path] = text;
|
||||
if (okToSend()) {
|
||||
if (!mainpath) mainpath = path;
|
||||
self.callbackBuildStatus(true);
|
||||
self.sendBuild();
|
||||
}
|
||||
};
|
||||
|
||||
self.processBuildResult = function(data) {
|
||||
// TODO: link listings with source files
|
||||
listings = data.listings;
|
||||
if (listings) {
|
||||
for (var lstname in listings) {
|
||||
var lst = listings[lstname];
|
||||
if (lst.lines)
|
||||
lst.sourcefile = new SourceFile(lst.lines);
|
||||
if (lst.asmlines)
|
||||
lst.assemblyfile = new SourceFile(lst.asmlines, lst.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.getListings = function() {
|
||||
return listings;
|
||||
}
|
||||
|
||||
// returns first listing in format [prefix].lst (TODO: could be better)
|
||||
self.getListingForFile = function(path) {
|
||||
var fnprefix = getFilenamePrefix(getFilenameForPath(path));
|
||||
var listings = self.getListings();
|
||||
for (var lstfn in listings) {
|
||||
if (getFilenamePrefix(lstfn) == fnprefix) {
|
||||
return listings[lstfn];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
worker.onmessage = function(e) {
|
||||
if (pendingWorkerMessages > 1) {
|
||||
self.sendBuild();
|
||||
pendingWorkerMessages = 0;
|
||||
} else {
|
||||
pendingWorkerMessages = 0;
|
||||
}
|
||||
self.callbackBuildStatus(false);
|
||||
if (e.data && !e.data.unchanged) {
|
||||
self.processBuildResult(e.data);
|
||||
self.callbackBuildResult(e.data); // call with data when changed
|
||||
}
|
||||
};
|
||||
}
|
||||
|
301
src/project.ts
Normal file
301
src/project.ts
Normal file
@ -0,0 +1,301 @@
|
||||
"use strict";
|
||||
|
||||
type FileData = string | Uint8Array;
|
||||
|
||||
interface SourceLine {
|
||||
offset:number;
|
||||
line:number;
|
||||
}
|
||||
|
||||
interface Dependency {
|
||||
path:string,
|
||||
filename:string,
|
||||
data:FileData // TODO: or binary?
|
||||
}
|
||||
|
||||
interface WorkerFileUpdate { path:string, data:FileData };
|
||||
interface WorkerBuildStep { path:string, platform:string, tool:string, mainfile?:boolean };
|
||||
|
||||
interface WorkerMessage {
|
||||
updates:WorkerFileUpdate[],
|
||||
buildsteps:WorkerBuildStep[]
|
||||
}
|
||||
|
||||
interface WorkerError {
|
||||
line:number,
|
||||
msg:string,
|
||||
//TODO
|
||||
}
|
||||
|
||||
export class SourceFile {
|
||||
lines: SourceLine[];
|
||||
text: string;
|
||||
offset2line: {[offset:number]:number};
|
||||
line2offset: {[line:number]:number};
|
||||
|
||||
constructor(lines:SourceLine[], text?:string) {
|
||||
lines = lines || [];
|
||||
this.lines = lines;
|
||||
this.text = text;
|
||||
this.offset2line = {};
|
||||
this.line2offset = {};
|
||||
for (var info of lines) {
|
||||
if (info.offset >= 0) {
|
||||
this.offset2line[info.offset] = info.line;
|
||||
this.line2offset[info.line] = info.offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
findLineForOffset(PC:number):number {
|
||||
if (this.offset2line) {
|
||||
for (var i=0; i<16; i++) {
|
||||
var line = this.offset2line[PC];
|
||||
if (line >= 0) {
|
||||
return line;
|
||||
}
|
||||
PC--;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
lineCount():number { return this.lines.length; }
|
||||
}
|
||||
|
||||
interface CodeListing {
|
||||
lines:SourceLine[],
|
||||
asmlines:SourceLine[],
|
||||
text:string,
|
||||
sourcefile:SourceFile,
|
||||
assemblyfile:SourceFile
|
||||
}
|
||||
|
||||
type CodeListingMap = {[path:string]:CodeListing};
|
||||
|
||||
interface WorkerResult {
|
||||
output:Uint8Array, //TODO
|
||||
errors:WorkerError[],
|
||||
listings:CodeListingMap,
|
||||
}
|
||||
|
||||
type BuildResultCallback = (result:WorkerResult) => void;
|
||||
type BuildStatusCallback = (busy:boolean) => void;
|
||||
type LoadFilesCallback = (err:string, result?:Dependency[]) => void;
|
||||
type IterateFilesCallback = (path:string, data:FileData) => void;
|
||||
type GetRemoteCallback = any; // TODO (path:string, (text:string) => FileData) => void;
|
||||
|
||||
export class CodeProject {
|
||||
filedata : {[path:string]:FileData} = {};
|
||||
listings : CodeListingMap;
|
||||
mainpath : string;
|
||||
pendingWorkerMessages = 0;
|
||||
tools_preloaded = {};
|
||||
callbackBuildResult : BuildResultCallback;
|
||||
callbackBuildStatus : BuildStatusCallback;
|
||||
worker: any;
|
||||
platform_id : string;
|
||||
platform: any;
|
||||
store: any;
|
||||
callbackGetRemote : GetRemoteCallback;
|
||||
mainPath: string;
|
||||
|
||||
constructor(worker, platform_id:string, platform, store) {
|
||||
this.worker = worker;
|
||||
this.platform_id = platform_id;
|
||||
this.platform = platform;
|
||||
this.store = store;
|
||||
|
||||
worker.onmessage = (e) => {
|
||||
if (this.pendingWorkerMessages > 1) {
|
||||
this.sendBuild();
|
||||
this.pendingWorkerMessages = 0;
|
||||
} else {
|
||||
this.pendingWorkerMessages = 0;
|
||||
}
|
||||
if (this.callbackBuildStatus) this.callbackBuildStatus(false);
|
||||
if (e.data && !e.data.unchanged) {
|
||||
this.processBuildResult(e.data);
|
||||
if (this.callbackBuildResult) this.callbackBuildResult(e.data); // call with data when changed
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
preloadWorker(path:string) {
|
||||
var tool = this.platform.getToolForFilename(path);
|
||||
if (tool && !this.tools_preloaded[tool]) {
|
||||
this.worker.postMessage({preload:tool, platform:this.platform_id});
|
||||
this.tools_preloaded[tool] = true;
|
||||
}
|
||||
}
|
||||
|
||||
parseFileDependencies(text:string):string[] {
|
||||
var files = [];
|
||||
if (this.platform_id == 'verilog') {
|
||||
var re = /^(`include|[.]include)\s+"(.+?)"/gm;
|
||||
var m;
|
||||
while (m = re.exec(text)) {
|
||||
files.push(m[2]);
|
||||
}
|
||||
} else {
|
||||
var re = /^([;#]|[/][/][#])link\s+"(.+?)"/gm;
|
||||
var m;
|
||||
while (m = re.exec(text)) {
|
||||
files.push(m[2]);
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
loadFileDependencies(text:string, callback:LoadFilesCallback) {
|
||||
var paths = this.parseFileDependencies(text);
|
||||
this.loadFiles(paths, callback);
|
||||
}
|
||||
|
||||
okToSend():boolean {
|
||||
return this.pendingWorkerMessages++ == 0;
|
||||
}
|
||||
|
||||
updateFileInStore(path:string, text:FileData) {
|
||||
// protect against accidential whole-file deletion
|
||||
if ((<string>text).trim && (<string>text).trim().length) {
|
||||
// TODO? (originalFileID != path || text != originalText)) {
|
||||
this.store.setItem(path, text);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: test duplicate files, local paths mixed with presets
|
||||
buildWorkerMessage(depends:Dependency[]) {
|
||||
this.preloadWorker(this.mainpath);
|
||||
var msg = {updates:[], buildsteps:[]};
|
||||
// TODO: add preproc directive for __MAINFILE__
|
||||
var mainfilename = getFilenameForPath(this.mainpath);
|
||||
var maintext = this.getFile(this.mainpath);
|
||||
msg.updates.push({path:mainfilename, data:maintext});
|
||||
msg.buildsteps.push({path:mainfilename, platform:this.platform_id, tool:this.platform.getToolForFilename(this.mainpath), mainfile:true});
|
||||
for (var i=0; i<depends.length; i++) {
|
||||
var dep = depends[i];
|
||||
if (dep.data) {
|
||||
this.preloadWorker(dep.filename);
|
||||
msg.updates.push({path:dep.filename, data:dep.data});
|
||||
msg.buildsteps.push({path:dep.filename, platform:this.platform_id, tool:this.platform.getToolForFilename(dep.path)});
|
||||
}
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
// TODO: get local file as well as presets?
|
||||
loadFiles(paths:string[], callback:LoadFilesCallback) {
|
||||
var result = [];
|
||||
var loadNext = () => {
|
||||
var path = paths.shift();
|
||||
if (!path) {
|
||||
callback(null, result); // TODO?
|
||||
} else {
|
||||
this.store.getItem(path, (err, value) => {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else if (value) {
|
||||
result.push({
|
||||
path:path,
|
||||
filename:getFilenameForPath(path),
|
||||
data:value
|
||||
});
|
||||
this.filedata[path] = value;
|
||||
loadNext();
|
||||
} else {
|
||||
var webpath = "presets/" + this.platform_id + "/" + path;
|
||||
if (this.platform_id == 'vcs' && path.indexOf('.') <= 0)
|
||||
webpath += ".a"; // legacy stuff
|
||||
// TODO: cache files
|
||||
this.callbackGetRemote( webpath, (text:string) => {
|
||||
console.log("GET",webpath,text.length,'bytes');
|
||||
result.push({
|
||||
path:path,
|
||||
filename:getFilenameForPath(path),
|
||||
data:text
|
||||
});
|
||||
this.filedata[path] = text;
|
||||
loadNext();
|
||||
}, 'text')
|
||||
.fail(function() {
|
||||
callback("Could not load preset " + path);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
loadNext(); // load first file
|
||||
}
|
||||
|
||||
getFile(path:string):FileData {
|
||||
return this.filedata[path];
|
||||
}
|
||||
|
||||
iterateFiles(callback:IterateFilesCallback) {
|
||||
for (var path in this.filedata) {
|
||||
callback(path, this.getFile(path));
|
||||
}
|
||||
}
|
||||
|
||||
sendBuild() {
|
||||
var self = this;
|
||||
var maindata = this.getFile(this.mainpath);
|
||||
var text = typeof maindata === "string" ? maindata : '';
|
||||
this.loadFileDependencies(text, (err, depends) => {
|
||||
if (err) {
|
||||
console.log(err); // TODO?
|
||||
}
|
||||
if (this.platform_id == 'verilog') {
|
||||
// TODO: should get rid of this msg format
|
||||
this.worker.postMessage({
|
||||
code:text,
|
||||
dependencies:depends,
|
||||
platform:this.platform_id,
|
||||
tool:this.platform.getToolForFilename(this.mainpath)
|
||||
});
|
||||
} else {
|
||||
var workermsg = this.buildWorkerMessage(depends);
|
||||
this.worker.postMessage(workermsg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updateFile(path:string, text:FileData) {
|
||||
this.updateFileInStore(path, text); // TODO: isBinary
|
||||
this.filedata[path] = text;
|
||||
if (this.okToSend()) {
|
||||
if (!this.mainpath) this.mainpath = path;
|
||||
if (this.callbackBuildStatus) this.callbackBuildStatus(true);
|
||||
this.sendBuild();
|
||||
}
|
||||
};
|
||||
|
||||
processBuildResult(data:WorkerResult) {
|
||||
// TODO: link listings with source files
|
||||
this.listings = data.listings;
|
||||
if (this.listings) {
|
||||
for (var lstname in this.listings) {
|
||||
var lst = this.listings[lstname];
|
||||
if (lst.lines)
|
||||
lst.sourcefile = new SourceFile(lst.lines);
|
||||
if (lst.asmlines)
|
||||
lst.assemblyfile = new SourceFile(lst.asmlines, lst.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getListings() : CodeListingMap {
|
||||
return this.listings;
|
||||
}
|
||||
|
||||
// returns first listing in format [prefix].lst (TODO: could be better)
|
||||
getListingForFile(path) : CodeListing {
|
||||
var fnprefix = getFilenamePrefix(getFilenameForPath(path));
|
||||
var listings = this.getListings();
|
||||
for (var lstfn in listings) {
|
||||
if (getFilenamePrefix(lstfn) == fnprefix) {
|
||||
return listings[lstfn];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
623
src/ui.js
623
src/ui.js
@ -13,6 +13,9 @@ var toolbar = $("#controls_top");
|
||||
|
||||
var current_project; // current CodeProject object
|
||||
|
||||
var projectWindows; // window manager
|
||||
|
||||
|
||||
// TODO: codemirror multiplex support?
|
||||
var TOOL_TO_SOURCE_STYLE = {
|
||||
'dasm': '6502',
|
||||
@ -66,6 +69,8 @@ function setLastPreset(id) {
|
||||
|
||||
function initProject() {
|
||||
current_project = new CodeProject(newWorker(), platform_id, platform, store);
|
||||
projectWindows = new ProjectWindows($("#workspace")[0], current_project);
|
||||
current_project.callbackGetRemote = $.get;
|
||||
current_project.callbackBuildResult = function(result) {
|
||||
setCompileOutput(result);
|
||||
refreshWindowList();
|
||||
@ -82,622 +87,6 @@ function initProject() {
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: remove some calls of global functions
|
||||
function SourceEditor(path, mode) {
|
||||
var self = this;
|
||||
var editor;
|
||||
var dirtylisting = true;
|
||||
var sourcefile;
|
||||
var currentDebugLine;
|
||||
|
||||
self.createDiv = function(parent, text) {
|
||||
var div = document.createElement('div');
|
||||
div.setAttribute("class", "editor");
|
||||
parent.appendChild(div);
|
||||
newEditor(div);
|
||||
if (text)
|
||||
self.setText(text); // TODO: this calls setCode() and builds... it shouldn't
|
||||
return div;
|
||||
}
|
||||
|
||||
function newEditor(parent) {
|
||||
var isAsm = mode=='6502' || mode =='z80' || mode=='verilog' || mode=='gas'; // TODO
|
||||
editor = CodeMirror(parent, {
|
||||
theme: 'mbo',
|
||||
lineNumbers: true,
|
||||
matchBrackets: true,
|
||||
tabSize: 8,
|
||||
indentAuto: true,
|
||||
gutters: isAsm ? ["CodeMirror-linenumbers", "gutter-offset", "gutter-bytes", "gutter-clock", "gutter-info"]
|
||||
: ["CodeMirror-linenumbers", "gutter-offset", "gutter-info"],
|
||||
});
|
||||
var timer;
|
||||
editor.on('changes', function(ed, changeobj) {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(function() {
|
||||
current_project.updateFile(path, editor.getValue(), false);
|
||||
}, 200);
|
||||
});
|
||||
editor.on('cursorActivity', function(ed) {
|
||||
var start = editor.getCursor(true);
|
||||
var end = editor.getCursor(false);
|
||||
if (start.line == end.line && start.ch < end.ch) {
|
||||
var name = editor.getSelection();
|
||||
inspectVariable(editor, name);
|
||||
} else {
|
||||
inspectVariable(editor);
|
||||
}
|
||||
});
|
||||
//scrollProfileView(editor);
|
||||
editor.setOption("mode", mode);
|
||||
}
|
||||
|
||||
self.setText = function(text) {
|
||||
editor.setValue(text); // calls setCode()
|
||||
editor.clearHistory();
|
||||
}
|
||||
|
||||
self.getValue = function() {
|
||||
return editor.getValue();
|
||||
}
|
||||
|
||||
self.getPath = function() { return path; }
|
||||
|
||||
var lines2errmsg = [];
|
||||
self.addErrorMarker = function(line, msg) {
|
||||
var div = document.createElement("div");
|
||||
div.setAttribute("class", "tooltipbox tooltiperror");
|
||||
div.appendChild(document.createTextNode("\u24cd"));
|
||||
var tooltip = document.createElement("span");
|
||||
tooltip.setAttribute("class", "tooltiptext");
|
||||
if (lines2errmsg[line])
|
||||
msg = lines2errmsg[line] + "\n" + msg;
|
||||
tooltip.appendChild(document.createTextNode(msg));
|
||||
lines2errmsg[line] = msg;
|
||||
div.appendChild(tooltip);
|
||||
editor.setGutterMarker(line, "gutter-info", div);
|
||||
}
|
||||
|
||||
self.markErrors = function(errors) {
|
||||
// TODO: move cursor to error line if offscreen?
|
||||
self.clearErrors();
|
||||
var numLines = editor.lineCount();
|
||||
for (var info of errors) {
|
||||
// only mark errors with this filename, or without any filename
|
||||
if (!info.path || path.endsWith(info.path)) {
|
||||
var line = info.line-1;
|
||||
if (line < 0 || line >= numLines) line = 0;
|
||||
self.addErrorMarker(line, info.msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.clearErrors = function() {
|
||||
editor.clearGutter("gutter-info");
|
||||
refreshDebugState();
|
||||
dirtylisting = true;
|
||||
}
|
||||
|
||||
self.getSourceFile = function() { return sourcefile; }
|
||||
|
||||
// TODO: update gutter only when refreshing this window
|
||||
self.updateListing = function(_sourcefile) {
|
||||
sourcefile = _sourcefile;
|
||||
// update editor annotations
|
||||
editor.clearGutter("gutter-info");
|
||||
editor.clearGutter("gutter-bytes");
|
||||
editor.clearGutter("gutter-offset");
|
||||
editor.clearGutter("gutter-clock");
|
||||
var lstlines = sourcefile.lines || [];
|
||||
for (var info of lstlines) {
|
||||
if (info.offset >= 0) {
|
||||
var textel = document.createTextNode(hex(info.offset,4));
|
||||
editor.setGutterMarker(info.line-1, "gutter-offset", textel);
|
||||
}
|
||||
if (info.insns) {
|
||||
var insnstr = info.insns.length > 9 ? ("...") : info.insns;
|
||||
var textel = document.createTextNode(insnstr);
|
||||
editor.setGutterMarker(info.line-1, "gutter-bytes", textel);
|
||||
if (info.iscode) {
|
||||
var opcode = parseInt(info.insns.split()[0], 16);
|
||||
if (platform.getOpcodeMetadata) {
|
||||
var meta = platform.getOpcodeMetadata(opcode, info.offset);
|
||||
var clockstr = meta.minCycles+"";
|
||||
var textel = document.createTextNode(clockstr);
|
||||
editor.setGutterMarker(info.line-1, "gutter-clock", textel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.setGutterBytes = function(line, s) {
|
||||
var textel = document.createTextNode(s);
|
||||
editor.setGutterMarker(line-1, "gutter-bytes", textel);
|
||||
}
|
||||
|
||||
self.setCurrentLine = function(line) {
|
||||
function addCurrentMarker(line) {
|
||||
var div = document.createElement("div");
|
||||
div.style.color = '#66ffff';
|
||||
div.appendChild(document.createTextNode("\u25b6"));
|
||||
editor.setGutterMarker(line, "gutter-info", div);
|
||||
}
|
||||
self.clearCurrentLine();
|
||||
if (line>0) {
|
||||
addCurrentMarker(line-1);
|
||||
editor.setSelection({line:line,ch:0}, {line:line-1,ch:0}, {scroll:true});
|
||||
currentDebugLine = line;
|
||||
}
|
||||
}
|
||||
|
||||
self.clearCurrentLine = function() {
|
||||
if (currentDebugLine) {
|
||||
editor.clearGutter("gutter-info");
|
||||
editor.setSelection(editor.getCursor());
|
||||
currentDebugLine = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function refreshDebugState() {
|
||||
self.clearCurrentLine();
|
||||
var state = lastDebugState;
|
||||
if (state && state.c) {
|
||||
var PC = state.c.PC;
|
||||
var line = sourcefile.findLineForOffset(PC);
|
||||
if (line >= 0) {
|
||||
self.setCurrentLine(line);
|
||||
// TODO: switch to disasm?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function refreshListing() {
|
||||
if (!dirtylisting) return;
|
||||
dirtylisting = false;
|
||||
var lst = current_project.getListingForFile(path);
|
||||
if (lst && lst.sourcefile) {
|
||||
self.updateListing(lst.sourcefile); // updates sourcefile variable
|
||||
}
|
||||
}
|
||||
|
||||
self.refresh = function() {
|
||||
refreshListing();
|
||||
refreshDebugState();
|
||||
}
|
||||
|
||||
self.getLine = function(line) {
|
||||
return editor.getLine(line-1);
|
||||
}
|
||||
|
||||
self.getCurrentLine = function() {
|
||||
return editor.getCursor().line+1;
|
||||
}
|
||||
|
||||
self.getCursorPC = function() {
|
||||
var line = self.getCurrentLine();
|
||||
while (sourcefile && line >= 0) {
|
||||
var pc = sourcefile.line2offset[line];
|
||||
if (pc >= 0) return pc;
|
||||
line--;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// bitmap editor (TODO: refactor)
|
||||
|
||||
function handleWindowMessage(e) {
|
||||
//console.log("window message", e.data);
|
||||
if (e.data.bytes) {
|
||||
editor.replaceSelection(e.data.bytestr);
|
||||
}
|
||||
if (e.data.close) {
|
||||
$("#pixeditback").hide();
|
||||
}
|
||||
}
|
||||
|
||||
function openBitmapEditorWithParams(fmt, bytestr, palfmt, palstr) {
|
||||
$("#pixeditback").show();
|
||||
window.addEventListener("message", handleWindowMessage, false); // TODO: remove listener
|
||||
pixeditframe.contentWindow.postMessage({fmt:fmt, bytestr:bytestr, palfmt:palfmt, palstr:palstr}, '*');
|
||||
}
|
||||
|
||||
function lookBackwardsForJSONComment(line, req) {
|
||||
var re = /[/;][*;]([{].+[}])[*;][/;]/;
|
||||
while (--line >= 0) {
|
||||
var s = editor.getLine(line);
|
||||
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--;
|
||||
while (++line < editor.lineCount()) {
|
||||
var l = editor.getLine(line);
|
||||
var endsection;
|
||||
if (platform_id == 'verilog')
|
||||
endsection = l.indexOf('end') >= pos0;
|
||||
else
|
||||
endsection = l.indexOf(';') >= pos0;
|
||||
if (endsection) {
|
||||
var end = {line:line, ch:editor.getLine(line).length};
|
||||
return {obj:obj, start:start, end:end};
|
||||
}
|
||||
pos0 = 0;
|
||||
}
|
||||
line = line0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.openBitmapEditorAtCursor = function() {
|
||||
if ($("#pixeditback").is(":visible")) {
|
||||
$("#pixeditback").hide(250);
|
||||
return;
|
||||
}
|
||||
var line = editor.getCursor().line + 1;
|
||||
var data = lookBackwardsForJSONComment(self.getCurrentLine(), 'w');
|
||||
if (data && data.obj && data.obj.w>0 && data.obj.h>0) {
|
||||
var paldata = lookBackwardsForJSONComment(data.start.line-1, 'pal');
|
||||
var palbytestr;
|
||||
if (paldata) {
|
||||
palbytestr = editor.getRange(paldata.start, paldata.end);
|
||||
paldata = paldata.obj;
|
||||
}
|
||||
editor.setSelection(data.end, data.start);
|
||||
openBitmapEditorWithParams(data.obj, editor.getSelection(), paldata, palbytestr);
|
||||
} 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)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
function DisassemblerView() {
|
||||
var self = this;
|
||||
var disasmview;
|
||||
|
||||
self.getDisasmView = function() { return disasmview; }
|
||||
|
||||
self.createDiv = function(parent) {
|
||||
var div = document.createElement('div');
|
||||
div.setAttribute("class", "editor");
|
||||
parent.appendChild(div);
|
||||
newEditor(div);
|
||||
return div;
|
||||
}
|
||||
|
||||
function newEditor(parent) {
|
||||
disasmview = CodeMirror(parent, {
|
||||
mode: 'z80', // TODO: pick correct one
|
||||
theme: 'cobalt',
|
||||
tabSize: 8,
|
||||
readOnly: true,
|
||||
styleActiveLine: true
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: too many globals
|
||||
self.refresh = function() {
|
||||
var state = lastDebugState || platform.saveState();
|
||||
var pc = state.c ? state.c.PC : 0;
|
||||
var curline = 0;
|
||||
var selline = 0;
|
||||
// TODO: not perfect disassembler
|
||||
function disassemble(start, end) {
|
||||
if (start < 0) start = 0;
|
||||
if (end > 0xffff) end = 0xffff;
|
||||
// TODO: use pc2visits
|
||||
var a = start;
|
||||
var s = "";
|
||||
while (a < end) {
|
||||
var disasm = platform.disassemble(a, platform.readAddress);
|
||||
/* TODO: look thru all source files
|
||||
var srclinenum = sourcefile && sourcefile.offset2line[a];
|
||||
if (srclinenum) {
|
||||
var srcline = getActiveEditor().getLine(srclinenum);
|
||||
if (srcline && srcline.trim().length) {
|
||||
s += "; " + srclinenum + ":\t" + srcline + "\n";
|
||||
curline++;
|
||||
}
|
||||
}
|
||||
*/
|
||||
var bytes = "";
|
||||
for (var i=0; i<disasm.nbytes; i++)
|
||||
bytes += hex(platform.readAddress(a+i));
|
||||
while (bytes.length < 14)
|
||||
bytes += ' ';
|
||||
var dline = hex(parseInt(a)) + "\t" + bytes + "\t" + disasm.line + "\n";
|
||||
s += dline;
|
||||
if (a == pc) selline = curline;
|
||||
curline++;
|
||||
a += disasm.nbytes || 1;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
var text = disassemble(pc-96, pc) + disassemble(pc, pc+96);
|
||||
disasmview.setValue(text);
|
||||
disasmview.setCursor(selline, 0);
|
||||
jumpToLine(disasmview, selline);
|
||||
}
|
||||
|
||||
self.getCursorPC = function() {
|
||||
var line = disasmview.getCursor().line;
|
||||
if (line >= 0) {
|
||||
var toks = disasmview.getLine(line).split(/\s+/);
|
||||
if (toks && toks.length >= 1) {
|
||||
var pc = parseInt(toks[0], 16);
|
||||
if (pc >= 0) return pc;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
function ListingView(assemblyfile) {
|
||||
var self = this;
|
||||
this.__proto__ = new DisassemblerView();
|
||||
|
||||
self.refresh = function() {
|
||||
var state = lastDebugState || platform.saveState();
|
||||
var pc = state.c ? state.c.PC : 0;
|
||||
var asmtext = assemblyfile.text;
|
||||
var disasmview = self.getDisasmView();
|
||||
if (platform_id == 'base_z80') { // TODO
|
||||
asmtext = asmtext.replace(/[ ]+\d+\s+;.+\n/g, '');
|
||||
asmtext = asmtext.replace(/[ ]+\d+\s+.area .+\n/g, '');
|
||||
}
|
||||
disasmview.setValue(asmtext);
|
||||
var findPC = platform.getDebugCallback() ? pc : -1;
|
||||
if (findPC >= 0) {
|
||||
var lineno = assemblyfile.findLineForOffset(findPC);
|
||||
if (lineno) {
|
||||
// set cursor while debugging
|
||||
if (platform.getDebugCallback())
|
||||
disasmview.setCursor(lineno-1, 0);
|
||||
jumpToLine(disasmview, lineno-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
function MemoryView() {
|
||||
var self = this;
|
||||
var memorylist;
|
||||
var dumplines;
|
||||
var div;
|
||||
|
||||
// TODO?
|
||||
function getVisibleEditorLineHeight() {
|
||||
return $(".CodeMirror-line:visible").first().height();
|
||||
}
|
||||
|
||||
self.createDiv = function(parent) {
|
||||
div = document.createElement('div');
|
||||
div.setAttribute("class", "memdump");
|
||||
parent.appendChild(div);
|
||||
showMemoryWindow(div);
|
||||
return div;
|
||||
}
|
||||
|
||||
function showMemoryWindow(parent) {
|
||||
memorylist = new VirtualList({
|
||||
w:$("#workspace").width(),
|
||||
h:$("#workspace").height(),
|
||||
itemHeight: getVisibleEditorLineHeight(),
|
||||
totalRows: 0x1000,
|
||||
generatorFn: function(row) {
|
||||
var s = getMemoryLineAt(row);
|
||||
var div = document.createElement("div");
|
||||
if (dumplines) {
|
||||
var dlr = dumplines[row];
|
||||
if (dlr) div.classList.add('seg_' + getMemorySegment(dumplines[row].a));
|
||||
}
|
||||
div.appendChild(document.createTextNode(s));
|
||||
return div;
|
||||
}
|
||||
});
|
||||
$(parent).append(memorylist.container);
|
||||
self.tick();
|
||||
if (compparams && dumplines)
|
||||
memorylist.scrollToItem(findMemoryWindowLine(compparams.data_start));
|
||||
}
|
||||
|
||||
self.tick = function() {
|
||||
if (memorylist) {
|
||||
$(div).find('[data-index]').each(function(i,e) {
|
||||
var div = $(e);
|
||||
var row = div.attr('data-index');
|
||||
var oldtext = div.text();
|
||||
var newtext = getMemoryLineAt(row);
|
||||
if (oldtext != newtext)
|
||||
div.text(newtext);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getMemoryLineAt(row) {
|
||||
var offset = row * 16;
|
||||
var n1 = 0;
|
||||
var n2 = 16;
|
||||
var sym;
|
||||
if (getDumpLines()) {
|
||||
var dl = dumplines[row];
|
||||
if (dl) {
|
||||
offset = dl.a & 0xfff0;
|
||||
n1 = dl.a - offset;
|
||||
n2 = n1 + dl.l;
|
||||
sym = dl.s;
|
||||
} else {
|
||||
return '.';
|
||||
}
|
||||
}
|
||||
var s = hex(offset,4) + ' ';
|
||||
for (var i=0; i<n1; i++) s += ' ';
|
||||
if (n1 > 8) s += ' ';
|
||||
for (var i=n1; i<n2; i++) {
|
||||
var read = platform.readAddress(offset+i);
|
||||
if (i==8) s += ' ';
|
||||
s += ' ' + (read>=0?hex(read,2):'??');
|
||||
}
|
||||
for (var i=n2; i<16; i++) s += ' ';
|
||||
if (sym) s += ' ' + sym;
|
||||
return s;
|
||||
}
|
||||
|
||||
function getDumpLineAt(line) {
|
||||
var d = dumplines[line];
|
||||
if (d) {
|
||||
return d.a + " " + d.s;
|
||||
}
|
||||
}
|
||||
|
||||
var IGNORE_SYMS = {s__INITIALIZER:true, /* s__GSINIT:true, */ _color_prom:true};
|
||||
|
||||
// TODO: addr2symbol for ca65; and make it work without symbols
|
||||
function getDumpLines() {
|
||||
if (!dumplines && addr2symbol) {
|
||||
dumplines = [];
|
||||
var ofs = 0;
|
||||
var sym;
|
||||
for (var nextofs in addr2symbol) {
|
||||
nextofs |= 0;
|
||||
var nextsym = addr2symbol[nextofs];
|
||||
if (sym) {
|
||||
if (IGNORE_SYMS[sym]) {
|
||||
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);
|
||||
dumplines.push({a:ofs, l:ofs2-ofs, s:sym});
|
||||
ofs = ofs2;
|
||||
}
|
||||
}
|
||||
}
|
||||
sym = nextsym;
|
||||
}
|
||||
}
|
||||
return dumplines;
|
||||
}
|
||||
|
||||
function getMemorySegment(a) {
|
||||
if (!compparams) return 'unknown';
|
||||
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)
|
||||
return 'code';
|
||||
else
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
function findMemoryWindowLine(a) {
|
||||
for (var i=0; i<dumplines.length; i++)
|
||||
if (dumplines[i].a >= a)
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
/////
|
||||
|
||||
function ProjectWindows(containerdiv) {
|
||||
var self = this;
|
||||
var id2window = {};
|
||||
var id2createfn = {};
|
||||
var id2div = {};
|
||||
var activewnd;
|
||||
var activediv;
|
||||
var lasterrors;
|
||||
// TODO: delete windows ever?
|
||||
|
||||
this.setCreateFunc = function(id, createfn) {
|
||||
id2createfn[id] = createfn;
|
||||
}
|
||||
|
||||
this.createOrShow = function(id) {
|
||||
var wnd = id2window[id];
|
||||
if (!wnd) {
|
||||
wnd = id2window[id] = id2createfn[id](id);
|
||||
}
|
||||
var div = id2div[id];
|
||||
if (!div) {
|
||||
div = id2div[id] = wnd.createDiv(containerdiv, current_project.getFile(id));
|
||||
}
|
||||
if (activewnd != wnd) {
|
||||
if (activediv)
|
||||
$(activediv).hide();
|
||||
activediv = div;
|
||||
activewnd = wnd;
|
||||
$(div).show();
|
||||
this.refresh();
|
||||
this.refreshErrors();
|
||||
}
|
||||
return wnd;
|
||||
}
|
||||
|
||||
this.put = function(id, window, category) {
|
||||
id2window[id] = window;
|
||||
if (!categories[category])
|
||||
categories[category] = [];
|
||||
// TODO: remove/replace window
|
||||
if (!(id in categories[category]))
|
||||
categories[category].push(id);
|
||||
}
|
||||
|
||||
this.filesForCategory = function(id) {
|
||||
return categories[id] || [];
|
||||
}
|
||||
|
||||
this.refresh = function() {
|
||||
if (activewnd && activewnd.refresh)
|
||||
activewnd.refresh();
|
||||
}
|
||||
|
||||
this.tick = function() {
|
||||
if (activewnd && activewnd.tick)
|
||||
activewnd.tick();
|
||||
}
|
||||
|
||||
this.setErrors = function(errors) {
|
||||
lasterrors = errors;
|
||||
this.refreshErrors();
|
||||
}
|
||||
|
||||
this.refreshErrors = function() {
|
||||
if (activewnd && activewnd.markErrors) {
|
||||
if (lasterrors && lasterrors.length)
|
||||
activewnd.markErrors(lasterrors);
|
||||
else
|
||||
activewnd.clearErrors();
|
||||
}
|
||||
}
|
||||
|
||||
this.getActive = function() { return activewnd; }
|
||||
|
||||
this.getCurrentText = function() {
|
||||
if (activewnd && activewnd.getValue)
|
||||
return activewnd.getValue();
|
||||
else
|
||||
alert("Please switch to an editor window.");
|
||||
}
|
||||
};
|
||||
|
||||
var projectWindows = new ProjectWindows($("#workspace")[0]);
|
||||
|
||||
function refreshWindowList() {
|
||||
var ul = $("#windowMenuList").empty();
|
||||
var separate = false;
|
||||
@ -781,7 +170,7 @@ function loadProject(preset_id) {
|
||||
// set current file ID
|
||||
main_file_id = preset_id;
|
||||
setLastPreset(preset_id);
|
||||
current_project.setMainPath(preset_id);
|
||||
current_project.mainPath = preset_id;
|
||||
// load files from storage or web URLs
|
||||
current_project.loadFiles([preset_id], function(err, result) {
|
||||
if (err) {
|
||||
|
@ -1,23 +1,23 @@
|
||||
"use strict";
|
||||
|
||||
function lpad(s,n) { while(s.length<n) s=" "+s; return s; }
|
||||
function lpad(s:string, n:number):string { while(s.length<n) s=" "+s; return s; }
|
||||
|
||||
function byte2signed(b) {
|
||||
function byte2signed(b:number):number {
|
||||
b &= 0xff;
|
||||
return (b < 0x80) ? b : -(256-b);
|
||||
}
|
||||
|
||||
function getFilenameForPath(s) {
|
||||
function getFilenameForPath(s:string):string {
|
||||
var toks = s.split('/');
|
||||
return toks[toks.length-1];
|
||||
}
|
||||
|
||||
function getFilenamePrefix(s) {
|
||||
function getFilenamePrefix(s:string):string {
|
||||
var pos = s.lastIndexOf('.');
|
||||
return (pos > 0) ? s.substr(0, pos) : s;
|
||||
}
|
||||
|
||||
function hex(v, nd) {
|
||||
function hex(v:number, nd?:number) {
|
||||
try {
|
||||
if (!nd) nd = 2;
|
||||
var s = v.toString(16).toUpperCase();
|
||||
@ -29,7 +29,7 @@ function hex(v, nd) {
|
||||
}
|
||||
}
|
||||
|
||||
function arrayCompare(a,b) {
|
||||
function arrayCompare(a:any[], b:any[]):boolean {
|
||||
if (a == null && b == null) return true;
|
||||
if (a == null) return false;
|
||||
if (b == null) return false;
|
||||
@ -40,7 +40,7 @@ function arrayCompare(a,b) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function invertMap(m) {
|
||||
function invertMap(m:{}):{} {
|
||||
var r = {};
|
||||
if (m) {
|
||||
for (var k in m) r[m[k]] = k;
|
||||
@ -48,7 +48,7 @@ function invertMap(m) {
|
||||
return r;
|
||||
}
|
||||
|
||||
function highlightDifferences(s1, s2) {
|
||||
function highlightDifferences(s1:string, s2:string):string {
|
||||
var split1 = s1.split(/(\S+\s+)/).filter(function(n) {return n});
|
||||
var split2 = s2.split(/(\S+\s+)/).filter(function(n) {return n});
|
||||
var i = 0;
|
||||
@ -104,7 +104,7 @@ function lzgmini() {
|
||||
|
||||
// Decode LZG coded data. The function returns the size of the decoded data.
|
||||
// Use any of the get* methods to retrieve the decoded data.
|
||||
this.decode = function(data) {
|
||||
this.decode = function(data:number[]):number[] {
|
||||
// Start by clearing the decompressed array in this object
|
||||
outdata = null;
|
||||
|
||||
@ -112,7 +112,7 @@ function lzgmini() {
|
||||
if ((data.length < LZG_HEADER_SIZE) || (data[0] != 76) ||
|
||||
(data[1] != 90) || (data[2] != 71))
|
||||
{
|
||||
return 0;
|
||||
return null;
|
||||
}
|
||||
|
||||
// Calculate & check the checksum
|
||||
@ -122,7 +122,7 @@ function lzgmini() {
|
||||
(data[14] & 0xff);
|
||||
if (calcChecksum(data) != checksum)
|
||||
{
|
||||
return 0;
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check which method to use
|
||||
@ -223,13 +223,13 @@ function lzgmini() {
|
||||
}
|
||||
|
||||
// Get the decoded byte array
|
||||
this.getByteArray = function()
|
||||
this.getByteArray = function():number[]
|
||||
{
|
||||
return outdata;
|
||||
}
|
||||
|
||||
// Get the decoded string from a Latin 1 (or ASCII) encoded array
|
||||
this.getStringLatin1 = function()
|
||||
this.getStringLatin1 = function():string
|
||||
{
|
||||
var str = "";
|
||||
if (outdata != null)
|
||||
@ -245,7 +245,7 @@ function lzgmini() {
|
||||
}
|
||||
|
||||
// Get the decoded string from an UTF-8 encoded array
|
||||
this.getStringUTF8 = function()
|
||||
this.getStringUTF8 = function():string
|
||||
{
|
||||
var str = "";
|
||||
if (outdata != null)
|
547
src/views.ts
Normal file
547
src/views.ts
Normal file
@ -0,0 +1,547 @@
|
||||
"use strict";
|
||||
|
||||
// TODO: move to different namespace
|
||||
var CodeMirror;
|
||||
var platform;
|
||||
var platform_id : string;
|
||||
var compparams;
|
||||
var addr2symbol : {[addr:number]:string};
|
||||
var current_project;
|
||||
var VirtualList;
|
||||
var lastDebugState;
|
||||
var pixeditframe;
|
||||
|
||||
// TODO: functions
|
||||
var inspectVariable;
|
||||
var jumpToLine;
|
||||
|
||||
// TODO: remove some calls of global functions
|
||||
function SourceEditor(path, mode) {
|
||||
var self = this;
|
||||
var editor;
|
||||
var dirtylisting = true;
|
||||
var sourcefile;
|
||||
var currentDebugLine;
|
||||
|
||||
self.createDiv = function(parent, text) {
|
||||
var div = document.createElement('div');
|
||||
div.setAttribute("class", "editor");
|
||||
parent.appendChild(div);
|
||||
newEditor(div);
|
||||
if (text)
|
||||
self.setText(text); // TODO: this calls setCode() and builds... it shouldn't
|
||||
return div;
|
||||
}
|
||||
|
||||
function newEditor(parent) {
|
||||
var isAsm = mode=='6502' || mode =='z80' || mode=='verilog' || mode=='gas'; // TODO
|
||||
editor = CodeMirror(parent, {
|
||||
theme: 'mbo',
|
||||
lineNumbers: true,
|
||||
matchBrackets: true,
|
||||
tabSize: 8,
|
||||
indentAuto: true,
|
||||
gutters: isAsm ? ["CodeMirror-linenumbers", "gutter-offset", "gutter-bytes", "gutter-clock", "gutter-info"]
|
||||
: ["CodeMirror-linenumbers", "gutter-offset", "gutter-info"],
|
||||
});
|
||||
var timer;
|
||||
editor.on('changes', function(ed, changeobj) {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(function() {
|
||||
current_project.updateFile(path, editor.getValue(), false);
|
||||
}, 200);
|
||||
});
|
||||
editor.on('cursorActivity', function(ed) {
|
||||
var start = editor.getCursor(true);
|
||||
var end = editor.getCursor(false);
|
||||
if (start.line == end.line && start.ch < end.ch) {
|
||||
var name = editor.getSelection();
|
||||
inspectVariable(editor, name);
|
||||
} else {
|
||||
inspectVariable(editor);
|
||||
}
|
||||
});
|
||||
//scrollProfileView(editor);
|
||||
editor.setOption("mode", mode);
|
||||
}
|
||||
|
||||
self.setText = function(text) {
|
||||
editor.setValue(text); // calls setCode()
|
||||
editor.clearHistory();
|
||||
}
|
||||
|
||||
self.getValue = function() {
|
||||
return editor.getValue();
|
||||
}
|
||||
|
||||
self.getPath = function() { return path; }
|
||||
|
||||
var lines2errmsg = [];
|
||||
self.addErrorMarker = function(line, msg) {
|
||||
var div = document.createElement("div");
|
||||
div.setAttribute("class", "tooltipbox tooltiperror");
|
||||
div.appendChild(document.createTextNode("\u24cd"));
|
||||
var tooltip = document.createElement("span");
|
||||
tooltip.setAttribute("class", "tooltiptext");
|
||||
if (lines2errmsg[line])
|
||||
msg = lines2errmsg[line] + "\n" + msg;
|
||||
tooltip.appendChild(document.createTextNode(msg));
|
||||
lines2errmsg[line] = msg;
|
||||
div.appendChild(tooltip);
|
||||
editor.setGutterMarker(line, "gutter-info", div);
|
||||
}
|
||||
|
||||
self.markErrors = function(errors) {
|
||||
// TODO: move cursor to error line if offscreen?
|
||||
self.clearErrors();
|
||||
var numLines = editor.lineCount();
|
||||
for (var info of errors) {
|
||||
// only mark errors with this filename, or without any filename
|
||||
if (!info.path || path.endsWith(info.path)) {
|
||||
var line = info.line-1;
|
||||
if (line < 0 || line >= numLines) line = 0;
|
||||
self.addErrorMarker(line, info.msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.clearErrors = function() {
|
||||
editor.clearGutter("gutter-info");
|
||||
refreshDebugState();
|
||||
dirtylisting = true;
|
||||
}
|
||||
|
||||
self.getSourceFile = function() { return sourcefile; }
|
||||
|
||||
// TODO: update gutter only when refreshing this window
|
||||
self.updateListing = function(_sourcefile) {
|
||||
sourcefile = _sourcefile;
|
||||
// update editor annotations
|
||||
editor.clearGutter("gutter-info");
|
||||
editor.clearGutter("gutter-bytes");
|
||||
editor.clearGutter("gutter-offset");
|
||||
editor.clearGutter("gutter-clock");
|
||||
var lstlines = sourcefile.lines || [];
|
||||
for (var info of lstlines) {
|
||||
if (info.offset >= 0) {
|
||||
var textel = document.createTextNode(hex(info.offset,4));
|
||||
editor.setGutterMarker(info.line-1, "gutter-offset", textel);
|
||||
}
|
||||
if (info.insns) {
|
||||
var insnstr = info.insns.length > 9 ? ("...") : info.insns;
|
||||
var textel = document.createTextNode(insnstr);
|
||||
editor.setGutterMarker(info.line-1, "gutter-bytes", textel);
|
||||
if (info.iscode) {
|
||||
var opcode = parseInt(info.insns.split()[0], 16);
|
||||
if (platform.getOpcodeMetadata) {
|
||||
var meta = platform.getOpcodeMetadata(opcode, info.offset);
|
||||
var clockstr = meta.minCycles+"";
|
||||
var textel = document.createTextNode(clockstr);
|
||||
editor.setGutterMarker(info.line-1, "gutter-clock", textel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.setGutterBytes = function(line, s) {
|
||||
var textel = document.createTextNode(s);
|
||||
editor.setGutterMarker(line-1, "gutter-bytes", textel);
|
||||
}
|
||||
|
||||
self.setCurrentLine = function(line) {
|
||||
function addCurrentMarker(line) {
|
||||
var div = document.createElement("div");
|
||||
div.style.color = '#66ffff';
|
||||
div.appendChild(document.createTextNode("\u25b6"));
|
||||
editor.setGutterMarker(line, "gutter-info", div);
|
||||
}
|
||||
self.clearCurrentLine();
|
||||
if (line>0) {
|
||||
addCurrentMarker(line-1);
|
||||
editor.setSelection({line:line,ch:0}, {line:line-1,ch:0}, {scroll:true});
|
||||
currentDebugLine = line;
|
||||
}
|
||||
}
|
||||
|
||||
self.clearCurrentLine = function() {
|
||||
if (currentDebugLine) {
|
||||
editor.clearGutter("gutter-info");
|
||||
editor.setSelection(editor.getCursor());
|
||||
currentDebugLine = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function refreshDebugState() {
|
||||
self.clearCurrentLine();
|
||||
var state = lastDebugState;
|
||||
if (state && state.c) {
|
||||
var PC = state.c.PC;
|
||||
var line = sourcefile.findLineForOffset(PC);
|
||||
if (line >= 0) {
|
||||
self.setCurrentLine(line);
|
||||
// TODO: switch to disasm?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function refreshListing() {
|
||||
if (!dirtylisting) return;
|
||||
dirtylisting = false;
|
||||
var lst = current_project.getListingForFile(path);
|
||||
if (lst && lst.sourcefile) {
|
||||
self.updateListing(lst.sourcefile); // updates sourcefile variable
|
||||
}
|
||||
}
|
||||
|
||||
self.refresh = function() {
|
||||
refreshListing();
|
||||
refreshDebugState();
|
||||
}
|
||||
|
||||
self.getLine = function(line) {
|
||||
return editor.getLine(line-1);
|
||||
}
|
||||
|
||||
self.getCurrentLine = function() {
|
||||
return editor.getCursor().line+1;
|
||||
}
|
||||
|
||||
self.getCursorPC = function() {
|
||||
var line = self.getCurrentLine();
|
||||
while (sourcefile && line >= 0) {
|
||||
var pc = sourcefile.line2offset[line];
|
||||
if (pc >= 0) return pc;
|
||||
line--;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// bitmap editor (TODO: refactor)
|
||||
|
||||
function handleWindowMessage(e) {
|
||||
//console.log("window message", e.data);
|
||||
if (e.data.bytes) {
|
||||
editor.replaceSelection(e.data.bytestr);
|
||||
}
|
||||
if (e.data.close) {
|
||||
$("#pixeditback").hide();
|
||||
}
|
||||
}
|
||||
|
||||
function openBitmapEditorWithParams(fmt, bytestr, palfmt, palstr) {
|
||||
$("#pixeditback").show();
|
||||
window.addEventListener("message", handleWindowMessage, false); // TODO: remove listener
|
||||
pixeditframe.contentWindow.postMessage({fmt:fmt, bytestr:bytestr, palfmt:palfmt, palstr:palstr}, '*');
|
||||
}
|
||||
|
||||
function lookBackwardsForJSONComment(line, req) {
|
||||
var re = /[/;][*;]([{].+[}])[*;][/;]/;
|
||||
while (--line >= 0) {
|
||||
var s = editor.getLine(line);
|
||||
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--;
|
||||
while (++line < editor.lineCount()) {
|
||||
var l = editor.getLine(line);
|
||||
var endsection;
|
||||
if (platform_id == 'verilog')
|
||||
endsection = l.indexOf('end') >= pos0;
|
||||
else
|
||||
endsection = l.indexOf(';') >= pos0;
|
||||
if (endsection) {
|
||||
var end = {line:line, ch:editor.getLine(line).length};
|
||||
return {obj:obj, start:start, end:end};
|
||||
}
|
||||
pos0 = 0;
|
||||
}
|
||||
line = line0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.openBitmapEditorAtCursor = function() {
|
||||
if ($("#pixeditback").is(":visible")) {
|
||||
$("#pixeditback").hide(250);
|
||||
return;
|
||||
}
|
||||
var line = editor.getCursor().line + 1;
|
||||
var data = lookBackwardsForJSONComment(self.getCurrentLine(), 'w');
|
||||
if (data && data.obj && data.obj.w>0 && data.obj.h>0) {
|
||||
var paldata = lookBackwardsForJSONComment(data.start.line-1, 'pal');
|
||||
var palbytestr;
|
||||
if (paldata) {
|
||||
palbytestr = editor.getRange(paldata.start, paldata.end);
|
||||
paldata = paldata.obj;
|
||||
}
|
||||
editor.setSelection(data.end, data.start);
|
||||
openBitmapEditorWithParams(data.obj, editor.getSelection(), paldata, palbytestr);
|
||||
} 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)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
function DisassemblerView() {
|
||||
var self = this;
|
||||
var disasmview;
|
||||
|
||||
self.getDisasmView = function() { return disasmview; }
|
||||
|
||||
self.createDiv = function(parent) {
|
||||
var div = document.createElement('div');
|
||||
div.setAttribute("class", "editor");
|
||||
parent.appendChild(div);
|
||||
newEditor(div);
|
||||
return div;
|
||||
}
|
||||
|
||||
function newEditor(parent) {
|
||||
disasmview = CodeMirror(parent, {
|
||||
mode: 'z80', // TODO: pick correct one
|
||||
theme: 'cobalt',
|
||||
tabSize: 8,
|
||||
readOnly: true,
|
||||
styleActiveLine: true
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: too many globals
|
||||
self.refresh = function() {
|
||||
var state = lastDebugState || platform.saveState();
|
||||
var pc = state.c ? state.c.PC : 0;
|
||||
var curline = 0;
|
||||
var selline = 0;
|
||||
// TODO: not perfect disassembler
|
||||
function disassemble(start, end) {
|
||||
if (start < 0) start = 0;
|
||||
if (end > 0xffff) end = 0xffff;
|
||||
// TODO: use pc2visits
|
||||
var a = start;
|
||||
var s = "";
|
||||
while (a < end) {
|
||||
var disasm = platform.disassemble(a, platform.readAddress);
|
||||
/* TODO: look thru all source files
|
||||
var srclinenum = sourcefile && sourcefile.offset2line[a];
|
||||
if (srclinenum) {
|
||||
var srcline = getActiveEditor().getLine(srclinenum);
|
||||
if (srcline && srcline.trim().length) {
|
||||
s += "; " + srclinenum + ":\t" + srcline + "\n";
|
||||
curline++;
|
||||
}
|
||||
}
|
||||
*/
|
||||
var bytes = "";
|
||||
for (var i=0; i<disasm.nbytes; i++)
|
||||
bytes += hex(platform.readAddress(a+i));
|
||||
while (bytes.length < 14)
|
||||
bytes += ' ';
|
||||
var dline = hex(parseInt(a)) + "\t" + bytes + "\t" + disasm.line + "\n";
|
||||
s += dline;
|
||||
if (a == pc) selline = curline;
|
||||
curline++;
|
||||
a += disasm.nbytes || 1;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
var text = disassemble(pc-96, pc) + disassemble(pc, pc+96);
|
||||
disasmview.setValue(text);
|
||||
disasmview.setCursor(selline, 0);
|
||||
jumpToLine(disasmview, selline);
|
||||
}
|
||||
|
||||
self.getCursorPC = function() {
|
||||
var line = disasmview.getCursor().line;
|
||||
if (line >= 0) {
|
||||
var toks = disasmview.getLine(line).split(/\s+/);
|
||||
if (toks && toks.length >= 1) {
|
||||
var pc = parseInt(toks[0], 16);
|
||||
if (pc >= 0) return pc;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
function ListingView(assemblyfile) {
|
||||
var self = this;
|
||||
this.__proto__ = new DisassemblerView();
|
||||
|
||||
self.refresh = function() {
|
||||
var state = lastDebugState || platform.saveState();
|
||||
var pc = state.c ? state.c.PC : 0;
|
||||
var asmtext = assemblyfile.text;
|
||||
var disasmview = self.getDisasmView();
|
||||
if (platform_id == 'base_z80') { // TODO
|
||||
asmtext = asmtext.replace(/[ ]+\d+\s+;.+\n/g, '');
|
||||
asmtext = asmtext.replace(/[ ]+\d+\s+.area .+\n/g, '');
|
||||
}
|
||||
disasmview.setValue(asmtext);
|
||||
var findPC = platform.getDebugCallback() ? pc : -1;
|
||||
if (findPC >= 0) {
|
||||
var lineno = assemblyfile.findLineForOffset(findPC);
|
||||
if (lineno) {
|
||||
// set cursor while debugging
|
||||
if (platform.getDebugCallback())
|
||||
disasmview.setCursor(lineno-1, 0);
|
||||
jumpToLine(disasmview, lineno-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
function MemoryView() {
|
||||
var self = this;
|
||||
var memorylist;
|
||||
var dumplines;
|
||||
var div;
|
||||
|
||||
// TODO?
|
||||
function getVisibleEditorLineHeight() {
|
||||
return $(".CodeMirror-line:visible").first().height();
|
||||
}
|
||||
|
||||
self.createDiv = function(parent) {
|
||||
div = document.createElement('div');
|
||||
div.setAttribute("class", "memdump");
|
||||
parent.appendChild(div);
|
||||
showMemoryWindow(div);
|
||||
return div;
|
||||
}
|
||||
|
||||
function showMemoryWindow(parent) {
|
||||
memorylist = new VirtualList({
|
||||
w:$("#workspace").width(),
|
||||
h:$("#workspace").height(),
|
||||
itemHeight: getVisibleEditorLineHeight(),
|
||||
totalRows: 0x1000,
|
||||
generatorFn: function(row) {
|
||||
var s = getMemoryLineAt(row);
|
||||
var div = document.createElement("div");
|
||||
if (dumplines) {
|
||||
var dlr = dumplines[row];
|
||||
if (dlr) div.classList.add('seg_' + getMemorySegment(dumplines[row].a));
|
||||
}
|
||||
div.appendChild(document.createTextNode(s));
|
||||
return div;
|
||||
}
|
||||
});
|
||||
$(parent).append(memorylist.container);
|
||||
self.tick();
|
||||
if (compparams && dumplines)
|
||||
memorylist.scrollToItem(findMemoryWindowLine(compparams.data_start));
|
||||
}
|
||||
|
||||
self.tick = function() {
|
||||
if (memorylist) {
|
||||
$(div).find('[data-index]').each(function(i,e) {
|
||||
var div = $(e);
|
||||
var row = div.attr('data-index');
|
||||
var oldtext = div.text();
|
||||
var newtext = getMemoryLineAt(row);
|
||||
if (oldtext != newtext)
|
||||
div.text(newtext);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getMemoryLineAt(row) {
|
||||
var offset = row * 16;
|
||||
var n1 = 0;
|
||||
var n2 = 16;
|
||||
var sym;
|
||||
if (getDumpLines()) {
|
||||
var dl = dumplines[row];
|
||||
if (dl) {
|
||||
offset = dl.a & 0xfff0;
|
||||
n1 = dl.a - offset;
|
||||
n2 = n1 + dl.l;
|
||||
sym = dl.s;
|
||||
} else {
|
||||
return '.';
|
||||
}
|
||||
}
|
||||
var s = hex(offset,4) + ' ';
|
||||
for (var i=0; i<n1; i++) s += ' ';
|
||||
if (n1 > 8) s += ' ';
|
||||
for (var i=n1; i<n2; i++) {
|
||||
var read = platform.readAddress(offset+i);
|
||||
if (i==8) s += ' ';
|
||||
s += ' ' + (read>=0?hex(read,2):'??');
|
||||
}
|
||||
for (var i=n2; i<16; i++) s += ' ';
|
||||
if (sym) s += ' ' + sym;
|
||||
return s;
|
||||
}
|
||||
|
||||
function getDumpLineAt(line) {
|
||||
var d = dumplines[line];
|
||||
if (d) {
|
||||
return d.a + " " + d.s;
|
||||
}
|
||||
}
|
||||
|
||||
var IGNORE_SYMS = {s__INITIALIZER:true, /* s__GSINIT:true, */ _color_prom:true};
|
||||
|
||||
// TODO: addr2symbol for ca65; and make it work without symbols
|
||||
function getDumpLines() {
|
||||
if (!dumplines && addr2symbol) {
|
||||
dumplines = [];
|
||||
var ofs = 0;
|
||||
var sym;
|
||||
for (const _nextofs of Object.keys(addr2symbol)) {
|
||||
var nextofs = parseInt(_nextofs); // convert from string (stupid JS)
|
||||
var nextsym = addr2symbol[nextofs];
|
||||
if (sym) {
|
||||
if (IGNORE_SYMS[sym]) {
|
||||
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);
|
||||
dumplines.push({a:ofs, l:ofs2-ofs, s:sym});
|
||||
ofs = ofs2;
|
||||
}
|
||||
}
|
||||
}
|
||||
sym = nextsym;
|
||||
}
|
||||
}
|
||||
return dumplines;
|
||||
}
|
||||
|
||||
function getMemorySegment(a) {
|
||||
if (!compparams) return 'unknown';
|
||||
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)
|
||||
return 'code';
|
||||
else
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
function findMemoryWindowLine(a) {
|
||||
for (var i=0; i<dumplines.length; i++)
|
||||
if (dumplines[i].a >= a)
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
78
src/windows.ts
Normal file
78
src/windows.ts
Normal file
@ -0,0 +1,78 @@
|
||||
"use strict";
|
||||
|
||||
import $ = require("jquery");
|
||||
import { CodeProject } from "./project";
|
||||
|
||||
function ProjectWindows(containerdiv:HTMLElement, project:CodeProject) {
|
||||
var self = this;
|
||||
var id2window = {};
|
||||
var id2createfn = {};
|
||||
var id2div = {};
|
||||
var activewnd;
|
||||
var activediv;
|
||||
var lasterrors;
|
||||
// TODO: delete windows ever?
|
||||
|
||||
this.setCreateFunc = function(id, createfn) {
|
||||
id2createfn[id] = createfn;
|
||||
}
|
||||
|
||||
this.createOrShow = function(id) {
|
||||
var wnd = id2window[id];
|
||||
if (!wnd) {
|
||||
wnd = id2window[id] = id2createfn[id](id);
|
||||
}
|
||||
var div = id2div[id];
|
||||
if (!div) {
|
||||
div = id2div[id] = wnd.createDiv(containerdiv, project.getFile(id));
|
||||
}
|
||||
if (activewnd != wnd) {
|
||||
if (activediv)
|
||||
$(activediv).hide();
|
||||
activediv = div;
|
||||
activewnd = wnd;
|
||||
$(div).show();
|
||||
this.refresh();
|
||||
this.refreshErrors();
|
||||
}
|
||||
return wnd;
|
||||
}
|
||||
|
||||
this.put = function(id, window) {
|
||||
id2window[id] = window;
|
||||
}
|
||||
|
||||
this.refresh = function() {
|
||||
if (activewnd && activewnd.refresh)
|
||||
activewnd.refresh();
|
||||
}
|
||||
|
||||
this.tick = function() {
|
||||
if (activewnd && activewnd.tick)
|
||||
activewnd.tick();
|
||||
}
|
||||
|
||||
this.setErrors = function(errors) {
|
||||
lasterrors = errors;
|
||||
this.refreshErrors();
|
||||
}
|
||||
|
||||
this.refreshErrors = function() {
|
||||
if (activewnd && activewnd.markErrors) {
|
||||
if (lasterrors && lasterrors.length)
|
||||
activewnd.markErrors(lasterrors);
|
||||
else
|
||||
activewnd.clearErrors();
|
||||
}
|
||||
}
|
||||
|
||||
this.getActive = function() { return activewnd; }
|
||||
|
||||
this.getCurrentText = function() {
|
||||
if (activewnd && activewnd.getValue)
|
||||
return activewnd.getValue();
|
||||
else
|
||||
alert("Please switch to an editor window.");
|
||||
}
|
||||
};
|
||||
|
@ -7,7 +7,7 @@ var includeInThisContext = function(path) {
|
||||
vm.runInThisContext(code, path);
|
||||
};
|
||||
|
||||
includeInThisContext("src/util.js");
|
||||
includeInThisContext("gen/util.js");
|
||||
includeInThisContext("src/pixed/pixeleditor.js");
|
||||
|
||||
describe('Pixel editor', function() {
|
||||
|
@ -45,9 +45,9 @@ global.localStorage = {
|
||||
};
|
||||
|
||||
includeInThisContext("localForage/dist/localforage.js");
|
||||
includeInThisContext("src/util.js");
|
||||
includeInThisContext("gen/util.js");
|
||||
includeInThisContext("src/store.js");
|
||||
includeInThisContext("src/project.js");
|
||||
var prj = require("../../gen/project.js");
|
||||
|
||||
var test_platform_id = "_TEST";
|
||||
|
||||
@ -74,7 +74,7 @@ describe('Store', function() {
|
||||
var store = createNewPersistentStore(test_platform_id, function() {
|
||||
var worker = {};
|
||||
var platform = {};
|
||||
var project = new CodeProject(worker, test_platform_id, platform, store);
|
||||
var project = new prj.CodeProject(worker, test_platform_id, platform, store);
|
||||
project.loadFiles(['test'], function(err, result) {
|
||||
assert.equal(null, err);
|
||||
assert.deepEqual([ { path: 'test', filename: 'test', data: 'a' } ], result);
|
||||
@ -106,7 +106,7 @@ describe('Store', function() {
|
||||
var platform = {
|
||||
getToolForFilename: function(fn) { return 'dasm'; },
|
||||
};
|
||||
var project = new CodeProject(worker, test_platform_id, platform, store);
|
||||
var project = new prj.CodeProject(worker, test_platform_id, platform, store);
|
||||
project.callbackBuildStatus = function(b) { msgs.push(b) };
|
||||
project.updateFile('test.a', ' lda #0');
|
||||
project.updateFile('test.a', ' lda #1'); // don't send twice (yet)
|
||||
@ -129,7 +129,7 @@ describe('Store', function() {
|
||||
};
|
||||
var platform = {
|
||||
};
|
||||
var project = new CodeProject(worker, test_platform_id, platform, store);
|
||||
var project = new prj.CodeProject(worker, test_platform_id, platform, store);
|
||||
project.callbackBuildStatus = function(b) { msgs.push(b) };
|
||||
var buildresult = {
|
||||
listings: {
|
||||
@ -143,8 +143,8 @@ describe('Store', function() {
|
||||
var lst = buildresult.listings.test;
|
||||
console.log(lst);
|
||||
assert.equal(3, lst.sourcefile.findLineForOffset(61440+15));
|
||||
assert.equal(0, lst.sourcefile.findLineForOffset(61440+16));
|
||||
assert.equal(0, lst.sourcefile.findLineForOffset(61440-1));
|
||||
assert.equal(null, lst.sourcefile.findLineForOffset(61440+16));
|
||||
assert.equal(null, lst.sourcefile.findLineForOffset(61440-1));
|
||||
assert.equal(1, lst.sourcefile.lineCount());
|
||||
done();
|
||||
});
|
||||
|
15
tsconfig.json
Normal file
15
tsconfig.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "./gen",
|
||||
"allowJs": true,
|
||||
"sourceMap": true,
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"es2016",
|
||||
"dom"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./src/*.ts"
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue
Block a user