diff options
Diffstat (limited to '_includes/editor.js')
-rw-r--r-- | _includes/editor.js | 259 |
1 files changed, 259 insertions, 0 deletions
diff --git a/_includes/editor.js b/_includes/editor.js new file mode 100644 index 0000000..c99fe52 --- /dev/null +++ b/_includes/editor.js @@ -0,0 +1,259 @@ +(function () { + "use strict"; + // ECMAScript 6 Backwards compatability + if (typeof String.prototype.startsWith !== 'function') { + String.prototype.startsWith = function(str) { + return this.slice(0, str.length) === str; + }; + } + + // Regex for finding new lines + var newLineRegex = /(?:\r\n|\r|\n)/g; + + // Fetching DOM items + var activeCode = document.getElementById("active-code"); + var editorDiv = document.getElementById("editor"); + var staticCode = document.getElementById("static-code"); + var runButton = document.getElementById("run-code"); + var resultDiv = document.getElementById("result"); + var playLink = document.getElementById("playlink"); + + // Background colors for program result on success/error + var successColor = "#E2EEF6"; + var errorColor = "#F6E2E2"; + var warningColor = "#FFFBCB"; + + // Error message to return when there's a server failure + var errMsg = "The server encountered an error while running the program."; + + // Stores ACE editor markers (highights) for errors + var markers = []; + + // Status codes, because there are no enums in Javascript + var SUCCESS = 0; + var ERROR = 1; + var WARNING = 2; + + // JS exists, display ACE editor + staticCode.style.display = "none"; + activeCode.style.display = "block"; + + // Setting up ace editor + var editor = ace.edit("editor"); + var Range = ace.require('ace/range').Range; + editor.setTheme("ace/theme/chrome"); + editor.getSession().setMode("ace/mode/rust"); + editor.setShowPrintMargin(false); + editor.renderer.setShowGutter(false); + editor.setHighlightActiveLine(false); + + // Changes the height of the editor to match its contents + function updateEditorHeight() { + // http://stackoverflow.com/questions/11584061/ + var newHeight = editor.getSession().getScreenLength() + * editor.renderer.lineHeight + + editor.renderer.scrollBar.getWidth(); + + editorDiv.style.height = Math.ceil(newHeight).toString() + "px"; + editor.resize(); + } + + // Set initial size to match initial content + updateEditorHeight(); + + // Safely remove all content from the result div + function clearResultDiv() { + // Clearing the result div will break our reference to + // the playlink icon, so let's save it if it exists + var newPlayLink = document.getElementById("playlink"); + if (newPlayLink) { + playLink = resultDiv.removeChild(newPlayLink); + } + resultDiv.innerHTML = ""; + } + + function escapeHTML(unsafe) { + return unsafe + .replace(/&/g, "&") + .replace(/</g, "<") + .replace(/>/g, ">") + .replace(/"/g, """) + .replace(/'/g, "'") + .replace(newLineRegex, '<br />'); + } + + // Dispatches a XMLHttpRequest to the Rust playpen, running the program, and + // issues a callback to `callback` with the result (or null on error) + function runProgram(program, callback) { + var req = new XMLHttpRequest(); + var data = JSON.stringify({ + version: "beta", + optimize: "2", + code: program + }); + + // console.log("Sending", data); + req.open('POST', "https://play.rust-lang.org/evaluate.json", true); + req.onload = function(e) { + var statusCode = false; + var result = null; + + if (req.readyState === 4 && req.status === 200) { + result = JSON.parse(req.response); + + // handle application errors from playpen + if (typeof result['error'] === 'string') { + statusCode = ERROR; + result = 'Playpen Error: ' + result['error']; + } else if (typeof result['result'] === 'string') { + statusCode = SUCCESS; + result = result['result']; + + // handle rustc errors/warnings + // Need server support to get an accurate version of this. + if (result.indexOf("error:") !== -1) { + statusCode = ERROR; + } else if (result.indexOf("warning:") !== -1) { + statusCode = WARNING; + } + } + } + + callback(statusCode, result); + }; + + req.onerror = function(e) { + callback(false, null); + }; + + req.setRequestHeader("Content-Type", "application/json"); + req.send(data); + } + + // The callback to runProgram + function handleResult(statusCode, message) { + // Dispatch depending on result type + if (result == null) { + clearResultDiv(); + resultDiv.style.backgroundColor = errorColor; + resultDiv.innerHTML = errMsg; + } else if (statusCode === SUCCESS) { + handleSuccess(message); + } else if (statusCode === WARNING) { + handleWarning(message); + } else { + handleError(message); + } + } + + // Called on successful program run: display output and playground icon + function handleSuccess(message) { + resultDiv.style.backgroundColor = successColor; + displayOutput(escapeHTML(message), editor.getValue()); + } + + // Called when program run results in warning(s) + function handleWarning(message) { + resultDiv.style.backgroundColor = warningColor; + handleProblem(message, "warning"); + } + + // Called when program run results in error(s) + function handleError(message) { + resultDiv.style.backgroundColor = errorColor; + handleProblem(message, "error"); + } + + // Called on unsuccessful program run. Detects and prints problems (either + // warnings or errors) in program output and highlights relevant lines and text + // in the code. + function handleProblem(message, problem) { + // Getting list of ranges with problems + var lines = message.split(newLineRegex); + + // Cleaning up the message: keeps only relevant problem output + var cleanMessage = lines.map(function(line) { + if (line.startsWith("<anon>") || line.indexOf("^") !== -1) { + var errIndex = line.indexOf(problem + ": "); + if (errIndex !== -1) {return line.slice(errIndex);} + return ""; + } + + // Discard playpen messages, keep the rest + if (line.startsWith("playpen:")) {return "";} + return line; + }).filter(function(line) { + return line !== ""; + }).map(function(line) { + return escapeHTML(line); + }).join("<br />"); + + // Setting message + displayOutput(cleanMessage, editor.getValue()); + + // Highlighting the lines + var ranges = parseProblems(lines); + markers = ranges.map(function(range) { + return editor.getSession().addMarker(range, "ace-" + problem + "-line", + "fullLine", false); + }); + + // Highlighting the specific text + markers = markers.concat(ranges.map(function(range) { + return editor.getSession().addMarker(range, "ace-" + problem + "-text", + "text", false); + })); + } + + // Parses a problem message returning a list of ranges (row:col, row:col) where + // problems in the code have occured. + function parseProblems(lines) { + var ranges = []; + for (var i in lines) { + var line = lines[i]; + if (line.startsWith("<anon>:") && line.indexOf(": ") !== -1) { + var parts = line.split(/:\s?|\s+/, 5).slice(1, 5); + var ip = parts.map(function(p) { return parseInt(p, 10) - 1; }); + // console.log("line:", line, parts, ip); + ranges.push(new Range(ip[0], ip[1], ip[2], ip[3])); + } + } + + return ranges; + } + + // Registering handler for run button click + runButton.addEventListener("click", function(ev) { + resultDiv.style.display = "block"; + clearResultDiv(); + resultDiv.innerHTML = "Running..."; + + // clear previous markers, if any + markers.map(function(id) { editor.getSession().removeMarker(id); }); + + // Get the code, run the program + var program = editor.getValue(); + runProgram(program, handleResult); + }); + + // Display an output message and a link to the Rust playground + function displayOutput(message, program) { + var programUrl = "https://play.rust-lang.org/?code=" + + encodeURIComponent(program) + "&run=1"; + playLink.href = programUrl; + + clearResultDiv(); // clear resultDiv, then add + resultDiv.appendChild(playLink); // playLink icon and message + resultDiv.innerHTML += message; + } + + // Highlight active line when focused + editor.on('focus', function() { + editor.setHighlightActiveLine(true); + }); + + // Don't when not + editor.on('blur', function() { + editor.setHighlightActiveLine(false); + }); +}()); |