]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/html/static/main.js
rustdoc: Fix links to static items in the search results
[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 === "primitive") {
581                         displayPath = "";
582                         href = rootPath + item.path.replace(/::/g, '/') +
583                                '/' + type + '.' + name + '.html';
584                     } else if (type === "externcrate") {
585                         displayPath = "";
586                         href = rootPath + name + '/index.html';
587                     } else if (item.parent !== undefined) {
588                         var myparent = item.parent;
589                         var anchor = '#' + type + '.' + name;
590                         displayPath = item.path + '::' + myparent.name + '::';
591                         href = rootPath + item.path.replace(/::/g, '/') +
592                                '/' + itemTypes[myparent.ty] +
593                                '.' + myparent.name +
594                                '.html' + anchor;
595                     } else {
596                         displayPath = item.path + '::';
597                         href = rootPath + item.path.replace(/::/g, '/') +
598                                '/' + type + '.' + name + '.html';
599                     }
600
601                     output += '<tr class="' + type + ' result"><td>' +
602                               '<a href="' + href + '">' +
603                               displayPath + '<span class="' + type + '">' +
604                               name + '</span></a></td><td>' +
605                               '<a href="' + href + '">' +
606                               '<span class="desc">' + item.desc +
607                               '&nbsp;</span></a></td></tr>';
608                 });
609             } else {
610                 output += 'No results :( <a href="https://duckduckgo.com/?q=' +
611                     encodeURIComponent('rust ' + query.query) +
612                     '">Try on DuckDuckGo?</a>';
613             }
614
615             output += "</p>";
616             $('#main.content').addClass('hidden');
617             $('#search.content').removeClass('hidden').html(output);
618             $('#search .desc').width($('#search').width() - 40 -
619                 $('#search td:first-child').first().width());
620             initSearchNav();
621         }
622
623         function search(e) {
624             var query,
625                 filterdata = [],
626                 obj, i, len,
627                 results = [],
628                 maxResults = 200,
629                 resultIndex;
630             var params = getQueryStringParams();
631
632             query = getQuery();
633             if (e) {
634                 e.preventDefault();
635             }
636
637             if (!query.query || query.id === currentResults) {
638                 return;
639             }
640
641             // Update document title to maintain a meaningful browser history
642             $(document).prop("title", "Results for " + query.query + " - Rust");
643
644             // Because searching is incremental by character, only the most
645             // recent search query is added to the browser history.
646             if (browserSupportsHistoryApi()) {
647                 if (!history.state && !params.search) {
648                     history.pushState(query, "", "?search=" +
649                                                 encodeURIComponent(query.raw));
650                 } else {
651                     history.replaceState(query, "", "?search=" +
652                                                 encodeURIComponent(query.raw));
653                 }
654             }
655
656             resultIndex = execQuery(query, 20000, index);
657             len = resultIndex.length;
658             for (i = 0; i < len; ++i) {
659                 if (resultIndex[i].id > -1) {
660                     obj = searchIndex[resultIndex[i].id];
661                     filterdata.push([obj.name, obj.ty, obj.path, obj.desc]);
662                     results.push(obj);
663                 }
664                 if (results.length >= maxResults) {
665                     break;
666                 }
667             }
668
669             showResults(results);
670         }
671
672         function itemTypeFromName(typename) {
673             for (var i = 0; i < itemTypes.length; ++i) {
674                 if (itemTypes[i] === typename) { return i; }
675             }
676             return -1;
677         }
678
679         function buildIndex(rawSearchIndex) {
680             searchIndex = [];
681             var searchWords = [];
682             for (var crate in rawSearchIndex) {
683                 if (!rawSearchIndex.hasOwnProperty(crate)) { continue; }
684
685                 searchWords.push(crate);
686                 searchIndex.push({
687                     crate: crate,
688                     ty: 1, // == ExternCrate
689                     name: crate,
690                     path: "",
691                     desc: rawSearchIndex[crate].doc,
692                     type: null,
693                 });
694
695                 // an array of [(Number) item type,
696                 //              (String) name,
697                 //              (String) full path or empty string for previous path,
698                 //              (String) description,
699                 //              (Number | null) the parent path index to `paths`]
700                 //              (Object | null) the type of the function (if any)
701                 var items = rawSearchIndex[crate].items;
702                 // an array of [(Number) item type,
703                 //              (String) name]
704                 var paths = rawSearchIndex[crate].paths;
705
706                 // convert `paths` into an object form
707                 var len = paths.length;
708                 for (var i = 0; i < len; ++i) {
709                     paths[i] = {ty: paths[i][0], name: paths[i][1]};
710                 }
711
712                 // convert `items` into an object form, and construct word indices.
713                 //
714                 // before any analysis is performed lets gather the search terms to
715                 // search against apart from the rest of the data.  This is a quick
716                 // operation that is cached for the life of the page state so that
717                 // all other search operations have access to this cached data for
718                 // faster analysis operations
719                 var len = items.length;
720                 var lastPath = "";
721                 for (var i = 0; i < len; ++i) {
722                     var rawRow = items[i];
723                     var row = {crate: crate, ty: rawRow[0], name: rawRow[1],
724                                path: rawRow[2] || lastPath, desc: rawRow[3],
725                                parent: paths[rawRow[4]], type: rawRow[5]};
726                     searchIndex.push(row);
727                     if (typeof row.name === "string") {
728                         var word = row.name.toLowerCase();
729                         searchWords.push(word);
730                     } else {
731                         searchWords.push("");
732                     }
733                     lastPath = row.path;
734                 }
735             }
736             return searchWords;
737         }
738
739         function startSearch() {
740             var searchTimeout;
741             $(".search-input").on("keyup input",function() {
742                 clearTimeout(searchTimeout);
743                 if ($(this).val().length === 0) {
744                     if (browserSupportsHistoryApi()) {
745                         history.replaceState("", "std - Rust", "?search=");
746                     }
747                     $('#main.content').removeClass('hidden');
748                     $('#search.content').addClass('hidden');
749                 } else {
750                     searchTimeout = setTimeout(search, 500);
751                 }
752             });
753             $('.search-form').on('submit', function(e){
754                 e.preventDefault();
755                 clearTimeout(searchTimeout);
756                 search();
757             });
758             $('.search-input').on('change paste', function(e) {
759                 // Do NOT e.preventDefault() here. It will prevent pasting.
760                 clearTimeout(searchTimeout);
761                 // zero-timeout necessary here because at the time of event handler execution the
762                 // pasted content is not in the input field yet. Shouldn’t make any difference for
763                 // change, though.
764                 setTimeout(search, 0);
765             });
766
767             // Push and pop states are used to add search results to the browser
768             // history.
769             if (browserSupportsHistoryApi()) {
770                 // Store the previous <title> so we can revert back to it later.
771                 var previousTitle = $(document).prop("title");
772
773                 $(window).on('popstate', function(e) {
774                     var params = getQueryStringParams();
775                     // When browsing back from search results the main page
776                     // visibility must be reset.
777                     if (!params.search) {
778                         $('#main.content').removeClass('hidden');
779                         $('#search.content').addClass('hidden');
780                     }
781                     // Revert to the previous title manually since the History
782                     // API ignores the title parameter.
783                     $(document).prop("title", previousTitle);
784                     // When browsing forward to search results the previous
785                     // search will be repeated, so the currentResults are
786                     // cleared to ensure the search is successful.
787                     currentResults = null;
788                     // Synchronize search bar with query string state and
789                     // perform the search. This will empty the bar if there's
790                     // nothing there, which lets you really go back to a
791                     // previous state with nothing in the bar.
792                     $('.search-input').val(params.search);
793                     // Some browsers fire 'onpopstate' for every page load
794                     // (Chrome), while others fire the event only when actually
795                     // popping a state (Firefox), which is why search() is
796                     // called both here and at the end of the startSearch()
797                     // function.
798                     search();
799                 });
800             }
801             search();
802         }
803
804         function plainSummaryLine(markdown) {
805             markdown.replace(/\n/g, ' ')
806             .replace(/'/g, "\'")
807             .replace(/^#+? (.+?)/, "$1")
808             .replace(/\[(.*?)\]\(.*?\)/g, "$1")
809             .replace(/\[(.*?)\]\[.*?\]/g, "$1");
810         }
811
812         index = buildIndex(rawSearchIndex);
813         startSearch();
814
815         // Draw a convenient sidebar of known crates if we have a listing
816         if (rootPath === '../') {
817             var sidebar = $('.sidebar');
818             var div = $('<div>').attr('class', 'block crate');
819             div.append($('<h3>').text('Crates'));
820             var ul = $('<ul>').appendTo(div);
821
822             var crates = [];
823             for (var crate in rawSearchIndex) {
824                 if (!rawSearchIndex.hasOwnProperty(crate)) { continue; }
825                 crates.push(crate);
826             }
827             crates.sort();
828             for (var i = 0; i < crates.length; ++i) {
829                 var klass = 'crate';
830                 if (crates[i] === window.currentCrate) {
831                     klass += ' current';
832                 }
833                 if (rawSearchIndex[crates[i]].items[0]) {
834                     var desc = rawSearchIndex[crates[i]].items[0][3];
835                     var link = $('<a>', {'href': '../' + crates[i] + '/index.html',
836                                          'title': plainSummaryLine(desc),
837                                          'class': klass}).text(crates[i]);
838                     ul.append($('<li>').append(link));
839                 }
840             }
841             sidebar.append(div);
842         }
843     }
844
845     window.initSearch = initSearch;
846
847     // delayed sidebar rendering.
848     function initSidebarItems(items) {
849         var sidebar = $('.sidebar');
850         var current = window.sidebarCurrent;
851
852         function block(shortty, longty) {
853             var filtered = items[shortty];
854             if (!filtered) { return; }
855
856             var div = $('<div>').attr('class', 'block ' + shortty);
857             div.append($('<h3>').text(longty));
858             var ul = $('<ul>').appendTo(div);
859
860             for (var i = 0; i < filtered.length; ++i) {
861                 var item = filtered[i];
862                 var name = item[0];
863                 var desc = item[1]; // can be null
864
865                 var klass = shortty;
866                 if (name === current.name && shortty === current.ty) {
867                     klass += ' current';
868                 }
869                 var path;
870                 if (shortty === 'mod') {
871                     path = name + '/index.html';
872                 } else {
873                     path = shortty + '.' + name + '.html';
874                 }
875                 var link = $('<a>', {'href': current.relpath + path,
876                                      'title': desc,
877                                      'class': klass}).text(name);
878                 ul.append($('<li>').append(link));
879             }
880             sidebar.append(div);
881         }
882
883         block("primitive", "Primitive Types");
884         block("mod", "Modules");
885         block("macro", "Macros");
886         block("struct", "Structs");
887         block("enum", "Enums");
888         block("constant", "Constants");
889         block("static", "Statics");
890         block("trait", "Traits");
891         block("fn", "Functions");
892         block("type", "Type Definitions");
893     }
894
895     window.initSidebarItems = initSidebarItems;
896
897     window.register_implementors = function(imp) {
898         var list = $('#implementors-list');
899         var libs = Object.getOwnPropertyNames(imp);
900         for (var i = 0; i < libs.length; ++i) {
901             if (libs[i] === currentCrate) { continue; }
902             var structs = imp[libs[i]];
903             for (var j = 0; j < structs.length; ++j) {
904                 var code = $('<code>').append(structs[j]);
905                 $.each(code.find('a'), function(idx, a) {
906                     var href = $(a).attr('href');
907                     if (href && href.indexOf('http') !== 0) {
908                         $(a).attr('href', rootPath + href);
909                     }
910                 });
911                 var li = $('<li>').append(code);
912                 list.append(li);
913             }
914         }
915     };
916     if (window.pending_implementors) {
917         window.register_implementors(window.pending_implementors);
918     }
919
920     // See documentation in html/render.rs for what this is doing.
921     var query = getQueryStringParams();
922     if (query['gotosrc']) {
923         window.location = $('#src-' + query['gotosrc']).attr('href');
924     }
925     if (query['gotomacrosrc']) {
926         window.location = $('.srclink').attr('href');
927     }
928
929     function labelForToggleButton(sectionIsCollapsed) {
930         if (sectionIsCollapsed) {
931             // button will expand the section
932             return "+";
933         }
934         // button will collapse the section
935         // note that this text is also set in the HTML template in render.rs
936         return "\u2212"; // "\u2212" is '−' minus sign
937     }
938
939     function toggleAllDocs() {
940         var toggle = $("#toggle-all-docs");
941         if (toggle.hasClass("will-expand")) {
942             toggle.removeClass("will-expand");
943             toggle.children(".inner").text(labelForToggleButton(false));
944             toggle.attr("title", "collapse all docs");
945             $(".docblock").show();
946             $(".toggle-label").hide();
947             $(".toggle-wrapper").removeClass("collapsed");
948             $(".collapse-toggle").children(".inner").text(labelForToggleButton(false));
949         } else {
950             toggle.addClass("will-expand");
951             toggle.children(".inner").text(labelForToggleButton(true));
952             toggle.attr("title", "expand all docs");
953             $(".docblock").hide();
954             $(".toggle-label").show();
955             $(".toggle-wrapper").addClass("collapsed");
956             $(".collapse-toggle").children(".inner").text(labelForToggleButton(true));
957         }
958     }
959
960     $("#toggle-all-docs").on("click", toggleAllDocs);
961
962     $(document).on("click", ".collapse-toggle", function() {
963         var toggle = $(this);
964         var relatedDoc = toggle.parent().next();
965         if (relatedDoc.is(".stability")) {
966             relatedDoc = relatedDoc.next();
967         }
968         if (relatedDoc.is(".docblock")) {
969             if (relatedDoc.is(":visible")) {
970                 relatedDoc.slideUp({duration: 'fast', easing: 'linear'});
971                 toggle.parent(".toggle-wrapper").addClass("collapsed");
972                 toggle.children(".inner").text(labelForToggleButton(true));
973                 toggle.children(".toggle-label").fadeIn();
974             } else {
975                 relatedDoc.slideDown({duration: 'fast', easing: 'linear'});
976                 toggle.parent(".toggle-wrapper").removeClass("collapsed");
977                 toggle.children(".inner").text(labelForToggleButton(false));
978                 toggle.children(".toggle-label").hide();
979             }
980         }
981     });
982
983     $(function() {
984         var toggle = $("<a/>", {'href': 'javascript:void(0)', 'class': 'collapse-toggle'})
985             .html("[<span class='inner'></span>]");
986         toggle.children(".inner").text(labelForToggleButton(false));
987
988         $(".method").each(function() {
989             if ($(this).next().is(".docblock") ||
990                 ($(this).next().is(".stability") && $(this).next().next().is(".docblock"))) {
991                     $(this).children().last().after(toggle.clone());
992             }
993         });
994
995         var mainToggle =
996             $(toggle).append(
997                 $('<span/>', {'class': 'toggle-label'})
998                     .css('display', 'none')
999                     .html('&nbsp;Expand&nbsp;description'));
1000         var wrapper = $("<div class='toggle-wrapper'>").append(mainToggle);
1001         $("#main > .docblock").before(wrapper);
1002     });
1003
1004     $('pre.line-numbers').on('click', 'span', function() {
1005         var prev_id = 0;
1006
1007         function set_fragment(name) {
1008             if (browserSupportsHistoryApi()) {
1009                 history.replaceState(null, null, '#' + name);
1010                 $(window).trigger('hashchange');
1011             } else {
1012                 location.replace('#' + name);
1013             }
1014         }
1015
1016         return function(ev) {
1017             var cur_id = parseInt(ev.target.id, 10);
1018
1019             if (ev.shiftKey && prev_id) {
1020                 if (prev_id > cur_id) {
1021                     var tmp = prev_id;
1022                     prev_id = cur_id;
1023                     cur_id = tmp;
1024                 }
1025
1026                 set_fragment(prev_id + '-' + cur_id);
1027             } else {
1028                 prev_id = cur_id;
1029
1030                 set_fragment(cur_id);
1031             }
1032         };
1033     }());
1034
1035 }());
1036
1037 // Sets the focus on the search bar at the top of the page
1038 function focusSearchBar() {
1039     $('.search-input').focus();
1040 }