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