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, '<'); 193 m = m.replace(/>/g, '>'); 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 })();