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