]> Cypherpunks repositories - gostls13.git/commitdiff
doc: update playground.js
authorAndrew Gerrand <adg@golang.org>
Thu, 4 Jul 2013 04:24:21 +0000 (14:24 +1000)
committerAndrew Gerrand <adg@golang.org>
Thu, 4 Jul 2013 04:24:21 +0000 (14:24 +1000)
R=dsymonds
CC=golang-dev
https://golang.org/cl/10933044

doc/play/playground.js
doc/style.css

index 709136627ba548544bad6129f26d204b6ca3b333..849015d1db6511cee6ad608f5dff63f3cef6c90a 100644 (file)
 // 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);
-       }
+// This is a copy of present/js/playground.js from the repository at
+// https://code.google.com/p/go.talks
+// Please make changes to that repository.
 
-       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']);
+/*
+In the absence of any formal way to specify interfaces in JavaScript,
+here's a skeleton implementation of a playground transport.
 
-       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 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) {
+        }
+*/
+
+function HTTPTransport() {
+       'use strict';
+
+       // TODO(adg): support stderr
+
+       function playback(output, events) {
+               var timeout;
+               output({Kind: 'start'});
                function next() {
-                       if (events.length == 0) {
-                               var exit = $('<span class="exit"/>');
-                               exit.text("\nProgram exited.");
-                               exit.appendTo(pre);
+                       if (events.length === 0) {
+                               output({Kind: 'end'});
                                return;
                        }
                        var e = events.shift();
-                       if (e.Delay == 0) {
-                               show(e.Message);
+                       if (e.Delay === 0) {
+                               output({Kind: 'stdout', Body: e.Message});
                                next();
-                       } else {
-                               playbackTimeout = setTimeout(function() {
-                                       show(e.Message);
-                                       next();
-                               }, e.Delay / 1000000);
+                               return;
                        }
+                       timeout = setTimeout(function() {
+                               output({Kind: 'stdout', Body: 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");
+               return {
+                       Stop: function() {
+                               clearTimeout(timeout);
                        }
-                       $("<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;
-               }
+       function error(output, msg) {
+               output({Kind: 'start'});
+               output({Kind: 'stderr', Body: msg});
+               output({Kind: 'end'});
+       }
 
-               // Play back events.
-               if (events !== null) {
-                       var pre = $("<pre/>").appendTo(output);
-                       playback(pre, events);
+       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');
        }
 
-       var pushedEmpty = (window.location.pathname == "/");
-       function inputChanged() {
-               if (pushedEmpty) {
+       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;
                }
-               pushedEmpty = true;
+               output({Kind: m.Kind, Body: m.Body});
+       }
 
-               $(opts['shareURLEl']).hide();
-               window.history.pushState(null, "", "/");
+       function send(m) {
+               websocket.send(JSON.stringify(m));
        }
 
-       function popState(e) {
-               if (e == null) {
-                       return;
+       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';
 
-               if (e && e.state && e.state.code) {
-                       setBody(e.state.code);
+       return function(write) {
+               if (write.Kind == 'start') {
+                       el.innerHTML = '';
+                       return;
                }
-       }
 
-       var rewriteHistory = false;
+               var cl = 'system';
+               if (write.Kind == 'stdout' || write.Kind == 'stderr')
+                       cl = write.Kind;
 
-       if (window.history &&
-               window.history.pushState &&
-               window.addEventListener &&
-               opts['enableHistory']) {
-               rewriteHistory = true;
-               code[0].addEventListener('input', inputChanged);
-               window.addEventListener('popstate', popState)
-       }
+               var m = write.Body;
+               if (write.Kind == 'end') 
+                       m = '\nProgram exited' + (m?(': '+m):'.');
 
-       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 (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;
+               }
 
-       if (opts['shareEl'] != null && (opts['shareURLEl'] != null || opts['shareRedirect'] != null)) {
-               var shareURL;
-               if (opts['shareURLEl']) {
-                       shareURL = $(opts['shareURLEl']).hide();
+               // ^L clears the screen.
+               var s = m.split('\x0c');
+               if (s.length > 1) {
+                       el.innerHTML = '';
+                       m = s.pop();
                }
-               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);
-                               }
-                       });
-               });
+               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);
+    while (r) {
+      $(".lines div").eq(r[1]-1).addClass("lineerror");
+      r = regex.exec(error);
+    }
+  }
+  function highlightOutput(wrappedOutput) {
+    return function(write) {
+      if (write.Body) lineHighlight(write.Body);
+      wrappedOutput(write);
+    }
+  }
+  function lineClear() {
+    $(".lineerror").removeClass("lineerror");
+  }
+
+  // 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)
+  //  transport - playground transport to use (default is HTTPTransport)
+  function playground(opts) {
+    var code = $(opts.codeEl);
+    var transport = opts['transport'] || new HTTPTransport();
+    var running;
+  
+    // 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 (running) running.Kill();
+      lineClear();
+      lineHighlight(error);
+      output.empty().addClass("error").text(error);
+    }
+    function loading() {
+      lineClear();
+      if (running) running.Kill();
+      output.removeClass("error").text('Waiting for remote server...');
+    }
+    function run() {
+      loading();
+      running = transport.Run(body(), highlightOutput(PlaygroundOutput(output[0])));
+    }
+    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.playground = playground;
+})();
index ca8006b8dc5c51d862d5935344ae1b76e5c409fa..37b0e32c2263733f2eb286c52ad3e6acfdd2f369 100644 (file)
@@ -497,7 +497,10 @@ div.play .buttons a {
        padding: 10px;
        cursor: pointer;
 }
-div.play .output .exit {
+.output .stderr {
+       color: #933;
+}
+.output .system {
        color: #999;
 }