GoPLS Viewer

Home|gopls/godoc/static/godocs.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/* A little code to ease navigation of these documents.
6 *
7 * On window load we:
8 *  + Generate a table of contents (generateTOC)
9 *  + Bind foldable sections (bindToggles)
10 *  + Bind links to foldable sections (bindToggleLinks)
11 */
12
13(function() {
14  'use strict';
15
16  // Mobile-friendly topbar menu
17  $(function() {
18    var menu = $('#menu');
19    var menuButton = $('#menu-button');
20    var menuButtonArrow = $('#menu-button-arrow');
21    menuButton.click(function(event) {
22      menu.toggleClass('menu-visible');
23      menuButtonArrow.toggleClass('vertical-flip');
24      event.preventDefault();
25      return false;
26    });
27  });
28
29  /* Generates a table of contents: looks for h2 and h3 elements and generates
30   * links. "Decorates" the element with id=="nav" with this table of contents.
31   */
32  function generateTOC() {
33    if ($('#manual-nav').length > 0) {
34      return;
35    }
36
37    // For search, we send the toc precomputed from server-side.
38    // TODO: Ideally, this should always be precomputed for all pages, but then
39    // we need to do HTML parsing on the server-side.
40    if (location.pathname === '/search') {
41      return;
42    }
43
44    var nav = $('#nav');
45    if (nav.length === 0) {
46      return;
47    }
48
49    var toc_items = [];
50    $(nav)
51      .nextAll('h2, h3')
52      .each(function() {
53        var node = this;
54        if (node.id == '') node.id = 'tmp_' + toc_items.length;
55        var link = $('<a/>')
56          .attr('href', '#' + node.id)
57          .text($(node).text());
58        var item;
59        if ($(node).is('h2')) {
60          item = $('<dt/>');
61        } else {
62          // h3
63          item = $('<dd class="indent"/>');
64        }
65        item.append(link);
66        toc_items.push(item);
67      });
68    if (toc_items.length <= 1) {
69      return;
70    }
71    var dl1 = $('<dl/>');
72    var dl2 = $('<dl/>');
73
74    var split_index = toc_items.length / 2 + 1;
75    if (split_index < 8) {
76      split_index = toc_items.length;
77    }
78    for (var i = 0; i < split_index; i++) {
79      dl1.append(toc_items[i]);
80    }
81    for (; /* keep using i */ i < toc_items.length; i++) {
82      dl2.append(toc_items[i]);
83    }
84
85    var tocTable = $('<table class="unruled"/>').appendTo(nav);
86    var tocBody = $('<tbody/>').appendTo(tocTable);
87    var tocRow = $('<tr/>').appendTo(tocBody);
88
89    // 1st column
90    $('<td class="first"/>')
91      .appendTo(tocRow)
92      .append(dl1);
93    // 2nd column
94    $('<td/>')
95      .appendTo(tocRow)
96      .append(dl2);
97  }
98
99  function bindToggle(el) {
100    $('.toggleButton', el).click(function() {
101      if ($(this).closest('.toggle, .toggleVisible')[0] != el) {
102        // Only trigger the closest toggle header.
103        return;
104      }
105
106      if ($(el).is('.toggle')) {
107        $(el)
108          .addClass('toggleVisible')
109          .removeClass('toggle');
110      } else {
111        $(el)
112          .addClass('toggle')
113          .removeClass('toggleVisible');
114      }
115    });
116  }
117
118  function bindToggles(selector) {
119    $(selector).each(function(i, el) {
120      bindToggle(el);
121    });
122  }
123
124  function bindToggleLink(el, prefix) {
125    $(el).click(function() {
126      var href = $(el).attr('href');
127      var i = href.indexOf('#' + prefix);
128      if (i < 0) {
129        return;
130      }
131      var id = '#' + prefix + href.slice(i + 1 + prefix.length);
132      if ($(id).is('.toggle')) {
133        $(id)
134          .find('.toggleButton')
135          .first()
136          .click();
137      }
138    });
139  }
140  function bindToggleLinks(selector, prefix) {
141    $(selector).each(function(i, el) {
142      bindToggleLink(el, prefix);
143    });
144  }
145
146  function setupDropdownPlayground() {
147    if (!$('#page').is('.wide')) {
148      return; // don't show on front page
149    }
150    var button = $('#playgroundButton');
151    var div = $('#playground');
152    var setup = false;
153    button.toggle(
154      function() {
155        button.addClass('active');
156        div.show();
157        if (setup) {
158          return;
159        }
160        setup = true;
161        playground({
162          codeEl: $('.code', div),
163          outputEl: $('.output', div),
164          runEl: $('.run', div),
165          fmtEl: $('.fmt', div),
166          shareEl: $('.share', div),
167          shareRedirect: '//play.golang.org/p/',
168        });
169      },
170      function() {
171        button.removeClass('active');
172        div.hide();
173      }
174    );
175    $('#menu').css('min-width', '+=60');
176
177    // Hide inline playground if we click somewhere on the page.
178    // This is needed in mobile devices, where the "Play" button
179    // is not clickable once the playground opens up.
180    $('#page').click(function() {
181      if (button.hasClass('active')) {
182        button.click();
183      }
184    });
185  }
186
187  function setupInlinePlayground() {
188    'use strict';
189    // Set up playground when each element is toggled.
190    $('div.play').each(function(i, el) {
191      // Set up playground for this example.
192      var setup = function() {
193        var code = $('.code', el);
194        playground({
195          codeEl: code,
196          outputEl: $('.output', el),
197          runEl: $('.run', el),
198          fmtEl: $('.fmt', el),
199          shareEl: $('.share', el),
200          shareRedirect: '//play.golang.org/p/',
201        });
202
203        // Make the code textarea resize to fit content.
204        var resize = function() {
205          code.height(0);
206          var h = code[0].scrollHeight;
207          code.height(h + 20); // minimize bouncing.
208          code.closest('.input').height(h);
209        };
210        code.on('keydown', resize);
211        code.on('keyup', resize);
212        code.keyup(); // resize now.
213      };
214
215      // If example already visible, set up playground now.
216      if ($(el).is(':visible')) {
217        setup();
218        return;
219      }
220
221      // Otherwise, set up playground when example is expanded.
222      var built = false;
223      $(el)
224        .closest('.toggle')
225        .click(function() {
226          // Only set up once.
227          if (!built) {
228            setup();
229            built = true;
230          }
231        });
232    });
233  }
234
235  // fixFocus tries to put focus to div#page so that keyboard navigation works.
236  function fixFocus() {
237    var page = $('div#page');
238    var topbar = $('div#topbar');
239    page.css('outline', 0); // disable outline when focused
240    page.attr('tabindex', -1); // and set tabindex so that it is focusable
241    $(window)
242      .resize(function(evt) {
243        // only focus page when the topbar is at fixed position (that is, it's in
244        // front of page, and keyboard event will go to the former by default.)
245        // by focusing page, keyboard event will go to page so that up/down arrow,
246        // space, etc. will work as expected.
247        if (topbar.css('position') == 'fixed') page.focus();
248      })
249      .resize();
250  }
251
252  function toggleHash() {
253    var id = window.location.hash.substring(1);
254    // Open all of the toggles for a particular hash.
255    var els = $(
256      document.getElementById(id),
257      $('a[name]').filter(function() {
258        return $(this).attr('name') == id;
259      })
260    );
261
262    while (els.length) {
263      for (var i = 0; i < els.length; i++) {
264        var el = $(els[i]);
265        if (el.is('.toggle')) {
266          el.find('.toggleButton')
267            .first()
268            .click();
269        }
270      }
271      els = el.parent();
272    }
273  }
274
275  function personalizeInstallInstructions() {
276    var prefix = '?download=';
277    var s = window.location.search;
278    if (s.indexOf(prefix) != 0) {
279      // No 'download' query string; detect "test" instructions from User Agent.
280      if (navigator.platform.indexOf('Win') != -1) {
281        $('.testUnix').hide();
282        $('.testWindows').show();
283      } else {
284        $('.testUnix').show();
285        $('.testWindows').hide();
286      }
287      return;
288    }
289
290    var filename = s.substr(prefix.length);
291    var filenameRE = /^go1\.\d+(\.\d+)?([a-z0-9]+)?\.([a-z0-9]+)(-[a-z0-9]+)?(-osx10\.[68])?\.([a-z.]+)$/;
292    var m = filenameRE.exec(filename);
293    if (!m) {
294      // Can't interpret file name; bail.
295      return;
296    }
297    $('.downloadFilename').text(filename);
298    $('.hideFromDownload').hide();
299
300    var os = m[3];
301    var ext = m[6];
302    if (ext != 'tar.gz') {
303      $('#tarballInstructions').hide();
304    }
305    if (os != 'darwin' || ext != 'pkg') {
306      $('#darwinPackageInstructions').hide();
307    }
308    if (os != 'windows') {
309      $('#windowsInstructions').hide();
310      $('.testUnix').show();
311      $('.testWindows').hide();
312    } else {
313      if (ext != 'msi') {
314        $('#windowsInstallerInstructions').hide();
315      }
316      if (ext != 'zip') {
317        $('#windowsZipInstructions').hide();
318      }
319      $('.testUnix').hide();
320      $('.testWindows').show();
321    }
322
323    var download = 'https://dl.google.com/go/' + filename;
324
325    var message = $(
326      '<p class="downloading">' +
327        'Your download should begin shortly. ' +
328        'If it does not, click <a>this link</a>.</p>'
329    );
330    message.find('a').attr('href', download);
331    message.insertAfter('#nav');
332
333    window.location = download;
334  }
335
336  function updateVersionTags() {
337    var v = window.goVersion;
338    if (/^go[0-9.]+$/.test(v)) {
339      $('.versionTag')
340        .empty()
341        .text(v);
342      $('.whereTag').hide();
343    }
344  }
345
346  function addPermalinks() {
347    function addPermalink(source, parent) {
348      var id = source.attr('id');
349      if (id == '' || id.indexOf('tmp_') === 0) {
350        // Auto-generated permalink.
351        return;
352      }
353      if (parent.find('> .permalink').length) {
354        // Already attached.
355        return;
356      }
357      parent
358        .append(' ')
359        .append($("<a class='permalink'>&#xb6;</a>").attr('href', '#' + id));
360    }
361
362    $('#page .container')
363      .find('h2[id], h3[id]')
364      .each(function() {
365        var el = $(this);
366        addPermalink(el, el);
367      });
368
369    $('#page .container')
370      .find('dl[id]')
371      .each(function() {
372        var el = $(this);
373        // Add the anchor to the "dt" element.
374        addPermalink(el, el.find('> dt').first());
375      });
376  }
377
378  $('.js-expandAll').click(function() {
379    if ($(this).hasClass('collapsed')) {
380      toggleExamples('toggle');
381      $(this).text('(Collapse All)');
382    } else {
383      toggleExamples('toggleVisible');
384      $(this).text('(Expand All)');
385    }
386    $(this).toggleClass('collapsed');
387  });
388
389  function toggleExamples(className) {
390    // We need to explicitly iterate through divs starting with "example_"
391    // to avoid toggling Overview and Index collapsibles.
392    $("[id^='example_']").each(function() {
393      // Check for state and click it only if required.
394      if ($(this).hasClass(className)) {
395        $(this)
396          .find('.toggleButton')
397          .first()
398          .click();
399      }
400    });
401  }
402
403  $(document).ready(function() {
404    generateTOC();
405    addPermalinks();
406    bindToggles('.toggle');
407    bindToggles('.toggleVisible');
408    bindToggleLinks('.exampleLink', 'example_');
409    bindToggleLinks('.overviewLink', '');
410    bindToggleLinks('.examplesLink', '');
411    bindToggleLinks('.indexLink', '');
412    setupDropdownPlayground();
413    setupInlinePlayground();
414    fixFocus();
415    setupTypeInfo();
416    setupCallgraphs();
417    toggleHash();
418    personalizeInstallInstructions();
419    updateVersionTags();
420
421    // godoc.html defines window.initFuncs in the <head> tag, and root.html and
422    // codewalk.js push their on-page-ready functions to the list.
423    // We execute those functions here, to avoid loading jQuery until the page
424    // content is loaded.
425    for (var i = 0; i < window.initFuncs.length; i++) window.initFuncs[i]();
426  });
427
428  // -- analysis ---------------------------------------------------------
429
430  // escapeHTML returns HTML for s, with metacharacters quoted.
431  // It is safe for use in both elements and attributes
432  // (unlike the "set innerText, read innerHTML" trick).
433  function escapeHTML(s) {
434    return s
435      .replace(/&/g, '&amp;')
436      .replace(/\"/g, '&quot;')
437      .replace(/\'/g, '&#39;')
438      .replace(/</g, '&lt;')
439      .replace(/>/g, '&gt;');
440  }
441
442  // makeAnchor returns HTML for an <a> element, given an anchorJSON object.
443  function makeAnchor(json) {
444    var html = escapeHTML(json.Text);
445    if (json.Href != '') {
446      html = "<a href='" + escapeHTML(json.Href) + "'>" + html + '</a>';
447    }
448    return html;
449  }
450
451  function showLowFrame(html) {
452    var lowframe = document.getElementById('lowframe');
453    lowframe.style.height = '200px';
454    lowframe.innerHTML =
455      "<p style='text-align: left;'>" +
456      html +
457      '</p>\n' +
458      "<div onclick='hideLowFrame()' style='position: absolute; top: 0; right: 0; cursor: pointer;'>✘</div>";
459  }
460
461  document.hideLowFrame = function() {
462    var lowframe = document.getElementById('lowframe');
463    lowframe.style.height = '0px';
464  };
465
466  // onClickCallers is the onclick action for the 'func' tokens of a
467  // function declaration.
468  document.onClickCallers = function(index) {
469    var data = document.ANALYSIS_DATA[index];
470    if (data.Callers.length == 1 && data.Callers[0].Sites.length == 1) {
471      document.location = data.Callers[0].Sites[0].Href; // jump to sole caller
472      return;
473    }
474
475    var html =
476      'Callers of <code>' + escapeHTML(data.Callee) + '</code>:<br/>\n';
477    for (var i = 0; i < data.Callers.length; i++) {
478      var caller = data.Callers[i];
479      html += '<code>' + escapeHTML(caller.Func) + '</code>';
480      var sites = caller.Sites;
481      if (sites != null && sites.length > 0) {
482        html += ' at line ';
483        for (var j = 0; j < sites.length; j++) {
484          if (j > 0) {
485            html += ', ';
486          }
487          html += '<code>' + makeAnchor(sites[j]) + '</code>';
488        }
489      }
490      html += '<br/>\n';
491    }
492    showLowFrame(html);
493  };
494
495  // onClickCallees is the onclick action for the '(' token of a function call.
496  document.onClickCallees = function(index) {
497    var data = document.ANALYSIS_DATA[index];
498    if (data.Callees.length == 1) {
499      document.location = data.Callees[0].Href; // jump to sole callee
500      return;
501    }
502
503    var html = 'Callees of this ' + escapeHTML(data.Descr) + ':<br/>\n';
504    for (var i = 0; i < data.Callees.length; i++) {
505      html += '<code>' + makeAnchor(data.Callees[i]) + '</code><br/>\n';
506    }
507    showLowFrame(html);
508  };
509
510  // onClickTypeInfo is the onclick action for identifiers declaring a named type.
511  document.onClickTypeInfo = function(index) {
512    var data = document.ANALYSIS_DATA[index];
513    var html =
514      'Type <code>' +
515      data.Name +
516      '</code>: ' +
517      '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<small>(size=' +
518      data.Size +
519      ', align=' +
520      data.Align +
521      ')</small><br/>\n';
522    html += implementsHTML(data);
523    html += methodsetHTML(data);
524    showLowFrame(html);
525  };
526
527  // implementsHTML returns HTML for the implements relation of the
528  // specified TypeInfoJSON value.
529  function implementsHTML(info) {
530    var html = '';
531    if (info.ImplGroups != null) {
532      for (var i = 0; i < info.ImplGroups.length; i++) {
533        var group = info.ImplGroups[i];
534        var x = '<code>' + escapeHTML(group.Descr) + '</code> ';
535        for (var j = 0; j < group.Facts.length; j++) {
536          var fact = group.Facts[j];
537          var y = '<code>' + makeAnchor(fact.Other) + '</code>';
538          if (fact.ByKind != null) {
539            html += escapeHTML(fact.ByKind) + ' type ' + y + ' implements ' + x;
540          } else {
541            html += x + ' implements ' + y;
542          }
543          html += '<br/>\n';
544        }
545      }
546    }
547    return html;
548  }
549
550  // methodsetHTML returns HTML for the methodset of the specified
551  // TypeInfoJSON value.
552  function methodsetHTML(info) {
553    var html = '';
554    if (info.Methods != null) {
555      for (var i = 0; i < info.Methods.length; i++) {
556        html += '<code>' + makeAnchor(info.Methods[i]) + '</code><br/>\n';
557      }
558    }
559    return html;
560  }
561
562  // onClickComm is the onclick action for channel "make" and "<-"
563  // send/receive tokens.
564  document.onClickComm = function(index) {
565    var ops = document.ANALYSIS_DATA[index].Ops;
566    if (ops.length == 1) {
567      document.location = ops[0].Op.Href; // jump to sole element
568      return;
569    }
570
571    var html = 'Operations on this channel:<br/>\n';
572    for (var i = 0; i < ops.length; i++) {
573      html +=
574        makeAnchor(ops[i].Op) +
575        ' by <code>' +
576        escapeHTML(ops[i].Fn) +
577        '</code><br/>\n';
578    }
579    if (ops.length == 0) {
580      html += '(none)<br/>\n';
581    }
582    showLowFrame(html);
583  };
584
585  $(window).load(function() {
586    // Scroll window so that first selection is visible.
587    // (This means we don't need to emit id='L%d' spans for each line.)
588    // TODO(adonovan): ideally, scroll it so that it's under the pointer,
589    // but I don't know how to get the pointer y coordinate.
590    var elts = document.getElementsByClassName('selection');
591    if (elts.length > 0) {
592      elts[0].scrollIntoView();
593    }
594  });
595
596  // setupTypeInfo populates the "Implements" and "Method set" toggle for
597  // each type in the package doc.
598  function setupTypeInfo() {
599    for (var i in document.ANALYSIS_DATA) {
600      var data = document.ANALYSIS_DATA[i];
601
602      var el = document.getElementById('implements-' + i);
603      if (el != null) {
604        // el != null => data is TypeInfoJSON.
605        if (data.ImplGroups != null) {
606          el.innerHTML = implementsHTML(data);
607          el.parentNode.parentNode.style.display = 'block';
608        }
609      }
610
611      var el = document.getElementById('methodset-' + i);
612      if (el != null) {
613        // el != null => data is TypeInfoJSON.
614        if (data.Methods != null) {
615          el.innerHTML = methodsetHTML(data);
616          el.parentNode.parentNode.style.display = 'block';
617        }
618      }
619    }
620  }
621
622  function setupCallgraphs() {
623    if (document.CALLGRAPH == null) {
624      return;
625    }
626    document.getElementById('pkg-callgraph').style.display = 'block';
627
628    var treeviews = document.getElementsByClassName('treeview');
629    for (var i = 0; i < treeviews.length; i++) {
630      var tree = treeviews[i];
631      if (tree.id == null || tree.id.indexOf('callgraph-') != 0) {
632        continue;
633      }
634      var id = tree.id.substring('callgraph-'.length);
635      $(tree).treeview({ collapsed: true, animated: 'fast' });
636      document.cgAddChildren(tree, tree, [id]);
637      tree.parentNode.parentNode.style.display = 'block';
638    }
639  }
640
641  document.cgAddChildren = function(tree, ul, indices) {
642    if (indices != null) {
643      for (var i = 0; i < indices.length; i++) {
644        var li = cgAddChild(tree, ul, document.CALLGRAPH[indices[i]]);
645        if (i == indices.length - 1) {
646          $(li).addClass('last');
647        }
648      }
649    }
650    $(tree).treeview({ animated: 'fast', add: ul });
651  };
652
653  // cgAddChild adds an <li> element for document.CALLGRAPH node cgn to
654  // the parent <ul> element ul. tree is the tree's root <ul> element.
655  function cgAddChild(tree, ul, cgn) {
656    var li = document.createElement('li');
657    ul.appendChild(li);
658    li.className = 'closed';
659
660    var code = document.createElement('code');
661
662    if (cgn.Callees != null) {
663      $(li).addClass('expandable');
664
665      // Event handlers and innerHTML updates don't play nicely together,
666      // hence all this explicit DOM manipulation.
667      var hitarea = document.createElement('div');
668      hitarea.className = 'hitarea expandable-hitarea';
669      li.appendChild(hitarea);
670
671      li.appendChild(code);
672
673      var childUL = document.createElement('ul');
674      li.appendChild(childUL);
675      childUL.setAttribute('style', 'display: none;');
676
677      var onClick = function() {
678        document.cgAddChildren(tree, childUL, cgn.Callees);
679        hitarea.removeEventListener('click', onClick);
680      };
681      hitarea.addEventListener('click', onClick);
682    } else {
683      li.appendChild(code);
684    }
685    code.innerHTML += '&nbsp;' + makeAnchor(cgn.Func);
686    return li;
687  }
688})();
689
MembersX
Members
X