Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.0.0-20201105173854-bc9fc8d8c4bc / godoc / static / playground.js
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 // HTTPTransport is the default transport.
44 // enableVet enables running vet if a program was compiled and ran successfully.
45 // If vet returned any errors, display them before the output of a program.
46 function HTTPTransport(enableVet) {
47   'use strict';
48
49   function playback(output, data) {
50     // Backwards compatibility: default values do not affect the output.
51     var events = data.Events || [];
52     var errors = data.Errors || '';
53     var status = data.Status || 0;
54     var isTest = data.IsTest || false;
55     var testsFailed = data.TestsFailed || 0;
56
57     var timeout;
58     output({ Kind: 'start' });
59     function next() {
60       if (!events || events.length === 0) {
61         if (isTest) {
62           if (testsFailed > 0) {
63             output({
64               Kind: 'system',
65               Body:
66                 '\n' +
67                 testsFailed +
68                 ' test' +
69                 (testsFailed > 1 ? 's' : '') +
70                 ' failed.',
71             });
72           } else {
73             output({ Kind: 'system', Body: '\nAll tests passed.' });
74           }
75         } else {
76           if (status > 0) {
77             output({ Kind: 'end', Body: 'status ' + status + '.' });
78           } else {
79             if (errors !== '') {
80               // errors are displayed only in the case of timeout.
81               output({ Kind: 'end', Body: errors + '.' });
82             } else {
83               output({ Kind: 'end' });
84             }
85           }
86         }
87         return;
88       }
89       var e = events.shift();
90       if (e.Delay === 0) {
91         output({ Kind: e.Kind, Body: e.Message });
92         next();
93         return;
94       }
95       timeout = setTimeout(function() {
96         output({ Kind: e.Kind, Body: e.Message });
97         next();
98       }, e.Delay / 1000000);
99     }
100     next();
101     return {
102       Stop: function() {
103         clearTimeout(timeout);
104       },
105     };
106   }
107
108   function error(output, msg) {
109     output({ Kind: 'start' });
110     output({ Kind: 'stderr', Body: msg });
111     output({ Kind: 'end' });
112   }
113
114   function buildFailed(output, msg) {
115     output({ Kind: 'start' });
116     output({ Kind: 'stderr', Body: msg });
117     output({ Kind: 'system', Body: '\nGo build failed.' });
118   }
119
120   var seq = 0;
121   return {
122     Run: function(body, output, options) {
123       seq++;
124       var cur = seq;
125       var playing;
126       $.ajax('/compile', {
127         type: 'POST',
128         data: { version: 2, body: body, withVet: enableVet },
129         dataType: 'json',
130         success: function(data) {
131           if (seq != cur) return;
132           if (!data) return;
133           if (playing != null) playing.Stop();
134           if (data.Errors) {
135             if (data.Errors === 'process took too long') {
136               // Playback the output that was captured before the timeout.
137               playing = playback(output, data);
138             } else {
139               buildFailed(output, data.Errors);
140             }
141             return;
142           }
143           if (!data.Events) {
144             data.Events = [];
145           }
146           if (data.VetErrors) {
147             // Inject errors from the vet as the first events in the output.
148             data.Events.unshift({
149               Message: 'Go vet exited.\n\n',
150               Kind: 'system',
151               Delay: 0,
152             });
153             data.Events.unshift({
154               Message: data.VetErrors,
155               Kind: 'stderr',
156               Delay: 0,
157             });
158           }
159
160           if (!enableVet || data.VetOK || data.VetErrors) {
161             playing = playback(output, data);
162             return;
163           }
164
165           // In case the server support doesn't support
166           // compile+vet in same request signaled by the
167           // 'withVet' parameter above, also try the old way.
168           // TODO: remove this when it falls out of use.
169           // It is 2019-05-13 now.
170           $.ajax('/vet', {
171             data: { body: body },
172             type: 'POST',
173             dataType: 'json',
174             success: function(dataVet) {
175               if (dataVet.Errors) {
176                 // inject errors from the vet as the first events in the output
177                 data.Events.unshift({
178                   Message: 'Go vet exited.\n\n',
179                   Kind: 'system',
180                   Delay: 0,
181                 });
182                 data.Events.unshift({
183                   Message: dataVet.Errors,
184                   Kind: 'stderr',
185                   Delay: 0,
186                 });
187               }
188               playing = playback(output, data);
189             },
190             error: function() {
191               playing = playback(output, data);
192             },
193           });
194         },
195         error: function() {
196           error(output, 'Error communicating with remote server.');
197         },
198       });
199       return {
200         Kill: function() {
201           if (playing != null) playing.Stop();
202           output({ Kind: 'end', Body: 'killed' });
203         },
204       };
205     },
206   };
207 }
208
209 function SocketTransport() {
210   'use strict';
211
212   var id = 0;
213   var outputs = {};
214   var started = {};
215   var websocket;
216   if (window.location.protocol == 'http:') {
217     websocket = new WebSocket('ws://' + window.location.host + '/socket');
218   } else if (window.location.protocol == 'https:') {
219     websocket = new WebSocket('wss://' + window.location.host + '/socket');
220   }
221
222   websocket.onclose = function() {
223     console.log('websocket connection closed');
224   };
225
226   websocket.onmessage = function(e) {
227     var m = JSON.parse(e.data);
228     var output = outputs[m.Id];
229     if (output === null) return;
230     if (!started[m.Id]) {
231       output({ Kind: 'start' });
232       started[m.Id] = true;
233     }
234     output({ Kind: m.Kind, Body: m.Body });
235   };
236
237   function send(m) {
238     websocket.send(JSON.stringify(m));
239   }
240
241   return {
242     Run: function(body, output, options) {
243       var thisID = id + '';
244       id++;
245       outputs[thisID] = output;
246       send({ Id: thisID, Kind: 'run', Body: body, Options: options });
247       return {
248         Kill: function() {
249           send({ Id: thisID, Kind: 'kill' });
250         },
251       };
252     },
253   };
254 }
255
256 function PlaygroundOutput(el) {
257   'use strict';
258
259   return function(write) {
260     if (write.Kind == 'start') {
261       el.innerHTML = '';
262       return;
263     }
264
265     var cl = 'system';
266     if (write.Kind == 'stdout' || write.Kind == 'stderr') cl = write.Kind;
267
268     var m = write.Body;
269     if (write.Kind == 'end') {
270       m = '\nProgram exited' + (m ? ': ' + m : '.');
271     }
272
273     if (m.indexOf('IMAGE:') === 0) {
274       // TODO(adg): buffer all writes before creating image
275       var url = 'data:image/png;base64,' + m.substr(6);
276       var img = document.createElement('img');
277       img.src = url;
278       el.appendChild(img);
279       return;
280     }
281
282     // ^L clears the screen.
283     var s = m.split('\x0c');
284     if (s.length > 1) {
285       el.innerHTML = '';
286       m = s.pop();
287     }
288
289     m = m.replace(/&/g, '&');
290     m = m.replace(/</g, '&lt;');
291     m = m.replace(/>/g, '&gt;');
292
293     var needScroll = el.scrollTop + el.offsetHeight == el.scrollHeight;
294
295     var span = document.createElement('span');
296     span.className = cl;
297     span.innerHTML = m;
298     el.appendChild(span);
299
300     if (needScroll) el.scrollTop = el.scrollHeight - el.offsetHeight;
301   };
302 }
303
304 (function() {
305   function lineHighlight(error) {
306     var regex = /prog.go:([0-9]+)/g;
307     var r = regex.exec(error);
308     while (r) {
309       $('.lines div')
310         .eq(r[1] - 1)
311         .addClass('lineerror');
312       r = regex.exec(error);
313     }
314   }
315   function highlightOutput(wrappedOutput) {
316     return function(write) {
317       if (write.Body) lineHighlight(write.Body);
318       wrappedOutput(write);
319     };
320   }
321   function lineClear() {
322     $('.lineerror').removeClass('lineerror');
323   }
324
325   // opts is an object with these keys
326   //  codeEl - code editor element
327   //  outputEl - program output element
328   //  runEl - run button element
329   //  fmtEl - fmt button element (optional)
330   //  fmtImportEl - fmt "imports" checkbox element (optional)
331   //  shareEl - share button element (optional)
332   //  shareURLEl - share URL text input element (optional)
333   //  shareRedirect - base URL to redirect to on share (optional)
334   //  toysEl - toys select element (optional)
335   //  enableHistory - enable using HTML5 history API (optional)
336   //  transport - playground transport to use (default is HTTPTransport)
337   //  enableShortcuts - whether to enable shortcuts (Ctrl+S/Cmd+S to save) (default is false)
338   //  enableVet - enable running vet and displaying its errors
339   function playground(opts) {
340     var code = $(opts.codeEl);
341     var transport = opts['transport'] || new HTTPTransport(opts['enableVet']);
342     var running;
343
344     // autoindent helpers.
345     function insertTabs(n) {
346       // find the selection start and end
347       var start = code[0].selectionStart;
348       var end = code[0].selectionEnd;
349       // split the textarea content into two, and insert n tabs
350       var v = code[0].value;
351       var u = v.substr(0, start);
352       for (var i = 0; i < n; i++) {
353         u += '\t';
354       }
355       u += v.substr(end);
356       // set revised content
357       code[0].value = u;
358       // reset caret position after inserted tabs
359       code[0].selectionStart = start + n;
360       code[0].selectionEnd = start + n;
361     }
362     function autoindent(el) {
363       var curpos = el.selectionStart;
364       var tabs = 0;
365       while (curpos > 0) {
366         curpos--;
367         if (el.value[curpos] == '\t') {
368           tabs++;
369         } else if (tabs > 0 || el.value[curpos] == '\n') {
370           break;
371         }
372       }
373       setTimeout(function() {
374         insertTabs(tabs);
375       }, 1);
376     }
377
378     // NOTE(cbro): e is a jQuery event, not a DOM event.
379     function handleSaveShortcut(e) {
380       if (e.isDefaultPrevented()) return false;
381       if (!e.metaKey && !e.ctrlKey) return false;
382       if (e.key != 'S' && e.key != 's') return false;
383
384       e.preventDefault();
385
386       // Share and save
387       share(function(url) {
388         window.location.href = url + '.go?download=true';
389       });
390
391       return true;
392     }
393
394     function keyHandler(e) {
395       if (opts.enableShortcuts && handleSaveShortcut(e)) return;
396
397       if (e.keyCode == 9 && !e.ctrlKey) {
398         // tab (but not ctrl-tab)
399         insertTabs(1);
400         e.preventDefault();
401         return false;
402       }
403       if (e.keyCode == 13) {
404         // enter
405         if (e.shiftKey) {
406           // +shift
407           run();
408           e.preventDefault();
409           return false;
410         }
411         if (e.ctrlKey) {
412           // +control
413           fmt();
414           e.preventDefault();
415         } else {
416           autoindent(e.target);
417         }
418       }
419       return true;
420     }
421     code.unbind('keydown').bind('keydown', keyHandler);
422     var outdiv = $(opts.outputEl).empty();
423     var output = $('<pre/>').appendTo(outdiv);
424
425     function body() {
426       return $(opts.codeEl).val();
427     }
428     function setBody(text) {
429       $(opts.codeEl).val(text);
430     }
431     function origin(href) {
432       return ('' + href)
433         .split('/')
434         .slice(0, 3)
435         .join('/');
436     }
437
438     var pushedEmpty = window.location.pathname == '/';
439     function inputChanged() {
440       if (pushedEmpty) {
441         return;
442       }
443       pushedEmpty = true;
444       $(opts.shareURLEl).hide();
445       window.history.pushState(null, '', '/');
446     }
447     function popState(e) {
448       if (e === null) {
449         return;
450       }
451       if (e && e.state && e.state.code) {
452         setBody(e.state.code);
453       }
454     }
455     var rewriteHistory = false;
456     if (
457       window.history &&
458       window.history.pushState &&
459       window.addEventListener &&
460       opts.enableHistory
461     ) {
462       rewriteHistory = true;
463       code[0].addEventListener('input', inputChanged);
464       window.addEventListener('popstate', popState);
465     }
466
467     function setError(error) {
468       if (running) running.Kill();
469       lineClear();
470       lineHighlight(error);
471       output
472         .empty()
473         .addClass('error')
474         .text(error);
475     }
476     function loading() {
477       lineClear();
478       if (running) running.Kill();
479       output.removeClass('error').text('Waiting for remote server...');
480     }
481     function run() {
482       loading();
483       running = transport.Run(
484         body(),
485         highlightOutput(PlaygroundOutput(output[0]))
486       );
487     }
488
489     function fmt() {
490       loading();
491       var data = { body: body() };
492       if ($(opts.fmtImportEl).is(':checked')) {
493         data['imports'] = 'true';
494       }
495       $.ajax('/fmt', {
496         data: data,
497         type: 'POST',
498         dataType: 'json',
499         success: function(data) {
500           if (data.Error) {
501             setError(data.Error);
502           } else {
503             setBody(data.Body);
504             setError('');
505           }
506         },
507       });
508     }
509
510     var shareURL; // jQuery element to show the shared URL.
511     var sharing = false; // true if there is a pending request.
512     var shareCallbacks = [];
513     function share(opt_callback) {
514       if (opt_callback) shareCallbacks.push(opt_callback);
515
516       if (sharing) return;
517       sharing = true;
518
519       var sharingData = body();
520       $.ajax('/share', {
521         processData: false,
522         data: sharingData,
523         type: 'POST',
524         contentType: 'text/plain; charset=utf-8',
525         complete: function(xhr) {
526           sharing = false;
527           if (xhr.status != 200) {
528             alert('Server error; try again.');
529             return;
530           }
531           if (opts.shareRedirect) {
532             window.location = opts.shareRedirect + xhr.responseText;
533           }
534           var path = '/p/' + xhr.responseText;
535           var url = origin(window.location) + path;
536
537           for (var i = 0; i < shareCallbacks.length; i++) {
538             shareCallbacks[i](url);
539           }
540           shareCallbacks = [];
541
542           if (shareURL) {
543             shareURL
544               .show()
545               .val(url)
546               .focus()
547               .select();
548
549             if (rewriteHistory) {
550               var historyData = { code: sharingData };
551               window.history.pushState(historyData, '', path);
552               pushedEmpty = false;
553             }
554           }
555         },
556       });
557     }
558
559     $(opts.runEl).click(run);
560     $(opts.fmtEl).click(fmt);
561
562     if (
563       opts.shareEl !== null &&
564       (opts.shareURLEl !== null || opts.shareRedirect !== null)
565     ) {
566       if (opts.shareURLEl) {
567         shareURL = $(opts.shareURLEl).hide();
568       }
569       $(opts.shareEl).click(function() {
570         share();
571       });
572     }
573
574     if (opts.toysEl !== null) {
575       $(opts.toysEl).bind('change', function() {
576         var toy = $(this).val();
577         $.ajax('/doc/play/' + toy, {
578           processData: false,
579           type: 'GET',
580           complete: function(xhr) {
581             if (xhr.status != 200) {
582               alert('Server error; try again.');
583               return;
584             }
585             setBody(xhr.responseText);
586           },
587         });
588       });
589     }
590   }
591
592   window.playground = playground;
593 })();