diff options
author | Andrew Gerrand <adg@golang.org> | 2013-03-08 09:22:40 +1100 |
---|---|---|
committer | Andrew Gerrand <adg@golang.org> | 2013-03-08 09:22:40 +1100 |
commit | 0ee9872fcd5cf9023cb43a68faf67203cbe81295 (patch) | |
tree | faa873e0163e1d1b35e86b812de77afd3da7f6bb /static/play/playground.js | |
parent | c26ca9cbba2bc725e1d7d26754ac5167a44ddb76 (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.js | 323 |
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; + +})(); |