aboutsummaryrefslogtreecommitdiff
path: root/static/play/playground.js
diff options
context:
space:
mode:
authorAndrew Gerrand <adg@golang.org>2013-09-17 17:14:57 +1000
committerAndrew Gerrand <adg@golang.org>2013-09-17 17:14:57 +1000
commit7b08321066f219dca44c9447fd63d32cf05ff733 (patch)
tree6630df33f0347125513937c1a283ea9a2acda991 /static/play/playground.js
parent18e34e7ae7fa717b074f7fad0fed540222faf43c (diff)
go.blog: move blog-specific JavaScript into root template
Also update playground JavaScripts to the latest version. This will make it easier for us to switch to godoc's static assets down the line. R=r, dsymonds CC=golang-dev https://golang.org/cl/13412049
Diffstat (limited to 'static/play/playground.js')
-rw-r--r--static/play/playground.js330
1 files changed, 216 insertions, 114 deletions
diff --git a/static/play/playground.js b/static/play/playground.js
index 67961c2..d2be24c 100644
--- a/static/play/playground.js
+++ b/static/play/playground.js
@@ -2,9 +2,210 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-(function() {
+/*
+In the absence of any formal way to specify interfaces in JavaScript,
+here's a skeleton implementation of a playground transport.
+
+ function Transport() {
+ // Set up any transport state (eg, make a websocket connnection).
+ return {
+ Run: function(body, output, options) {
+ // Compile and run the program 'body' with 'options'.
+ // Call the 'output' callback to display program output.
+ return {
+ Kill: function() {
+ // Kill the running program.
+ }
+ };
+ }
+ };
+ }
+
+ // The output callback is called multiple times, and each time it is
+ // passed an object of this form.
+ var write = {
+ Kind: 'string', // 'start', 'stdout', 'stderr', 'end'
+ Body: 'string' // content of write or end status message
+ }
+
+ // The first call must be of Kind 'start' with no body.
+ // Subsequent calls may be of Kind 'stdout' or 'stderr'
+ // and must have a non-null Body string.
+ // The final call should be of Kind 'end' with an optional
+ // Body string, signifying a failure ("killed", for example).
+
+ // The output callback must be of this form.
+ // See PlaygroundOutput (below) for an implementation.
+ function outputCallback(write) {
+ // Append writes to
+ }
+*/
+
+function HTTPTransport() {
+ 'use strict';
+
+ // TODO(adg): support stderr
+
+ function playback(output, events) {
+ var timeout;
+ output({Kind: 'start'});
+ function next() {
+ if (events.length === 0) {
+ output({Kind: 'end'});
+ return;
+ }
+ var e = events.shift();
+ if (e.Delay === 0) {
+ output({Kind: 'stdout', Body: e.Message});
+ next();
+ return;
+ }
+ timeout = setTimeout(function() {
+ output({Kind: 'stdout', Body: e.Message});
+ next();
+ }, e.Delay / 1000000);
+ }
+ next();
+ return {
+ Stop: function() {
+ clearTimeout(timeout);
+ }
+ }
+ }
+
+ function error(output, msg) {
+ output({Kind: 'start'});
+ output({Kind: 'stderr', Body: msg});
+ output({Kind: 'end'});
+ }
+
+ var seq = 0;
+ return {
+ Run: function(body, output, options) {
+ seq++;
+ var cur = seq;
+ var playing;
+ $.ajax('/compile', {
+ type: 'POST',
+ data: {'version': 2, 'body': body},
+ dataType: 'json',
+ success: function(data) {
+ if (seq != cur) return;
+ if (!data) return;
+ if (playing != null) playing.Stop();
+ if (data.Errors) {
+ error(output, data.Errors);
+ return;
+ }
+ playing = playback(output, data.Events);
+ },
+ error: function() {
+ error(output, 'Error communicating with remote server.');
+ }
+ });
+ return {
+ Kill: function() {
+ if (playing != null) playing.Stop();
+ output({Kind: 'end', Body: 'killed'});
+ }
+ };
+ }
+ };
+}
+
+function SocketTransport() {
+ 'use strict';
+
+ var id = 0;
+ var outputs = {};
+ var started = {};
+ var websocket = new WebSocket('ws://' + window.location.host + '/socket');
+
+ websocket.onclose = function() {
+ console.log('websocket connection closed');
+ }
+
+ websocket.onmessage = function(e) {
+ var m = JSON.parse(e.data);
+ var output = outputs[m.Id];
+ if (output === null)
+ return;
+ if (!started[m.Id]) {
+ output({Kind: 'start'});
+ started[m.Id] = true;
+ }
+ output({Kind: m.Kind, Body: m.Body});
+ }
+
+ function send(m) {
+ websocket.send(JSON.stringify(m));
+ }
- // TODO(adg): make these functions operate only on a specific code div
+ return {
+ Run: function(body, output, options) {
+ var thisID = id+'';
+ id++;
+ outputs[thisID] = output;
+ send({Id: thisID, Kind: 'run', Body: body, Options: options});
+ return {
+ Kill: function() {
+ send({Id: thisID, Kind: 'kill'});
+ }
+ };
+ }
+ };
+}
+
+function PlaygroundOutput(el) {
+ 'use strict';
+
+ return function(write) {
+ if (write.Kind == 'start') {
+ el.innerHTML = '';
+ return;
+ }
+
+ var cl = 'system';
+ if (write.Kind == 'stdout' || write.Kind == 'stderr')
+ cl = write.Kind;
+
+ var m = write.Body;
+ if (write.Kind == 'end')
+ m = '\nProgram exited' + (m?(': '+m):'.');
+
+ if (m.indexOf('IMAGE:') === 0) {
+ // TODO(adg): buffer all writes before creating image
+ var url = 'data:image/png;base64,' + m.substr(6);
+ var img = document.createElement('img');
+ img.src = url;
+ el.appendChild(img);
+ return;
+ }
+
+ // ^L clears the screen.
+ var s = m.split('\x0c');
+ if (s.length > 1) {
+ el.innerHTML = '';
+ m = s.pop();
+ }
+
+ m = m.replace(/&/g, '&amp;');
+ m = m.replace(/</g, '&lt;');
+ m = m.replace(/>/g, '&gt;');
+
+ var needScroll = (el.scrollTop + el.offsetHeight) == el.scrollHeight;
+
+ var span = document.createElement('span');
+ span.className = cl;
+ span.innerHTML = m;
+ el.appendChild(span);
+
+ if (needScroll)
+ el.scrollTop = el.scrollHeight - el.offsetHeight;
+ }
+}
+
+(function() {
function lineHighlight(error) {
var regex = /prog.go:([0-9]+)/g;
var r = regex.exec(error);
@@ -13,115 +214,16 @@
r = regex.exec(error);
}
}
+ function highlightOutput(wrappedOutput) {
+ return function(write) {
+ if (write.Body) lineHighlight(write.Body);
+ wrappedOutput(write);
+ }
+ }
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
@@ -132,10 +234,11 @@
// shareRedirect - base URL to redirect to on share (optional)
// toysEl - toys select element (optional)
// enableHistory - enable using HTML5 history API (optional)
+ // transport - playground transport to use (default is HTTPTransport)
function playground(opts) {
var code = $(opts.codeEl);
- var runFunc = connectPlayground();
- var stopFunc;
+ var transport = opts['transport'] || new HTTPTransport();
+ var running;
// autoindent helpers.
function insertTabs(n) {
@@ -227,18 +330,19 @@
}
function setError(error) {
- if (stopFunc) stopFunc();
+ if (running) running.Kill();
lineClear();
lineHighlight(error);
output.empty().addClass("error").text(error);
}
function loading() {
- if (stopFunc) stopFunc();
+ lineClear();
+ if (running) running.Kill();
output.removeClass("error").text('Waiting for remote server...');
}
function run() {
loading();
- stopFunc = runFunc(body(), output);
+ running = transport.Run(body(), highlightOutput(PlaygroundOutput(output[0])));
}
function fmt() {
loading();
@@ -317,7 +421,5 @@
}
}
- window.connectPlayground = connectPlayground;
window.playground = playground;
-
})();