text: // 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.
  
  /*
  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 connection).
                  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) {
          }
  */
  
  // HTTPTransport is the default transport.
  // enableVet enables running vet if a program was compiled and ran successfully.
  // If vet returned any errors, display them before the output of a program.
  function HTTPTransport(enableVet) {
    'use strict';
  
    function playback(output, data) {
      // Backwards compatibility: default values do not affect the output.
      var events = data.Events || [];
      var errors = data.Errors || '';
      var status = data.Status || 0;
      var isTest = data.IsTest || false;
      var testsFailed = data.TestsFailed || 0;
  
      var timeout;
      output({ Kind: 'start' });
      function next() {
        if (!events || events.length === 0) {
          if (isTest) {
            if (testsFailed > 0) {
              output({
                Kind: 'system',
                Body:
                  '\n' +
                  testsFailed +
                  ' test' +
                  (testsFailed > 1 ? 's' : '') +
                  ' failed.',
              });
            } else {
              output({ Kind: 'system', Body: '\nAll tests passed.' });
            }
          } else {
            if (status > 0) {
              output({ Kind: 'end', Body: 'status ' + status + '.' });
            } else {
              if (errors !== '') {
                // errors are displayed only in the case of timeout.
                output({ Kind: 'end', Body: errors + '.' });
              } else {
                output({ Kind: 'end' });
              }
            }
          }
          return;
        }
        var e = events.shift();
        if (e.Delay === 0) {
          output({ Kind: e.Kind, Body: e.Message });
          next();
          return;
        }
        timeout = setTimeout(function() {
          output({ Kind: e.Kind, 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' });
    }
  
    function buildFailed(output, msg) {
      output({ Kind: 'start' });
      output({ Kind: 'stderr', Body: msg });
      output({ Kind: 'system', Body: '\nGo build failed.' });
    }
  
    var seq = 0;
    return {
      Run: function(body, output, options) {
        seq++;
        var cur = seq;
        var playing;
        $.ajax('/compile', {
          type: 'POST',
          data: { version: 2, body: body, withVet: enableVet },
          dataType: 'json',
          success: function(data) {
            if (seq != cur) return;
            if (!data) return;
            if (playing != null) playing.Stop();
            if (data.Errors) {
              if (data.Errors === 'process took too long') {
                // Playback the output that was captured before the timeout.
                playing = playback(output, data);
              } else {
                buildFailed(output, data.Errors);
              }
              return;
            }
            if (!data.Events) {
              data.Events = [];
            }
            if (data.VetErrors) {
              // Inject errors from the vet as the first events in the output.
              data.Events.unshift({
                Message: 'Go vet exited.\n\n',
                Kind: 'system',
                Delay: 0,
              });
              data.Events.unshift({
                Message: data.VetErrors,
                Kind: 'stderr',
                Delay: 0,
              });
            }
  
            if (!enableVet || data.VetOK || data.VetErrors) {
              playing = playback(output, data);
              return;
            }
  
            // In case the server support doesn't support
            // compile+vet in same request signaled by the
            // 'withVet' parameter above, also try the old way.
            // TODO: remove this when it falls out of use.
            // It is 2019-05-13 now.
            $.ajax('/vet', {
              data: { body: body },
              type: 'POST',
              dataType: 'json',
              success: function(dataVet) {
                if (dataVet.Errors) {
                  // inject errors from the vet as the first events in the output
                  data.Events.unshift({
                    Message: 'Go vet exited.\n\n',
                    Kind: 'system',
                    Delay: 0,
                  });
                  data.Events.unshift({
                    Message: dataVet.Errors,
                    Kind: 'stderr',
                    Delay: 0,
                  });
                }
                playing = playback(output, data);
              },
              error: function() {
                playing = playback(output, data);
              },
            });
          },
          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;
    if (window.location.protocol == 'http:') {
      websocket = new WebSocket('ws://' + window.location.host + '/socket');
    } else if (window.location.protocol == 'https:') {
      websocket = new WebSocket('wss://' + 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));
    }
  
    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);
      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)
    //  fmtImportEl - fmt "imports" checkbox 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)
    //  enableShortcuts - whether to enable shortcuts (Ctrl+S/Cmd+S to save) (default is false)
    //  enableVet - enable running vet and displaying its errors
    function playground(opts) {
      var code = $(opts.codeEl);
      var transport = opts['transport'] || new HTTPTransport(opts['enableVet']);
      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);
      }
  
      // NOTE(cbro): e is a jQuery event, not a DOM event.
      function handleSaveShortcut(e) {
        if (e.isDefaultPrevented()) return false;
        if (!e.metaKey && !e.ctrlKey) return false;
        if (e.key != 'S' && e.key != 's') return false;
  
        e.preventDefault();
  
        // Share and save
        share(function(url) {
          window.location.href = url + '.go?download=true';
        });
  
        return true;
      }
  
      function keyHandler(e) {
        if (opts.enableShortcuts && handleSaveShortcut(e)) return;
  
        if (e.keyCode == 9 && !e.ctrlKey) {
          // tab (but not ctrl-tab)
          insertTabs(1);
          e.preventDefault();
          return false;
        }
        if (e.keyCode == 13) {
          // enter
          if (e.shiftKey) {
            // +shift
            run();
            e.preventDefault();
            return false;
          }
          if (e.ctrlKey) {
            // +control
            fmt();
            e.preventDefault();
          } 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();
        var data = { body: body() };
        if ($(opts.fmtImportEl).is(':checked')) {
          data['imports'] = 'true';
        }
        $.ajax('/fmt', {
          data: data,
          type: 'POST',
          dataType: 'json',
          success: function(data) {
            if (data.Error) {
              setError(data.Error);
            } else {
              setBody(data.Body);
              setError('');
            }
          },
        });
      }
  
      var shareURL; // jQuery element to show the shared URL.
      var sharing = false; // true if there is a pending request.
      var shareCallbacks = [];
      function share(opt_callback) {
        if (opt_callback) shareCallbacks.push(opt_callback);
  
        if (sharing) return;
        sharing = true;
  
        var sharingData = body();
        $.ajax('/share', {
          processData: false,
          data: sharingData,
          type: 'POST',
          contentType: 'text/plain; charset=utf-8',
          complete: function(xhr) {
            sharing = false;
            if (xhr.status != 200) {
              alert('Server error; try again.');
              return;
            }
            if (opts.shareRedirect) {
              window.location = opts.shareRedirect + xhr.responseText;
            }
            var path = '/p/' + xhr.responseText;
            var url = origin(window.location) + path;
  
            for (var i = 0; i < shareCallbacks.length; i++) {
              shareCallbacks[i](url);
            }
            shareCallbacks = [];
  
            if (shareURL) {
              shareURL
                .show()
                .val(url)
                .focus()
                .select();
  
              if (rewriteHistory) {
                var historyData = { code: sharingData };
                window.history.pushState(historyData, '', path);
                pushedEmpty = false;
              }
            }
          },
        });
      }
  
      $(opts.runEl).click(run);
      $(opts.fmtEl).click(fmt);
  
      if (
        opts.shareEl !== null &&
        (opts.shareURLEl !== null || opts.shareRedirect !== null)
      ) {
        if (opts.shareURLEl) {
          shareURL = $(opts.shareURLEl).hide();
        }
        $(opts.shareEl).click(function() {
          share();
        });
      }
  
      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;
  })();
  