| /* |
| * Duktape debugger web client |
| * |
| * Talks to the NodeJS server using socket.io. |
| * |
| * http://unixpapa.com/js/key.html |
| */ |
| |
| // Update interval for custom source highlighting. |
| var SOURCE_UPDATE_INTERVAL = 350; |
| |
| // Source view |
| var activeFileName = null; // file that we want to be loaded in source view |
| var activeLine = null; // scroll to line once file has been loaded |
| var activeHighlight = null; // line that we want to highlight (if any) |
| var loadedFileName = null; // currently loaded (shown) file |
| var loadedLineCount = 0; // currently loaded file line count |
| var loadedFileExecuting = false; // true if currFileName (loosely) matches loadedFileName |
| var loadedLinePending = null; // if set, scroll loaded file to requested line |
| var highlightLine = null; // highlight line |
| var sourceEditedLines = []; // line numbers which have been modified |
| // (added classes etc, tracked for removing) |
| var sourceUpdateInterval = null; // timer for updating source view |
| var sourceFetchXhr = null; // current AJAX request for fetching a source file (if any) |
| var forceButtonUpdate = false; // hack to reset button states |
| var bytecodeDialogOpen = false; // bytecode dialog active |
| var bytecodeIdxHighlight = null; // index of currently highlighted line (or null) |
| var bytecodeIdxInstr = 0; // index to first line of bytecode instructions |
| |
| // Execution state |
| var prevState = null; // previous execution state ('paused', 'running', etc) |
| var prevAttached = null; // previous debugger attached state (true, false, null) |
| var currFileName = null; // current filename being executed |
| var currFuncName = null; // current function name being executed |
| var currLine = 0; // current line being executed |
| var currPc = 0; // current bytecode PC being executed |
| var currState = 0; // current execution state ('paused', 'running', 'detached', etc) |
| var currAttached = false; // current debugger attached state (true or false) |
| var currLocals = []; // current local variables |
| var currCallstack = []; // current callstack (from top to bottom) |
| var currBreakpoints = []; // current breakpoints |
| var startedRunning = 0; // timestamp when last started running (if running) |
| // (used to grey out the source file if running for long enough) |
| |
| /* |
| * Helpers |
| */ |
| |
| function formatBytes(x) { |
| if (x < 1024) { |
| return String(x) + ' bytes'; |
| } else if (x < 1024 * 1024) { |
| return (x / 1024).toPrecision(3) + ' kB'; |
| } else { |
| return (x / (1024 * 1024)).toPrecision(3) + ' MB'; |
| } |
| } |
| |
| /* |
| * Source view periodic update handling |
| */ |
| |
| function doSourceUpdate() { |
| var elem; |
| |
| // Remove previously added custom classes |
| sourceEditedLines.forEach(function (linenum) { |
| elem = $('#source-code div')[linenum - 1]; |
| if (elem) { |
| elem.classList.remove('breakpoint'); |
| elem.classList.remove('execution'); |
| elem.classList.remove('highlight'); |
| } |
| }); |
| sourceEditedLines.length = 0; |
| |
| // If we're executing the file shown, highlight current line |
| if (loadedFileExecuting) { |
| elem = $('#source-code div')[currLine - 1]; |
| if (elem) { |
| sourceEditedLines.push(currLine); |
| elem.classList.add('execution'); |
| } |
| } |
| |
| // Add breakpoints |
| currBreakpoints.forEach(function (bp) { |
| if (bp.fileName === loadedFileName) { |
| elem = $('#source-code div')[bp.lineNumber - 1]; |
| if (elem) { |
| sourceEditedLines.push(bp.lineNumber); |
| elem.classList.add('breakpoint'); |
| } |
| } |
| }); |
| |
| if (highlightLine !== null) { |
| elem = $('#source-code div')[highlightLine - 1]; |
| if (elem) { |
| sourceEditedLines.push(highlightLine); |
| elem.classList.add('highlight'); |
| } |
| } |
| |
| // Bytecode dialog highlight |
| if (loadedFileExecuting && bytecodeDialogOpen && bytecodeIdxHighlight !== bytecodeIdxInstr + currPc) { |
| if (typeof bytecodeIdxHighlight === 'number') { |
| $('#bytecode-preformatted div')[bytecodeIdxHighlight].classList.remove('highlight'); |
| } |
| bytecodeIdxHighlight = bytecodeIdxInstr + currPc; |
| $('#bytecode-preformatted div')[bytecodeIdxHighlight].classList.add('highlight'); |
| } |
| |
| // If no-one requested us to scroll to a specific line, finish. |
| if (loadedLinePending == null) { |
| return; |
| } |
| |
| var reqLine = loadedLinePending; |
| loadedLinePending = null; |
| |
| // Scroll to requested line. This is not very clean, so a better solution |
| // should be found: |
| // https://developer.mozilla.org/en-US/docs/Web/API/Element.scrollIntoView |
| // http://erraticdev.blogspot.fi/2011/02/jquery-scroll-into-view-plugin-with.html |
| // http://flesler.blogspot.fi/2007/10/jqueryscrollto.html |
| var tmpLine = Math.max(reqLine - 5, 0); |
| elem = $('#source-code div')[tmpLine]; |
| if (elem) { |
| elem.scrollIntoView(); |
| } |
| } |
| |
| // Source is updated periodically. Other code can also call doSourceUpdate() |
| // directly if an immediate update is needed. |
| sourceUpdateInterval = setInterval(doSourceUpdate, SOURCE_UPDATE_INTERVAL); |
| |
| /* |
| * UI update handling when exec-status update arrives |
| */ |
| |
| function doUiUpdate() { |
| var now = Date.now(); |
| |
| // Note: loadedFileName can be either from target or from server, but they |
| // must match exactly. We could do a loose match here, but exact matches |
| // are needed for proper breakpoint handling anyway. |
| loadedFileExecuting = (loadedFileName === currFileName); |
| |
| // If we just started running, store a timestamp so we can grey out the |
| // source view only if we execute long enough (i.e. we're not just |
| // stepping). |
| if (currState !== prevState && currState === 'running') { |
| startedRunning = now; |
| } |
| |
| // If we just became paused, check for eval watch |
| if (currState !== prevState && currState === 'paused') { |
| if ($('#eval-watch').is(':checked')) { |
| submitEval(); // don't clear eval input |
| } |
| } |
| |
| // Update current execution state |
| if (currFileName === '' && currLine === 0) { |
| $('#current-fileline').text(''); |
| } else { |
| $('#current-fileline').text(String(currFileName) + ':' + String(currLine)); |
| } |
| if (currFuncName === '' && currPc === 0) { |
| $('#current-funcpc').text(''); |
| } else { |
| $('#current-funcpc').text(String(currFuncName) + '() pc ' + String(currPc)); |
| } |
| $('#current-state').text(String(currState)); |
| |
| // Update buttons |
| if (currState !== prevState || currAttached !== prevAttached || forceButtonUpdate) { |
| $('#stepinto-button').prop('disabled', !currAttached || currState !== 'paused'); |
| $('#stepover-button').prop('disabled', !currAttached || currState !== 'paused'); |
| $('#stepout-button').prop('disabled', !currAttached || currState !== 'paused'); |
| $('#resume-button').prop('disabled', !currAttached || currState !== 'paused'); |
| $('#pause-button').prop('disabled', !currAttached || currState !== 'running'); |
| $('#attach-button').prop('disabled', currAttached); |
| if (currAttached) { |
| $('#attach-button').removeClass('enabled'); |
| } else { |
| $('#attach-button').addClass('enabled'); |
| } |
| $('#detach-button').prop('disabled', !currAttached); |
| $('#eval-button').prop('disabled', !currAttached); |
| $('#add-breakpoint-button').prop('disabled', !currAttached); |
| $('#delete-all-breakpoints-button').prop('disabled', !currAttached); |
| $('.delete-breakpoint-button').prop('disabled', !currAttached); |
| $('#putvar-button').prop('disabled', !currAttached); |
| $('#getvar-button').prop('disabled', !currAttached); |
| $('#heap-dump-download-button').prop('disabled', !currAttached); |
| } |
| if (currState !== 'running' || forceButtonUpdate) { |
| // Remove pending highlight once we're no longer running. |
| $('#pause-button').removeClass('pending'); |
| $('#eval-button').removeClass('pending'); |
| } |
| forceButtonUpdate = false; |
| |
| // Make source window grey when running for a longer time, use a small |
| // delay to avoid flashing grey when stepping. |
| if (currState === 'running' && now - startedRunning >= 500) { |
| $('#source-pre').removeClass('notrunning'); |
| $('#current-state').removeClass('notrunning'); |
| } else { |
| $('#source-pre').addClass('notrunning'); |
| $('#current-state').addClass('notrunning'); |
| } |
| |
| // Force source view to match currFileName only when running or when |
| // just became paused (from running or detached). |
| var fetchSource = false; |
| if (typeof currFileName === 'string') { |
| if (currState === 'running' || |
| (prevState !== 'paused' && currState === 'paused') || |
| (currAttached !== prevAttached)) { |
| if (activeFileName !== currFileName) { |
| fetchSource = true; |
| activeFileName = currFileName; |
| activeLine = currLine; |
| activeHighlight = null; |
| requestSourceRefetch(); |
| } |
| } |
| } |
| |
| // Force line update (scrollTop) only when running or just became paused. |
| // Otherwise let user browse and scroll source files freely. |
| if (!fetchSource) { |
| if ((prevState !== 'paused' && currState === 'paused') || |
| currState === 'running') { |
| loadedLinePending = currLine || 0; |
| } |
| } |
| } |
| |
| /* |
| * Init socket.io and add handlers |
| */ |
| |
| var socket = io(); // returns a Manager |
| |
| setInterval(function () { |
| socket.emit('keepalive', { |
| userAgent: (navigator || {}).userAgent |
| }); |
| }, 30000); |
| |
| socket.on('connect', function () { |
| $('#socketio-info').text('connected'); |
| currState = 'connected'; |
| |
| fetchSourceList(); |
| }); |
| socket.on('disconnect', function () { |
| $('#socketio-info').text('not connected'); |
| currState = 'disconnected'; |
| }); |
| socket.on('reconnecting', function () { |
| $('#socketio-info').text('reconnecting'); |
| currState = 'reconnecting'; |
| }); |
| socket.on('error', function (err) { |
| $('#socketio-info').text(err); |
| }); |
| |
| socket.on('replaced', function () { |
| // XXX: how to minimize the chance we'll further communciate with the |
| // server or reconnect to it? socket.reconnection()? |
| |
| // We'd like to window.close() here but can't (not allowed from scripts). |
| // Alert is the next best thing. |
| alert('Debugger connection replaced by a new one, do you have multiple tabs open? If so, please close this tab.'); |
| }); |
| |
| socket.on('keepalive', function (msg) { |
| // Not really interesting in the UI |
| // $('#server-info').text(new Date() + ': ' + JSON.stringify(msg)); |
| }); |
| |
| socket.on('basic-info', function (msg) { |
| $('#duk-version').text(String(msg.duk_version)); |
| $('#duk-git-describe').text(String(msg.duk_git_describe)); |
| $('#target-info').text(String(msg.target_info)); |
| $('#endianness').text(String(msg.endianness)); |
| }); |
| |
| socket.on('exec-status', function (msg) { |
| // Not 100% reliable if callstack has several functions of the same name |
| if (bytecodeDialogOpen && (currFileName != msg.fileName || currFuncName != msg.funcName)) { |
| socket.emit('get-bytecode', {}); |
| } |
| |
| currFileName = msg.fileName; |
| currFuncName = msg.funcName; |
| currLine = msg.line; |
| currPc = msg.pc; |
| currState = msg.state; |
| currAttached = msg.attached; |
| |
| // Duktape now restricts execution status updates quite effectively so |
| // there's no need to rate limit UI updates now. |
| |
| doUiUpdate(); |
| |
| prevState = currState; |
| prevAttached = currAttached; |
| }); |
| |
| // Update the "console" output based on lines sent by the server. The server |
| // rate limits these updates to keep the browser load under control. Even |
| // better would be for the client to pull this (and other stuff) on its own. |
| socket.on('output-lines', function (msg) { |
| var elem = $('#output'); |
| var i, n, ent; |
| |
| elem.empty(); |
| for (i = 0, n = msg.length; i < n; i++) { |
| ent = msg[i]; |
| if (ent.type === 'print') { |
| elem.append($('<div></div>').text(ent.message)); |
| } else if (ent.type === 'alert') { |
| elem.append($('<div class="alert"></div>').text(ent.message)); |
| } else if (ent.type === 'log') { |
| elem.append($('<div class="log loglevel' + ent.level + '"></div>').text(ent.message)); |
| } else if (ent.type === 'debugger-info') { |
| elem.append($('<div class="debugger-info"><div>').text(ent.message)); |
| } else if (ent.type === 'debugger-debug') { |
| elem.append($('<div class="debugger-debug"><div>').text(ent.message)); |
| } else { |
| elem.append($('<div></div>').text(ent.message)); |
| } |
| } |
| |
| // http://stackoverflow.com/questions/14918787/jquery-scroll-to-bottom-of-div-even-after-it-updates |
| // Stop queued animations so that we always scroll quickly to bottom |
| $('#output').stop(true); |
| $('#output').animate({ scrollTop: $('#output')[0].scrollHeight}, 1000); |
| }); |
| |
| socket.on('callstack', function (msg) { |
| var elem = $('#callstack'); |
| var s1, s2, div; |
| |
| currCallstack = msg.callstack; |
| |
| elem.empty(); |
| msg.callstack.forEach(function (e) { |
| s1 = $('<a class="rest"></a>').text(e.fileName + ':' + e.lineNumber + ' (pc ' + e.pc + ')'); // float |
| s1.on('click', function () { |
| activeFileName = e.fileName; |
| activeLine = e.lineNumber || 1; |
| activeHighlight = activeLine; |
| requestSourceRefetch(); |
| }); |
| s2 = $('<span class="func"></span>').text(e.funcName + '()'); |
| div = $('<div></div>'); |
| div.append(s1); |
| div.append(s2); |
| elem.append(div); |
| }); |
| }); |
| |
| socket.on('locals', function (msg) { |
| var elem = $('#locals'); |
| var s1, s2, div; |
| var i, n, e; |
| |
| currLocals = msg.locals; |
| |
| elem.empty(); |
| for (i = 0, n = msg.locals.length; i < n; i++) { |
| e = msg.locals[i]; |
| s1 = $('<span class="value"></span>').text(e.value); // float |
| s2 = $('<span class="key"></span>').text(e.key); |
| div = $('<div></div>'); |
| div.append(s1); |
| div.append(s2); |
| elem.append(div); |
| } |
| }); |
| |
| socket.on('debug-stats', function (msg) { |
| $('#debug-rx-bytes').text(formatBytes(msg.rxBytes)); |
| $('#debug-rx-dvalues').text(msg.rxDvalues); |
| $('#debug-rx-messages').text(msg.rxMessages); |
| $('#debug-rx-kbrate').text((msg.rxBytesPerSec / 1024).toFixed(2)); |
| $('#debug-tx-bytes').text(formatBytes(msg.txBytes)); |
| $('#debug-tx-dvalues').text(msg.txDvalues); |
| $('#debug-tx-messages').text(msg.txMessages); |
| $('#debug-tx-kbrate').text((msg.txBytesPerSec / 1024).toFixed(2)); |
| }); |
| |
| socket.on('breakpoints', function (msg) { |
| var elem = $('#breakpoints'); |
| var div; |
| var sub; |
| |
| currBreakpoints = msg.breakpoints; |
| |
| elem.empty(); |
| |
| // First line is special |
| div = $('<div></div>'); |
| sub = $('<button id="delete-all-breakpoints-button"></button>').text('Delete all breakpoints'); |
| sub.on('click', function () { |
| socket.emit('delete-all-breakpoints'); |
| }); |
| div.append(sub); |
| sub = $('<input id="add-breakpoint-file"></input>').val('file.js'); |
| div.append(sub); |
| sub = $('<span></span>').text(':'); |
| div.append(sub); |
| sub = $('<input id="add-breakpoint-line"></input>').val('123'); |
| div.append(sub); |
| sub = $('<button id="add-breakpoint-button"></button>').text('Add breakpoint'); |
| sub.on('click', function () { |
| socket.emit('add-breakpoint', { |
| fileName: $('#add-breakpoint-file').val(), |
| lineNumber: Number($('#add-breakpoint-line').val()) |
| }); |
| }); |
| div.append(sub); |
| sub = $('<span id="breakpoint-hint"></span>').text('or dblclick source'); |
| div.append(sub); |
| elem.append(div); |
| |
| // Active breakpoints follow |
| msg.breakpoints.forEach(function (bp) { |
| var div; |
| var sub; |
| |
| div = $('<div class="breakpoint-line"></div>'); |
| sub = $('<button class="delete-breakpoint-button"></button>').text('Delete'); |
| sub.on('click', function () { |
| socket.emit('delete-breakpoint', { |
| fileName: bp.fileName, |
| lineNumber: bp.lineNumber |
| }); |
| }); |
| div.append(sub); |
| sub = $('<a></a>').text((bp.fileName || '?') + ':' + (bp.lineNumber || 0)); |
| sub.on('click', function () { |
| activeFileName = bp.fileName || ''; |
| activeLine = bp.lineNumber || 1; |
| activeHighlight = activeLine; |
| requestSourceRefetch(); |
| }); |
| div.append(sub); |
| elem.append(div); |
| }); |
| |
| forceButtonUpdate = true; |
| doUiUpdate(); |
| }); |
| |
| socket.on('eval-result', function (msg) { |
| $('#eval-output').text((msg.error ? 'ERROR: ' : '') + msg.result); |
| |
| // Remove eval button "pulsating" glow when we get a result |
| $('#eval-button').removeClass('pending'); |
| }); |
| |
| socket.on('getvar-result', function (msg) { |
| $('#var-output').text(msg.found ? msg.result : 'NOTFOUND'); |
| }); |
| |
| socket.on('bytecode', function (msg) { |
| var elem, div; |
| var div; |
| |
| elem = $('#bytecode-preformatted'); |
| elem.empty(); |
| |
| msg.preformatted.split('\n').forEach(function (line, idx) { |
| div = $('<div></div>'); |
| div.text(line); |
| elem.append(div); |
| }); |
| |
| bytecodeIdxHighlight = null; |
| bytecodeIdxInstr = msg.idxPreformattedInstructions; |
| }); |
| |
| $('#stepinto-button').click(function () { |
| socket.emit('stepinto', {}); |
| }); |
| |
| $('#stepover-button').click(function () { |
| socket.emit('stepover', {}); |
| }); |
| |
| $('#stepout-button').click(function () { |
| socket.emit('stepout', {}); |
| }); |
| |
| $('#pause-button').click(function () { |
| socket.emit('pause', {}); |
| |
| // Pause may take seconds to complete so indicate it is pending. |
| $('#pause-button').addClass('pending'); |
| }); |
| |
| $('#resume-button').click(function () { |
| socket.emit('resume', {}); |
| }); |
| |
| $('#attach-button').click(function () { |
| socket.emit('attach', {}); |
| }); |
| |
| $('#detach-button').click(function () { |
| socket.emit('detach', {}); |
| }); |
| |
| $('#about-button').click(function () { |
| $('#about-dialog').dialog('open'); |
| }); |
| |
| $('#show-bytecode-button').click(function () { |
| bytecodeDialogOpen = true; |
| $('#bytecode-dialog').dialog('open'); |
| |
| elem = $('#bytecode-preformatted'); |
| elem.empty().text('Loading bytecode...'); |
| |
| socket.emit('get-bytecode', {}); |
| }); |
| |
| function submitEval() { |
| socket.emit('eval', { input: $('#eval-input').val() }); |
| |
| // Eval may take seconds to complete so indicate it is pending. |
| $('#eval-button').addClass('pending'); |
| } |
| |
| $('#eval-button').click(function () { |
| submitEval(); |
| $('#eval-input').val(''); |
| }); |
| |
| $('#getvar-button').click(function () { |
| socket.emit('getvar', { varname: $('#varname-input').val() }); |
| }); |
| |
| $('#putvar-button').click(function () { |
| // The variable value is parsed as JSON right now, but it'd be better to |
| // also be able to parse buffer values etc. |
| var val = JSON.parse($('#varvalue-input').val()); |
| socket.emit('putvar', { varname: $('#varname-input').val(), varvalue: val }); |
| }); |
| |
| $('#source-code').dblclick(function (event) { |
| var target = event.target; |
| var elems = $('#source-code div'); |
| var i, n; |
| var line = 0; |
| |
| // XXX: any faster way; elems doesn't have e.g. indexOf() |
| for (i = 0, n = elems.length; i < n; i++) { |
| if (target === elems[i]) { |
| line = i + 1; |
| } |
| } |
| |
| socket.emit('toggle-breakpoint', { |
| fileName: loadedFileName, |
| lineNumber: line |
| }); |
| }); |
| |
| function setSourceText(data) { |
| var elem, div; |
| |
| elem = $('#source-code'); |
| elem.empty(); |
| data.split('\n').forEach(function (line) { |
| div = $('<div></div>'); |
| div.text(line); |
| elem.append(div); |
| }); |
| |
| sourceEditedLines = []; |
| } |
| |
| function setSourceSelect(fileName) { |
| var elem; |
| var i, n, t; |
| |
| if (fileName == null) { |
| $('#source-select').val('__none__'); |
| return; |
| } |
| |
| elem = $('#source-select option'); |
| for (i = 0, n = elem.length; i < n; i++) { |
| // Exact match is required. |
| t = $(elem[i]).val(); |
| if (t === fileName) { |
| $('#source-select').val(t); |
| return; |
| } |
| } |
| } |
| |
| /* |
| * AJAX request handling to fetch source files |
| */ |
| |
| function requestSourceRefetch() { |
| // If previous update is pending, abort and start a new one. |
| if (sourceFetchXhr) { |
| sourceFetchXhr.abort(); |
| sourceFetchXhr = null; |
| } |
| |
| // Make copies of the requested file/line so that we have the proper |
| // values in case they've changed. |
| var fileName = activeFileName; |
| var lineNumber = activeLine; |
| |
| // AJAX request for the source. |
| sourceFetchXhr = $.ajax({ |
| type: 'POST', |
| url: '/source', |
| data: JSON.stringify({ fileName: fileName }), |
| contentType: 'application/json', |
| success: function (data, status, jqxhr) { |
| var elem; |
| |
| sourceFetchXhr = null; |
| |
| loadedFileName = fileName; |
| loadedLineCount = data.split('\n').length; // XXX: ignore issue with last empty line for now |
| loadedFileExecuting = (loadedFileName === currFileName); |
| setSourceText(data); |
| setSourceSelect(fileName); |
| loadedLinePending = activeLine || 1; |
| highlightLine = activeHighlight; // may be null |
| activeLine = null; |
| activeHighlight = null; |
| doSourceUpdate(); |
| |
| // XXX: hacky transition, make source change visible |
| $('#source-pre').fadeTo('fast', 0.25, function () { |
| $('#source-pre').fadeTo('fast', 1.0); |
| }); |
| }, |
| error: function (jqxhr, status, err) { |
| // Not worth alerting about because source fetch errors happen |
| // all the time, e.g. for dynamically evaluated code. |
| |
| sourceFetchXhr = null; |
| |
| // XXX: prevent retry of no-such-file by negative caching? |
| loadedFileName = fileName; |
| loadedLineCount = 1; |
| loadedFileExecuting = false; |
| setSourceText('// Cannot load source file: ' + fileName); |
| setSourceSelect(null); |
| loadedLinePending = 1; |
| activeLine = null; |
| activeHighlight = null; |
| doSourceUpdate(); |
| |
| // XXX: error transition here |
| $('#source-pre').fadeTo('fast', 0.25, function () { |
| $('#source-pre').fadeTo('fast', 1.0); |
| }); |
| }, |
| dataType: 'text' |
| }); |
| } |
| |
| /* |
| * AJAX request for fetching the source list |
| */ |
| |
| function fetchSourceList() { |
| $.ajax({ |
| type: 'POST', |
| url: '/sourceList', |
| data: JSON.stringify({}), |
| contentType: 'application/json', |
| success: function (data, status, jqxhr) { |
| var elem = $('#source-select'); |
| |
| data = JSON.parse(data); |
| |
| elem.empty(); |
| var opt = $('<option></option>').attr({ 'value': '__none__' }).text('No source file selected'); |
| elem.append(opt); |
| data.forEach(function (ent) { |
| var opt = $('<option></option>').attr({ 'value': ent }).text(ent); |
| elem.append(opt); |
| }); |
| elem.change(function () { |
| activeFileName = elem.val(); |
| activeLine = 1; |
| requestSourceRefetch(); |
| }); |
| }, |
| error: function (jqxhr, status, err) { |
| // This is worth alerting about as the UI is somewhat unusable |
| // if we don't get a source list. |
| |
| alert('Failed to load source list: ' + err); |
| }, |
| dataType: 'text' |
| }); |
| } |
| |
| /* |
| * Initialization |
| */ |
| |
| $(document).ready(function () { |
| var showAbout = true; |
| |
| // About dialog, shown automatically on first startup. |
| $('#about-dialog').dialog({ |
| autoOpen: false, |
| hide: 'fade', // puff |
| show: 'fade', // slide, puff |
| width: 500, |
| height: 300 |
| }); |
| |
| // Bytecode dialog |
| $('#bytecode-dialog').dialog({ |
| autoOpen: false, |
| hide: 'fade', // puff |
| show: 'fade', // slide, puff |
| width: 700, |
| height: 600, |
| close: function () { |
| bytecodeDialogOpen = false; |
| bytecodeIdxHighlight = null; |
| bytecodeIdxInstr = 0; |
| } |
| }); |
| |
| // http://diveintohtml5.info/storage.html |
| if (typeof localStorage !== 'undefined') { |
| if (localStorage.getItem('about-shown')) { |
| showAbout = false; |
| } else { |
| localStorage.setItem('about-shown', 'yes'); |
| } |
| } |
| if (showAbout) { |
| $('#about-dialog').dialog('open'); |
| } |
| |
| // onclick handler for exec status text |
| function loadCurrFunc() { |
| activeFileName = currFileName; |
| activeLine = currLine; |
| requestSourceRefetch(); |
| } |
| $('#exec-other').on('click', loadCurrFunc); |
| |
| // Enter handling for eval input |
| // https://forum.jquery.com/topic/bind-html-input-to-enter-key-keypress |
| $('#eval-input').keypress(function (event) { |
| if (event.keyCode == 13) { |
| submitEval(); |
| $('#eval-input').val(''); |
| } |
| }); |
| |
| // Eval watch handling |
| $('#eval-watch').change(function () { |
| // nop |
| }); |
| |
| forceButtonUpdate = true; |
| doUiUpdate(); |
| }); |