github.com/jgarto/itcv@v0.0.0-20180826224514-4eea09c1aa0d/_vendor/src/golang.org/x/tools/godoc/static/playground.js (about)

     1  // Copyright 2012 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  /*
     6  In the absence of any formal way to specify interfaces in JavaScript,
     7  here's a skeleton implementation of a playground transport.
     8  
     9          function Transport() {
    10                  // Set up any transport state (eg, make a websocket connection).
    11                  return {
    12                          Run: function(body, output, options) {
    13                                  // Compile and run the program 'body' with 'options'.
    14  				// Call the 'output' callback to display program output.
    15                                  return {
    16                                          Kill: function() {
    17                                                  // Kill the running program.
    18                                          }
    19                                  };
    20                          }
    21                  };
    22          }
    23  
    24  	// The output callback is called multiple times, and each time it is
    25  	// passed an object of this form.
    26          var write = {
    27                  Kind: 'string', // 'start', 'stdout', 'stderr', 'end'
    28                  Body: 'string'  // content of write or end status message
    29          }
    30  
    31  	// The first call must be of Kind 'start' with no body.
    32  	// Subsequent calls may be of Kind 'stdout' or 'stderr'
    33  	// and must have a non-null Body string.
    34  	// The final call should be of Kind 'end' with an optional
    35  	// Body string, signifying a failure ("killed", for example).
    36  
    37  	// The output callback must be of this form.
    38  	// See PlaygroundOutput (below) for an implementation.
    39          function outputCallback(write) {
    40          }
    41  */
    42  
    43  function HTTPTransport() {
    44  	'use strict';
    45  
    46  	// TODO(adg): support stderr
    47  
    48  	function playback(output, events) {
    49  		var timeout;
    50  		output({Kind: 'start'});
    51  		function next() {
    52  			if (!events || events.length === 0) {
    53  				output({Kind: 'end'});
    54  				return;
    55  			}
    56  			var e = events.shift();
    57  			if (e.Delay === 0) {
    58  				output({Kind: 'stdout', Body: e.Message});
    59  				next();
    60  				return;
    61  			}
    62  			timeout = setTimeout(function() {
    63  				output({Kind: 'stdout', Body: e.Message});
    64  				next();
    65  			}, e.Delay / 1000000);
    66  		}
    67  		next();
    68  		return {
    69  			Stop: function() {
    70  				clearTimeout(timeout);
    71  			}
    72  		}
    73  	}
    74  
    75  	function error(output, msg) {
    76  		output({Kind: 'start'});
    77  		output({Kind: 'stderr', Body: msg});
    78  		output({Kind: 'end'});
    79  	}
    80  
    81  	var seq = 0;
    82  	return {
    83  		Run: function(body, output, options) {
    84  			seq++;
    85  			var cur = seq;
    86  			var playing;
    87  			$.ajax('/compile', {
    88  				type: 'POST',
    89  				data: {'version': 2, 'body': body},
    90  				dataType: 'json',
    91  				success: function(data) {
    92  					if (seq != cur) return;
    93  					if (!data) return;
    94  					if (playing != null) playing.Stop();
    95  					if (data.Errors) {
    96  						error(output, data.Errors);
    97  						return;
    98  					}
    99  					playing = playback(output, data.Events);
   100  				},
   101  				error: function() {
   102  					error(output, 'Error communicating with remote server.');
   103  				}
   104  			});
   105  			return {
   106  				Kill: function() {
   107  					if (playing != null) playing.Stop();
   108  					output({Kind: 'end', Body: 'killed'});
   109  				}
   110  			};
   111  		}
   112  	};
   113  }
   114  
   115  function SocketTransport() {
   116  	'use strict';
   117  
   118  	var id = 0;
   119  	var outputs = {};
   120  	var started = {};
   121  	var websocket;
   122  	if (window.location.protocol == "http:") {
   123  		websocket = new WebSocket('ws://' + window.location.host + '/socket');
   124  	} else if (window.location.protocol == "https:") {
   125  		websocket = new WebSocket('wss://' + window.location.host + '/socket');
   126  	}
   127  
   128  	websocket.onclose = function() {
   129  		console.log('websocket connection closed');
   130  	}
   131  
   132  	websocket.onmessage = function(e) {
   133  		var m = JSON.parse(e.data);
   134  		var output = outputs[m.Id];
   135  		if (output === null)
   136  			return;
   137  		if (!started[m.Id]) {
   138  			output({Kind: 'start'});
   139  			started[m.Id] = true;
   140  		}
   141  		output({Kind: m.Kind, Body: m.Body});
   142  	}
   143  
   144  	function send(m) {
   145  		websocket.send(JSON.stringify(m));
   146  	}
   147  
   148  	return {
   149  		Run: function(body, output, options) {
   150  			var thisID = id+'';
   151  			id++;
   152  			outputs[thisID] = output;
   153  			send({Id: thisID, Kind: 'run', Body: body, Options: options});
   154  			return {
   155  				Kill: function() {
   156  					send({Id: thisID, Kind: 'kill'});
   157  				}
   158  			};
   159  		}
   160  	};
   161  }
   162  
   163  function PlaygroundOutput(el) {
   164  	'use strict';
   165  
   166  	return function(write) {
   167  		if (write.Kind == 'start') {
   168  			el.innerHTML = '';
   169  			return;
   170  		}
   171  
   172  		var cl = 'system';
   173  		if (write.Kind == 'stdout' || write.Kind == 'stderr')
   174  			cl = write.Kind;
   175  
   176  		var m = write.Body;
   177  		if (write.Kind == 'end') {
   178  			m = '\nProgram exited' + (m?(': '+m):'.');
   179  		}
   180  
   181  		if (m.indexOf('IMAGE:') === 0) {
   182  			// TODO(adg): buffer all writes before creating image
   183  			var url = 'data:image/png;base64,' + m.substr(6);
   184  			var img = document.createElement('img');
   185  			img.src = url;
   186  			el.appendChild(img);
   187  			return;
   188  		}
   189  
   190  		// ^L clears the screen.
   191  		var s = m.split('\x0c');
   192  		if (s.length > 1) {
   193  			el.innerHTML = '';
   194  			m = s.pop();
   195  		}
   196  
   197  		m = m.replace(/&/g, '&');
   198  		m = m.replace(/</g, '&lt;');
   199  		m = m.replace(/>/g, '&gt;');
   200  
   201  		var needScroll = (el.scrollTop + el.offsetHeight) == el.scrollHeight;
   202  
   203  		var span = document.createElement('span');
   204  		span.className = cl;
   205  		span.innerHTML = m;
   206  		el.appendChild(span);
   207  
   208  		if (needScroll)
   209  			el.scrollTop = el.scrollHeight - el.offsetHeight;
   210  	}
   211  }
   212  
   213  (function() {
   214    function lineHighlight(error) {
   215      var regex = /prog.go:([0-9]+)/g;
   216      var r = regex.exec(error);
   217      while (r) {
   218        $(".lines div").eq(r[1]-1).addClass("lineerror");
   219        r = regex.exec(error);
   220      }
   221    }
   222    function highlightOutput(wrappedOutput) {
   223      return function(write) {
   224        if (write.Body) lineHighlight(write.Body);
   225        wrappedOutput(write);
   226      }
   227    }
   228    function lineClear() {
   229      $(".lineerror").removeClass("lineerror");
   230    }
   231  
   232    // opts is an object with these keys
   233    //  codeEl - code editor element
   234    //  outputEl - program output element
   235    //  runEl - run button element
   236    //  fmtEl - fmt button element (optional)
   237    //  fmtImportEl - fmt "imports" checkbox element (optional)
   238    //  shareEl - share button element (optional)
   239    //  shareURLEl - share URL text input element (optional)
   240    //  shareRedirect - base URL to redirect to on share (optional)
   241    //  vetEl - vet button element (optional)
   242    //  toysEl - toys select element (optional)
   243    //  enableHistory - enable using HTML5 history API (optional)
   244    //  transport - playground transport to use (default is HTTPTransport)
   245    //  enableShortcuts - whether to enable shortcuts (Ctrl+S/Cmd+S to save) (default is false)
   246    function playground(opts) {
   247      var code = $(opts.codeEl);
   248      var transport = opts['transport'] || new HTTPTransport();
   249      var running;
   250  
   251      // autoindent helpers.
   252      function insertTabs(n) {
   253        // find the selection start and end
   254        var start = code[0].selectionStart;
   255        var end   = code[0].selectionEnd;
   256        // split the textarea content into two, and insert n tabs
   257        var v = code[0].value;
   258        var u = v.substr(0, start);
   259        for (var i=0; i<n; i++) {
   260          u += "\t";
   261        }
   262        u += v.substr(end);
   263        // set revised content
   264        code[0].value = u;
   265        // reset caret position after inserted tabs
   266        code[0].selectionStart = start+n;
   267        code[0].selectionEnd = start+n;
   268      }
   269      function autoindent(el) {
   270        var curpos = el.selectionStart;
   271        var tabs = 0;
   272        while (curpos > 0) {
   273          curpos--;
   274          if (el.value[curpos] == "\t") {
   275            tabs++;
   276          } else if (tabs > 0 || el.value[curpos] == "\n") {
   277            break;
   278          }
   279        }
   280        setTimeout(function() {
   281          insertTabs(tabs);
   282        }, 1);
   283      }
   284  
   285      // NOTE(cbro): e is a jQuery event, not a DOM event.
   286      function handleSaveShortcut(e) {
   287        if (e.isDefaultPrevented()) return false;
   288        if (!e.metaKey && !e.ctrlKey) return false;
   289        if (e.key != "S" && e.key != "s") return false;
   290  
   291        e.preventDefault();
   292  
   293        // Share and save
   294        share(function(url) {
   295          window.location.href = url + ".go?download=true";
   296        });
   297  
   298        return true;
   299      }
   300  
   301      function keyHandler(e) {
   302        if (opts.enableShortcuts && handleSaveShortcut(e)) return;
   303  
   304        if (e.keyCode == 9 && !e.ctrlKey) { // tab (but not ctrl-tab)
   305          insertTabs(1);
   306          e.preventDefault();
   307          return false;
   308        }
   309        if (e.keyCode == 13) { // enter
   310          if (e.shiftKey) { // +shift
   311            run();
   312            e.preventDefault();
   313            return false;
   314          } if (e.ctrlKey) { // +control
   315            fmt();
   316            e.preventDefault();
   317          } else {
   318            autoindent(e.target);
   319          }
   320        }
   321        return true;
   322      }
   323      code.unbind('keydown').bind('keydown', keyHandler);
   324      var outdiv = $(opts.outputEl).empty();
   325      var output = $('<pre/>').appendTo(outdiv);
   326  
   327      function body() {
   328        return $(opts.codeEl).val();
   329      }
   330      function setBody(text) {
   331        $(opts.codeEl).val(text);
   332      }
   333      function origin(href) {
   334        return (""+href).split("/").slice(0, 3).join("/");
   335      }
   336  
   337      var pushedEmpty = (window.location.pathname == "/");
   338      function inputChanged() {
   339        if (pushedEmpty) {
   340          return;
   341        }
   342        pushedEmpty = true;
   343        $(opts.shareURLEl).hide();
   344        window.history.pushState(null, "", "/");
   345      }
   346      function popState(e) {
   347        if (e === null) {
   348          return;
   349        }
   350        if (e && e.state && e.state.code) {
   351          setBody(e.state.code);
   352        }
   353      }
   354      var rewriteHistory = false;
   355      if (window.history && window.history.pushState && window.addEventListener && opts.enableHistory) {
   356        rewriteHistory = true;
   357        code[0].addEventListener('input', inputChanged);
   358        window.addEventListener('popstate', popState);
   359      }
   360  
   361      function setError(error) {
   362        if (running) running.Kill();
   363        lineClear();
   364        lineHighlight(error);
   365        output.empty().addClass("error").text(error);
   366      }
   367      function loading() {
   368        lineClear();
   369        if (running) running.Kill();
   370        output.removeClass("error").text('Waiting for remote server...');
   371      }
   372      function noError() {
   373        lineClear();
   374        if (running) running.Kill();
   375        output.removeClass("error").text('No errors.');
   376      }
   377      function run() {
   378        loading();
   379        running = transport.Run(body(), highlightOutput(PlaygroundOutput(output[0])));
   380      }
   381  
   382      function fmt() {
   383        loading();
   384        var data = {"body": body()};
   385        if ($(opts.fmtImportEl).is(":checked")) {
   386          data["imports"] = "true";
   387        }
   388        $.ajax("/fmt", {
   389          data: data,
   390          type: "POST",
   391          dataType: "json",
   392          success: function(data) {
   393            if (data.Error) {
   394              setError(data.Error);
   395            } else {
   396              setBody(data.Body);
   397              setError("");
   398            }
   399          }
   400        });
   401      }
   402  
   403      function vet() {
   404        loading();
   405        var data = {"body": body()};
   406        $.ajax("/vet", {
   407          data: data,
   408          type: "POST",
   409          dataType: "json",
   410          success: function(data) {
   411            if (data.Errors) {
   412              setError(data.Errors);
   413            } else {
   414              noError();
   415            }
   416          },
   417          error: function() {
   418            setError('Error communicating with remote server.');
   419          }
   420        });
   421      }
   422  
   423      var shareURL; // jQuery element to show the shared URL.
   424      var sharing = false; // true if there is a pending request.
   425      var shareCallbacks = [];
   426      function share(opt_callback) {
   427        if (opt_callback) shareCallbacks.push(opt_callback);
   428  
   429        if (sharing) return;
   430        sharing = true;
   431  
   432        var sharingData = body();
   433        $.ajax("/share", {
   434          processData: false,
   435          data: sharingData,
   436          type: "POST",
   437          contentType: "text/plain; charset=utf-8",
   438          complete: function(xhr) {
   439            sharing = false;
   440            if (xhr.status != 200) {
   441              alert("Server error; try again.");
   442              return;
   443            }
   444            if (opts.shareRedirect) {
   445              window.location = opts.shareRedirect + xhr.responseText;
   446            }
   447            var path = "/p/" + xhr.responseText;
   448            var url = origin(window.location) + path;
   449  
   450            for (var i = 0; i < shareCallbacks.length; i++) {
   451              shareCallbacks[i](url);
   452            }
   453            shareCallbacks = [];
   454  
   455            if (shareURL) {
   456              shareURL.show().val(url).focus().select();
   457  
   458              if (rewriteHistory) {
   459                var historyData = {"code": sharingData};
   460                window.history.pushState(historyData, "", path);
   461                pushedEmpty = false;
   462              }
   463            }
   464          }
   465        });
   466      }
   467  
   468      $(opts.runEl).click(run);
   469      $(opts.fmtEl).click(fmt);
   470      $(opts.vetEl).click(vet);
   471  
   472      if (opts.shareEl !== null && (opts.shareURLEl !== null || opts.shareRedirect !== null)) {
   473        if (opts.shareURLEl) {
   474          shareURL = $(opts.shareURLEl).hide();
   475        }
   476        $(opts.shareEl).click(function() {
   477          share();
   478        });
   479      }
   480  
   481      if (opts.toysEl !== null) {
   482        $(opts.toysEl).bind('change', function() {
   483          var toy = $(this).val();
   484          $.ajax("/doc/play/"+toy, {
   485            processData: false,
   486            type: "GET",
   487            complete: function(xhr) {
   488              if (xhr.status != 200) {
   489                alert("Server error; try again.");
   490                return;
   491              }
   492              setBody(xhr.responseText);
   493            }
   494          });
   495        });
   496      }
   497    }
   498  
   499    window.playground = playground;
   500  })();