diff options
author | Andrew Gerrand <adg@golang.org> | 2013-09-17 17:14:57 +1000 |
---|---|---|
committer | Andrew Gerrand <adg@golang.org> | 2013-09-17 17:14:57 +1000 |
commit | 7b08321066f219dca44c9447fd63d32cf05ff733 (patch) | |
tree | 6630df33f0347125513937c1a283ea9a2acda991 /static/play/playground.js | |
parent | 18e34e7ae7fa717b074f7fad0fed540222faf43c (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.js | 330 |
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, '&'); + m = m.replace(/</g, '<'); + m = m.replace(/>/g, '>'); + + 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; - })(); |