2012-02-14 18:59:50 -07:00
|
|
|
// Copyright 2012 The Go Authors. All rights reserved.
|
2011-08-22 15:06:07 -06:00
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
2012-02-14 18:59:50 -07:00
|
|
|
// opts is an object with these keys
|
2012-10-10 16:53:37 -06:00
|
|
|
// codeEl - code editor element
|
|
|
|
// outputEl - program output element
|
2012-10-04 00:53:05 -06:00
|
|
|
// runEl - run button element
|
2012-10-10 16:53:37 -06:00
|
|
|
// 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)
|
2012-10-04 00:53:05 -06:00
|
|
|
// enableHistory - enable using HTML5 history API (optional)
|
2012-02-14 18:59:50 -07:00
|
|
|
function playground(opts) {
|
|
|
|
var code = $(opts['codeEl']);
|
|
|
|
|
2012-10-04 00:53:05 -06:00
|
|
|
// autoindent helpers.
|
2012-02-14 18:59:50 -07:00
|
|
|
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() {
|
2012-10-04 00:53:05 -06:00
|
|
|
insertTabs(tabs);
|
2012-02-14 18:59:50 -07:00
|
|
|
}, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
function keyHandler(e) {
|
2012-10-04 00:53:05 -06:00
|
|
|
if (e.keyCode == 9) { // tab
|
2012-02-14 18:59:50 -07:00
|
|
|
insertTabs(1);
|
|
|
|
e.preventDefault();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (e.keyCode == 13) { // enter
|
|
|
|
if (e.shiftKey) { // +shift
|
|
|
|
run();
|
|
|
|
e.preventDefault();
|
|
|
|
return false;
|
2012-10-04 00:53:05 -06:00
|
|
|
} else {
|
2012-02-14 18:59:50 -07:00
|
|
|
autoindent(e.target);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
2012-10-04 00:53:05 -06:00
|
|
|
code.unbind('keydown').bind('keydown', keyHandler);
|
2012-02-14 18:59:50 -07:00
|
|
|
var output = $(opts['outputEl']);
|
|
|
|
|
|
|
|
function body() {
|
|
|
|
return $(opts['codeEl']).val();
|
|
|
|
}
|
2012-03-15 00:44:47 -06:00
|
|
|
function setBody(text) {
|
|
|
|
$(opts['codeEl']).val(text);
|
|
|
|
}
|
|
|
|
function origin(href) {
|
|
|
|
return (""+href).split("/").slice(0, 3).join("/");
|
|
|
|
}
|
2012-04-02 16:10:21 -06:00
|
|
|
function loading() {
|
|
|
|
output.removeClass("error").html(
|
|
|
|
'<div class="loading">Waiting for remote server...</div>'
|
|
|
|
);
|
|
|
|
}
|
2012-12-12 20:32:03 -07:00
|
|
|
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(events, error) {
|
|
|
|
stopPlayback();
|
2012-04-02 16:10:21 -06:00
|
|
|
output.empty();
|
2012-10-04 00:53:05 -06:00
|
|
|
$(".lineerror").removeClass("lineerror");
|
2012-12-12 20:32:03 -07:00
|
|
|
|
|
|
|
// Display errors.
|
2012-04-02 16:10:21 -06:00
|
|
|
if (error) {
|
|
|
|
output.addClass("error");
|
2012-10-04 00:53:05 -06:00
|
|
|
var regex = /prog.go:([0-9]+)/g;
|
|
|
|
var r;
|
2012-12-12 20:32:03 -07:00
|
|
|
while (r = regex.exec(error)) {
|
2012-10-04 00:53:05 -06:00
|
|
|
$(".lines div").eq(r[1]-1).addClass("lineerror");
|
|
|
|
}
|
2012-12-12 20:32:03 -07:00
|
|
|
$("<pre/>").text(error).appendTo(output);
|
|
|
|
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) {
|
|
|
|
var pre = $("<pre/>").appendTo(output);
|
|
|
|
playback(pre, events);
|
2012-04-02 16:10:21 -06:00
|
|
|
}
|
|
|
|
}
|
2012-02-14 18:59:50 -07:00
|
|
|
|
2012-10-04 00:53:05 -06:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2012-02-14 18:59:50 -07:00
|
|
|
var seq = 0;
|
|
|
|
function run() {
|
2012-04-02 16:10:21 -06:00
|
|
|
loading();
|
2012-02-14 18:59:50 -07:00
|
|
|
seq++;
|
|
|
|
var cur = seq;
|
2012-12-12 20:32:03 -07:00
|
|
|
var data = {
|
|
|
|
"version": 2,
|
|
|
|
"body": body()
|
|
|
|
};
|
2012-02-14 18:59:50 -07:00
|
|
|
$.ajax("/compile", {
|
|
|
|
data: data,
|
|
|
|
type: "POST",
|
|
|
|
dataType: "json",
|
|
|
|
success: function(data) {
|
|
|
|
if (seq != cur) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!data) {
|
|
|
|
return;
|
|
|
|
}
|
2012-12-12 20:32:03 -07:00
|
|
|
if (data.Errors) {
|
|
|
|
setOutput(null, data.Errors);
|
2012-02-14 18:59:50 -07:00
|
|
|
return;
|
|
|
|
}
|
2012-12-12 20:32:03 -07:00
|
|
|
setOutput(data.Events, false);
|
2012-02-14 18:59:50 -07:00
|
|
|
},
|
2012-10-04 00:53:05 -06:00
|
|
|
error: function() {
|
|
|
|
output.addClass("error").text(
|
|
|
|
"Error communicating with remote server."
|
|
|
|
);
|
2012-02-14 18:59:50 -07:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
$(opts['runEl']).click(run);
|
|
|
|
|
2012-04-02 16:10:21 -06:00
|
|
|
$(opts['fmtEl']).click(function() {
|
|
|
|
loading();
|
|
|
|
$.ajax("/fmt", {
|
|
|
|
data: {"body": body()},
|
|
|
|
type: "POST",
|
|
|
|
dataType: "json",
|
|
|
|
success: function(data) {
|
|
|
|
if (data.Error) {
|
2012-12-12 20:32:03 -07:00
|
|
|
setOutput(null, data.Error);
|
2012-04-02 16:10:21 -06:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
setBody(data.Body);
|
2012-12-12 20:32:03 -07:00
|
|
|
setOutput(null);
|
2012-04-02 16:10:21 -06:00
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2012-03-15 00:44:47 -06:00
|
|
|
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;
|
2012-10-04 00:53:05 -06:00
|
|
|
var sharingData = body();
|
2012-03-15 00:44:47 -06:00
|
|
|
$.ajax("/share", {
|
|
|
|
processData: false,
|
2012-10-04 00:53:05 -06:00
|
|
|
data: sharingData,
|
2012-03-15 00:44:47 -06:00
|
|
|
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) {
|
2012-10-04 00:53:05 -06:00
|
|
|
var path = "/p/" + xhr.responseText
|
|
|
|
var url = origin(window.location) + path;
|
2012-03-15 00:44:47 -06:00
|
|
|
shareURL.show().val(url).focus().select();
|
2012-10-04 00:53:05 -06:00
|
|
|
|
|
|
|
if (rewriteHistory) {
|
|
|
|
var historyData = {
|
|
|
|
"code": sharingData,
|
|
|
|
};
|
|
|
|
window.history.pushState(historyData, "", path);
|
|
|
|
pushedEmpty = false;
|
|
|
|
}
|
2012-03-15 00:44:47 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
2012-02-14 18:59:50 -07:00
|
|
|
}
|
2012-10-10 16:53:37 -06:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2012-02-14 18:59:50 -07:00
|
|
|
}
|