aboutsummaryrefslogtreecommitdiff
path: root/static/play/playground.js
diff options
context:
space:
mode:
authorAndrew Gerrand <adg@golang.org>2013-03-08 09:22:40 +1100
committerAndrew Gerrand <adg@golang.org>2013-03-08 09:22:40 +1100
commit0ee9872fcd5cf9023cb43a68faf67203cbe81295 (patch)
treefaa873e0163e1d1b35e86b812de77afd3da7f6bb /static/play/playground.js
parentc26ca9cbba2bc725e1d7d26754ac5167a44ddb76 (diff)
go.blog: blog server
R=golang-dev, dsymonds, r CC=golang-dev https://golang.org/cl/7382064
Diffstat (limited to 'static/play/playground.js')
-rw-r--r--static/play/playground.js323
1 files changed, 323 insertions, 0 deletions
diff --git a/static/play/playground.js b/static/play/playground.js
new file mode 100644
index 0000000..67961c2
--- /dev/null
+++ b/static/play/playground.js
@@ -0,0 +1,323 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+(function() {
+
+ // TODO(adg): make these functions operate only on a specific code div
+ function lineHighlight(error) {
+ var regex = /prog.go:([0-9]+)/g;
+ var r = regex.exec(error);
+ while (r) {
+ $(".lines div").eq(r[1]-1).addClass("lineerror");
+ r = regex.exec(error);
+ }
+ }
+ function lineClear() {
+ $(".lineerror").removeClass("lineerror");
+ }
+
+ function connectPlayground() {
+ var playbackTimeout;
+
+ function playback(pre, events) {
+ function show(msg) {
+ // ^L clears the screen.
+ var msgs = msg.split("\x0c");
+ if (msgs.length == 1) {
+ pre.text(pre.text() + msg);
+ return;
+ }
+ pre.text(msgs.pop());
+ }
+ function next() {
+ if (events.length === 0) {
+ var exit = $('<span class="exit"/>');
+ exit.text("\nProgram exited.");
+ exit.appendTo(pre);
+ return;
+ }
+ var e = events.shift();
+ if (e.Delay === 0) {
+ show(e.Message);
+ next();
+ } else {
+ playbackTimeout = setTimeout(function() {
+ show(e.Message);
+ next();
+ }, e.Delay / 1000000);
+ }
+ }
+ next();
+ }
+
+ function stopPlayback() {
+ clearTimeout(playbackTimeout);
+ }
+
+ function setOutput(output, events, error) {
+ stopPlayback();
+ output.empty();
+ lineClear();
+
+ // Display errors.
+ if (error) {
+ lineHighlight(error);
+ output.addClass("error").text(error);
+ return;
+ }
+
+ // Display image output.
+ if (events.length > 0 && events[0].Message.indexOf("IMAGE:") === 0) {
+ var out = "";
+ for (var i = 0; i < events.length; i++) {
+ out += events[i].Message;
+ }
+ var url = "data:image/png;base64," + out.substr(6);
+ $("<img/>").attr("src", url).appendTo(output);
+ return;
+ }
+
+ // Play back events.
+ if (events !== null) {
+ playback(output, events);
+ }
+ }
+
+ var seq = 0;
+ function runFunc(body, output) {
+ output = $(output);
+ seq++;
+ var cur = seq;
+ var data = {
+ "version": 2,
+ "body": body
+ };
+ $.ajax("/compile", {
+ data: data,
+ type: "POST",
+ dataType: "json",
+ success: function(data) {
+ if (seq != cur) {
+ return;
+ }
+ if (!data) {
+ return;
+ }
+ if (data.Errors) {
+ setOutput(output, null, data.Errors);
+ return;
+ }
+ setOutput(output, data.Events, false);
+ },
+ error: function() {
+ output.addClass("error").text(
+ "Error communicating with remote server."
+ );
+ }
+ });
+ return stopPlayback;
+ }
+
+ return runFunc;
+ }
+
+ // opts is an object with these keys
+ // codeEl - code editor element
+ // outputEl - program output element
+ // runEl - run button element
+ // fmtEl - fmt button element (optional)
+ // shareEl - share button element (optional)
+ // shareURLEl - share URL text input element (optional)
+ // shareRedirect - base URL to redirect to on share (optional)
+ // toysEl - toys select element (optional)
+ // enableHistory - enable using HTML5 history API (optional)
+ function playground(opts) {
+ var code = $(opts.codeEl);
+ var runFunc = connectPlayground();
+ var stopFunc;
+
+ // autoindent helpers.
+ function insertTabs(n) {
+ // find the selection start and end
+ var start = code[0].selectionStart;
+ var end = code[0].selectionEnd;
+ // split the textarea content into two, and insert n tabs
+ var v = code[0].value;
+ var u = v.substr(0, start);
+ for (var i=0; i<n; i++) {
+ u += "\t";
+ }
+ u += v.substr(end);
+ // set revised content
+ code[0].value = u;
+ // reset caret position after inserted tabs
+ code[0].selectionStart = start+n;
+ code[0].selectionEnd = start+n;
+ }
+ function autoindent(el) {
+ var curpos = el.selectionStart;
+ var tabs = 0;
+ while (curpos > 0) {
+ curpos--;
+ if (el.value[curpos] == "\t") {
+ tabs++;
+ } else if (tabs > 0 || el.value[curpos] == "\n") {
+ break;
+ }
+ }
+ setTimeout(function() {
+ insertTabs(tabs);
+ }, 1);
+ }
+
+ function keyHandler(e) {
+ if (e.keyCode == 9) { // tab
+ insertTabs(1);
+ e.preventDefault();
+ return false;
+ }
+ if (e.keyCode == 13) { // enter
+ if (e.shiftKey) { // +shift
+ run();
+ e.preventDefault();
+ return false;
+ } else {
+ autoindent(e.target);
+ }
+ }
+ return true;
+ }
+ code.unbind('keydown').bind('keydown', keyHandler);
+ var outdiv = $(opts.outputEl).empty();
+ var output = $('<pre/>').appendTo(outdiv);
+
+ function body() {
+ return $(opts.codeEl).val();
+ }
+ function setBody(text) {
+ $(opts.codeEl).val(text);
+ }
+ function origin(href) {
+ return (""+href).split("/").slice(0, 3).join("/");
+ }
+
+ var pushedEmpty = (window.location.pathname == "/");
+ function inputChanged() {
+ if (pushedEmpty) {
+ return;
+ }
+ pushedEmpty = true;
+ $(opts.shareURLEl).hide();
+ window.history.pushState(null, "", "/");
+ }
+ function popState(e) {
+ if (e === null) {
+ return;
+ }
+ if (e && e.state && e.state.code) {
+ setBody(e.state.code);
+ }
+ }
+ var rewriteHistory = false;
+ if (window.history && window.history.pushState && window.addEventListener && opts.enableHistory) {
+ rewriteHistory = true;
+ code[0].addEventListener('input', inputChanged);
+ window.addEventListener('popstate', popState);
+ }
+
+ function setError(error) {
+ if (stopFunc) stopFunc();
+ lineClear();
+ lineHighlight(error);
+ output.empty().addClass("error").text(error);
+ }
+ function loading() {
+ if (stopFunc) stopFunc();
+ output.removeClass("error").text('Waiting for remote server...');
+ }
+ function run() {
+ loading();
+ stopFunc = runFunc(body(), output);
+ }
+ function fmt() {
+ loading();
+ $.ajax("/fmt", {
+ data: {"body": body()},
+ type: "POST",
+ dataType: "json",
+ success: function(data) {
+ if (data.Error) {
+ setError(data.Error);
+ } else {
+ setBody(data.Body);
+ setError("");
+ }
+ }
+ });
+ }
+
+ $(opts.runEl).click(run);
+ $(opts.fmtEl).click(fmt);
+
+ if (opts.shareEl !== null && (opts.shareURLEl !== null || opts.shareRedirect !== null)) {
+ var shareURL;
+ if (opts.shareURLEl) {
+ shareURL = $(opts.shareURLEl).hide();
+ }
+ var sharing = false;
+ $(opts.shareEl).click(function() {
+ if (sharing) return;
+ sharing = true;
+ var sharingData = body();
+ $.ajax("/share", {
+ processData: false,
+ data: sharingData,
+ type: "POST",
+ complete: function(xhr) {
+ sharing = false;
+ if (xhr.status != 200) {
+ alert("Server error; try again.");
+ return;
+ }
+ if (opts.shareRedirect) {
+ window.location = opts.shareRedirect + xhr.responseText;
+ }
+ if (shareURL) {
+ var path = "/p/" + xhr.responseText;
+ var url = origin(window.location) + path;
+ shareURL.show().val(url).focus().select();
+
+ if (rewriteHistory) {
+ var historyData = {"code": sharingData};
+ window.history.pushState(historyData, "", path);
+ pushedEmpty = false;
+ }
+ }
+ }
+ });
+ });
+ }
+
+ if (opts.toysEl !== null) {
+ $(opts.toysEl).bind('change', function() {
+ var toy = $(this).val();
+ $.ajax("/doc/play/"+toy, {
+ processData: false,
+ type: "GET",
+ complete: function(xhr) {
+ if (xhr.status != 200) {
+ alert("Server error; try again.");
+ return;
+ }
+ setBody(xhr.responseText);
+ }
+ });
+ });
+ }
+ }
+
+ window.connectPlayground = connectPlayground;
+ window.playground = playground;
+
+})();