]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/html/static/main.js
Rollup merge of #24717 - liigo:add-back-toggle-links, r=alexcrichton
[rust.git] / src / librustdoc / html / static / main.js
1 // Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 /*jslint browser: true, es5: true */
12 /*globals $: true, rootPath: true */
13
14 (function() {
15     "use strict";
16     var resizeTimeout, interval;
17
18     // This mapping table should match the discriminants of
19     // `rustdoc::html::item_type::ItemType` type in Rust.
20     var itemTypes = ["mod",
21                      "externcrate",
22                      "import",
23                      "struct",
24                      "enum",
25                      "fn",
26                      "type",
27                      "static",
28                      "trait",
29                      "impl",
30                      "tymethod",
31                      "method",
32                      "structfield",
33                      "variant",
34                      "macro",
35                      "primitive",
36                      "associatedtype",
37                      "constant"];
38
39     $('.js-only').removeClass('js-only');
40
41     function getQueryStringParams() {
42         var params = {};
43         window.location.search.substring(1).split("&").
44             map(function(s) {
45                 var pair = s.split("=");
46                 params[decodeURIComponent(pair[0])] =
47                     typeof pair[1] === "undefined" ?
48                             null : decodeURIComponent(pair[1]);
49             });
50         return params;
51     }
52
53     function browserSupportsHistoryApi() {
54         return window.history && typeof window.history.pushState === "function";
55     }
56
57     function highlightSourceLines(ev) {
58         var i, from, to, match = window.location.hash.match(/^#?(\d+)(?:-(\d+))?$/);
59         if (match) {
60             from = parseInt(match[1], 10);
61             to = Math.min(50000, parseInt(match[2] || match[1], 10));
62             from = Math.min(from, to);
63             if ($('#' + from).length === 0) {
64                 return;
65             }
66             if (ev === null) $('#' + from)[0].scrollIntoView();
67             $('.line-numbers span').removeClass('line-highlighted');
68             for (i = from; i <= to; ++i) {
69                 $('#' + i).addClass('line-highlighted');
70             }
71         }
72     }
73     highlightSourceLines(null);
74     $(window).on('hashchange', highlightSourceLines);
75
76     $(document).on('keyup', function(e) {
77         if (document.activeElement.tagName === 'INPUT') {
78             return;
79         }
80
81         if (e.which === 191) { // question mark
82             if (e.shiftKey && $('#help').hasClass('hidden')) {
83                 e.preventDefault();
84                 $('#help').removeClass('hidden');
85             }
86         } else if (e.which === 27) { // esc
87             if (!$('#help').hasClass('hidden')) {
88                 e.preventDefault();
89                 $('#help').addClass('hidden');
90             } else if (!$('#search').hasClass('hidden')) {
91                 e.preventDefault();
92                 $('#search').addClass('hidden');
93                 $('#main').removeClass('hidden');
94             }
95         } else if (e.which === 83) { // S
96             e.preventDefault();
97             $('.search-input').focus();
98         }
99     }).on('click', function(e) {
100         if (!$(e.target).closest('#help').length) {
101             $('#help').addClass('hidden');
102         }
103     });
104
105     $('.version-selector').on('change', function() {
106         var i, match,
107             url = document.location.href,
108             stripped = '',
109             len = rootPath.match(/\.\.\//g).length + 1;
110
111         for (i = 0; i < len; ++i) {
112             match = url.match(/\/[^\/]*$/);
113             if (i < len - 1) {
114                 stripped = match[0] + stripped;
115             }
116             url = url.substring(0, url.length - match[0].length);
117         }
118
119         url += '/' + $('.version-selector').val() + stripped;
120
121         document.location.href = url;
122     });
123     /**
124      * A function to compute the Levenshtein distance between two strings
125      * Licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported
126      * Full License can be found at http://creativecommons.org/licenses/by-sa/3.0/legalcode
127      * This code is an unmodified version of the code written by Marco de Wit
128      * and was found at http://stackoverflow.com/a/18514751/745719
129      */
130     var levenshtein = (function() {
131         var row2 = [];
132         return function(s1, s2) {
133             if (s1 === s2) {
134                 return 0;
135             } else {
136                 var s1_len = s1.length, s2_len = s2.length;
137                 if (s1_len && s2_len) {
138                     var i1 = 0, i2 = 0, a, b, c, c2, row = row2;
139                     while (i1 < s1_len)
140                         row[i1] = ++i1;
141                     while (i2 < s2_len) {
142                         c2 = s2.charCodeAt(i2);
143                         a = i2;
144                         ++i2;
145                         b = i2;
146                         for (i1 = 0; i1 < s1_len; ++i1) {
147                             c = a + (s1.charCodeAt(i1) !== c2 ? 1 : 0);
148                             a = row[i1];
149                             b = b < a ? (b < c ? b + 1 : c) : (a < c ? a + 1 : c);
150                             row[i1] = b;
151                         }
152                     }
153                     return b;
154                 } else {
155                     return s1_len + s2_len;
156                 }
157             }
158         };
159     })();
160
161     function initSearch(rawSearchIndex) {
162         var currentResults, index, searchIndex;
163         var MAX_LEV_DISTANCE = 3;
164         var params = getQueryStringParams();
165
166         // Populate search bar with query string search term when provided,
167         // but only if the input bar is empty. This avoid the obnoxious issue
168         // where you start trying to do a search, and the index loads, and
169         // suddenly your search is gone!
170         if ($(".search-input")[0].value === "") {
171             $(".search-input")[0].value = params.search || '';
172         }
173
174         /**
175          * Executes the query and builds an index of results
176          * @param  {[Object]} query     [The user query]
177          * @param  {[type]} max         [The maximum results returned]
178          * @param  {[type]} searchWords [The list of search words to query
179          *                               against]
180          * @return {[type]}             [A search index of results]
181          */
182         function execQuery(query, max, searchWords) {
183             var valLower = query.query.toLowerCase(),
184                 val = valLower,
185                 typeFilter = itemTypeFromName(query.type),
186                 results = [],
187                 split = valLower.split("::");
188
189             //remove empty keywords
190             for (var j = 0; j < split.length; ++j) {
191                 split[j].toLowerCase();
192                 if (split[j] === "") {
193                     split.splice(j, 1);
194                 }
195             }
196
197             // quoted values mean literal search
198             var nSearchWords = searchWords.length;
199             if ((val.charAt(0) === "\"" || val.charAt(0) === "'") &&
200                 val.charAt(val.length - 1) === val.charAt(0))
201             {
202                 val = val.substr(1, val.length - 2);
203                 for (var i = 0; i < nSearchWords; ++i) {
204                     if (searchWords[i] === val) {
205                         // filter type: ... queries
206                         if (typeFilter < 0 || typeFilter === searchIndex[i].ty) {
207                             results.push({id: i, index: -1});
208                         }
209                     }
210                     if (results.length === max) {
211                         break;
212                     }
213                 }
214             // searching by type
215             } else if (val.search("->") > -1) {
216                 var trimmer = function (s) { return s.trim(); };
217                 var parts = val.split("->").map(trimmer);
218                 var input = parts[0];
219                 // sort inputs so that order does not matter
220                 var inputs = input.split(",").map(trimmer).sort();
221                 var output = parts[1];
222
223                 for (var i = 0; i < nSearchWords; ++i) {
224                     var type = searchIndex[i].type;
225                     if (!type) {
226                         continue;
227                     }
228
229                     // sort index inputs so that order does not matter
230                     var typeInputs = type.inputs.map(function (input) {
231                         return input.name;
232                     }).sort();
233
234                     // allow searching for void (no output) functions as well
235                     var typeOutput = type.output ? type.output.name : "";
236                     if (inputs.toString() === typeInputs.toString() &&
237                         output == typeOutput) {
238                         results.push({id: i, index: -1, dontValidate: true});
239                     }
240                 }
241             } else {
242                 // gather matching search results up to a certain maximum
243                 val = val.replace(/\_/g, "");
244                 for (var i = 0; i < split.length; ++i) {
245                     for (var j = 0; j < nSearchWords; ++j) {
246                         var lev_distance;
247                         if (searchWords[j].indexOf(split[i]) > -1 ||
248                             searchWords[j].indexOf(val) > -1 ||
249                             searchWords[j].replace(/_/g, "").indexOf(val) > -1)
250                         {
251                             // filter type: ... queries
252                             if (typeFilter < 0 || typeFilter === searchIndex[j].ty) {
253                                 results.push({
254                                     id: j,
255                                     index: searchWords[j].replace(/_/g, "").indexOf(val),
256                                     lev: 0,
257                                 });
258                             }
259                         } else if (
260                             (lev_distance = levenshtein(searchWords[j], val)) <=
261                                 MAX_LEV_DISTANCE) {
262                             if (typeFilter < 0 || typeFilter === searchIndex[j].ty) {
263                                 results.push({
264                                     id: j,
265                                     index: 0,
266                                     // we want lev results to go lower than others
267                                     lev: lev_distance,
268                                 });
269                             }
270                         }
271                         if (results.length === max) {
272                             break;
273                         }
274                     }
275                 }
276             }
277
278             var nresults = results.length;
279             for (var i = 0; i < nresults; ++i) {
280                 results[i].word = searchWords[results[i].id];
281                 results[i].item = searchIndex[results[i].id] || {};
282             }
283             // if there are no results then return to default and fail
284             if (results.length === 0) {
285                 return [];
286             }
287
288             results.sort(function(aaa, bbb) {
289                 var a, b;
290
291                 // Sort by non levenshtein results and then levenshtein results by the distance
292                 // (less changes required to match means higher rankings)
293                 a = (aaa.lev);
294                 b = (bbb.lev);
295                 if (a !== b) return a - b;
296
297                 // sort by crate (non-current crate goes later)
298                 a = (aaa.item.crate !== window.currentCrate);
299                 b = (bbb.item.crate !== window.currentCrate);
300                 if (a !== b) return a - b;
301
302                 // sort by exact match (mismatch goes later)
303                 a = (aaa.word !== valLower);
304                 b = (bbb.word !== valLower);
305                 if (a !== b) return a - b;
306
307                 // sort by item name length (longer goes later)
308                 a = aaa.word.length;
309                 b = bbb.word.length;
310                 if (a !== b) return a - b;
311
312                 // sort by item name (lexicographically larger goes later)
313                 a = aaa.word;
314                 b = bbb.word;
315                 if (a !== b) return (a > b ? +1 : -1);
316
317                 // sort by index of keyword in item name (no literal occurrence goes later)
318                 a = (aaa.index < 0);
319                 b = (bbb.index < 0);
320                 if (a !== b) return a - b;
321                 // (later literal occurrence, if any, goes later)
322                 a = aaa.index;
323                 b = bbb.index;
324                 if (a !== b) return a - b;
325
326                 // sort by description (no description goes later)
327                 a = (aaa.item.desc === '');
328                 b = (bbb.item.desc === '');
329                 if (a !== b) return a - b;
330
331                 // sort by type (later occurrence in `itemTypes` goes later)
332                 a = aaa.item.ty;
333                 b = bbb.item.ty;
334                 if (a !== b) return a - b;
335
336                 // sort by path (lexicographically larger goes later)
337                 a = aaa.item.path;
338                 b = bbb.item.path;
339                 if (a !== b) return (a > b ? +1 : -1);
340
341                 // que sera, sera
342                 return 0;
343             });
344
345             // remove duplicates, according to the data provided
346             for (var i = results.length - 1; i > 0; i -= 1) {
347                 if (results[i].word === results[i - 1].word &&
348                     results[i].item.ty === results[i - 1].item.ty &&
349                     results[i].item.path === results[i - 1].item.path &&
350                     (results[i].item.parent || {}).name === (results[i - 1].item.parent || {}).name)
351                 {
352                     results[i].id = -1;
353                 }
354             }
355             for (var i = 0; i < results.length; ++i) {
356                 var result = results[i],
357                     name = result.item.name.toLowerCase(),
358                     path = result.item.path.toLowerCase(),
359                     parent = result.item.parent;
360
361                 // this validation does not make sense when searching by types
362                 if (result.dontValidate) {
363                     continue;
364                 }
365
366                 var valid = validateResult(name, path, split, parent);
367                 if (!valid) {
368                     result.id = -1;
369                 }
370             }
371             return results;
372         }
373
374         /**
375          * Validate performs the following boolean logic. For example:
376          * "File::open" will give IF A PARENT EXISTS => ("file" && "open")
377          * exists in (name || path || parent) OR => ("file" && "open") exists in
378          * (name || path )
379          *
380          * This could be written functionally, but I wanted to minimise
381          * functions on stack.
382          *
383          * @param  {[string]} name   [The name of the result]
384          * @param  {[string]} path   [The path of the result]
385          * @param  {[string]} keys   [The keys to be used (["file", "open"])]
386          * @param  {[object]} parent [The parent of the result]
387          * @return {[boolean]}       [Whether the result is valid or not]
388          */
389         function validateResult(name, path, keys, parent) {
390             for (var i=0; i < keys.length; ++i) {
391                 // each check is for validation so we negate the conditions and invalidate
392                 if (!(
393                     // check for an exact name match
394                     name.toLowerCase().indexOf(keys[i]) > -1 ||
395                     // then an exact path match
396                     path.toLowerCase().indexOf(keys[i]) > -1 ||
397                     // next if there is a parent, check for exact parent match
398                     (parent !== undefined &&
399                         parent.name.toLowerCase().indexOf(keys[i]) > -1) ||
400                     // lastly check to see if the name was a levenshtein match
401                     levenshtein(name.toLowerCase(), keys[i]) <=
402                         MAX_LEV_DISTANCE)) {
403                     return false;
404                 }
405             }
406             return true;
407         }
408
409         function getQuery() {
410             var matches, type, query, raw = $('.search-input').val();
411             query = raw;
412
413             matches = query.match(/^(fn|mod|struct|enum|trait|t(ype)?d(ef)?)\s*:\s*/i);
414             if (matches) {
415                 type = matches[1].replace(/^td$/, 'typedef')
416                                  .replace(/^tdef$/, 'typedef')
417                                  .replace(/^typed$/, 'typedef');
418                 query = query.substring(matches[0].length);
419             }
420
421             return {
422                 raw: raw,
423                 query: query,
424                 type: type,
425                 id: query + type,
426             };
427         }
428
429         function initSearchNav() {
430             var hoverTimeout, $results = $('.search-results .result');
431
432             $results.on('click', function() {
433                 var dst = $(this).find('a')[0];
434                 if (window.location.pathname == dst.pathname) {
435                     $('#search').addClass('hidden');
436                     $('#main').removeClass('hidden');
437                     document.location.href = dst.href;
438                 }
439             }).on('mouseover', function() {
440                 var $el = $(this);
441                 clearTimeout(hoverTimeout);
442                 hoverTimeout = setTimeout(function() {
443                     $results.removeClass('highlighted');
444                     $el.addClass('highlighted');
445                 }, 20);
446             });
447
448             $(document).off('keydown.searchnav');
449             $(document).on('keydown.searchnav', function(e) {
450                 var $active = $results.filter('.highlighted');
451
452                 if (e.which === 38) { // up
453                     e.preventDefault();
454                     if (!$active.length || !$active.prev()) {
455                         return;
456                     }
457
458                     $active.prev().addClass('highlighted');
459                     $active.removeClass('highlighted');
460                 } else if (e.which === 40) { // down
461                     e.preventDefault();
462                     if (!$active.length) {
463                         $results.first().addClass('highlighted');
464                     } else if ($active.next().length) {
465                         $active.next().addClass('highlighted');
466                         $active.removeClass('highlighted');
467                     }
468                 } else if (e.which === 13) { // return
469                     e.preventDefault();
470                     if ($active.length) {
471                         document.location.href = $active.find('a').prop('href');
472                     }
473                 } else {
474                   $active.removeClass('highlighted');
475                 }
476             });
477         }
478
479         function escape(content) {
480             return $('<h1/>').text(content).html();
481         }
482
483         function showResults(results) {
484             var output, shown, query = getQuery();
485
486             currentResults = query.id;
487             output = '<h1>Results for ' + escape(query.query) +
488                 (query.type ? ' (type: ' + escape(query.type) + ')' : '') + '</h1>';
489             output += '<table class="search-results">';
490
491             if (results.length > 0) {
492                 shown = [];
493
494                 results.forEach(function(item) {
495                     var name, type, href, displayPath;
496
497                     if (shown.indexOf(item) !== -1) {
498                         return;
499                     }
500
501                     shown.push(item);
502                     name = item.name;
503                     type = itemTypes[item.ty];
504
505                     if (type === 'mod') {
506                         displayPath = item.path + '::';
507                         href = rootPath + item.path.replace(/::/g, '/') + '/' +
508                                name + '/index.html';
509                     } else if (type === 'static' || type === 'reexport') {
510                         displayPath = item.path + '::';
511                         href = rootPath + item.path.replace(/::/g, '/') +
512                                '/index.html';
513                     } else if (item.parent !== undefined) {
514                         var myparent = item.parent;
515                         var anchor = '#' + type + '.' + name;
516                         displayPath = item.path + '::' + myparent.name + '::';
517                         href = rootPath + item.path.replace(/::/g, '/') +
518                                '/' + itemTypes[myparent.ty] +
519                                '.' + myparent.name +
520                                '.html' + anchor;
521                     } else {
522                         displayPath = item.path + '::';
523                         href = rootPath + item.path.replace(/::/g, '/') +
524                                '/' + type + '.' + name + '.html';
525                     }
526
527                     output += '<tr class="' + type + ' result"><td>' +
528                               '<a href="' + href + '">' +
529                               displayPath + '<span class="' + type + '">' +
530                               name + '</span></a></td><td>' +
531                               '<a href="' + href + '">' +
532                               '<span class="desc">' + item.desc +
533                               '&nbsp;</span></a></td></tr>';
534                 });
535             } else {
536                 output += 'No results :( <a href="https://duckduckgo.com/?q=' +
537                     encodeURIComponent('rust ' + query.query) +
538                     '">Try on DuckDuckGo?</a>';
539             }
540
541             output += "</p>";
542             $('#main.content').addClass('hidden');
543             $('#search.content').removeClass('hidden').html(output);
544             $('#search .desc').width($('#search').width() - 40 -
545                 $('#search td:first-child').first().width());
546             initSearchNav();
547         }
548
549         function search(e) {
550             var query,
551                 filterdata = [],
552                 obj, i, len,
553                 results = [],
554                 maxResults = 200,
555                 resultIndex;
556             var params = getQueryStringParams();
557
558             query = getQuery();
559             if (e) {
560                 e.preventDefault();
561             }
562
563             if (!query.query || query.id === currentResults) {
564                 return;
565             }
566
567             // Because searching is incremental by character, only the most
568             // recent search query is added to the browser history.
569             if (browserSupportsHistoryApi()) {
570                 if (!history.state && !params.search) {
571                     history.pushState(query, "", "?search=" +
572                                                 encodeURIComponent(query.raw));
573                 } else {
574                     history.replaceState(query, "", "?search=" +
575                                                 encodeURIComponent(query.raw));
576                 }
577             }
578
579             resultIndex = execQuery(query, 20000, index);
580             len = resultIndex.length;
581             for (i = 0; i < len; ++i) {
582                 if (resultIndex[i].id > -1) {
583                     obj = searchIndex[resultIndex[i].id];
584                     filterdata.push([obj.name, obj.ty, obj.path, obj.desc]);
585                     results.push(obj);
586                 }
587                 if (results.length >= maxResults) {
588                     break;
589                 }
590             }
591
592             showResults(results);
593         }
594
595         function itemTypeFromName(typename) {
596             for (var i = 0; i < itemTypes.length; ++i) {
597                 if (itemTypes[i] === typename) return i;
598             }
599             return -1;
600         }
601
602         function buildIndex(rawSearchIndex) {
603             searchIndex = [];
604             var searchWords = [];
605             for (var crate in rawSearchIndex) {
606                 if (!rawSearchIndex.hasOwnProperty(crate)) { continue }
607
608                 // an array of [(Number) item type,
609                 //              (String) name,
610                 //              (String) full path or empty string for previous path,
611                 //              (String) description,
612                 //              (Number | null) the parent path index to `paths`]
613                 //              (Object | null) the type of the function (if any)
614                 var items = rawSearchIndex[crate].items;
615                 // an array of [(Number) item type,
616                 //              (String) name]
617                 var paths = rawSearchIndex[crate].paths;
618
619                 // convert `paths` into an object form
620                 var len = paths.length;
621                 for (var i = 0; i < len; ++i) {
622                     paths[i] = {ty: paths[i][0], name: paths[i][1]};
623                 }
624
625                 // convert `items` into an object form, and construct word indices.
626                 //
627                 // before any analysis is performed lets gather the search terms to
628                 // search against apart from the rest of the data.  This is a quick
629                 // operation that is cached for the life of the page state so that
630                 // all other search operations have access to this cached data for
631                 // faster analysis operations
632                 var len = items.length;
633                 var lastPath = "";
634                 for (var i = 0; i < len; ++i) {
635                     var rawRow = items[i];
636                     var row = {crate: crate, ty: rawRow[0], name: rawRow[1],
637                                path: rawRow[2] || lastPath, desc: rawRow[3],
638                                parent: paths[rawRow[4]], type: rawRow[5]};
639                     searchIndex.push(row);
640                     if (typeof row.name === "string") {
641                         var word = row.name.toLowerCase();
642                         searchWords.push(word);
643                     } else {
644                         searchWords.push("");
645                     }
646                     lastPath = row.path;
647                 }
648             }
649             return searchWords;
650         }
651
652         function startSearch() {
653             var keyUpTimeout;
654             $('.do-search').on('click', search);
655             $('.search-input').on('keyup', function() {
656                 clearTimeout(keyUpTimeout);
657                 keyUpTimeout = setTimeout(search, 500);
658             });
659
660             // Push and pop states are used to add search results to the browser
661             // history.
662             if (browserSupportsHistoryApi()) {
663                 $(window).on('popstate', function(e) {
664                     var params = getQueryStringParams();
665                     // When browsing back from search results the main page
666                     // visibility must be reset.
667                     if (!params.search) {
668                         $('#main.content').removeClass('hidden');
669                         $('#search.content').addClass('hidden');
670                     }
671                     // When browsing forward to search results the previous
672                     // search will be repeated, so the currentResults are
673                     // cleared to ensure the search is successful.
674                     currentResults = null;
675                     // Synchronize search bar with query string state and
676                     // perform the search. This will empty the bar if there's
677                     // nothing there, which lets you really go back to a
678                     // previous state with nothing in the bar.
679                     $('.search-input').val(params.search);
680                     // Some browsers fire 'onpopstate' for every page load
681                     // (Chrome), while others fire the event only when actually
682                     // popping a state (Firefox), which is why search() is
683                     // called both here and at the end of the startSearch()
684                     // function.
685                     search();
686                 });
687             }
688             search();
689         }
690
691         function plainSummaryLine(markdown) {
692             var str = markdown.replace(/\n/g, ' ')
693             str = str.replace(/'/g, "\'")
694             str = str.replace(/^#+? (.+?)/, "$1")
695             str = str.replace(/\[(.*?)\]\(.*?\)/g, "$1")
696             str = str.replace(/\[(.*?)\]\[.*?\]/g, "$1")
697             return str;
698         }
699
700         index = buildIndex(rawSearchIndex);
701         startSearch();
702
703         // Draw a convenient sidebar of known crates if we have a listing
704         if (rootPath == '../') {
705             var sidebar = $('.sidebar');
706             var div = $('<div>').attr('class', 'block crate');
707             div.append($('<h2>').text('Crates'));
708
709             var crates = [];
710             for (var crate in rawSearchIndex) {
711                 if (!rawSearchIndex.hasOwnProperty(crate)) { continue }
712                 crates.push(crate);
713             }
714             crates.sort();
715             for (var i = 0; i < crates.length; ++i) {
716                 var klass = 'crate';
717                 if (crates[i] == window.currentCrate) {
718                     klass += ' current';
719                 }
720                 if (rawSearchIndex[crates[i]].items[0]) {
721                     var desc = rawSearchIndex[crates[i]].items[0][3];
722                     div.append($('<a>', {'href': '../' + crates[i] + '/index.html',
723                                          'title': plainSummaryLine(desc),
724                                          'class': klass}).text(crates[i]));
725                 }
726             }
727             sidebar.append(div);
728         }
729     }
730
731     window.initSearch = initSearch;
732
733     // delayed sidebar rendering.
734     function initSidebarItems(items) {
735         var sidebar = $('.sidebar');
736         var current = window.sidebarCurrent;
737
738         function block(shortty, longty) {
739             var filtered = items[shortty];
740             if (!filtered) return;
741
742             var div = $('<div>').attr('class', 'block ' + shortty);
743             div.append($('<h2>').text(longty));
744
745             for (var i = 0; i < filtered.length; ++i) {
746                 var item = filtered[i];
747                 var name = item[0];
748                 var desc = item[1]; // can be null
749
750                 var klass = shortty;
751                 if (name === current.name && shortty == current.ty) {
752                     klass += ' current';
753                 }
754                 var path;
755                 if (shortty === 'mod') {
756                     path = name + '/index.html';
757                 } else {
758                     path = shortty + '.' + name + '.html';
759                 }
760                 div.append($('<a>', {'href': current.relpath + path,
761                                      'title': desc,
762                                      'class': klass}).text(name));
763             }
764             sidebar.append(div);
765         }
766
767         block("mod", "Modules");
768         block("struct", "Structs");
769         block("enum", "Enums");
770         block("trait", "Traits");
771         block("fn", "Functions");
772         block("macro", "Macros");
773     }
774
775     window.initSidebarItems = initSidebarItems;
776
777     window.register_implementors = function(imp) {
778         var list = $('#implementors-list');
779         var libs = Object.getOwnPropertyNames(imp);
780         for (var i = 0; i < libs.length; ++i) {
781             if (libs[i] == currentCrate) continue;
782             var structs = imp[libs[i]];
783             for (var j = 0; j < structs.length; ++j) {
784                 var code = $('<code>').append(structs[j]);
785                 $.each(code.find('a'), function(idx, a) {
786                     var href = $(a).attr('href');
787                     if (href && href.indexOf('http') !== 0) {
788                         $(a).attr('href', rootPath + href);
789                     }
790                 });
791                 var li = $('<li>').append(code);
792                 list.append(li);
793             }
794         }
795     };
796     if (window.pending_implementors) {
797         window.register_implementors(window.pending_implementors);
798     }
799
800     // See documentation in html/render.rs for what this is doing.
801     var query = getQueryStringParams();
802     if (query['gotosrc']) {
803         window.location = $('#src-' + query['gotosrc']).attr('href');
804     }
805     if (query['gotomacrosrc']) {
806         window.location = $('.srclink').attr('href');
807     }
808
809     $("#toggle-all-docs").on("click", function() {
810         var toggle = $("#toggle-all-docs");
811         if (toggle.html() == "[-]") {
812             toggle.html("[+]");
813             toggle.attr("title", "expand all docs");
814             $(".docblock").hide();
815             $(".toggle-label").show();
816             $(".toggle-wrapper").addClass("collapsed");
817             $(".collapse-toggle").children(".inner").html("+");
818         } else {
819             toggle.html("[-]");
820             toggle.attr("title", "collapse all docs");
821             $(".docblock").show();
822             $(".toggle-label").hide();
823             $(".toggle-wrapper").removeClass("collapsed");
824             $(".collapse-toggle").children(".inner").html("-");
825         }
826     });
827
828     $(document).on("click", ".collapse-toggle", function() {
829         var toggle = $(this);
830         var relatedDoc = toggle.parent().next();
831         if (relatedDoc.is(".stability")) {
832             relatedDoc = relatedDoc.next();
833         }
834         if (relatedDoc.is(".docblock")) {
835             if (relatedDoc.is(":visible")) {
836                 relatedDoc.slideUp({duration:'fast', easing:'linear'});
837                 toggle.parent(".toggle-wrapper").addClass("collapsed");
838                 toggle.children(".inner").html("+");
839                 toggle.children(".toggle-label").fadeIn();
840             } else {
841                 relatedDoc.slideDown({duration:'fast', easing:'linear'});
842                 toggle.parent(".toggle-wrapper").removeClass("collapsed");
843                 toggle.children(".inner").html("-");
844                 toggle.children(".toggle-label").hide();
845             }
846         }
847     });
848
849     $(function() {
850         var toggle = $("<a/>", {'href': 'javascript:void(0)', 'class': 'collapse-toggle'})
851             .html("[<span class='inner'>-</span>]");
852
853         $(".method").each(function() {
854             if ($(this).next().is(".docblock") ||
855                 ($(this).next().is(".stability") && $(this).next().next().is(".docblock"))) {
856                     $(this).children().first().after(toggle.clone());
857             }
858         });
859
860         var mainToggle =
861             $(toggle).append(
862                 $('<span/>', {'class': 'toggle-label'})
863                     .css('display', 'none')
864                     .html('&nbsp;Expand&nbsp;description'));
865         var wrapper =  $("<div class='toggle-wrapper'>").append(mainToggle);
866         $("#main > .docblock").before(wrapper);
867     });
868
869     $('pre.line-numbers').on('click', 'span', function() {
870         var prev_id = 0;
871
872         function set_fragment(name) {
873             if (history.replaceState) {
874                 history.replaceState(null, null, '#' + name);
875                 $(window).trigger('hashchange');
876             } else {
877                 location.replace('#' + name);
878             }
879         }
880
881         return function(ev) {
882             var cur_id = parseInt(ev.target.id);
883
884             if (ev.shiftKey && prev_id) {
885                 if (prev_id > cur_id) {
886                     var tmp = prev_id;
887                     prev_id = cur_id;
888                     cur_id = tmp;
889                 }
890
891                 set_fragment(prev_id + '-' + cur_id);
892             } else {
893                 prev_id = cur_id;
894
895                 set_fragment(cur_id);
896             }
897         };
898     }());
899
900 }());