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