PageSourceSearch

https://golang-jp.org/lib/godoc/playground.js

js golang-jp.org collected 2026-10-04 13:31:10 UTC 11,454 bytes, 433 lines download raw bytes

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/*
6In the absence of any formal way to specify interfaces in JavaScript,
7here's a skeleton implementation of a playground transport.
8
9        function Transport() {
10                // Set up any transport state (eg, make a websocket connnection).
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
43function 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.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
115function 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
158function 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.propertyIsEnumerable(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.propertyIsEnumerable(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})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.