blob: 709136627ba548544bad6129f26d204b6ca3b333 [file] [log] [blame]
// 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.
// 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']);
// 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 output = $(opts['outputEl']);
function body() {
return $(opts['codeEl']).val();
}
function setBody(text) {
$(opts['codeEl']).val(text);
}
function origin(href) {
return (""+href).split("/").slice(0, 3).join("/");
}
function loading() {
output.removeClass("error").html(
'<div class="loading">Waiting for remote server...</div>'
);
}
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();
output.empty();
$(".lineerror").removeClass("lineerror");
// Display errors.
if (error) {
output.addClass("error");
var regex = /prog.go:([0-9]+)/g;
var r;
while (r = regex.exec(error)) {
$(".lines div").eq(r[1]-1).addClass("lineerror");
}
$("<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);
}
}
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)
}
var seq = 0;
function run() {
loading();
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(null, data.Errors);
return;
}
setOutput(data.Events, false);
},
error: function() {
output.addClass("error").text(
"Error communicating with remote server."
);
}
});
}
$(opts['runEl']).click(run);
$(opts['fmtEl']).click(function() {
loading();
$.ajax("/fmt", {
data: {"body": body()},
type: "POST",
dataType: "json",
success: function(data) {
if (data.Error) {
setOutput(null, data.Error);
return;
}
setBody(data.Body);
setOutput(null);
}
});
});
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);
}
});
});
}
}