github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/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 = new WebSocket('ws://' + window.location.host + '/socket');
   122  
   123  	websocket.onclose = function() {
   124  		console.log('websocket connection closed');
   125  	}
   126  
   127  	websocket.onmessage = function(e) {
   128  		var m = JSON.parse(e.data);
   129  		var output = outputs[m.Id];
   130  		if (output === null)
   131  			return;
   132  		if (!started[m.Id]) {
   133  			output({Kind: 'start'});
   134  			started[m.Id] = true;
   135  		}
   136  		output({Kind: m.Kind, Body: m.Body});
   137  	}
   138  
   139  	function send(m) {
   140  		websocket.send(JSON.stringify(m));
   141  	}
   142  
   143  	return {
   144  		Run: function(body, output, options) {
   145  			var thisID = id+'';
   146  			id++;
   147  			outputs[thisID] = output;
   148  			send({Id: thisID, Kind: 'run', Body: body, Options: options});
   149  			return {
   150  				Kill: function() {
   151  					send({Id: thisID, Kind: 'kill'});
   152  				}
   153  			};
   154  		}
   155  	};
   156  }
   157  
   158  function PlaygroundOutput(el) {
   159  	'use strict';
   160  
   161  	return function(write) {
   162  		if (write.Kind == 'start') {
   163  			el.innerHTML = '';
   164  			return;
   165  		}
   166  
   167  		var cl = 'system';
   168  		if (write.Kind == 'stdout' || write.Kind == 'stderr')
   169  			cl = write.Kind;
   170  
   171  		var m = write.Body;
   172  		if (write.Kind == 'end') 
   173  			m = '\nProgram exited' + (m?(': '+m):'.');
   174  
   175  		if (m.indexOf('IMAGE:') === 0) {
   176  			// TODO(adg): buffer all writes before creating image
   177  			var url = 'data:image/png;base64,' + m.substr(6);
   178  			var img = document.createElement('img');
   179  			img.src = url;
   180  			el.appendChild(img);
   181  			return;
   182  		}
   183  
   184  		// ^L clears the screen.
   185  		var s = m.split('\x0c');
   186  		if (s.length > 1) {
   187  			el.innerHTML = '';
   188  			m = s.pop();
   189  		}
   190  
   191  		m = m.replace(/&/g, '&');
   192  		m = m.replace(/</g, '&lt;');
   193  		m = m.replace(/>/g, '&gt;');
   194  
   195  		var needScroll = (el.scrollTop + el.offsetHeight) == el.scrollHeight;
   196  
   197  		var span = document.createElement('span');
   198  		span.className = cl;
   199  		span.innerHTML = m;
   200  		el.appendChild(span);
   201  
   202  		if (needScroll)
   203  			el.scrollTop = el.scrollHeight - el.offsetHeight;
   204  	}
   205  }
   206  
   207  (function() {
   208    function lineHighlight(error) {
   209      var regex = /prog.go:([0-9]+)/g;
   210      var r = regex.exec(error);
   211      while (r) {
   212        $(".lines div").eq(r[1]-1).addClass("lineerror");
   213        r = regex.exec(error);
   214      }
   215    }
   216    function highlightOutput(wrappedOutput) {
   217      return function(write) {
   218        if (write.Body) lineHighlight(write.Body);
   219        wrappedOutput(write);
   220      }
   221    }
   222    function lineClear() {
   223      $(".lineerror").removeClass("lineerror");
   224    }
   225  
   226    // opts is an object with these keys
   227    //  codeEl - code editor element
   228    //  outputEl - program output element
   229    //  runEl - run button element
   230    //  fmtEl - fmt button element (optional)
   231    //  fmtImportEl - fmt "imports" checkbox element (optional)
   232    //  shareEl - share button element (optional)
   233    //  shareURLEl - share URL text input element (optional)
   234    //  shareRedirect - base URL to redirect to on share (optional)
   235    //  toysEl - toys select element (optional)
   236    //  enableHistory - enable using HTML5 history API (optional)
   237    //  transport - playground transport to use (default is HTTPTransport)
   238    function playground(opts) {
   239      var code = $(opts.codeEl);
   240      var transport = opts['transport'] || new HTTPTransport();
   241      var running;
   242    
   243      // autoindent helpers.
   244      function insertTabs(n) {
   245        // find the selection start and end
   246        var start = code[0].selectionStart;
   247        var end   = code[0].selectionEnd;
   248        // split the textarea content into two, and insert n tabs
   249        var v = code[0].value;
   250        var u = v.substr(0, start);
   251        for (var i=0; i<n; i++) {
   252          u += "\t";
   253        }
   254        u += v.substr(end);
   255        // set revised content
   256        code[0].value = u;
   257        // reset caret position after inserted tabs
   258        code[0].selectionStart = start+n;
   259        code[0].selectionEnd = start+n;
   260      }
   261      function autoindent(el) {
   262        var curpos = el.selectionStart;
   263        var tabs = 0;
   264        while (curpos > 0) {
   265          curpos--;
   266          if (el.value[curpos] == "\t") {
   267            tabs++;
   268          } else if (tabs > 0 || el.value[curpos] == "\n") {
   269            break;
   270          }
   271        }
   272        setTimeout(function() {
   273          insertTabs(tabs);
   274        }, 1);
   275      }
   276    
   277      function keyHandler(e) {
   278        if (e.keyCode == 9 && !e.ctrlKey) { // tab (but not ctrl-tab)
   279          insertTabs(1);
   280          e.preventDefault();
   281          return false;
   282        }
   283        if (e.keyCode == 13) { // enter
   284          if (e.shiftKey) { // +shift
   285            run();
   286            e.preventDefault();
   287            return false;
   288          } if (e.ctrlKey) { // +control
   289            fmt();
   290            e.preventDefault();
   291          } else {
   292            autoindent(e.target);
   293          }
   294        }
   295        return true;
   296      }
   297      code.unbind('keydown').bind('keydown', keyHandler);
   298      var outdiv = $(opts.outputEl).empty();
   299      var output = $('<pre/>').appendTo(outdiv);
   300    
   301      function body() {
   302        return $(opts.codeEl).val();
   303      }
   304      function setBody(text) {
   305        $(opts.codeEl).val(text);
   306      }
   307      function origin(href) {
   308        return (""+href).split("/").slice(0, 3).join("/");
   309      }
   310    
   311      var pushedEmpty = (window.location.pathname == "/");
   312      function inputChanged() {
   313        if (pushedEmpty) {
   314          return;
   315        }
   316        pushedEmpty = true;
   317        $(opts.shareURLEl).hide();
   318        window.history.pushState(null, "", "/");
   319      }
   320      function popState(e) {
   321        if (e === null) {
   322          return;
   323        }
   324        if (e && e.state && e.state.code) {
   325          setBody(e.state.code);
   326        }
   327      }
   328      var rewriteHistory = false;
   329      if (window.history && window.history.pushState && window.addEventListener && opts.enableHistory) {
   330        rewriteHistory = true;
   331        code[0].addEventListener('input', inputChanged);
   332        window.addEventListener('popstate', popState);
   333      }
   334  
   335      function setError(error) {
   336        if (running) running.Kill();
   337        lineClear();
   338        lineHighlight(error);
   339        output.empty().addClass("error").text(error);
   340      }
   341      function loading() {
   342        lineClear();
   343        if (running) running.Kill();
   344        output.removeClass("error").text('Waiting for remote server...');
   345      }
   346      function run() {
   347        loading();
   348        running = transport.Run(body(), highlightOutput(PlaygroundOutput(output[0])));
   349      }
   350  
   351      function fmt() {
   352        loading();
   353        var data = {"body": body()}; 
   354        if ($(opts.fmtImportEl).is(":checked")) {
   355          data["imports"] = "true";
   356        }
   357        $.ajax("/fmt", {
   358          data: data,
   359          type: "POST",
   360          dataType: "json",
   361          success: function(data) {
   362            if (data.Error) {
   363              setError(data.Error);
   364            } else {
   365              setBody(data.Body);
   366              setError("");
   367            }
   368          }
   369        });
   370      }
   371  
   372      $(opts.runEl).click(run);
   373      $(opts.fmtEl).click(fmt);
   374  
   375      if (opts.shareEl !== null && (opts.shareURLEl !== null || opts.shareRedirect !== null)) {
   376        var shareURL;
   377        if (opts.shareURLEl) {
   378          shareURL = $(opts.shareURLEl).hide();
   379        }
   380        var sharing = false;
   381        $(opts.shareEl).click(function() {
   382          if (sharing) return;
   383          sharing = true;
   384          var sharingData = body();
   385          $.ajax("/share", {
   386            processData: false,
   387            data: sharingData,
   388            type: "POST",
   389            complete: function(xhr) {
   390              sharing = false;
   391              if (xhr.status != 200) {
   392                alert("Server error; try again.");
   393                return;
   394              }
   395              if (opts.shareRedirect) {
   396                window.location = opts.shareRedirect + xhr.responseText;
   397              }
   398              if (shareURL) {
   399                var path = "/p/" + xhr.responseText;
   400                var url = origin(window.location) + path;
   401                shareURL.show().val(url).focus().select();
   402    
   403                if (rewriteHistory) {
   404                  var historyData = {"code": sharingData};
   405                  window.history.pushState(historyData, "", path);
   406                  pushedEmpty = false;
   407                }
   408              }
   409            }
   410          });
   411        });
   412      }
   413    
   414      if (opts.toysEl !== null) {
   415        $(opts.toysEl).bind('change', function() {
   416          var toy = $(this).val();
   417          $.ajax("/doc/play/"+toy, {
   418            processData: false,
   419            type: "GET",
   420            complete: function(xhr) {
   421              if (xhr.status != 200) {
   422                alert("Server error; try again.");
   423                return;
   424              }
   425              setBody(xhr.responseText);
   426            }
   427          });
   428        });
   429      }
   430    }
   431  
   432    window.playground = playground;
   433  })();