');
- var addCategoryLink = (cat:string) => {
- var catlink = $('
'+cat+'');
+ var addCategoryLink = (cat: string) => {
+ var catlink = $('
' + cat + '');
if (cat == debugCategory)
catlink.addClass('selected');
catlink.click((e) => {
@@ -945,9 +947,9 @@ function showDebugInfo(state?) {
}
}
-function setDebugButtonState(btnid:string, btnstate:string) {
+function setDebugButtonState(btnid: string, btnstate: string) {
$("#debug_bar, #run_bar").find("button").removeClass("btn_active").removeClass("btn_stopped");
- $("#dbg_"+btnid).addClass("btn_"+btnstate);
+ $("#dbg_" + btnid).addClass("btn_" + btnstate);
}
function isPlatformReady() {
@@ -966,7 +968,7 @@ function openRelevantListing(state: EmuState) {
// if we clicked on a specific tool, don't switch windows
if (lastViewClicked && lastViewClicked.startsWith('#')) return;
// don't switch windows for specific debug commands
- if (['toline','restart','tovsync','stepover'].includes(lastDebugCommand)) return;
+ if (['toline', 'restart', 'tovsync', 'stepover'].includes(lastDebugCommand)) return;
// has to support disassembly, at least
if (!platform.disassemble) return;
// search through listings
@@ -987,7 +989,7 @@ function openRelevantListing(state: EmuState) {
let srcline1 = file && file.findLineForOffset(pc, PC_LINE_LOOKAHEAD);
if (srcline1) {
// try to find the next line and bound the PC
- let srcline2 = file.lines[srcline1.line+1];
+ let srcline2 = file.lines[srcline1.line + 1];
if (!srcline2 || pc < srcline2.offset) {
let score = pc - srcline1.offset;
if (score < bestscore) {
@@ -1012,18 +1014,18 @@ function uiDebugCallback(state: EmuState) {
debugTickPaused = true;
}
-function setupDebugCallback(btnid? : DebugCommandType) {
+function setupDebugCallback(btnid?: DebugCommandType) {
if (platform.setupDebug) {
- platform.setupDebug((state:EmuState, msg:string) => {
+ platform.setupDebug((state: EmuState, msg: string) => {
uiDebugCallback(state);
- setDebugButtonState(btnid||"pause", "stopped");
- msg && showErrorAlert([{msg:"STOPPED: " + msg, line:0}], true);
+ setDebugButtonState(btnid || "pause", "stopped");
+ msg && showErrorAlert([{ msg: "STOPPED: " + msg, line: 0 }], true);
});
lastDebugCommand = btnid;
}
}
-export function setupBreakpoint(btnid? : DebugCommandType) {
+export function setupBreakpoint(btnid?: DebugCommandType) {
if (!checkRunReady()) return;
_disableRecording();
setupDebugCallback(btnid);
@@ -1056,8 +1058,24 @@ function _resume() {
function resume() {
if (!checkRunReady()) return;
+
+ // If the active editor has breakpoints, resume with them
+ var wnd = projectWindows.getActive();
+ if (wnd instanceof SourceEditor) {
+ var bpPCs = wnd.getBreakpointPCs();
+ if (bpPCs.length > 0) {
+ if (!platform.isRunning()) {
+ projectWindows.refresh(false);
+ }
+ runToPC(bpPCs);
+ userPaused = false;
+ lastViewClicked = null;
+ return;
+ }
+ }
+
clearBreakpoint();
- if (! platform.isRunning() ) {
+ if (!platform.isRunning()) {
projectWindows.refresh(false);
}
_resume();
@@ -1083,20 +1101,25 @@ function singleFrameStep() {
platform.runToVsync();
}
-function getEditorPC() : number {
+function getEditorPC(): number {
var wnd = projectWindows.getActive();
return wnd && wnd.getCursorPC && wnd.getCursorPC();
}
-export function runToPC(pc: number) {
- if (!checkRunReady() || !(pc >= 0)) return;
+export function runToPC(pc: number[]) {
+ if (!checkRunReady()) return;
+ if (pc.length === 0 || pc.some(p => !(p >= 0))) return;
+ lastDebugState = null;
+ hideDebugInfo();
+ projectWindows.refresh(false);
setupBreakpoint("toline");
- console.log("Run to", pc.toString(16));
+ console.log("runToPC", pc.map(p => p.toString(16)).join(" "));
if (platform.runToPC) {
platform.runToPC(pc);
} else {
+ const pcSet = new Set(pc);
platform.runEval((c) => {
- return c.PC == pc;
+ return pcSet.has(c.PC);
});
}
}
@@ -1108,7 +1131,7 @@ function restartAtCursor() {
}
function runToCursor() {
- runToPC(getEditorPC());
+ runToPC([getEditorPC()]);
}
function runUntilReturn() {
@@ -1166,13 +1189,13 @@ function _breakExpression() {
$("#debugExprExamples").text(getDebugExprExamples());
modal.modal('show');
btn.off('click').on('click', () => {
- var exprs = $("#debugExprInput").val()+"";
+ var exprs = $("#debugExprInput").val() + "";
modal.modal('hide');
breakExpression(exprs);
});
}
-function getDebugExprExamples() : string {
+function getDebugExprExamples(): string {
var state = platform.saveState && platform.saveState();
var cpu = state.c;
console.log(cpu, state);
@@ -1186,7 +1209,7 @@ function getDebugExprExamples() : string {
return s;
}
-function breakExpression(exprs : string) {
+function breakExpression(exprs: string) {
var fn = new Function('c', 'return (' + exprs + ');').bind(platform);
setupBreakpoint();
platform.runEval(fn as DebugEvalCondition);
@@ -1204,28 +1227,28 @@ function updateDebugWindows() {
setTimeout(updateDebugWindows, 100);
}
-export function setFrameRateUI(fps:number) {
+export function setFrameRateUI(fps: number) {
platform.setFrameRate(fps);
if (fps > 0.01)
$("#fps_label").text(fps.toFixed(2));
else
- $("#fps_label").text("1/"+Math.round(1/fps));
+ $("#fps_label").text("1/" + Math.round(1 / fps));
}
function _slowerFrameRate() {
var fps = platform.getFrameRate();
- fps = fps/2;
+ fps = fps / 2;
if (fps > 0.00001) setFrameRateUI(fps);
}
function _fasterFrameRate() {
var fps = platform.getFrameRate();
- fps = Math.min(60, fps*2);
+ fps = Math.min(60, fps * 2);
setFrameRateUI(fps);
}
function _slowestFrameRate() {
- setFrameRateUI(60/65536);
+ setFrameRateUI(60 / 65536);
}
function _fastestFrameRate() {
@@ -1279,9 +1302,9 @@ function addFileToProject(type, ext, linefn) {
var wnd = projectWindows.getActive();
if (wnd && wnd.insertText) {
bootbox.prompt({
- title:"Add "+DOMPurify.sanitize(type)+" File to Project",
- value:"filename"+DOMPurify.sanitize(ext),
- callback:(filename:string) => {
+ title: "Add " + DOMPurify.sanitize(type) + " File to Project",
+ value: "filename" + DOMPurify.sanitize(ext),
+ callback: (filename: string) => {
if (filename && filename.trim().length > 0) {
if (!checkEnteredFilename(filename)) return;
var path = filename;
@@ -1308,19 +1331,19 @@ function _addIncludeFile() {
var tool = platform.getToolForFilename(fn);
// TODO: more tools? make this a function of the platform / tool provider
if (fn.endsWith(".c") || tool == 'sdcc' || tool == 'cc65' || tool == 'cmoc' || tool == 'smlrc')
- addFileToProject("Header", ".h", (s) => { return '#include "'+s+'"' });
+ addFileToProject("Header", ".h", (s) => { return '#include "' + s + '"' });
else if (tool == 'dasm' || tool == 'zmac')
- addFileToProject("Include", ".inc", (s) => { return '\tinclude "'+s+'"' });
+ addFileToProject("Include", ".inc", (s) => { return '\tinclude "' + s + '"' });
else if (tool == 'ca65' || tool == 'sdasz80' || tool == 'vasm' || tool == 'armips')
- addFileToProject("Include", ".inc", (s) => { return '\t.include "'+s+'"' });
+ addFileToProject("Include", ".inc", (s) => { return '\t.include "' + s + '"' });
else if (tool == 'verilator')
- addFileToProject("Verilog", ".v", (s) => { return '`include "'+s+'"' });
+ addFileToProject("Verilog", ".v", (s) => { return '`include "' + s + '"' });
else if (tool == 'wiz')
- addFileToProject("Include", ".wiz", (s) => { return 'import "'+s+'";' });
+ addFileToProject("Include", ".wiz", (s) => { return 'import "' + s + '";' });
else if (tool == 'ecs')
- addFileToProject("Include", ".ecs", (s) => { return 'import "'+s+'"' });
+ addFileToProject("Include", ".ecs", (s) => { return 'import "' + s + '"' });
else if (tool == 'acme')
- addFileToProject("Include", ".acme", (s) => { return '!src "'+s+'"' });
+ addFileToProject("Include", ".acme", (s) => { return '!src "' + s + '"' });
else
alertError("Can't add include file to this project type (" + tool + ")");
}
@@ -1329,9 +1352,9 @@ function _addLinkFile() {
var fn = getCurrentMainFilename();
var tool = platform.getToolForFilename(fn);
if (fn.endsWith(".c") || tool == 'sdcc' || tool == 'cc65' || tool == 'cmoc' || tool == 'smlrc')
- addFileToProject("Linked C (or .s)", ".c", (s) => { return '//#link "'+s+'"' });
+ addFileToProject("Linked C (or .s)", ".c", (s) => { return '//#link "' + s + '"' });
else if (fn.endsWith("asm") || fn.endsWith(".s") || tool == 'ca65' || tool == 'lwasm')
- addFileToProject("Linked ASM", ".inc", (s) => { return ';#link "'+s+'"' });
+ addFileToProject("Linked ASM", ".inc", (s) => { return ';#link "' + s + '"' });
else
alertError("Can't add linked file to this project type (" + tool + ")");
}
@@ -1339,40 +1362,40 @@ function _addLinkFile() {
function setupDebugControls() {
// create toolbar buttons
uitoolbar = new Toolbar($("#toolbar")[0], null);
- uitoolbar.grp.prop('id','run_bar');
- uitoolbar.add('ctrl+alt+r', 'Reset', 'glyphicon-refresh', resetAndRun).prop('id','dbg_reset');
- uitoolbar.add('ctrl+alt+,', 'Pause', 'glyphicon-pause', pause).prop('id','dbg_pause');
- uitoolbar.add('ctrl+alt+.', 'Resume', 'glyphicon-play', resume).prop('id','dbg_go');
+ uitoolbar.grp.prop('id', 'run_bar');
+ uitoolbar.add('ctrl+alt+r', 'Reset', 'glyphicon-refresh', resetAndRun).prop('id', 'dbg_reset');
+ uitoolbar.add('ctrl+alt+,', 'Pause', 'glyphicon-pause', pause).prop('id', 'dbg_pause');
+ uitoolbar.add('ctrl+alt+.', 'Resume', 'glyphicon-play', resume).prop('id', 'dbg_go');
if (platform.restartAtPC) {
- uitoolbar.add('ctrl+alt+/', 'Restart at Cursor', 'glyphicon-play-circle', restartAtCursor).prop('id','dbg_restartatline');
+ uitoolbar.add('ctrl+alt+/', 'Restart at Cursor', 'glyphicon-play-circle', restartAtCursor).prop('id', 'dbg_restartatline');
}
uitoolbar.newGroup();
- uitoolbar.grp.prop('id','debug_bar');
+ uitoolbar.grp.prop('id', 'debug_bar');
if (platform.runEval) {
- uitoolbar.add('ctrl+alt+e', 'Reset and Debug', 'glyphicon-fast-backward', resetAndDebug).prop('id','dbg_restart');
+ uitoolbar.add('ctrl+alt+e', 'Reset and Debug', 'glyphicon-fast-backward', resetAndDebug).prop('id', 'dbg_restart');
}
if (platform.stepBack) {
- uitoolbar.add('ctrl+alt+b', 'Step Backwards', 'glyphicon-step-backward', runStepBackwards).prop('id','dbg_stepback');
+ uitoolbar.add('ctrl+alt+b', 'Step Backwards', 'glyphicon-step-backward', runStepBackwards).prop('id', 'dbg_stepback');
}
if (platform.step) {
- uitoolbar.add('ctrl+alt+s', 'Single Step', 'glyphicon-step-forward', singleStep).prop('id','dbg_step');
+ uitoolbar.add('ctrl+alt+s', 'Single Step', 'glyphicon-step-forward', singleStep).prop('id', 'dbg_step');
}
if (platform.stepOver) {
- uitoolbar.add('ctrl+alt+t', 'Step Over', 'glyphicon-hand-right', stepOver).prop('id','dbg_stepover');
+ uitoolbar.add('ctrl+alt+t', 'Step Over', 'glyphicon-hand-right', stepOver).prop('id', 'dbg_stepover');
}
if (platform.runUntilReturn) {
- uitoolbar.add('ctrl+alt+o', 'Step Out of Subroutine', 'glyphicon-hand-up', runUntilReturn).prop('id','dbg_stepout');
+ uitoolbar.add('ctrl+alt+o', 'Step Out of Subroutine', 'glyphicon-hand-up', runUntilReturn).prop('id', 'dbg_stepout');
}
if (platform.runToVsync) {
- uitoolbar.add('ctrl+alt+n', 'Next Frame/Interrupt', 'glyphicon-forward', singleFrameStep).prop('id','dbg_tovsync');
+ uitoolbar.add('ctrl+alt+n', 'Next Frame/Interrupt', 'glyphicon-forward', singleFrameStep).prop('id', 'dbg_tovsync');
}
if ((platform.runEval || platform.runToPC) && !platform_id.startsWith('verilog')) {
- uitoolbar.add('ctrl+alt+l', 'Run To Line', 'glyphicon-save', runToCursor).prop('id','dbg_toline');
+ uitoolbar.add('ctrl+alt+l', 'Run To Line', 'glyphicon-save', runToCursor).prop('id', 'dbg_toline');
}
uitoolbar.newGroup();
- uitoolbar.grp.prop('id','xtra_bar');
+ uitoolbar.grp.prop('id', 'xtra_bar');
// add menu clicks
- $(".dropdown-menu").collapse({toggle: false});
+ $(".dropdown-menu").collapse({ toggle: false });
$("#item_new_file").click(_createNewFile);
$("#item_upload_file").click(_uploadNewFile);
$("#item_open_directory").click(_openLocalDirectory);
@@ -1425,7 +1448,7 @@ function setupDebugControls() {
}
// help menu items
if (platform.showHelp) {
- let {li,a} = newDropdownListItem('help__'+platform_id, platform_name+' Help');
+ let { li, a } = newDropdownListItem('help__' + platform_id, platform_name + ' Help');
$("#help_menu").append(li);
$(a).click(() => window.open(platform.showHelp(), '_8bws_help'));
}
@@ -1433,69 +1456,69 @@ function setupDebugControls() {
let tool = platform.getToolForFilename(getCurrentMainFilename());
let toolhelpurl = TOOL_TO_HELPURL[tool];
if (toolhelpurl) {
- let {li,a} = newDropdownListItem('help__'+tool, tool+' Help');
+ let { li, a } = newDropdownListItem('help__' + tool, tool + ' Help');
$("#help_menu").append(li);
$(a).click(() => window.open(toolhelpurl, '_8bws_help'));
}
}
function setupReplaySlider() {
- var replayslider = $("#replayslider");
- var clockslider = $("#clockslider");
- var replayframeno = $("#replay_frame");
- var clockno = $("#replay_clock");
- if (!platform.advanceFrameClock) $("#clockdiv").hide(); // TODO: put this test in recorder?
- var updateFrameNo = () => {
- replayframeno.text(stateRecorder.lastSeekFrame+"");
- clockno.text(stateRecorder.lastSeekStep+"");
- };
- var sliderChanged = (e) => {
- _pause();
- var frame : number = parseInt(replayslider.val().toString());
- var step : number = parseInt(clockslider.val().toString());
- if (stateRecorder.loadFrame(frame, step) >= 0) {
- clockslider.attr('min', 0);
- clockslider.attr('max', stateRecorder.lastStepCount);
- updateFrameNo();
- uiDebugCallback(platform.saveState());
- }
- };
- var setFrameTo = (frame:number) => {
- _pause();
- if (stateRecorder.loadFrame(frame) >= 0) {
- replayslider.val(frame);
- updateFrameNo();
- uiDebugCallback(platform.saveState());
- }
- };
- var setClockTo = (clock:number) => {
- _pause();
- var frame : number = parseInt(replayslider.val().toString());
- if (stateRecorder.loadFrame(frame, clock) >= 0) {
- clockslider.val(clock);
- updateFrameNo();
- uiDebugCallback(platform.saveState());
- }
- };
- stateRecorder.callbackStateChanged = () => {
- replayslider.attr('min', 0);
- replayslider.attr('max', stateRecorder.numFrames());
- replayslider.val(stateRecorder.currentFrame());
- clockslider.val(stateRecorder.currentStep());
+ var replayslider = $("#replayslider");
+ var clockslider = $("#clockslider");
+ var replayframeno = $("#replay_frame");
+ var clockno = $("#replay_clock");
+ if (!platform.advanceFrameClock) $("#clockdiv").hide(); // TODO: put this test in recorder?
+ var updateFrameNo = () => {
+ replayframeno.text(stateRecorder.lastSeekFrame + "");
+ clockno.text(stateRecorder.lastSeekStep + "");
+ };
+ var sliderChanged = (e) => {
+ _pause();
+ var frame: number = parseInt(replayslider.val().toString());
+ var step: number = parseInt(clockslider.val().toString());
+ if (stateRecorder.loadFrame(frame, step) >= 0) {
+ clockslider.attr('min', 0);
+ clockslider.attr('max', stateRecorder.lastStepCount);
+ updateFrameNo();
+ uiDebugCallback(platform.saveState());
+ }
+ };
+ var setFrameTo = (frame: number) => {
+ _pause();
+ if (stateRecorder.loadFrame(frame) >= 0) {
+ replayslider.val(frame);
+ updateFrameNo();
+ uiDebugCallback(platform.saveState());
+ }
+ };
+ var setClockTo = (clock: number) => {
+ _pause();
+ var frame: number = parseInt(replayslider.val().toString());
+ if (stateRecorder.loadFrame(frame, clock) >= 0) {
+ clockslider.val(clock);
updateFrameNo();
- showDebugInfo(platform.saveState());
- };
- replayslider.on('input', sliderChanged);
- clockslider.on('input', sliderChanged);
- //replayslider.on('change', sliderChanged);
- $("#replay_min").click(() => { setFrameTo(1) });
- $("#replay_max").click(() => { setFrameTo(stateRecorder.numFrames()); });
- $("#replay_back").click(() => { setFrameTo(parseInt(replayslider.val().toString()) - 1); });
- $("#replay_fwd").click(() => { setFrameTo(parseInt(replayslider.val().toString()) + 1); });
- $("#clock_back").click(() => { setClockTo(parseInt(clockslider.val().toString()) - 1); });
- $("#clock_fwd").click(() => { setClockTo(parseInt(clockslider.val().toString()) + 1); });
- $("#replay_bar").show();
- uitoolbar.add('ctrl+alt+0', 'Start/Stop Replay Recording', 'glyphicon-record', _toggleRecording).prop('id','dbg_record');
+ uiDebugCallback(platform.saveState());
+ }
+ };
+ stateRecorder.callbackStateChanged = () => {
+ replayslider.attr('min', 0);
+ replayslider.attr('max', stateRecorder.numFrames());
+ replayslider.val(stateRecorder.currentFrame());
+ clockslider.val(stateRecorder.currentStep());
+ updateFrameNo();
+ showDebugInfo(platform.saveState());
+ };
+ replayslider.on('input', sliderChanged);
+ clockslider.on('input', sliderChanged);
+ //replayslider.on('change', sliderChanged);
+ $("#replay_min").click(() => { setFrameTo(1) });
+ $("#replay_max").click(() => { setFrameTo(stateRecorder.numFrames()); });
+ $("#replay_back").click(() => { setFrameTo(parseInt(replayslider.val().toString()) - 1); });
+ $("#replay_fwd").click(() => { setFrameTo(parseInt(replayslider.val().toString()) + 1); });
+ $("#clock_back").click(() => { setClockTo(parseInt(clockslider.val().toString()) - 1); });
+ $("#clock_fwd").click(() => { setClockTo(parseInt(clockslider.val().toString()) + 1); });
+ $("#replay_bar").show();
+ uitoolbar.add('ctrl+alt+0', 'Start/Stop Replay Recording', 'glyphicon-record', _toggleRecording).prop('id', 'dbg_record');
}
@@ -1524,46 +1547,46 @@ async function showWelcomeMessage() {
await loadScript('lib/bootstrap-tourist.js');
var is_vcs = platform_id.startsWith('vcs');
var steps = [
- {
- element: "#platformsMenuButton",
- placement: 'right',
- title: "Platform Selector",
- content: "You're currently on the \"
" + platform_id + "\" platform. You can choose a different one from the menu."
- },
- {
- element: "#preset_select",
- title: "Project Selector",
- content: "You can choose different code examples, create your own files, or import projects from GitHub."
- },
- {
- element: "#workspace",
- title: "Code Editor",
- content: is_vcs ? "Type your 6502 assembly code into the editor, and it'll be assembled in real-time."
- : "Type your source code into the editor, and it'll be compiled in real-time."
- },
- {
- element: "#emulator",
- placement: 'left',
- title: "Emulator",
- content: "We'll load your compiled code into the emulator whenever you make changes."
- },
- {
- element: "#debug_bar",
- placement: 'bottom',
- title: "Debug Tools",
- content: "Use these buttons to set breakpoints, single step through code, pause/resume, and use debugging tools."
- },
- {
- element: "#dropdownMenuButton",
- title: "Main Menu",
- content: "Click the menu to create new files, download your code, or share your work with others."
- },
- {
- element: "#sidebar",
- title: "Sidebar",
- content: "Pull right to expose the sidebar. It lets you switch between source files, view assembly listings, and use other tools like Disassembler, Memory Browser, and Asset Editor."
- }
- ];
+ {
+ element: "#platformsMenuButton",
+ placement: 'right',
+ title: "Platform Selector",
+ content: "You're currently on the \"
" + platform_id + "\" platform. You can choose a different one from the menu."
+ },
+ {
+ element: "#preset_select",
+ title: "Project Selector",
+ content: "You can choose different code examples, create your own files, or import projects from GitHub."
+ },
+ {
+ element: "#workspace",
+ title: "Code Editor",
+ content: is_vcs ? "Type your 6502 assembly code into the editor, and it'll be assembled in real-time."
+ : "Type your source code into the editor, and it'll be compiled in real-time."
+ },
+ {
+ element: "#emulator",
+ placement: 'left',
+ title: "Emulator",
+ content: "We'll load your compiled code into the emulator whenever you make changes."
+ },
+ {
+ element: "#debug_bar",
+ placement: 'bottom',
+ title: "Debug Tools",
+ content: "Use these buttons to set breakpoints, single step through code, pause/resume, and use debugging tools."
+ },
+ {
+ element: "#dropdownMenuButton",
+ title: "Main Menu",
+ content: "Click the menu to create new files, download your code, or share your work with others."
+ },
+ {
+ element: "#sidebar",
+ title: "Sidebar",
+ content: "Pull right to expose the sidebar. It lets you switch between source files, view assembly listings, and use other tools like Disassembler, Memory Browser, and Asset Editor."
+ }
+ ];
if (!isLandscape()) {
steps.unshift({
element: "#controls_top",
@@ -1601,9 +1624,9 @@ async function showWelcomeMessage() {
});
}
var tour = new Tour({
- autoscroll:false,
+ autoscroll: false,
//storage:false,
- steps:steps,
+ steps: steps,
onEnd: () => {
userPrefs.completedTour();
//requestPersistPermission(false, true);
@@ -1616,7 +1639,7 @@ async function showWelcomeMessage() {
///////////////////////////////////////////////////
function globalErrorHandler(msgevent) {
- var msg = (msgevent.message || msgevent.error || msgevent)+"";
+ var msg = (msgevent.message || msgevent.error || msgevent) + "";
// storage quota full? (Chrome) try to expand it
if (msg.indexOf("QuotaExceededError") >= 0) {
requestPersistPermission(false, false);
@@ -1640,13 +1663,13 @@ function installErrorHandler() {
window.addEventListener('error', globalErrorHandler);
window.addEventListener('unhandledrejection', globalErrorHandler);
}
-
+
function uninstallErrorHandler() {
window.removeEventListener('error', globalErrorHandler);
window.removeEventListener('unhandledrejection', globalErrorHandler);
}
-export function gotoNewLocation(replaceHistory? : boolean, newQueryString?: {}) {
+export function gotoNewLocation(replaceHistory?: boolean, newQueryString?: {}) {
if (newQueryString) {
qs = newQueryString;
}
@@ -1718,7 +1741,7 @@ function installGAHooks() {
gaEvent('menu', e.target.id);
}
});
- gaPageView(location.pathname+'?platform='+platform_id+(repo_id?('&repo='+repo_id):('&file='+qs.file)));
+ gaPageView(location.pathname + '?platform=' + platform_id + (repo_id ? ('&repo=' + repo_id) : ('&file=' + qs.file)));
}
}
@@ -1781,7 +1804,7 @@ function updateBooksMenu() {
}
function revealTopBar() {
- setTimeout(() => { $("#controls_dynamic").css('visibility','inherit'); }, 250);
+ setTimeout(() => { $("#controls_dynamic").css('visibility', 'inherit'); }, 250);
}
export function setupSplits() {
@@ -1813,7 +1836,7 @@ export function setupSplits() {
});
}
-function loadImportedURL(url : string) {
+function loadImportedURL(url: string) {
// TODO: zip file?
const ignore = parseBool(qs.ignore) || isEmbed;
setWaitDialog(true);
@@ -1851,14 +1874,14 @@ async function loadFormDataUpload() {
} else {
force = false; // can't use force w/o embed=1
}
- for (var i=0; i<20; i++) {
- let path = qs['file'+i+'_name'];
- let dataenc = qs['file'+i+'_data'];
+ for (var i = 0; i < 20; i++) {
+ let path = qs['file' + i + '_name'];
+ let dataenc = qs['file' + i + '_data'];
if (path == null || dataenc == null) break;
var olddata = await store.getItem(path);
if (!(ignore && olddata)) {
let value = dataenc;
- if (qs['file'+i+'_type'] == 'binary') {
+ if (qs['file' + i + '_type'] == 'binary') {
value = stringToByteArray(atob(value));
}
if (!olddata || force || confirm("Replace existing file '" + path + "'?")) {
@@ -1866,9 +1889,9 @@ async function loadFormDataUpload() {
}
}
if (i == 0) { qs.file = path; } // set main filename
- delete qs['file'+i+'_name'];
- delete qs['file'+i+'_data'];
- delete qs['file'+i+'_type'];
+ delete qs['file' + i + '_name'];
+ delete qs['file' + i + '_data'];
+ delete qs['file' + i + '_type'];
}
delete qs.ignore;
delete qs.force;
@@ -1877,7 +1900,7 @@ async function loadFormDataUpload() {
function setPlatformUI() {
var name = platform.getPlatformName && platform.getPlatformName();
- var menuitem = $('a[href="?platform='+platform_id+'"]');
+ var menuitem = $('a[href="?platform=' + platform_id + '"]');
if (menuitem.length) {
menuitem.addClass("dropdown-item-checked");
name = name || menuitem.text() || name;
@@ -1892,7 +1915,7 @@ export function getPlatformAndRepo() {
repo_id = qs.repo;
// only look at cached repo_id if file= is not present, so back button works
if (!qs.repo && !qs.file)
- repo_id = userPrefs.getLastRepoID(platform_id);
+ repo_id = userPrefs.getLastRepoID(platform_id);
// are we in a repo?
if (hasLocalStorage && repo_id && repo_id !== '/') {
var repo = getRepos()[repo_id];
@@ -1953,7 +1976,7 @@ async function loadAndStartPlatform() {
var module = await importPlatform(getRootBasePlatform(platform_id));
console.log("starting platform", platform_id); // loaded required
.js file
await startPlatform();
- document.title = document.title + " [" + platform_id + "] - " + (repo_id?('['+repo_id+'] - '):'') + current_project.mainPath;
+ document.title = document.title + " [" + platform_id + "] - " + (repo_id ? ('[' + repo_id + '] - ') : '') + current_project.mainPath;
} catch (e) {
console.log(e);
alertError('Platform "' + platform_id + '" failed to load.');
@@ -1966,11 +1989,11 @@ async function loadAndStartPlatform() {
const useHTTPSCookieName = "__use_https";
-function setHTTPSCookie(val : number) {
+function setHTTPSCookie(val: number) {
document.cookie = useHTTPSCookieName + "=" + val + ";domain=8bitworkshop.com;path=/;max-age=315360000";
}
-function shouldRedirectHTTPS() : boolean {
+function shouldRedirectHTTPS(): boolean {
// cookie set? either true or false
var shouldRedir = getCookie(useHTTPSCookieName);
if (typeof shouldRedir === 'string') {
@@ -1983,14 +2006,14 @@ function shouldRedirectHTTPS() : boolean {
}
function _switchToHTTPS() {
- bootbox.confirm('Do you want to force the browser to use HTTPS from now on?
'+
- 'WARNING: This will make all of your local files unavailable, so you should "Download All Changes" first for each platform where you have done work.
'+
- 'You can go back to HTTP by setting the "'+useHTTPSCookieName+'" cookie to 0.
', (ok) => {
- if (ok) {
- setHTTPSCookie(1);
- redirectToHTTPS();
- }
- });
+ bootbox.confirm('Do you want to force the browser to use HTTPS from now on?
' +
+ 'WARNING: This will make all of your local files unavailable, so you should "Download All Changes" first for each platform where you have done work.
' +
+ 'You can go back to HTTP by setting the "' + useHTTPSCookieName + '" cookie to 0.
', (ok) => {
+ if (ok) {
+ setHTTPSCookie(1);
+ redirectToHTTPS();
+ }
+ });
}
function redirectToHTTPS() {
@@ -2013,7 +2036,7 @@ export function setTestInput(path: string, data: FileData) {
platform.writeFile(path, data);
}
-export function getTestOutput(path: string) : FileData {
+export function getTestOutput(path: string): FileData {
return platform.readFile(path);
}
@@ -2029,7 +2052,7 @@ export function emulationHalted(err: EmuHalt) {
}
// get remote file from local fs
-declare var alternateLocalFilesystem : ProjectFilesystem;
+declare var alternateLocalFilesystem: ProjectFilesystem;
export async function reloadWorkspaceFile(path: string) {
var oldval = current_project.filedata[path];
@@ -2041,7 +2064,7 @@ export async function reloadWorkspaceFile(path: string) {
function writeOutputROMFile() {
if (isElectron && current_output instanceof Uint8Array) {
var prefix = getFilenamePrefix(getCurrentMainFilename());
- var suffix = (platform.getROMExtension && platform.getROMExtension(current_output))
+ var suffix = (platform.getROMExtension && platform.getROMExtension(current_output))
|| "-" + getBasePlatform(platform_id) + ".bin";
alternateLocalFilesystem.setFileData(`bin/${prefix}${suffix}`, current_output);
}
@@ -2071,7 +2094,7 @@ function startUIWhenVisible() {
_resume();
}
}
- }, { });
+ }, {});
observer.observe($("#emulator")[0]); //window.document.body);
}
diff --git a/src/ide/views/asseteditor.ts b/src/ide/views/asseteditor.ts
index 1cdf47c9..a8745edc 100644
--- a/src/ide/views/asseteditor.ts
+++ b/src/ide/views/asseteditor.ts
@@ -385,9 +385,9 @@ export class AssetEditorView implements ProjectView, pixed.EditorContext {
setVisible?(showing : boolean) : void {
// TODO: make into toolbar?
if (showing) {
- if (Mousetrap.bind) Mousetrap.bind('ctrl+z', projectWindows.undoStep.bind(projectWindows));
+ if (Mousetrap.bind) Mousetrap.bind('mod+z', projectWindows.undoStep.bind(projectWindows));
} else {
- if (Mousetrap.unbind) Mousetrap.unbind('ctrl+z');
+ if (Mousetrap.unbind) Mousetrap.unbind('mod+z');
}
}
diff --git a/src/ide/views/debug.ts b/src/ide/views/debug.ts
new file mode 100644
index 00000000..4056e838
--- /dev/null
+++ b/src/ide/views/debug.ts
@@ -0,0 +1,56 @@
+import { LRLanguage, StreamLanguage, language, syntaxTree } from "@codemirror/language";
+import { hoverTooltip } from "@codemirror/view";
+import { getStyleTags } from "@lezer/highlight";
+
+// Tooltip to show tags, useful for theme and parser development.
+export const debugHighlightTagsTooltip = hoverTooltip((view, pos, side) => {
+ let tree = syntaxTree(view.state).resolveInner(pos, side);
+ let style = getStyleTags(tree);
+ let tagList = "";
+ if (style) {
+ for (let tag of style.tags) {
+ tagList += tag.set.join(" < ") + "\n";
+ }
+ }
+
+ let lang = view.state.facet(language);
+ let parserType = (lang instanceof LRLanguage) ? "Lezer LRLanguage" :
+ (lang instanceof StreamLanguage) ? "StreamLanguage" : "Unknown";
+ let treeName = (lang instanceof LRLanguage) ? "AST " :
+ (lang instanceof StreamLanguage) ? "Token " : "Name ";
+
+ return {
+ pos: pos,
+ above: true,
+ arrow: true,
+ create(view) {
+ let dom = document.createElement("div");
+ // Zero size element, so tooltip quickly dismisses when mouse moves over it, force
+ // CodeMirror's getBoundingClientRect() in isInTooltip() to always return false.
+ dom.style.overflow = "visible";
+ dom.style.height = "0";
+ dom.style.width = "0";
+ // Tooltip content.
+ let inner = document.createElement("div");
+ inner.className = "cm-debug-tooltip";
+ inner.textContent = `Parser: ${parserType}\n${treeName}: ${tree.name}\nTags : ${tagList}`;
+ inner.style.whiteSpace = "pre-wrap";
+ inner.style.fontFamily = "monospace";
+ inner.style.background = "#333";
+ inner.style.color = "white";
+ inner.style.padding = "2px 8px";
+ inner.style.borderRadius = "4px";
+ inner.style.fontSize = "12px";
+ inner.style.border = "1px solid #555";
+ inner.style.pointerEvents = "none";
+ inner.style.position = "absolute";
+ inner.style.bottom = "0";
+ inner.style.width = "max-content";
+ dom.appendChild(inner);
+ return { dom };
+ }
+ };
+}, {
+ hoverTime: 10,
+ hideOnChange: true,
+});
\ No newline at end of file
diff --git a/src/ide/views/debugviews.ts b/src/ide/views/debugviews.ts
index 089d2691..a344f4c7 100644
--- a/src/ide/views/debugviews.ts
+++ b/src/ide/views/debugviews.ts
@@ -1,18 +1,18 @@
-import { newDiv, ProjectView } from "./baseviews";
-import { Segment } from "../../common/workertypes";
-import { platform, current_project, projectWindows, runToPC, setupBreakpoint, getWorkerParams } from "../ui";
-import { hex, lpad, rpad } from "../../common/util";
-import { VirtualList } from "../../common/vlist";
+import { BaseZ80MachinePlatform, BaseZ80Platform } from "../../common/baseplatform";
import { getMousePos, getVisibleEditorLineHeight, VirtualTextLine, VirtualTextScroller } from "../../common/emu";
import { ProbeFlags, ProbeRecorder } from "../../common/probe";
-import { BaseZ80MachinePlatform, BaseZ80Platform } from "../../common/baseplatform";
+import { hex, lpad, rpad } from "../../common/util";
+import { VirtualList } from "../../common/vlist";
+import { Segment } from "../../common/workertypes";
+import { current_project, getWorkerParams, platform, projectWindows, runToPC, setupBreakpoint } from "../ui";
+import { newDiv, ProjectView } from "./baseviews";
///
-function ignoreSymbol(sym:string) {
- return sym.endsWith('_SIZE__') || sym.endsWith('_LAST__') || sym.endsWith('STACKSIZE__') || sym.endsWith('FILEOFFS__')
- || sym.startsWith('l__') || sym.startsWith('s__') || sym.startsWith('.__.');
+function ignoreSymbol(sym: string) {
+ return sym.endsWith('_SIZE__') || sym.endsWith('_LAST__') || sym.endsWith('STACKSIZE__') || sym.endsWith('FILEOFFS__')
+ || sym.startsWith('l__') || sym.startsWith('s__') || sym.startsWith('.__.');
}
// TODO: make it use debug state
@@ -21,12 +21,12 @@ function ignoreSymbol(sym:string) {
export class MemoryView implements ProjectView {
memorylist;
dumplines;
- maindiv : HTMLElement;
+ maindiv: HTMLElement;
recreateOnResize = true;
hibits = 0; // a hack to make it work with 32-bit addresses
totalRows = 0x1400; // a little more room in case we split lots of lines
- createDiv(parent : HTMLElement) {
+ createDiv(parent: HTMLElement) {
var div = document.createElement('div');
div.setAttribute("class", "memdump");
parent.appendChild(div);
@@ -34,13 +34,13 @@ export class MemoryView implements ProjectView {
return this.maindiv = div;
}
- showMemoryWindow(workspace:HTMLElement, parent:HTMLElement) {
+ showMemoryWindow(workspace: HTMLElement, parent: HTMLElement) {
this.memorylist = new VirtualList({
w: $(workspace).width(),
h: $(workspace).height(),
itemHeight: getVisibleEditorLineHeight(),
totalRows: this.totalRows,
- generatorFn: (row : number) => {
+ generatorFn: (row: number) => {
var s = this.getMemoryLineAt(row);
var linediv = document.createElement("div");
if (this.dumplines) {
@@ -58,7 +58,7 @@ export class MemoryView implements ProjectView {
this.scrollToAddress(compparams.data_start);
}
- scrollToAddress(addr : number) {
+ scrollToAddress(addr: number) {
if (this.dumplines) {
this.hibits = addr & 0xffff0000;
this.memorylist.scrollToItem(this.findMemoryWindowLine(addr & 0xffff));
@@ -72,7 +72,7 @@ export class MemoryView implements ProjectView {
tick() {
if (this.memorylist) {
- $(this.maindiv).find('[data-index]').each( (i,e) => {
+ $(this.maindiv).find('[data-index]').each((i, e) => {
var div = $(e);
var row = parseInt(div.attr('data-index'));
var oldtext = div.text();
@@ -83,7 +83,7 @@ export class MemoryView implements ProjectView {
}
}
- getMemoryLineAt(row : number) : string {
+ getMemoryLineAt(row: number): string {
var offset = row * 16;
var n1 = 0;
var n2 = 16;
@@ -99,24 +99,24 @@ export class MemoryView implements ProjectView {
return '.';
}
}
- var s = hex(offset+n1,4) + ' ';
- for (var i=0; i 8) s += ' ';
- for (var i=n1; i nextofs) ofs2 = nextofs;
//if (ofs < 1000) console.log(ofs, ofs2, nextofs, sym);
- this.dumplines.push({a:ofs, l:ofs2-ofs, s:sym});
+ this.dumplines.push({ a: ofs, l: ofs2 - ofs, s: sym });
ofs = ofs2;
}
}
@@ -153,22 +153,22 @@ export class MemoryView implements ProjectView {
}
// TODO: use segments list?
- getMemorySegment(a:number) : string {
+ getMemorySegment(a: number): string {
const compparams = getWorkerParams();
if (compparams) {
- if (a >= compparams.data_start && a < compparams.data_start+compparams.data_size) {
+ 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))
+ else if (a >= compparams.code_start && a < compparams.code_start + (compparams.code_size || compparams.rom_size))
return 'code';
}
var segments = current_project.segments;
if (segments) {
for (var seg of segments) {
- if (a >= seg.start && a < seg.start+seg.size) {
+ 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';
@@ -178,8 +178,8 @@ export class MemoryView implements ProjectView {
return 'unknown';
}
- findMemoryWindowLine(a:number) : number {
- for (var i=0; i= a)
return i;
}
@@ -187,10 +187,10 @@ export class MemoryView implements ProjectView {
export class VRAMMemoryView extends MemoryView {
totalRows = 0x800;
- readAddress(n : number) {
+ readAddress(n: number) {
return platform.readVRAMAddress(n);
}
- getMemorySegment(a:number) : string {
+ getMemorySegment(a: number): string {
return 'video';
}
getDumpLines() {
@@ -201,51 +201,51 @@ export class VRAMMemoryView extends MemoryView {
///
export class BinaryFileView implements ProjectView {
- vlist : VirtualTextScroller;
- maindiv : HTMLElement;
- path:string;
- data:Uint8Array;
+ vlist: VirtualTextScroller;
+ maindiv: HTMLElement;
+ path: string;
+ data: Uint8Array;
recreateOnResize = true;
- constructor(path:string, data:Uint8Array) {
+ constructor(path: string, data: Uint8Array) {
this.path = path;
this.data = data;
}
- createDiv(parent : HTMLElement) {
+ createDiv(parent: HTMLElement) {
this.vlist = new VirtualTextScroller(parent);
- this.vlist.create(parent, ((this.data.length+15) >> 4), this.getMemoryLineAt.bind(this));
+ this.vlist.create(parent, ((this.data.length + 15) >> 4), this.getMemoryLineAt.bind(this));
return this.vlist.maindiv;
}
- getMemoryLineAt(row : number) : VirtualTextLine {
+ getMemoryLineAt(row: number): VirtualTextLine {
var offset = row * 16;
var n1 = 0;
var n2 = 16;
- var s = hex(offset+n1,4) + ' ';
- for (var i=0; i 8) s += ' ';
- for (var i=n1; i=0?hex(read,2):' ');
+ for (var i = n1; i < n2; i++) {
+ var read = this.data[offset + i];
+ if (i == 8) s += ' ';
+ s += ' ' + (read >= 0 ? hex(read, 2) : ' ');
}
- return {text:s};
+ return { text: s };
}
refresh() {
this.vlist.refresh();
}
-
+
getPath() { return this.path; }
}
///
export class MemoryMapView implements ProjectView {
- maindiv : JQuery;
+ maindiv: JQuery;
- createDiv(parent : HTMLElement) {
+ createDiv(parent: HTMLElement) {
this.maindiv = newDiv(parent, 'vertical-scroll');
this.maindiv.css('display', 'grid');
this.maindiv.css('grid-template-columns', '5em 40% 40%');
@@ -255,24 +255,24 @@ export class MemoryMapView implements ProjectView {
}
// TODO: overlapping segments (e.g. ROM + LC)
- addSegment(seg : Segment, newrow : boolean) {
+ addSegment(seg: Segment, newrow: boolean) {
if (newrow) {
var offset = $('');
- offset.text('$'+hex(seg.start,4));
+ offset.text('$' + hex(seg.start, 4));
this.maindiv.append(offset);
}
var segdiv = $('');
segdiv.text(seg.name);
- let alttext = `$${hex(seg.start)} - $${hex(seg.last || seg.start+seg.size-1)}`
+ let alttext = `$${hex(seg.start)} - $${hex(seg.last || seg.start + seg.size - 1)}`
alttext += ` (${seg.size} bytes)`;
// set alttext of div
segdiv.attr('title', alttext);
if (!newrow || seg.source == 'linker')
segdiv.css('grid-column-start', 3); // make sure it's on right side
- var pad = Math.max(3.0, Math.log(seg.size+1)) * 0.5;
- segdiv.css('height', pad+'em');
+ var pad = Math.max(3.0, Math.log(seg.size + 1)) * 0.5;
+ segdiv.css('height', pad + 'em');
if (seg.type) {
- segdiv.addClass('segment-'+seg.type);
+ segdiv.addClass('segment-' + seg.type);
}
this.maindiv.append(segdiv);
//var row = $('').append(offset, segdiv);
@@ -312,14 +312,14 @@ export class MemoryMapView implements ProjectView {
const OPAQUE_BLACK = 0xff000000;
export abstract class ProbeViewBaseBase {
- probe : ProbeRecorder;
- tooldiv : HTMLElement;
- cumulativeData : boolean = false;
- cyclesPerLine : number;
- totalScanlines : number;
- sp : number; // stack pointer
+ probe: ProbeRecorder;
+ tooldiv: HTMLElement;
+ cumulativeData: boolean = false;
+ cyclesPerLine: number;
+ totalScanlines: number;
+ sp: number; // stack pointer
- abstract tick() : void;
+ abstract tick(): void;
constructor() {
var width = 160;
@@ -333,12 +333,12 @@ export abstract class ProbeViewBaseBase {
this.totalScanlines = height;
}
- addr2symbol(addr : number) : string {
+ addr2symbol(addr: number): string {
var _addr2sym = (platform.debugSymbols && platform.debugSymbols.addr2symbol) || {};
return _addr2sym[addr];
}
- addr2str(addr : number) : string {
+ addr2str(addr: number): string {
var sym = this.addr2symbol(addr);
if (typeof sym === 'string')
return '$' + hex(addr) + ' (' + sym + ')';
@@ -346,7 +346,7 @@ export abstract class ProbeViewBaseBase {
return '$' + hex(addr);
}
- showTooltip(s:string) {
+ showTooltip(s: string) {
if (s) {
if (!this.tooldiv) {
this.tooldiv = document.createElement("div");
@@ -359,7 +359,7 @@ export abstract class ProbeViewBaseBase {
}
}
- setVisible(showing : boolean) : void {
+ setVisible(showing: boolean): void {
if (showing) {
this.probe = platform.startProbing();
this.probe.singleFrame = !this.cumulativeData;
@@ -371,22 +371,22 @@ export abstract class ProbeViewBaseBase {
}
}
- redraw( eventfn:(op,addr,col,row,clk,value) => void ) {
+ redraw(eventfn: (op, addr, col, row, clk, value) => void) {
var p = this.probe;
if (!p || !p.idx) return; // if no probe, or if empty
- var row=0;
- var col=0;
- var clk=0;
+ var row = 0;
+ var col = 0;
+ var clk = 0;
this.sp = 0;
- for (var i=0; i> 16) & 0xff;
var op = word & OPAQUE_BLACK;
switch (op) {
- case ProbeFlags.SCANLINE: row++; col=0; break;
- case ProbeFlags.FRAME: row=0; col=0; break;
- case ProbeFlags.CLOCKS: col += addr; clk += addr; break;
+ case ProbeFlags.SCANLINE: row++; col = 0; break;
+ case ProbeFlags.FRAME: row = 0; col = 0; break;
+ case ProbeFlags.CLOCKS: col += addr; clk += addr; break;
case ProbeFlags.SP_PUSH:
case ProbeFlags.SP_POP:
this.sp = addr;
@@ -397,59 +397,59 @@ export abstract class ProbeViewBaseBase {
}
}
- opToString(op:number, addr?:number, value?:number) {
+ opToString(op: number, addr?: number, value?: number) {
var s = "";
switch (op) {
- case ProbeFlags.EXECUTE: s = "Exec"; break;
- case ProbeFlags.MEM_READ: s = "Read"; break;
- case ProbeFlags.MEM_WRITE: s = "Write"; break;
- case ProbeFlags.IO_READ: s = "IO Read"; break;
- case ProbeFlags.IO_WRITE: s = "IO Write"; break;
- case ProbeFlags.VRAM_READ: s = "VRAM Read"; break;
- case ProbeFlags.VRAM_WRITE: s = "VRAM Write"; break;
- case ProbeFlags.DMA_READ: s = "DMA Read"; break;
- case ProbeFlags.DMA_WRITE: s = "DMA Write"; break;
- case ProbeFlags.INTERRUPT: s = "Interrupt"; break;
- case ProbeFlags.ILLEGAL: s = "Error"; break;
- case ProbeFlags.WAIT: s = "Wait"; break;
- case ProbeFlags.SP_PUSH: s = "Stack Push"; break;
- case ProbeFlags.SP_POP: s = "Stack Pop"; break;
- default: return "";
+ case ProbeFlags.EXECUTE: s = "Exec"; break;
+ case ProbeFlags.MEM_READ: s = "Read"; break;
+ case ProbeFlags.MEM_WRITE: s = "Write"; break;
+ case ProbeFlags.IO_READ: s = "IO Read"; break;
+ case ProbeFlags.IO_WRITE: s = "IO Write"; break;
+ case ProbeFlags.VRAM_READ: s = "VRAM Read"; break;
+ case ProbeFlags.VRAM_WRITE: s = "VRAM Write"; break;
+ case ProbeFlags.DMA_READ: s = "DMA Read"; break;
+ case ProbeFlags.DMA_WRITE: s = "DMA Write"; break;
+ case ProbeFlags.INTERRUPT: s = "Interrupt"; break;
+ case ProbeFlags.ILLEGAL: s = "Error"; break;
+ case ProbeFlags.WAIT: s = "Wait"; break;
+ case ProbeFlags.SP_PUSH: s = "Stack Push"; break;
+ case ProbeFlags.SP_POP: s = "Stack Pop"; break;
+ default: return "";
}
if (typeof addr == 'number') s += " " + this.addr2str(addr);
- if ((op & ProbeFlags.HAS_VALUE) && typeof value == 'number') s += " = $" + hex(value,2);
+ if ((op & ProbeFlags.HAS_VALUE) && typeof value == 'number') s += " = $" + hex(value, 2);
return s;
}
-
- getOpRGB(op:number, addr:number) : number {
+
+ getOpRGB(op: number, addr: number): number {
switch (op) {
- case ProbeFlags.EXECUTE: return 0x018001;
- case ProbeFlags.MEM_READ: return 0x800101;
- case ProbeFlags.MEM_WRITE: return 0x010180;
- case ProbeFlags.IO_READ: return 0x018080;
- case ProbeFlags.IO_WRITE: return 0xc00180;
+ case ProbeFlags.EXECUTE: return 0x018001;
+ case ProbeFlags.MEM_READ: return 0x800101;
+ case ProbeFlags.MEM_WRITE: return 0x010180;
+ case ProbeFlags.IO_READ: return 0x018080;
+ case ProbeFlags.IO_WRITE: return 0xc00180;
case ProbeFlags.DMA_READ:
- case ProbeFlags.VRAM_READ: return 0x808001;
+ case ProbeFlags.VRAM_READ: return 0x808001;
case ProbeFlags.DMA_WRITE:
- case ProbeFlags.VRAM_WRITE: return 0x4080c0;
- case ProbeFlags.INTERRUPT: return 0x3fbf3f;
- case ProbeFlags.ILLEGAL: return 0x3f3fff;
- case ProbeFlags.WAIT: return 0xff3f3f;
- default: return 0;
+ case ProbeFlags.VRAM_WRITE: return 0x4080c0;
+ case ProbeFlags.INTERRUPT: return 0x3fbf3f;
+ case ProbeFlags.ILLEGAL: return 0x3f3fff;
+ case ProbeFlags.WAIT: return 0xff3f3f;
+ default: return 0;
}
}
}
abstract class ProbeViewBase extends ProbeViewBaseBase {
- maindiv : HTMLElement;
- canvas : HTMLCanvasElement;
- ctx : CanvasRenderingContext2D;
+ maindiv: HTMLElement;
+ canvas: HTMLCanvasElement;
+ ctx: CanvasRenderingContext2D;
recreateOnResize = true;
-
+
abstract drawEvent(op, addr, col, row);
- createCanvas(parent:HTMLElement, width:number, height:number) {
+ createCanvas(parent: HTMLElement, width: number, height: number) {
var div = document.createElement('div');
var canvas = document.createElement('canvas');
canvas.width = width;
@@ -462,7 +462,7 @@ abstract class ProbeViewBase extends ProbeViewBaseBase {
canvas.onmousemove = (e) => {
var pos = getMousePos(canvas, e);
this.showTooltip(this.getTooltipText(pos.x, pos.y));
- $(this.tooldiv).css('left',e.pageX+10).css('top',e.pageY-30);
+ $(this.tooldiv).css('left', e.pageX + 10).css('top', e.pageY - 30);
}
canvas.onmouseout = (e) => {
$(this.tooldiv).hide();
@@ -477,16 +477,16 @@ abstract class ProbeViewBase extends ProbeViewBaseBase {
initCanvas() {
}
-
- getTooltipText(x:number, y:number) : string {
+
+ getTooltipText(x: number, y: number): string {
return null;
}
- getOpAtPos(x:number, y:number, mask:number) : number {
- x = x|0;
- y = y|0;
+ getOpAtPos(x: number, y: number, mask: number): number {
+ x = x | 0;
+ y = y | 0;
let result = 0;
- this.redraw( (op,addr,col,row,clk,value) => {
+ this.redraw((op, addr, col, row, clk, value) => {
if (!result && row == y && col >= x && (op & mask) != 0) {
result = op | addr;
}
@@ -496,7 +496,7 @@ abstract class ProbeViewBase extends ProbeViewBaseBase {
clear() {
}
-
+
tick() {
this.clear();
this.redraw(this.drawEvent.bind(this));
@@ -505,25 +505,25 @@ abstract class ProbeViewBase extends ProbeViewBaseBase {
abstract class ProbeBitmapViewBase extends ProbeViewBase {
- imageData : ImageData;
- datau32 : Uint32Array;
+ imageData: ImageData;
+ datau32: Uint32Array;
recreateOnResize = false;
-
- createDiv(parent : HTMLElement) {
+
+ createDiv(parent: HTMLElement) {
return this.createCanvas(parent, this.cyclesPerLine, this.totalScanlines);
}
initCanvas() {
this.imageData = this.ctx.createImageData(this.canvas.width, this.canvas.height);
this.datau32 = new Uint32Array(this.imageData.data.buffer);
}
- getTooltipText(x:number, y:number) : string {
- x = x|0;
- y = y|0;
+ getTooltipText(x: number, y: number): string {
+ x = x | 0;
+ y = y | 0;
var s = "";
var lastroutine = null;
var symstack = [];
var lastcol = -1;
- this.redraw( (op,addr,col,row,clk,value) => {
+ this.redraw((op, addr, col, row, clk, value) => {
switch (op) {
case ProbeFlags.EXECUTE:
lastroutine = this.addr2symbol(addr) || lastroutine;
@@ -543,7 +543,7 @@ abstract class ProbeBitmapViewBase extends ProbeViewBase {
if (s == "" && lastroutine) { s += "\n" + lastroutine; }
s += "\n" + this.opToString(op, addr, value);
}
- } );
+ });
return 'X: ' + x + ' Y: ' + y + ' ' + s;
}
@@ -565,10 +565,10 @@ abstract class ProbeBitmapViewBase extends ProbeViewBase {
export class AddressHeatMapView extends ProbeBitmapViewBase implements ProjectView {
- createDiv(parent : HTMLElement) {
+ createDiv(parent: HTMLElement) {
return this.createCanvas(parent, 256, 256);
}
-
+
initCanvas() {
super.initCanvas();
this.canvas.onclick = (e) => {
@@ -576,21 +576,21 @@ export class AddressHeatMapView extends ProbeBitmapViewBase implements ProjectVi
var opaddr = Math.floor(pos.x) + Math.floor(pos.y) * 256;
var lastpc = -1;
var runpc = -1;
- this.redraw( (op,addr) => {
+ this.redraw((op, addr) => {
if (runpc < 0 && lastpc >= 0 && addr == opaddr) {
runpc = lastpc;
}
if (op == ProbeFlags.EXECUTE) lastpc = addr;
});
- if (runpc >= 0) runToPC(runpc);
+ if (runpc >= 0) runToPC([runpc]);
}
}
clear() {
- for (var i=0; i<=0xffff; i++) {
+ for (var i = 0; i <= 0xffff; i++) {
var v = platform.readAddress(i);
var rgb = (v >> 2) | (v & 0x1f);
- rgb |= (rgb<<8) | (rgb<<16);
+ rgb |= (rgb << 8) | (rgb << 16);
this.datau32[i] = rgb | OPAQUE_BLACK;
}
}
@@ -605,15 +605,15 @@ export class AddressHeatMapView extends ProbeBitmapViewBase implements ProjectVi
data = data | rgb | OPAQUE_BLACK;
this.datau32[addr & 0xffff] = data;
}
-
- getTooltipText(x:number, y:number) : string {
+
+ getTooltipText(x: number, y: number): string {
var a = (x & 0xff) + (y << 8);
var s = "";
var pc = -1;
var already = {};
var lastroutine = null;
var symstack = [];
- this.redraw( (op,addr,col,row,clk,value) => {
+ this.redraw((op, addr, col, row, clk, value) => {
switch (op) {
case ProbeFlags.EXECUTE:
pc = addr;
@@ -626,13 +626,13 @@ export class AddressHeatMapView extends ProbeBitmapViewBase implements ProjectVi
lastroutine = symstack.pop();
break;
}
- var key = op|pc;
+ var key = op | pc;
if (addr == a && !already[key]) {
if (s == "" && lastroutine) { s += "\n" + lastroutine; }
s += "\nPC " + this.addr2str(pc) + " " + this.opToString(op, null, value);
already[key] = 1;
}
- } );
+ });
return this.addr2str(a) + s;
}
}
@@ -671,7 +671,7 @@ export class RasterPCHeatMapView extends ProbeBitmapViewBase implements ProjectV
drawImage() {
// fill in the gaps
let last = OPAQUE_BLACK;
- for (let i=0; i= 8) sp = 16-sp;
+ if (sp >= 8) sp = 16 - sp;
if (Math.abs(this.lastpc) - addr > 16) { sp += 1; }
if (Math.abs(this.lastpc) - addr > 256) { sp += 1; }
data = this.rgb = (0x080808 * sp) + 0x202020;
@@ -722,27 +722,27 @@ export class RasterStackMapView extends RasterPCHeatMapView implements ProjectVi
}
export class ProbeLogView extends ProbeViewBaseBase {
- vlist : VirtualTextScroller;
- maindiv : HTMLElement;
+ vlist: VirtualTextScroller;
+ maindiv: HTMLElement;
recreateOnResize = true;
dumplines;
- createDiv(parent : HTMLElement) {
+ createDiv(parent: HTMLElement) {
this.vlist = new VirtualTextScroller(parent);
- this.vlist.create(parent, this.cyclesPerLine*this.totalScanlines, this.getMemoryLineAt.bind(this));
+ this.vlist.create(parent, this.cyclesPerLine * this.totalScanlines, this.getMemoryLineAt.bind(this));
return this.vlist.maindiv;
}
- getMemoryLineAt(row : number) : VirtualTextLine {
- var s : string = "";
- var c : string = "seg_data";
+ getMemoryLineAt(row: number): VirtualTextLine {
+ var s: string = "";
+ var c: string = "seg_data";
var line = this.dumplines && this.dumplines[row];
if (line != null) {
- var xtra : string = line.info.join(", ");
- s = "(" + lpad(line.row,4) + ", " + lpad(line.col,4) + ") " + rpad(line.asm||"",20) + xtra;
+ var xtra: string = line.info.join(", ");
+ s = "(" + lpad(line.row, 4) + ", " + lpad(line.col, 4) + ") " + rpad(line.asm || "", 20) + xtra;
if (xtra.indexOf("Write ") >= 0) c = "seg_io";
// if (xtra.indexOf("Stack ") >= 0) c = "seg_code";
}
- return {text:s, clas:c};
+ return { text: s, clas: c };
}
refresh() {
this.tick();
@@ -751,11 +751,11 @@ export class ProbeLogView extends ProbeViewBaseBase {
const isz80 = platform instanceof BaseZ80MachinePlatform || platform instanceof BaseZ80Platform; // TODO?
// cache each line in frame
this.dumplines = {};
- this.redraw((op,addr,col,row,clk,value) => {
+ this.redraw((op, addr, col, row, clk, value) => {
if (isz80) clk >>= 2;
var line = this.dumplines[clk];
if (line == null) {
- line = {op:op, addr:addr, row:row, col:col, asm:null, info:[]};
+ line = { op: op, addr: addr, row: row, col: col, asm: null, info: [] };
this.dumplines[clk] = line;
}
switch (op) {
@@ -776,22 +776,22 @@ export class ProbeLogView extends ProbeViewBaseBase {
}
export class ScanlineIOView extends ProbeViewBaseBase {
- vlist : VirtualTextScroller;
- maindiv : HTMLElement;
+ vlist: VirtualTextScroller;
+ maindiv: HTMLElement;
recreateOnResize = true;
dumplines;
- createDiv(parent : HTMLElement) {
+ createDiv(parent: HTMLElement) {
this.vlist = new VirtualTextScroller(parent);
this.vlist.create(parent, this.totalScanlines, this.getMemoryLineAt.bind(this));
return this.vlist.maindiv;
}
- getMemoryLineAt(row : number) : VirtualTextLine {
- var s = lpad(row+"",3) + ' ';
+ getMemoryLineAt(row: number): VirtualTextLine {
+ var s = lpad(row + "", 3) + ' ';
var c = 'seg_code';
var line = (this.dumplines && this.dumplines[row]) || [];
- var hblankCycle = Math.round(this.cyclesPerLine/3.3);
- for (var i=0; i {
+ this.redraw((op, addr, col, row, clk, value) => {
var line = this.dumplines[row];
if (line == null) {
this.dumplines[row] = line = [];
@@ -843,14 +843,14 @@ export class ScanlineIOView extends ProbeViewBaseBase {
///
export class ProbeSymbolView extends ProbeViewBaseBase {
- vlist : VirtualTextScroller;
- keys : string[];
+ vlist: VirtualTextScroller;
+ keys: string[];
recreateOnResize = true;
dumplines;
cumulativeData = true;
// TODO: auto resize
- createDiv(parent : HTMLElement) {
+ createDiv(parent: HTMLElement) {
// TODO: what if symbol list changes?
if (platform.debugSymbols && platform.debugSymbols.symbolmap) {
this.keys = Array.from(Object.keys(platform.debugSymbols.symbolmap).filter(sym => !ignoreSymbol(sym)));
@@ -862,21 +862,21 @@ export class ProbeSymbolView extends ProbeViewBaseBase {
return this.vlist.maindiv;
}
- getMemoryLineAt(row : number) : VirtualTextLine {
+ getMemoryLineAt(row: number): VirtualTextLine {
// header line
if (row == 0) {
- return {text: lpad("Symbol",35)+lpad("Reads",8)+lpad("Writes",8)};
+ return { text: lpad("Symbol", 35) + lpad("Reads", 8) + lpad("Writes", 8) };
}
- var sym = this.keys[row-1];
+ var sym = this.keys[row - 1];
var line = this.dumplines && this.dumplines[sym];
function getop(op) {
var n = line[op] | 0;
return lpad(n ? n.toString() : "", 8);
}
- var s : string;
- var c : string;
+ var s: string;
+ var c: string;
if (line != null) {
- s = lpad(sym, 35)
+ s = lpad(sym, 35)
+ getop(ProbeFlags.MEM_READ)
+ getop(ProbeFlags.MEM_WRITE);
if (line[ProbeFlags.EXECUTE])
@@ -889,7 +889,7 @@ export class ProbeSymbolView extends ProbeViewBaseBase {
s = lpad(sym, 35);
c = 'seg_unknown';
}
- return {text:s, clas:c};
+ return { text: s, clas: c };
}
refresh() {
@@ -899,7 +899,7 @@ export class ProbeSymbolView extends ProbeViewBaseBase {
tick() {
// cache each line in frame
this.dumplines = {};
- this.redraw((op,addr,col,row,clk,value) => {
+ this.redraw((op, addr, col, row, clk, value) => {
var sym = platform.debugSymbols.addr2symbol[addr];
if (sym != null) {
var line = this.dumplines[sym];
diff --git a/src/ide/views/editors.ts b/src/ide/views/editors.ts
index 566d4901..595282a0 100644
--- a/src/ide/views/editors.ts
+++ b/src/ide/views/editors.ts
@@ -1,25 +1,37 @@
-
-import { isMobileDevice, ProjectView } from "./baseviews";
-import { SourceFile, WorkerError, SourceLocation } from "../../common/workertypes";
+import { closeBrackets, deleteBracketPair } from "@codemirror/autocomplete";
+import { defaultKeymap, history, historyKeymap, indentWithTab, undo } from "@codemirror/commands";
+import { cpp } from "@codemirror/lang-cpp";
+import { markdown } from "@codemirror/lang-markdown";
+import { bracketMatching, foldGutter, indentOnInput, indentUnit } from "@codemirror/language";
+import { highlightSelectionMatches, search, searchKeymap } from "@codemirror/search";
+import { EditorState, Extension } from "@codemirror/state";
+import { crosshairCursor, drawSelection, dropCursor, EditorView, highlightActiveLine, highlightActiveLineGutter, highlightSpecialChars, keymap, lineNumbers, rectangularSelection, ViewUpdate } from "@codemirror/view";
import { CodeAnalyzer } from "../../common/analysis";
-import { platform, current_project, lastDebugState, runToPC, qs } from "../ui";
import { hex, rpad } from "../../common/util";
+import { SourceFile, SourceLocation, WorkerError } from "../../common/workertypes";
+import { asm6502 } from "../../parser/lang-6502";
+import { basic } from "../../parser/lang-basic";
+import { batariBasic } from "../../parser/lang-bataribasic";
+import { fastBasic } from "../../parser/lang-fastbasic";
+import { inform6 } from "../../parser/lang-inform6";
+import { verilog } from "../../parser/lang-verilog";
+import { wiz } from "../../parser/lang-wiz";
+import { asmZ80 } from "../../parser/lang-z80";
+import { cobalt } from "../../themes/cobalt";
+import { disassemblyTheme } from "../../themes/disassemblyTheme";
+import { editorTheme } from "../../themes/editorTheme";
+import { mbo } from "../../themes/mbo";
+import { clearBreakpoint, current_project, lastDebugState, platform, qs, runToPC } from "../ui";
+import { isMobileDevice, ProjectView } from "./baseviews";
+import { debugHighlightTagsTooltip } from "./debug";
+import { createTextTransformFilterEffect, textTransformFilterCompartment } from "./filters";
+import { breakpointMarkers, bytes, clock, currentPcMarker, errorMarkers, offset, statusMarkers } from "./gutter";
+import { currentPc, errorMessages, highlightLines, showValue } from "./visuals";
-declare var CodeMirror;
-
-// helper function for editor
-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);
-}
+// TODO: make this an easily toggleable debug setting.
+// Debug syntax highlighting. Useful when developing new parsers and themes.
+const debugHighlightTags = false;
-function createTextSpan(text:string, className:string) : HTMLElement {
- var span = document.createElement("span");
- span.setAttribute("class", className);
- span.appendChild(document.createTextNode(text));
- return span;
-}
/////
@@ -29,55 +41,47 @@ export const PC_LINE_LOOKAHEAD = 64;
const MAX_ERRORS = 200;
const MODEDEFS = {
- default: { theme: 'mbo' }, // NOTE: Not merged w/ other modes
+ default: { theme: mbo }, // NOTE: Not merged w/ other modes
'6502': { isAsm: true },
z80: { isAsm: true },
jsasm: { isAsm: true },
gas: { isAsm: true },
vasm: { isAsm: true },
- inform6: { theme: 'cobalt' },
+ inform6: { theme: cobalt },
markdown: { lineWrap: true },
fastbasic: { noGutters: true },
basic: { noLineNumbers: true, noGutters: true }, // TODO: not used?
- ecs: { theme: 'mbo', isAsm: true },
+ ecs: { theme: mbo, isAsm: true },
}
export var textMapFunctions = {
- input: null
+ input: null as ((text: string) => string) | null
};
export class SourceEditor implements ProjectView {
- constructor(path:string, mode:string) {
+ constructor(path: string, mode: string) {
this.path = path;
this.mode = mode;
}
- path : string;
- mode : string;
+ path: string;
+ mode: string;
editor;
updateTimer = null;
dirtylisting = true;
- sourcefile : SourceFile;
- currentDebugLine : SourceLocation;
- markCurrentPC; // TextMarker
- markHighlight; // TextMarker
- errormsgs = [];
- errorwidgets = [];
- errormarks = [];
- inspectWidget;
+ sourcefile: SourceFile;
+ currentDebugLine: SourceLocation;
refreshDelayMsec = 300;
- createDiv(parent:HTMLElement) {
+ createDiv(parent: HTMLElement) {
var div = document.createElement('div');
div.setAttribute("class", "editor");
parent.appendChild(div);
var text = current_project.getFile(this.path) as string;
- var asmOverride = text && this.mode=='verilog' && /__asm\b([\s\S]+?)\b__endasm\b/.test(text);
- this.newEditor(div, asmOverride);
- if (text) {
- this.setText(text); // TODO: this calls setCode() and builds... it shouldn't
- this.editor.setSelection({line:0,ch:0}, {line:0,ch:0}, {scroll:true}); // move cursor to start
- }
- this.setupEditor();
+ var asmOverride = text && this.mode == 'verilog' && /__asm\b([\s\S]+?)\b__endasm\b/.test(text);
+ this.newEditor(div, text, asmOverride);
+ this.editor.dispatch({
+ effects: createTextTransformFilterEffect(textMapFunctions),
+ });
if (current_project.getToolForFilename(this.path).startsWith("remote:")) {
this.refreshDelayMsec = 1000; // remote URLs get slower refresh
}
@@ -90,7 +94,7 @@ export class SourceEditor implements ProjectView {
}
}
- newEditor(parent:HTMLElement, isAsmOverride?:boolean) {
+ newEditor(parent: HTMLElement, text: string, isAsmOverride?: boolean) {
var modedef = MODEDEFS[this.mode] || MODEDEFS.default;
var isAsm = isAsmOverride || modedef.isAsm;
var lineWrap = !!modedef.lineWrap;
@@ -100,240 +104,309 @@ export class SourceEditor implements ProjectView {
lineNums = false; // no line numbers while embedded
isAsm = false; // no opcode bytes either
}
- var gutters = ["CodeMirror-linenumbers", "gutter-offset", "gutter-info"];
- if (isAsm) gutters = ["CodeMirror-linenumbers", "gutter-offset", "gutter-bytes", "gutter-clock", "gutter-info"];
- if (modedef.noGutters || isMobileDevice) gutters = ["gutter-info"];
- this.editor = CodeMirror(parent, {
- theme: theme,
- lineNumbers: lineNums,
- matchBrackets: true,
- tabSize: 8,
- indentAuto: true,
- lineWrapping: lineWrap,
- gutters: gutters
+ const minimalGutters = modedef.noGutters || isMobileDevice;
+
+ var parser: Extension;
+ switch (this.mode) {
+ case '6502':
+ parser = asm6502();
+ break;
+ case 'basic':
+ parser = basic();
+ break;
+ case 'bataribasic':
+ parser = batariBasic();
+ break;
+ case 'fastbasic':
+ parser = fastBasic();
+ break;
+ case 'inform6':
+ parser = inform6();
+ break;
+ case 'markdown':
+ parser = markdown();
+ break;
+ case 'text/x-csrc':
+ parser = cpp();
+ break;
+ case 'text/x-wiz':
+ parser = wiz();
+ break;
+ case 'verilog':
+ parser = verilog();
+ break;
+ case 'z80':
+ parser = asmZ80();
+ break;
+ default:
+ console.warn("Unknown mode: " + this.mode);
+ break;
+ }
+ this.editor = new EditorView({
+ parent: parent,
+ doc: text,
+ extensions: [
+
+ // Custom keybindings must appear before default keybindings.
+ keymap.of([
+ { key: "Backspace", run: deleteBracketPair },
+ ]),
+ keymap.of(defaultKeymap),
+
+ lineNums ? lineNumbers() : [],
+
+ highlightSpecialChars(),
+
+ // Undo history.
+ history(),
+ keymap.of(historyKeymap),
+
+ // Code fold gutter.
+ foldGutter(),
+
+ dropCursor(),
+
+ EditorState.allowMultipleSelections.of(true),
+ drawSelection(),
+
+ indentOnInput(),
+ bracketMatching(),
+ closeBrackets(),
+
+ // Rectangular selection and crosshair cursor.
+ rectangularSelection(),
+ crosshairCursor(),
+
+ highlightActiveLine(),
+ highlightActiveLineGutter(),
+ highlightSelectionMatches(),
+
+ search({ top: true }),
+ keymap.of(searchKeymap),
+
+ // lintGutter(),
+ // autocompletion(),
+
+ parser || [],
+ theme,
+ editorTheme,
+ debugHighlightTags ? debugHighlightTagsTooltip : [],
+ EditorState.tabSize.of(8),
+ indentUnit.of(" "),
+ keymap.of([indentWithTab]),
+ lineWrap ? EditorView.lineWrapping : [],
+
+ currentPc.field,
+
+ !minimalGutters ? [
+ offset.field,
+ offset.gutter,
+ ] : [],
+
+ isAsm && !minimalGutters ? [
+ bytes.field,
+ bytes.gutter,
+
+ clock.field,
+ clock.gutter,
+ ] : [],
+
+ breakpointMarkers.field,
+ statusMarkers.gutter,
+ EditorView.updateListener.of(update => {
+ for (let effect of update.transactions.flatMap(tr => tr.effects)) {
+ if (effect.is(breakpointMarkers.set)) {
+ if (platform.isRunning()) {
+ this.runToBreakpoints(update.state);
+ }
+ }
+ if (effect.is(currentPcMarker.runToLine)) {
+ const lineNum = effect.value;
+ if (this.sourcefile && this.sourcefile.line2offset) {
+ const pc = this.sourcefile.line2offset[lineNum];
+ if (pc >= 0) {
+ runToPC([pc]);
+ }
+ }
+ }
+ }
+ }),
+
+ errorMarkers.field,
+
+ errorMessages.field,
+
+ currentPcMarker.field,
+ currentPcMarker.gutter,
+
+ highlightLines.field,
+
+ textTransformFilterCompartment.of([]),
+
+ // update file in project (and recompile) when edits made
+ EditorView.updateListener.of(update => {
+ if (update.docChanged) {
+ this.editorChanged();
+ }
+ }),
+
+ // inspect symbol when it's highlighted (double-click)
+ showValue.field,
+ EditorView.updateListener.of(update => {
+ if (update.selectionSet) {
+ this.inspectUnderCursor(update);
+ }
+ }),
+ ],
});
}
editorChanged() {
clearTimeout(this.updateTimer);
- this.updateTimer = setTimeout( () => {
- current_project.updateFile(this.path, this.editor.getValue());
+ this.updateTimer = setTimeout(() => {
+ current_project.updateFile(this.path, this.editor.state.doc.toString());
}, this.refreshDelayMsec);
- if (this.markHighlight) {
- this.markHighlight.clear();
- this.markHighlight = null;
- }
}
- setupEditor() {
- // update file in project (and recompile) when edits made
- this.editor.on('changes', (ed, changeobj) => {
- this.editorChanged();
- });
- // inspect symbol when it's highlighted (double-click)
- this.editor.on('cursorActivity', (ed) => {
- this.inspectUnderCursor();
- });
- // gutter clicked
- this.editor.on("gutterClick", (cm, n) => {
- this.toggleBreakpoint(n);
- });
- // set editor mode for highlighting, etc
- this.editor.setOption("mode", this.mode);
- // change text?
- this.editor.on('beforeChange', (cm, chgobj) => {
- if (textMapFunctions.input && chgobj.text) chgobj.text = chgobj.text.map(textMapFunctions.input);
- });
- }
+ inspectUnderCursor(update: ViewUpdate) {
+ // TODO: handle multi-select
+ const range = update.state.selection.main;
+ const selectedText = update.state.sliceDoc(range.from, range.to).trim();
- inspectUnderCursor() {
- var start = this.editor.getCursor(true);
- var end = this.editor.getCursor(false);
- if (start.line == end.line && start.ch < end.ch && end.ch-start.ch < 80) {
- var name = this.editor.getSelection();
- this.inspect(name);
- } else {
- this.inspect(null);
- }
- }
-
- inspect(ident : string) : void {
var result;
if (platform.inspect) {
- result = platform.inspect(ident);
- }
- if (this.inspectWidget) {
- this.inspectWidget.clear();
- this.inspectWidget = null;
+ result = platform.inspect(selectedText);
}
- if (result) {
- var infospan = createTextSpan(result, "tooltipinfoline");
- var line = this.editor.getCursor().line;
- this.inspectWidget = this.editor.addLineWidget(line, infospan, {above:false});
+
+ if (!range.empty && result && result.length < 80) {
+ update.view.dispatch({
+ effects: showValue.effect.of({ range: range, val: result })
+ });
+ } else {
+ update.view.dispatch({
+ effects: showValue.effect.of(null)
+ });
}
}
- setText(text:string) {
- var i,j;
- var oldtext = this.editor.getValue();
+ setText(text: string) {
+ var oldtext = this.editor.state.doc.toString();
if (oldtext != text) {
- this.editor.setValue(text);
- /*
- // find minimum range to undo
- for (i=0; i= numLines) line = 0;
- this.addErrorMarker(line, info.msg);
- if (info.start != null) {
- var markOpts = {className:"mark-error", inclusiveLeft:true};
- var start = {line:line, ch:info.end?info.start:info.start-1};
- var end = {line:line, ch:info.end?info.end:info.start};
- var mark = this.editor.markText(start, end, markOpts);
- this.errormarks.push(mark);
- }
- }
+ insertText(text: string) {
+ const main = this.editor.state.selection.main;
+ this.editor.dispatch({
+ changes: { from: main.from, to: main.to, insert: text },
+ selection: { anchor: main.from + text.length },
+ userEvent: "input.paste"
+ });
}
- addErrorMarker(line:number, msg:string) {
- var div = document.createElement("div");
- div.setAttribute("class", "tooltipbox tooltiperror");
- div.appendChild(document.createTextNode("\u24cd"));
- this.editor.setGutterMarker(line, "gutter-info", div);
- this.errormsgs.push({line:line, msg:msg});
- // expand line widgets when mousing over errors
- $(div).mouseover((e) => {
- this.expandErrors();
+ highlightLines(start: number, end: number) {
+ const startLine = this.editor.state.doc.line(start + 1);
+ this.editor.dispatch({
+ effects: [
+ highlightLines.effect.of({ start: start + 1, end: end + 1 }),
+ EditorView.scrollIntoView(startLine.from, { y: "start", yMargin: 100/*pixels*/ }),
+ ]
});
}
- addErrorLine(line:number, msg:string) {
- var errspan = createTextSpan(msg, "tooltiperrorline");
- this.errorwidgets.push(this.editor.addLineWidget(line, errspan));
+ getValue(): string {
+ return this.editor.state.doc.toString();
}
- expandErrors() {
- var e;
- while (e = this.errormsgs.shift()) {
- this.addErrorLine(e.line, e.msg);
- }
- }
+ getPath(): string { return this.path; }
- markErrors(errors:WorkerError[]) {
+ markErrors(errors: WorkerError[]) {
// TODO: move cursor to error line if offscreen?
this.clearErrors();
errors = errors.slice(0, MAX_ERRORS);
+ const newErrors = new Map();
for (var info of errors) {
- this.addError(info);
+ // only mark errors with this filename, or without any filename
+ if (!info.path || this.path.endsWith(info.path)) {
+ var numLines = this.editor.state.doc.lines;
+ var line = info.line;
+ if (isNaN(line) || line < 1 || line > numLines) line = 1;
+ newErrors.set(line, info.msg);
+ }
}
+ this.editor.dispatch({
+ effects: [
+ errorMarkers.set.of(newErrors),
+ ],
+ });
}
clearErrors() {
this.dirtylisting = true;
- // clear line widgets
- this.editor.clearGutter("gutter-info");
- this.errormsgs = [];
- while (this.errorwidgets.length) this.errorwidgets.shift().clear();
- while (this.errormarks.length) this.errormarks.shift().clear();
+ this.editor.dispatch({
+ effects: [
+ errorMarkers.set.of(new Map()),
+ errorMarkers.showMessage.of(null),
+ ],
+ });
}
- getSourceFile() : SourceFile { return this.sourcefile; }
+ getSourceFile(): SourceFile { return this.sourcefile; }
updateListing() {
// update editor annotations
// TODO: recreate editor if gutter-bytes is used (verilog)
this.clearErrors();
- this.editor.clearGutter("gutter-bytes");
- this.editor.clearGutter("gutter-offset");
- this.editor.clearGutter("gutter-clock");
var lstlines = this.sourcefile.lines || [];
+
+ const newOffsets = new Map();
+ const newBytes = new Map();
+ const newClocks = new Map();
+
for (var info of lstlines) {
//if (info.path && info.path != this.path) continue;
if (info.offset >= 0) {
- this.setGutter("gutter-offset", info.line-1, hex(info.offset&0xffff,4));
+ newOffsets.set(info.line, hex(info.offset & 0xffff, 4));
}
if (info.insns) {
var insnstr = info.insns.length > 9 ? ("...") : info.insns;
- this.setGutter("gutter-bytes", info.line-1, insnstr);
+ newBytes.set(info.line, insnstr);
if (info.iscode) {
// TODO: labels trick this part?
if (info.cycles) {
- this.setGutter("gutter-clock", info.line-1, info.cycles+"");
+ newClocks.set(info.line, info.cycles + "");
} else if (platform.getOpcodeMetadata) {
var opcode = parseInt(info.insns.split(" ")[0], 16);
var meta = platform.getOpcodeMetadata(opcode, info.offset);
if (meta && meta.minCycles) {
- var clockstr = meta.minCycles+"";
- this.setGutter("gutter-clock", info.line-1, clockstr);
+ var clockstr = meta.minCycles + "";
+ newClocks.set(info.line, clockstr);
}
}
}
}
}
+ this.editor.dispatch({
+ effects: [
+ offset.set.of(newOffsets),
+ bytes.set.of(newBytes),
+ clock.set.of(newClocks),
+ ],
+ });
}
- setGutter(type:string, line:number, text:string) {
- var lineinfo = this.editor.lineInfo(line);
- if (lineinfo && lineinfo.gutterMarkers && lineinfo.gutterMarkers[type]) {
- // do not replace existing marker
- } else {
- var textel = document.createTextNode(text);
- this.editor.setGutterMarker(line, type, textel);
- }
- }
-
- setGutterBytes(line:number, s:string) {
- this.setGutter("gutter-bytes", line-1, s);
- }
-
- setTimingResult(result:CodeAnalyzer) : void {
- this.editor.clearGutter("gutter-bytes");
+ setTimingResult(result: CodeAnalyzer): void {
if (this.sourcefile == null) return;
- // show the lines
+ var newBytes = new Map();
for (const line of Object.keys(this.sourcefile.line2offset)) {
let pc = this.sourcefile.line2offset[line];
let clocks = result.pc2clockrange[pc];
var minclocks = clocks && clocks.minclocks;
var maxclocks = clocks && clocks.maxclocks;
- if (minclocks>=0 && maxclocks>=0) {
+ if (minclocks >= 0 && maxclocks >= 0) {
var s;
if (maxclocks == minclocks)
s = minclocks + "";
@@ -341,51 +414,60 @@ export class SourceEditor implements ProjectView {
s = minclocks + "-" + maxclocks;
if (maxclocks == result.MAX_CLOCKS)
s += "+";
- this.setGutterBytes(parseInt(line), s);
+ newBytes.set(parseInt(line), s);
}
}
+ this.editor.dispatch({
+ effects: [
+ bytes.set.of(newBytes),
+ ],
+ });
}
- setCurrentLine(line:SourceLocation, moveCursor:boolean) {
- var blocked = platform.isBlocked && platform.isBlocked();
-
- var addCurrentMarker = (line:SourceLocation) => {
- var div = document.createElement("div");
- var cls = blocked ? 'currentpc-marker-blocked' : 'currentpc-marker';
- div.classList.add(cls);
- div.appendChild(document.createTextNode("\u25b6"));
- this.editor.setGutterMarker(line.line-1, "gutter-info", div);
+ setCurrentLine(line: SourceLocation, moveCursor: boolean) {
+ var addCurrentMarker = (line: SourceLocation) => {
+ this.editor.dispatch({
+ effects: [
+ currentPcMarker.set.of(line.line),
+ currentPc.effect.of(line.line),
+ // Optional: follow the execution point
+ EditorView.scrollIntoView(this.editor.state.doc.line(line.line).from, { y: "center" }),
+ ]
+ });
}
this.clearCurrentLine(moveCursor);
if (line) {
addCurrentMarker(line);
if (moveCursor) {
- this.editor.setCursor({line:line.line-1,ch:line.start||0}, {scroll:true});
+ const targetLine = this.editor.state.doc.line(line.line);
+ const pos = targetLine.from + (line.start || 0);
+ this.editor.dispatch({
+ selection: { anchor: pos, head: pos },
+ effects: EditorView.scrollIntoView(pos, { y: "center" })
+ });
}
- var cls = blocked ? 'currentpc-span-blocked' : 'currentpc-span';
- var markOpts = {className:cls, inclusiveLeft:true};
- if (line.start || line.end)
- this.markCurrentPC = this.editor.markText({line:line.line-1,ch:line.start}, {line:line.line-1,ch:line.end||line.start+1}, markOpts);
- else
- this.markCurrentPC = this.editor.markText({line:line.line-1,ch:0}, {line:line.line,ch:0}, markOpts);
this.currentDebugLine = line;
}
}
- clearCurrentLine(moveCursor:boolean) {
+ clearCurrentLine(moveCursor: boolean) {
if (this.currentDebugLine) {
- this.editor.clearGutter("gutter-info");
- if (moveCursor) this.editor.setSelection(this.editor.getCursor());
+ if (moveCursor) {
+ const pos = this.editor.state.selection.main.head;
+ this.editor.dispatch({ selection: { anchor: pos, head: pos } });
+ }
this.currentDebugLine = null;
}
- if (this.markCurrentPC) {
- this.markCurrentPC.clear();
- this.markCurrentPC = null;
- }
+ this.editor.dispatch({
+ effects: [
+ currentPcMarker.set.of(null),
+ currentPc.effect.of(null),
+ ]
+ });
}
- getActiveLine() : SourceLocation {
+ getActiveLine(): SourceLocation {
if (this.sourcefile) {
var cpustate = lastDebugState && lastDebugState.c;
if (!cpustate && platform.getCPUState && !platform.isRunning())
@@ -398,7 +480,7 @@ export class SourceEditor implements ProjectView {
}
}
- refreshDebugState(moveCursor:boolean) {
+ refreshDebugState(moveCursor: boolean) {
// TODO: only if line changed
// TODO: remove after compilation
this.clearCurrentLine(moveCursor);
@@ -424,20 +506,21 @@ export class SourceEditor implements ProjectView {
this.refreshListing();
this.refreshDebugState(moveCursor);
}
-
+
tick() {
this.refreshDebugState(false);
}
- getLine(line : number) {
- return this.editor.getLine(line-1);
+ getLine(line: number) {
+ return this.editor.state.doc.line(line).text;
}
- getCurrentLine() : number {
- return this.editor.getCursor().line+1;
+ getCurrentLine(): number {
+ const pos = this.editor.state.selection.main.head;
+ return this.editor.state.doc.lineAt(pos).number;
}
- getCursorPC() : number {
+ getCursorPC(): number {
var line = this.getCurrentLine();
while (this.sourcefile && line >= 0) {
var pc = this.sourcefile.line2offset[line];
@@ -448,24 +531,29 @@ export class SourceEditor implements ProjectView {
}
undoStep() {
- this.editor.execCommand('undo');
- }
-
- toggleBreakpoint(lineno: number) {
- // TODO: we have to always start at beginning of frame
- if (this.sourcefile != null) {
- var targetPC = this.sourcefile.line2offset[lineno+1];
- /*
- var bpid = "pc" + targetPC;
- if (platform.hasBreakpoint(bpid)) {
- platform.clearBreakpoint(bpid);
- } else {
- platform.setBreakpoint(bpid, () => {
- return platform.getPC() == targetPC;
- });
- }
- */
- runToPC(targetPC);
+ undo(this.editor);
+ }
+
+ getBreakpointPCs(): number[] {
+ if (this.sourcefile == null) return [];
+ const pcs: number[] = [];
+ const bpField = this.editor.state.field(breakpointMarkers.field);
+ const cursor = bpField.iter();
+ while (cursor.value) {
+ const line = this.editor.state.doc.lineAt(cursor.from).number;
+ const pc = this.sourcefile.line2offset[line];
+ if (pc >= 0) pcs.push(pc);
+ cursor.next();
+ }
+ return pcs;
+ }
+
+ runToBreakpoints(state: EditorState) {
+ const pcs = this.getBreakpointPCs();
+ if (pcs.length > 0) {
+ runToPC(pcs);
+ } else {
+ clearBreakpoint();
}
}
}
@@ -475,11 +563,9 @@ export class SourceEditor implements ProjectView {
const disasmWindow = 1024; // disassemble this many bytes around cursor
export class DisassemblerView implements ProjectView {
- disasmview;
-
- getDisasmView() { return this.disasmview; }
+ disasmview: EditorView;
- createDiv(parent : HTMLElement) {
+ createDiv(parent: HTMLElement) {
var div = document.createElement('div');
div.setAttribute("class", "editor");
parent.appendChild(div);
@@ -487,13 +573,24 @@ export class DisassemblerView implements ProjectView {
return div;
}
- newEditor(parent : HTMLElement) {
- this.disasmview = CodeMirror(parent, {
- mode: 'z80', // TODO: pick correct one
- theme: 'cobalt',
- tabSize: 8,
- readOnly: true,
- styleActiveLine: true
+ newEditor(parent: HTMLElement) {
+ this.disasmview = new EditorView({
+ parent: parent,
+ extensions: [
+ rectangularSelection(),
+ crosshairCursor(),
+ EditorState.allowMultipleSelections.of(true),
+ drawSelection(),
+ highlightActiveLine(),
+ highlightSelectionMatches(),
+ debugHighlightTags ? debugHighlightTagsTooltip : [],
+ disassemblyTheme,
+ cobalt,
+ currentPc.field,
+ EditorState.tabSize.of(8),
+ EditorState.readOnly.of(true),
+ ],
+ // mode: 'z80', // TODO: pick correct one
});
}
@@ -524,17 +621,17 @@ export class DisassemblerView implements ProjectView {
*/
let bytes = "";
let comment = "";
- for (let i=0; i {
+ dstr = dstr.replace(/([^#])[$]([0-9A-F]+)/, (substr: string, ...args: any[]): string => {
let addr = parseInt(args[1], 16);
let sym = addr2symbol[addr];
if (sym) return (args[0] + sym);
- sym = addr2symbol[addr-1];
+ sym = addr2symbol[addr - 1];
if (sym) return (args[0] + sym + "+1");
return substr;
});
@@ -545,7 +642,7 @@ export class DisassemblerView implements ProjectView {
comment = "; " + sym;
}
}
- let dline = hex(a, 4) + "\t" + rpad(bytes,14) + "\t" + rpad(dstr,30) + comment + "\n";
+ let dline = hex(a, 4) + "\t" + rpad(bytes, 14) + "\t" + rpad(dstr, 30) + comment + "\n";
s += dline;
if (a == pc) selline = curline;
curline++;
@@ -553,21 +650,29 @@ export class DisassemblerView implements ProjectView {
}
return s;
}
- var startpc = pc < 0 ? pc-disasmWindow : Math.max(0, pc-disasmWindow); // for 32-bit PCs w/ hi bit set
- let text = disassemble(startpc, pc-startpc) + disassemble(pc, disasmWindow);
- this.disasmview.setValue(text);
- if (moveCursor) {
- this.disasmview.setCursor(selline, 0);
+ var startpc = pc < 0 ? pc - disasmWindow : Math.max(0, pc - disasmWindow); // for 32-bit PCs w/ hi bit set
+ let text = disassemble(startpc, pc - startpc) + disassemble(pc, disasmWindow);
+ this.disasmview.dispatch({
+ changes: { from: 0, to: this.disasmview.state.doc.length, insert: text }
+ })
+ if (moveCursor) {
+ const line = this.disasmview.state.doc.line(selline + 1);
+ this.disasmview.dispatch({
+ selection: { anchor: line.from, head: line.from },
+ effects: EditorView.scrollIntoView(line.from, { y: "center" }),
+ });
}
- jumpToLine(this.disasmview, selline);
}
- getCursorPC() : number {
- var line = this.disasmview.getCursor().line;
- if (line >= 0) {
- var toks = this.disasmview.getLine(line).trim().split(/\s+/);
+ getCursorPC(): number {
+ const pos = this.disasmview.state.selection.main.head;
+ const lineNum = this.disasmview.state.doc.lineAt(pos).number;
+ if (lineNum >= 0) {
+ const lineText = this.disasmview.state.doc.line(lineNum).text;
+ const toks = lineText.trim().split(/\s+/);
if (toks && toks.length >= 1) {
- var pc = parseInt(toks[0], 16);
+ const pc = parseInt(toks[0], 16);
+ console.log("getCursorPC", pc);
if (pc >= 0) return pc;
}
}
@@ -578,10 +683,10 @@ export class DisassemblerView implements ProjectView {
///
export class ListingView extends DisassemblerView implements ProjectView {
- assemblyfile : SourceFile;
- path : string;
+ assemblyfile: SourceFile;
+ path: string;
- constructor(lstfn : string) {
+ constructor(lstfn: string) {
super();
this.path = lstfn;
}
@@ -589,7 +694,7 @@ export class ListingView extends DisassemblerView implements ProjectView {
refreshListing() {
// lookup corresponding assemblyfile for this file, using listing
var lst = current_project.getListingForFile(this.path);
- // TODO?
+ // TODO?
this.assemblyfile = lst && (lst.assemblyfile || lst.sourcefile);
}
@@ -598,9 +703,11 @@ export class ListingView extends DisassemblerView implements ProjectView {
// load listing text into editor
if (!this.assemblyfile) return;
var asmtext = this.assemblyfile.text;
- var disasmview = this.getDisasmView();
+
// TODO: sometimes it picks one without a text file
- disasmview.setValue(asmtext);
+ this.disasmview.dispatch({
+ changes: { from: 0, to: this.disasmview.state.doc.length, insert: asmtext }
+ })
// go to PC
if (!platform.saveState) return;
var state = lastDebugState || platform.saveState();
@@ -610,9 +717,12 @@ export class ListingView extends DisassemblerView implements ProjectView {
if (res) {
// set cursor while debugging
if (moveCursor) {
- disasmview.setCursor(res.line-1, 0);
+ const line = this.disasmview.state.doc.line(res.line);
+ this.disasmview.dispatch({
+ selection: { anchor: line.from, head: line.from },
+ effects: EditorView.scrollIntoView(line.from, { y: "center" }),
+ });
}
- jumpToLine(disasmview, res.line-1);
}
}
}
diff --git a/src/ide/views/filters.ts b/src/ide/views/filters.ts
new file mode 100644
index 00000000..5149de54
--- /dev/null
+++ b/src/ide/views/filters.ts
@@ -0,0 +1,43 @@
+import { Compartment, EditorState, Transaction } from "@codemirror/state";
+
+type TextMapFunctions = { input: ((text: string) => string) | null };
+
+function createTextTransformFilter(textMapFunctions: TextMapFunctions) {
+ return EditorState.transactionFilter.of((tr: Transaction) => {
+ // Only transform user-initiated text, and only when textMapFunctions.input is set.
+ if (!tr.docChanged || !tr.isUserEvent("input") || !textMapFunctions.input) return tr;
+
+ // Apply the transform to each inserted text segment.
+ let changes: { from: number; to: number; insert: string }[] = [];
+ let changed = false;
+ tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => {
+ const original = inserted.toString();
+ const transformed = textMapFunctions.input(original);
+ changes.push({ from: fromA, to: toA, insert: transformed });
+ if (transformed !== original) changed = true;
+ });
+ if (!changed) return tr;
+
+ // Preserve annotations from the original transaction.
+ const userEvent = tr.annotation(Transaction.userEvent);
+ const addToHistory = tr.annotation(Transaction.addToHistory);
+ return {
+ changes,
+ selection: tr.selection,
+ effects: tr.effects,
+ scrollIntoView: tr.scrollIntoView,
+ annotations: [
+ ...(userEvent != null ? [Transaction.userEvent.of(userEvent)] : []),
+ ...(addToHistory != null ? [Transaction.addToHistory.of(addToHistory)] : []),
+ ],
+ };
+ });
+}
+
+const textTransformFilterCompartment = new Compartment();
+
+function createTextTransformFilterEffect(textMapFunctions: TextMapFunctions) {
+ return textTransformFilterCompartment.reconfigure(createTextTransformFilter(textMapFunctions));
+}
+
+export { createTextTransformFilterEffect, textTransformFilterCompartment };
diff --git a/src/ide/views/gutter.ts b/src/ide/views/gutter.ts
new file mode 100644
index 00000000..6f6c9fbd
--- /dev/null
+++ b/src/ide/views/gutter.ts
@@ -0,0 +1,349 @@
+import { RangeSet, StateEffect, StateField } from "@codemirror/state";
+import { gutter, GutterMarker } from "@codemirror/view";
+import { hex } from "../../common/util";
+
+const setOffset = StateEffect.define