]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/html/static/js/search.js
Rollup merge of #89876 - AlexApps99:const_ops, r=oli-obk
[rust.git] / src / librustdoc / html / static / js / search.js
1 /* global addClass, getNakedUrl, getSettingValue, hasOwnPropertyRustdoc, initSearch, onEach */
2 /* global onEachLazy, removeClass, searchState, updateLocalStorage */
3
4 (function() {
5 // This mapping table should match the discriminants of
6 // `rustdoc::html::item_type::ItemType` type in Rust.
7 var itemTypes = ["mod",
8                     "externcrate",
9                     "import",
10                     "struct",
11                     "enum",
12                     "fn",
13                     "type",
14                     "static",
15                     "trait",
16                     "impl",
17                     "tymethod",
18                     "method",
19                     "structfield",
20                     "variant",
21                     "macro",
22                     "primitive",
23                     "associatedtype",
24                     "constant",
25                     "associatedconstant",
26                     "union",
27                     "foreigntype",
28                     "keyword",
29                     "existential",
30                     "attr",
31                     "derive",
32                     "traitalias"];
33
34 // used for special search precedence
35 var TY_PRIMITIVE = itemTypes.indexOf("primitive");
36 var TY_KEYWORD = itemTypes.indexOf("keyword");
37
38 // In the search display, allows to switch between tabs.
39 function printTab(nb) {
40     if (nb === 0 || nb === 1 || nb === 2) {
41         searchState.currentTab = nb;
42     }
43     var nb_copy = nb;
44     onEachLazy(document.getElementById("titles").childNodes, function(elem) {
45         if (nb_copy === 0) {
46             addClass(elem, "selected");
47         } else {
48             removeClass(elem, "selected");
49         }
50         nb_copy -= 1;
51     });
52     onEachLazy(document.getElementById("results").childNodes, function(elem) {
53         if (nb === 0) {
54             addClass(elem, "active");
55         } else {
56             removeClass(elem, "active");
57         }
58         nb -= 1;
59     });
60 }
61
62 function removeEmptyStringsFromArray(x) {
63     for (var i = 0, len = x.length; i < len; ++i) {
64         if (x[i] === "") {
65             x.splice(i, 1);
66             i -= 1;
67         }
68     }
69 }
70
71 /**
72  * A function to compute the Levenshtein distance between two strings
73  * Licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported
74  * Full License can be found at http://creativecommons.org/licenses/by-sa/3.0/legalcode
75  * This code is an unmodified version of the code written by Marco de Wit
76  * and was found at https://stackoverflow.com/a/18514751/745719
77  */
78 var levenshtein_row2 = [];
79 function levenshtein(s1, s2) {
80     if (s1 === s2) {
81         return 0;
82     }
83     var s1_len = s1.length, s2_len = s2.length;
84     if (s1_len && s2_len) {
85         var i1 = 0, i2 = 0, a, b, c, c2, row = levenshtein_row2;
86         while (i1 < s1_len) {
87             row[i1] = ++i1;
88         }
89         while (i2 < s2_len) {
90             c2 = s2.charCodeAt(i2);
91             a = i2;
92             ++i2;
93             b = i2;
94             for (i1 = 0; i1 < s1_len; ++i1) {
95                 c = a + (s1.charCodeAt(i1) !== c2 ? 1 : 0);
96                 a = row[i1];
97                 b = b < a ? (b < c ? b + 1 : c) : (a < c ? a + 1 : c);
98                 row[i1] = b;
99             }
100         }
101         return b;
102     }
103     return s1_len + s2_len;
104 }
105
106 window.initSearch = function(rawSearchIndex) {
107     var MAX_LEV_DISTANCE = 3;
108     var MAX_RESULTS = 200;
109     var GENERICS_DATA = 2;
110     var NAME = 0;
111     var INPUTS_DATA = 0;
112     var OUTPUT_DATA = 1;
113     var NO_TYPE_FILTER = -1;
114     var currentResults, index, searchIndex;
115     var ALIASES = {};
116     var params = searchState.getQueryStringParams();
117
118     // Populate search bar with query string search term when provided,
119     // but only if the input bar is empty. This avoid the obnoxious issue
120     // where you start trying to do a search, and the index loads, and
121     // suddenly your search is gone!
122     if (searchState.input.value === "") {
123         searchState.input.value = params.search || "";
124     }
125
126     /**
127      * Executes the query and builds an index of results
128      * @param  {[Object]} query      [The user query]
129      * @param  {[type]} searchWords  [The list of search words to query
130      *                                against]
131      * @param  {[type]} filterCrates [Crate to search in if defined]
132      * @return {[type]}              [A search index of results]
133      */
134     function execQuery(query, searchWords, filterCrates) {
135         function itemTypeFromName(typename) {
136             for (var i = 0, len = itemTypes.length; i < len; ++i) {
137                 if (itemTypes[i] === typename) {
138                     return i;
139                 }
140             }
141             return NO_TYPE_FILTER;
142         }
143
144         var valLower = query.query.toLowerCase(),
145             val = valLower,
146             typeFilter = itemTypeFromName(query.type),
147             results = {}, results_in_args = {}, results_returned = {},
148             split = valLower.split("::");
149
150         removeEmptyStringsFromArray(split);
151
152         function transformResults(results) {
153             var out = [];
154             for (var i = 0, len = results.length; i < len; ++i) {
155                 if (results[i].id > -1) {
156                     var obj = searchIndex[results[i].id];
157                     obj.lev = results[i].lev;
158                     var res = buildHrefAndPath(obj);
159                     obj.displayPath = pathSplitter(res[0]);
160                     obj.fullPath = obj.displayPath + obj.name;
161                     // To be sure than it some items aren't considered as duplicate.
162                     obj.fullPath += "|" + obj.ty;
163                     obj.href = res[1];
164                     out.push(obj);
165                     if (out.length >= MAX_RESULTS) {
166                         break;
167                     }
168                 }
169             }
170             return out;
171         }
172
173         function sortResults(results, isType) {
174             var ar = [];
175             for (var entry in results) {
176                 if (hasOwnPropertyRustdoc(results, entry)) {
177                     ar.push(results[entry]);
178                 }
179             }
180             results = ar;
181             var i, len, result;
182             for (i = 0, len = results.length; i < len; ++i) {
183                 result = results[i];
184                 result.word = searchWords[result.id];
185                 result.item = searchIndex[result.id] || {};
186             }
187             // if there are no results then return to default and fail
188             if (results.length === 0) {
189                 return [];
190             }
191
192             results.sort(function(aaa, bbb) {
193                 var a, b;
194
195                 // sort by exact match with regard to the last word (mismatch goes later)
196                 a = (aaa.word !== val);
197                 b = (bbb.word !== val);
198                 if (a !== b) { return a - b; }
199
200                 // Sort by non levenshtein results and then levenshtein results by the distance
201                 // (less changes required to match means higher rankings)
202                 a = (aaa.lev);
203                 b = (bbb.lev);
204                 if (a !== b) { return a - b; }
205
206                 // sort by crate (non-current crate goes later)
207                 a = (aaa.item.crate !== window.currentCrate);
208                 b = (bbb.item.crate !== window.currentCrate);
209                 if (a !== b) { return a - b; }
210
211                 // sort by item name length (longer goes later)
212                 a = aaa.word.length;
213                 b = bbb.word.length;
214                 if (a !== b) { return a - b; }
215
216                 // sort by item name (lexicographically larger goes later)
217                 a = aaa.word;
218                 b = bbb.word;
219                 if (a !== b) { return (a > b ? +1 : -1); }
220
221                 // sort by index of keyword in item name (no literal occurrence goes later)
222                 a = (aaa.index < 0);
223                 b = (bbb.index < 0);
224                 if (a !== b) { return a - b; }
225                 // (later literal occurrence, if any, goes later)
226                 a = aaa.index;
227                 b = bbb.index;
228                 if (a !== b) { return a - b; }
229
230                 // special precedence for primitive and keyword pages
231                 if ((aaa.item.ty === TY_PRIMITIVE && bbb.item.ty !== TY_KEYWORD) ||
232                     (aaa.item.ty === TY_KEYWORD && bbb.item.ty !== TY_PRIMITIVE)) {
233                     return -1;
234                 }
235                 if ((bbb.item.ty === TY_PRIMITIVE && aaa.item.ty !== TY_PRIMITIVE) ||
236                     (bbb.item.ty === TY_KEYWORD && aaa.item.ty !== TY_KEYWORD)) {
237                     return 1;
238                 }
239
240                 // sort by description (no description goes later)
241                 a = (aaa.item.desc === "");
242                 b = (bbb.item.desc === "");
243                 if (a !== b) { return a - b; }
244
245                 // sort by type (later occurrence in `itemTypes` goes later)
246                 a = aaa.item.ty;
247                 b = bbb.item.ty;
248                 if (a !== b) { return a - b; }
249
250                 // sort by path (lexicographically larger goes later)
251                 a = aaa.item.path;
252                 b = bbb.item.path;
253                 if (a !== b) { return (a > b ? +1 : -1); }
254
255                 // que sera, sera
256                 return 0;
257             });
258
259             for (i = 0, len = results.length; i < len; ++i) {
260                 result = results[i];
261
262                 // this validation does not make sense when searching by types
263                 if (result.dontValidate) {
264                     continue;
265                 }
266                 var name = result.item.name.toLowerCase(),
267                     path = result.item.path.toLowerCase(),
268                     parent = result.item.parent;
269
270                 if (!isType && !validateResult(name, path, split, parent)) {
271                     result.id = -1;
272                 }
273             }
274             return transformResults(results);
275         }
276
277         function extractGenerics(val) {
278             val = val.toLowerCase();
279             if (val.indexOf("<") !== -1) {
280                 var values = val.substring(val.indexOf("<") + 1, val.lastIndexOf(">"));
281                 return {
282                     name: val.substring(0, val.indexOf("<")),
283                     generics: values.split(/\s*,\s*/),
284                 };
285             }
286             return {
287                 name: val,
288                 generics: [],
289             };
290         }
291
292         function checkGenerics(obj, val) {
293             // The names match, but we need to be sure that all generics kinda
294             // match as well.
295             var tmp_lev, elem_name;
296             if (val.generics.length > 0) {
297                 if (obj.length > GENERICS_DATA &&
298                       obj[GENERICS_DATA].length >= val.generics.length) {
299                     var elems = Object.create(null);
300                     var elength = obj[GENERICS_DATA].length;
301                     for (var x = 0; x < elength; ++x) {
302                         if (!elems[obj[GENERICS_DATA][x][NAME]]) {
303                             elems[obj[GENERICS_DATA][x][NAME]] = 0;
304                         }
305                         elems[obj[GENERICS_DATA][x][NAME]] += 1;
306                     }
307                     var total = 0;
308                     var done = 0;
309                     // We need to find the type that matches the most to remove it in order
310                     // to move forward.
311                     var vlength = val.generics.length;
312                     for (x = 0; x < vlength; ++x) {
313                         var lev = MAX_LEV_DISTANCE + 1;
314                         var firstGeneric = val.generics[x];
315                         var match = null;
316                         if (elems[firstGeneric]) {
317                             match = firstGeneric;
318                             lev = 0;
319                         } else {
320                             for (elem_name in elems) {
321                                 tmp_lev = levenshtein(elem_name, firstGeneric);
322                                 if (tmp_lev < lev) {
323                                     lev = tmp_lev;
324                                     match = elem_name;
325                                 }
326                             }
327                         }
328                         if (match !== null) {
329                             elems[match] -= 1;
330                             if (elems[match] == 0) {
331                                 delete elems[match];
332                             }
333                             total += lev;
334                             done += 1;
335                         } else {
336                             return MAX_LEV_DISTANCE + 1;
337                         }
338                     }
339                     return Math.ceil(total / done);
340                 }
341             }
342             return MAX_LEV_DISTANCE + 1;
343         }
344
345         // Check for type name and type generics (if any).
346         function checkType(obj, val, literalSearch) {
347             var lev_distance = MAX_LEV_DISTANCE + 1;
348             var tmp_lev = MAX_LEV_DISTANCE + 1;
349             var len, x, firstGeneric;
350             if (obj[NAME] === val.name) {
351                 if (literalSearch) {
352                     if (val.generics && val.generics.length !== 0) {
353                         if (obj.length > GENERICS_DATA &&
354                              obj[GENERICS_DATA].length > 0) {
355                             var elems = Object.create(null);
356                             len = obj[GENERICS_DATA].length;
357                             for (x = 0; x < len; ++x) {
358                                 if (!elems[obj[GENERICS_DATA][x][NAME]]) {
359                                     elems[obj[GENERICS_DATA][x][NAME]] = 0;
360                                 }
361                                 elems[obj[GENERICS_DATA][x][NAME]] += 1;
362                             }
363
364                             var allFound = true;
365                             len = val.generics.length;
366                             for (x = 0; x < len; ++x) {
367                                 firstGeneric = val.generics[x];
368                                 if (elems[firstGeneric]) {
369                                     elems[firstGeneric] -= 1;
370                                 } else {
371                                     allFound = false;
372                                     break;
373                                 }
374                             }
375                             if (allFound) {
376                                 return true;
377                             }
378                         }
379                         return false;
380                     }
381                     return true;
382                 } else {
383                     // If the type has generics but don't match, then it won't return at this point.
384                     // Otherwise, `checkGenerics` will return 0 and it'll return.
385                     if (obj.length > GENERICS_DATA && obj[GENERICS_DATA].length !== 0) {
386                         tmp_lev = checkGenerics(obj, val);
387                         if (tmp_lev <= MAX_LEV_DISTANCE) {
388                             return tmp_lev;
389                         }
390                     }
391                 }
392             } else if (literalSearch) {
393                 if ((!val.generics || val.generics.length === 0) &&
394                       obj.length > GENERICS_DATA && obj[GENERICS_DATA].length > 0) {
395                     return obj[GENERICS_DATA].some(
396                         function(gen) {
397                             return gen[NAME] === val.name;
398                         });
399                 }
400                 return false;
401             }
402             lev_distance = Math.min(levenshtein(obj[NAME], val.name), lev_distance);
403             if (lev_distance <= MAX_LEV_DISTANCE) {
404                 // The generics didn't match but the name kinda did so we give it
405                 // a levenshtein distance value that isn't *this* good so it goes
406                 // into the search results but not too high.
407                 lev_distance = Math.ceil((checkGenerics(obj, val) + lev_distance) / 2);
408             }
409             if (obj.length > GENERICS_DATA && obj[GENERICS_DATA].length > 0) {
410                 // We can check if the type we're looking for is inside the generics!
411                 var olength = obj[GENERICS_DATA].length;
412                 for (x = 0; x < olength; ++x) {
413                     tmp_lev = Math.min(levenshtein(obj[GENERICS_DATA][x][NAME], val.name), tmp_lev);
414                 }
415                 if (tmp_lev !== 0) {
416                     // If we didn't find a good enough result, we go check inside the generics of
417                     // the generics.
418                     for (x = 0; x < olength && tmp_lev !== 0; ++x) {
419                         tmp_lev = Math.min(
420                             checkType(obj[GENERICS_DATA][x], val, literalSearch),
421                             tmp_lev
422                         );
423                     }
424                 }
425             }
426             // Now whatever happens, the returned distance is "less good" so we should mark it
427             // as such, and so we add 1 to the distance to make it "less good".
428             return Math.min(lev_distance, tmp_lev) + 1;
429         }
430
431         function findArg(obj, val, literalSearch, typeFilter) {
432             var lev_distance = MAX_LEV_DISTANCE + 1;
433
434             if (obj && obj.type && obj.type[INPUTS_DATA] && obj.type[INPUTS_DATA].length > 0) {
435                 var length = obj.type[INPUTS_DATA].length;
436                 for (var i = 0; i < length; i++) {
437                     var tmp = obj.type[INPUTS_DATA][i];
438                     if (!typePassesFilter(typeFilter, tmp[1])) {
439                         continue;
440                     }
441                     tmp = checkType(tmp, val, literalSearch);
442                     if (literalSearch) {
443                         if (tmp) {
444                             return true;
445                         }
446                         continue;
447                     }
448                     lev_distance = Math.min(tmp, lev_distance);
449                     if (lev_distance === 0) {
450                         return 0;
451                     }
452                 }
453             }
454             return literalSearch ? false : lev_distance;
455         }
456
457         function checkReturned(obj, val, literalSearch, typeFilter) {
458             var lev_distance = MAX_LEV_DISTANCE + 1;
459
460             if (obj && obj.type && obj.type.length > OUTPUT_DATA) {
461                 var ret = obj.type[OUTPUT_DATA];
462                 if (typeof ret[0] === "string") {
463                     ret = [ret];
464                 }
465                 for (var x = 0, len = ret.length; x < len; ++x) {
466                     var tmp = ret[x];
467                     if (!typePassesFilter(typeFilter, tmp[1])) {
468                         continue;
469                     }
470                     tmp = checkType(tmp, val, literalSearch);
471                     if (literalSearch) {
472                         if (tmp) {
473                             return true;
474                         }
475                         continue;
476                     }
477                     lev_distance = Math.min(tmp, lev_distance);
478                     if (lev_distance === 0) {
479                         return 0;
480                     }
481                 }
482             }
483             return literalSearch ? false : lev_distance;
484         }
485
486         function checkPath(contains, lastElem, ty) {
487             if (contains.length === 0) {
488                 return 0;
489             }
490             var ret_lev = MAX_LEV_DISTANCE + 1;
491             var path = ty.path.split("::");
492
493             if (ty.parent && ty.parent.name) {
494                 path.push(ty.parent.name.toLowerCase());
495             }
496
497             var length = path.length;
498             var clength = contains.length;
499             if (clength > length) {
500                 return MAX_LEV_DISTANCE + 1;
501             }
502             for (var i = 0; i < length; ++i) {
503                 if (i + clength > length) {
504                     break;
505                 }
506                 var lev_total = 0;
507                 var aborted = false;
508                 for (var x = 0; x < clength; ++x) {
509                     var lev = levenshtein(path[i + x], contains[x]);
510                     if (lev > MAX_LEV_DISTANCE) {
511                         aborted = true;
512                         break;
513                     }
514                     lev_total += lev;
515                 }
516                 if (!aborted) {
517                     ret_lev = Math.min(ret_lev, Math.round(lev_total / clength));
518                 }
519             }
520             return ret_lev;
521         }
522
523         function typePassesFilter(filter, type) {
524             // No filter
525             if (filter <= NO_TYPE_FILTER) return true;
526
527             // Exact match
528             if (filter === type) return true;
529
530             // Match related items
531             var name = itemTypes[type];
532             switch (itemTypes[filter]) {
533                 case "constant":
534                     return name === "associatedconstant";
535                 case "fn":
536                     return name === "method" || name === "tymethod";
537                 case "type":
538                     return name === "primitive" || name === "associatedtype";
539                 case "trait":
540                     return name === "traitalias";
541             }
542
543             // No match
544             return false;
545         }
546
547         function createAliasFromItem(item) {
548             return {
549                 crate: item.crate,
550                 name: item.name,
551                 path: item.path,
552                 desc: item.desc,
553                 ty: item.ty,
554                 parent: item.parent,
555                 type: item.type,
556                 is_alias: true,
557             };
558         }
559
560         function handleAliases(ret, query, filterCrates) {
561             // We separate aliases and crate aliases because we want to have current crate
562             // aliases to be before the others in the displayed results.
563             var aliases = [];
564             var crateAliases = [];
565             if (filterCrates !== undefined) {
566                 if (ALIASES[filterCrates] && ALIASES[filterCrates][query.search]) {
567                     var query_aliases = ALIASES[filterCrates][query.search];
568                     var len = query_aliases.length;
569                     for (var i = 0; i < len; ++i) {
570                         aliases.push(createAliasFromItem(searchIndex[query_aliases[i]]));
571                     }
572                 }
573             } else {
574                 Object.keys(ALIASES).forEach(function(crate) {
575                     if (ALIASES[crate][query.search]) {
576                         var pushTo = crate === window.currentCrate ? crateAliases : aliases;
577                         var query_aliases = ALIASES[crate][query.search];
578                         var len = query_aliases.length;
579                         for (var i = 0; i < len; ++i) {
580                             pushTo.push(createAliasFromItem(searchIndex[query_aliases[i]]));
581                         }
582                     }
583                 });
584             }
585
586             var sortFunc = function(aaa, bbb) {
587                 if (aaa.path < bbb.path) {
588                     return 1;
589                 } else if (aaa.path === bbb.path) {
590                     return 0;
591                 }
592                 return -1;
593             };
594             crateAliases.sort(sortFunc);
595             aliases.sort(sortFunc);
596
597             var pushFunc = function(alias) {
598                 alias.alias = query.raw;
599                 var res = buildHrefAndPath(alias);
600                 alias.displayPath = pathSplitter(res[0]);
601                 alias.fullPath = alias.displayPath + alias.name;
602                 alias.href = res[1];
603
604                 ret.others.unshift(alias);
605                 if (ret.others.length > MAX_RESULTS) {
606                     ret.others.pop();
607                 }
608             };
609             onEach(aliases, pushFunc);
610             onEach(crateAliases, pushFunc);
611         }
612
613         // quoted values mean literal search
614         var nSearchWords = searchWords.length;
615         var i, it;
616         var ty;
617         var fullId;
618         var returned;
619         var in_args;
620         var len;
621         if ((val.charAt(0) === "\"" || val.charAt(0) === "'") &&
622             val.charAt(val.length - 1) === val.charAt(0))
623         {
624             val = extractGenerics(val.substr(1, val.length - 2));
625             for (i = 0; i < nSearchWords; ++i) {
626                 if (filterCrates !== undefined && searchIndex[i].crate !== filterCrates) {
627                     continue;
628                 }
629                 in_args = findArg(searchIndex[i], val, true, typeFilter);
630                 returned = checkReturned(searchIndex[i], val, true, typeFilter);
631                 ty = searchIndex[i];
632                 fullId = ty.id;
633
634                 if (searchWords[i] === val.name
635                     && typePassesFilter(typeFilter, searchIndex[i].ty)
636                     && results[fullId] === undefined) {
637                     results[fullId] = {
638                         id: i,
639                         index: -1,
640                         dontValidate: true,
641                     };
642                 }
643                 if (in_args && results_in_args[fullId] === undefined) {
644                     results_in_args[fullId] = {
645                         id: i,
646                         index: -1,
647                         dontValidate: true,
648                     };
649                 }
650                 if (returned && results_returned[fullId] === undefined) {
651                     results_returned[fullId] = {
652                         id: i,
653                         index: -1,
654                         dontValidate: true,
655                     };
656                 }
657             }
658             query.inputs = [val];
659             query.output = val;
660             query.search = val;
661         // searching by type
662         } else if (val.search("->") > -1) {
663             var trimmer = function(s) { return s.trim(); };
664             var parts = val.split("->").map(trimmer);
665             var input = parts[0];
666             // sort inputs so that order does not matter
667             var inputs = input.split(",").map(trimmer).sort();
668             for (i = 0, len = inputs.length; i < len; ++i) {
669                 inputs[i] = extractGenerics(inputs[i]);
670             }
671             var output = extractGenerics(parts[1]);
672
673             for (i = 0; i < nSearchWords; ++i) {
674                 if (filterCrates !== undefined && searchIndex[i].crate !== filterCrates) {
675                     continue;
676                 }
677                 var type = searchIndex[i].type;
678                 ty = searchIndex[i];
679                 if (!type) {
680                     continue;
681                 }
682                 fullId = ty.id;
683
684                 returned = checkReturned(ty, output, true, NO_TYPE_FILTER);
685                 if (output.name === "*" || returned) {
686                     in_args = false;
687                     var is_module = false;
688
689                     if (input === "*") {
690                         is_module = true;
691                     } else {
692                         var allFound = true;
693                         for (it = 0, len = inputs.length; allFound && it < len; it++) {
694                             allFound = checkType(type, inputs[it], true);
695                         }
696                         in_args = allFound;
697                     }
698                     if (in_args) {
699                         results_in_args[fullId] = {
700                             id: i,
701                             index: -1,
702                             dontValidate: true,
703                         };
704                     }
705                     if (returned) {
706                         results_returned[fullId] = {
707                             id: i,
708                             index: -1,
709                             dontValidate: true,
710                         };
711                     }
712                     if (is_module) {
713                         results[fullId] = {
714                             id: i,
715                             index: -1,
716                             dontValidate: true,
717                         };
718                     }
719                 }
720             }
721             query.inputs = inputs.map(function(input) {
722                 return input.name;
723             });
724             query.output = output.name;
725         } else {
726             query.inputs = [val];
727             query.output = val;
728             query.search = val;
729             // gather matching search results up to a certain maximum
730             val = val.replace(/_/g, "");
731
732             var valGenerics = extractGenerics(val);
733
734             var paths = valLower.split("::");
735             removeEmptyStringsFromArray(paths);
736             val = paths[paths.length - 1];
737             var contains = paths.slice(0, paths.length > 1 ? paths.length - 1 : 1);
738
739             var lev, j;
740             for (j = 0; j < nSearchWords; ++j) {
741                 ty = searchIndex[j];
742                 if (!ty || (filterCrates !== undefined && ty.crate !== filterCrates)) {
743                     continue;
744                 }
745                 var lev_add = 0;
746                 if (paths.length > 1) {
747                     lev = checkPath(contains, paths[paths.length - 1], ty);
748                     if (lev > MAX_LEV_DISTANCE) {
749                         continue;
750                     } else if (lev > 0) {
751                         lev_add = lev / 10;
752                     }
753                 }
754
755                 returned = MAX_LEV_DISTANCE + 1;
756                 in_args = MAX_LEV_DISTANCE + 1;
757                 var index = -1;
758                 // we want lev results to go lower than others
759                 lev = MAX_LEV_DISTANCE + 1;
760                 fullId = ty.id;
761
762                 if (searchWords[j].indexOf(split[i]) > -1 ||
763                     searchWords[j].indexOf(val) > -1 ||
764                     ty.normalizedName.indexOf(val) > -1)
765                 {
766                     // filter type: ... queries
767                     if (typePassesFilter(typeFilter, ty.ty) && results[fullId] === undefined) {
768                         index = ty.normalizedName.indexOf(val);
769                     }
770                 }
771                 if ((lev = levenshtein(searchWords[j], val)) <= MAX_LEV_DISTANCE) {
772                     if (typePassesFilter(typeFilter, ty.ty)) {
773                         lev += 1;
774                     } else {
775                         lev = MAX_LEV_DISTANCE + 1;
776                     }
777                 }
778                 in_args = findArg(ty, valGenerics, false, typeFilter);
779                 returned = checkReturned(ty, valGenerics, false, typeFilter);
780
781                 lev += lev_add;
782                 if (lev > 0 && val.length > 3 && searchWords[j].indexOf(val) > -1) {
783                     if (val.length < 6) {
784                         lev -= 1;
785                     } else {
786                         lev = 0;
787                     }
788                 }
789                 if (in_args <= MAX_LEV_DISTANCE) {
790                     if (results_in_args[fullId] === undefined) {
791                         results_in_args[fullId] = {
792                             id: j,
793                             index: index,
794                             lev: in_args,
795                         };
796                     }
797                     results_in_args[fullId].lev =
798                         Math.min(results_in_args[fullId].lev, in_args);
799                 }
800                 if (returned <= MAX_LEV_DISTANCE) {
801                     if (results_returned[fullId] === undefined) {
802                         results_returned[fullId] = {
803                             id: j,
804                             index: index,
805                             lev: returned,
806                         };
807                     }
808                     results_returned[fullId].lev =
809                         Math.min(results_returned[fullId].lev, returned);
810                 }
811                 if (typePassesFilter(typeFilter, ty.ty) &&
812                         (index !== -1 || lev <= MAX_LEV_DISTANCE)) {
813                     if (index !== -1 && paths.length < 2) {
814                         lev = 0;
815                     }
816                     if (results[fullId] === undefined) {
817                         results[fullId] = {
818                             id: j,
819                             index: index,
820                             lev: lev,
821                         };
822                     }
823                     results[fullId].lev = Math.min(results[fullId].lev, lev);
824                 }
825             }
826         }
827
828         var ret = {
829             "in_args": sortResults(results_in_args, true),
830             "returned": sortResults(results_returned, true),
831             "others": sortResults(results, false),
832         };
833         handleAliases(ret, query, filterCrates);
834         return ret;
835     }
836
837     /**
838      * Validate performs the following boolean logic. For example:
839      * "File::open" will give IF A PARENT EXISTS => ("file" && "open")
840      * exists in (name || path || parent) OR => ("file" && "open") exists in
841      * (name || path )
842      *
843      * This could be written functionally, but I wanted to minimise
844      * functions on stack.
845      *
846      * @param  {[string]} name   [The name of the result]
847      * @param  {[string]} path   [The path of the result]
848      * @param  {[string]} keys   [The keys to be used (["file", "open"])]
849      * @param  {[object]} parent [The parent of the result]
850      * @return {boolean}       [Whether the result is valid or not]
851      */
852     function validateResult(name, path, keys, parent) {
853         for (var i = 0, len = keys.length; i < len; ++i) {
854             // each check is for validation so we negate the conditions and invalidate
855             if (!(
856                 // check for an exact name match
857                 name.indexOf(keys[i]) > -1 ||
858                 // then an exact path match
859                 path.indexOf(keys[i]) > -1 ||
860                 // next if there is a parent, check for exact parent match
861                 (parent !== undefined && parent.name !== undefined &&
862                     parent.name.toLowerCase().indexOf(keys[i]) > -1) ||
863                 // lastly check to see if the name was a levenshtein match
864                 levenshtein(name, keys[i]) <= MAX_LEV_DISTANCE)) {
865                 return false;
866             }
867         }
868         return true;
869     }
870
871     function getQuery(raw) {
872         var matches, type, query;
873         query = raw;
874
875         matches = query.match(/^(fn|mod|struct|enum|trait|type|const|macro)\s*:\s*/i);
876         if (matches) {
877             type = matches[1].replace(/^const$/, "constant");
878             query = query.substring(matches[0].length);
879         }
880
881         return {
882             raw: raw,
883             query: query,
884             type: type,
885             id: query + type
886         };
887     }
888
889     function nextTab(direction) {
890         var next = (searchState.currentTab + direction + 3) % searchState.focusedByTab.length;
891         searchState.focusedByTab[searchState.currentTab] = document.activeElement;
892         printTab(next);
893         focusSearchResult();
894     }
895
896     // Focus the first search result on the active tab, or the result that
897     // was focused last time this tab was active.
898     function focusSearchResult() {
899         var target = searchState.focusedByTab[searchState.currentTab] ||
900             document.querySelectorAll(".search-results.active a").item(0) ||
901             document.querySelectorAll("#titles > button").item(searchState.currentTab);
902         if (target) {
903             target.focus();
904         }
905     }
906
907     function buildHrefAndPath(item) {
908         var displayPath;
909         var href;
910         var type = itemTypes[item.ty];
911         var name = item.name;
912         var path = item.path;
913
914         if (type === "mod") {
915             displayPath = path + "::";
916             href = window.rootPath + path.replace(/::/g, "/") + "/" +
917                    name + "/index.html";
918         } else if (type === "primitive" || type === "keyword") {
919             displayPath = "";
920             href = window.rootPath + path.replace(/::/g, "/") +
921                    "/" + type + "." + name + ".html";
922         } else if (type === "externcrate") {
923             displayPath = "";
924             href = window.rootPath + name + "/index.html";
925         } else if (item.parent !== undefined) {
926             var myparent = item.parent;
927             var anchor = "#" + type + "." + name;
928             var parentType = itemTypes[myparent.ty];
929             var pageType = parentType;
930             var pageName = myparent.name;
931
932             if (parentType === "primitive") {
933                 displayPath = myparent.name + "::";
934             } else if (type === "structfield" && parentType === "variant") {
935                 // Structfields belonging to variants are special: the
936                 // final path element is the enum name.
937                 var enumNameIdx = item.path.lastIndexOf("::");
938                 var enumName = item.path.substr(enumNameIdx + 2);
939                 path = item.path.substr(0, enumNameIdx);
940                 displayPath = path + "::" + enumName + "::" + myparent.name + "::";
941                 anchor = "#variant." + myparent.name + ".field." + name;
942                 pageType = "enum";
943                 pageName = enumName;
944             } else {
945                 displayPath = path + "::" + myparent.name + "::";
946             }
947             href = window.rootPath + path.replace(/::/g, "/") +
948                    "/" + pageType +
949                    "." + pageName +
950                    ".html" + anchor;
951         } else {
952             displayPath = item.path + "::";
953             href = window.rootPath + item.path.replace(/::/g, "/") +
954                    "/" + type + "." + name + ".html";
955         }
956         return [displayPath, href];
957     }
958
959     function escape(content) {
960         var h1 = document.createElement("h1");
961         h1.textContent = content;
962         return h1.innerHTML;
963     }
964
965     function pathSplitter(path) {
966         var tmp = "<span>" + path.replace(/::/g, "::</span><span>");
967         if (tmp.endsWith("<span>")) {
968             return tmp.slice(0, tmp.length - 6);
969         }
970         return tmp;
971     }
972
973     function addTab(array, query, display) {
974         var extraClass = "";
975         if (display === true) {
976             extraClass = " active";
977         }
978
979         var output = document.createElement("div");
980         var duplicates = {};
981         var length = 0;
982         if (array.length > 0) {
983             output.className = "search-results " + extraClass;
984
985             array.forEach(function(item) {
986                 if (item.is_alias !== true) {
987                     if (duplicates[item.fullPath]) {
988                         return;
989                     }
990                     duplicates[item.fullPath] = true;
991                 }
992
993                 var name = item.name;
994                 var type = itemTypes[item.ty];
995
996                 length += 1;
997
998                 var extra = "";
999                 if (type === "primitive") {
1000                     extra = " <i>(primitive type)</i>";
1001                 } else if (type === "keyword") {
1002                     extra = " <i>(keyword)</i>";
1003                 }
1004
1005                 var link = document.createElement("a");
1006                 link.className = "result-" + type;
1007                 link.href = item.href;
1008
1009                 var wrapper = document.createElement("div");
1010                 var resultName = document.createElement("div");
1011                 resultName.className = "result-name";
1012
1013                 if (item.is_alias) {
1014                     var alias = document.createElement("span");
1015                     alias.className = "alias";
1016
1017                     var bold = document.createElement("b");
1018                     bold.innerText = item.alias;
1019                     alias.appendChild(bold);
1020
1021                     alias.insertAdjacentHTML(
1022                         "beforeend",
1023                         "<span class=\"grey\"><i>&nbsp;- see&nbsp;</i></span>");
1024
1025                     resultName.appendChild(alias);
1026                 }
1027                 resultName.insertAdjacentHTML(
1028                     "beforeend",
1029                     item.displayPath + "<span class=\"" + type + "\">" + name + extra + "</span>");
1030                 wrapper.appendChild(resultName);
1031
1032                 var description = document.createElement("div");
1033                 description.className = "desc";
1034                 var spanDesc = document.createElement("span");
1035                 spanDesc.insertAdjacentHTML("beforeend", item.desc);
1036
1037                 description.appendChild(spanDesc);
1038                 wrapper.appendChild(description);
1039                 link.appendChild(wrapper);
1040                 output.appendChild(link);
1041             });
1042         } else {
1043             output.className = "search-failed" + extraClass;
1044             output.innerHTML = "No results :(<br/>" +
1045                 "Try on <a href=\"https://duckduckgo.com/?q=" +
1046                 encodeURIComponent("rust " + query.query) +
1047                 "\">DuckDuckGo</a>?<br/><br/>" +
1048                 "Or try looking in one of these:<ul><li>The <a " +
1049                 "href=\"https://doc.rust-lang.org/reference/index.html\">Rust Reference</a> " +
1050                 " for technical details about the language.</li><li><a " +
1051                 "href=\"https://doc.rust-lang.org/rust-by-example/index.html\">Rust By " +
1052                 "Example</a> for expository code examples.</a></li><li>The <a " +
1053                 "href=\"https://doc.rust-lang.org/book/index.html\">Rust Book</a> for " +
1054                 "introductions to language features and the language itself.</li><li><a " +
1055                 "href=\"https://docs.rs\">Docs.rs</a> for documentation of crates released on" +
1056                 " <a href=\"https://crates.io/\">crates.io</a>.</li></ul>";
1057         }
1058         return [output, length];
1059     }
1060
1061     function makeTabHeader(tabNb, text, nbElems) {
1062         if (searchState.currentTab === tabNb) {
1063             return "<button class=\"selected\">" + text +
1064                    " <div class=\"count\">(" + nbElems + ")</div></button>";
1065         }
1066         return "<button>" + text + " <div class=\"count\">(" + nbElems + ")</div></button>";
1067     }
1068
1069     function showResults(results, go_to_first) {
1070         var search = searchState.outputElement();
1071         if (go_to_first || (results.others.length === 1
1072             && getSettingValue("go-to-only-result") === "true"
1073             // By default, the search DOM element is "empty" (meaning it has no children not
1074             // text content). Once a search has been run, it won't be empty, even if you press
1075             // ESC or empty the search input (which also "cancels" the search).
1076             && (!search.firstChild || search.firstChild.innerText !== searchState.loadingText)))
1077         {
1078             var elem = document.createElement("a");
1079             elem.href = results.others[0].href;
1080             removeClass(elem, "active");
1081             // For firefox, we need the element to be in the DOM so it can be clicked.
1082             document.body.appendChild(elem);
1083             elem.click();
1084             return;
1085         }
1086         var query = getQuery(searchState.input.value);
1087
1088         currentResults = query.id;
1089
1090         var ret_others = addTab(results.others, query);
1091         var ret_in_args = addTab(results.in_args, query, false);
1092         var ret_returned = addTab(results.returned, query, false);
1093
1094         // Navigate to the relevant tab if the current tab is empty, like in case users search
1095         // for "-> String". If they had selected another tab previously, they have to click on
1096         // it again.
1097         var currentTab = searchState.currentTab;
1098         if ((currentTab === 0 && ret_others[1] === 0) ||
1099                 (currentTab === 1 && ret_in_args[1] === 0) ||
1100                 (currentTab === 2 && ret_returned[1] === 0)) {
1101             if (ret_others[1] !== 0) {
1102                 currentTab = 0;
1103             } else if (ret_in_args[1] !== 0) {
1104                 currentTab = 1;
1105             } else if (ret_returned[1] !== 0) {
1106                 currentTab = 2;
1107             }
1108         }
1109
1110         var output = "<h1>Results for " + escape(query.query) +
1111             (query.type ? " (type: " + escape(query.type) + ")" : "") + "</h1>" +
1112             "<div id=\"titles\">" +
1113             makeTabHeader(0, "In Names", ret_others[1]) +
1114             makeTabHeader(1, "In Parameters", ret_in_args[1]) +
1115             makeTabHeader(2, "In Return Types", ret_returned[1]) +
1116             "</div>";
1117
1118         var resultsElem = document.createElement("div");
1119         resultsElem.id = "results";
1120         resultsElem.appendChild(ret_others[0]);
1121         resultsElem.appendChild(ret_in_args[0]);
1122         resultsElem.appendChild(ret_returned[0]);
1123
1124         search.innerHTML = output;
1125         search.appendChild(resultsElem);
1126         // Reset focused elements.
1127         searchState.focusedByTab = [null, null, null];
1128         searchState.showResults(search);
1129         var elems = document.getElementById("titles").childNodes;
1130         elems[0].onclick = function() { printTab(0); };
1131         elems[1].onclick = function() { printTab(1); };
1132         elems[2].onclick = function() { printTab(2); };
1133         printTab(currentTab);
1134     }
1135
1136     function execSearch(query, searchWords, filterCrates) {
1137         function getSmallest(arrays, positions, notDuplicates) {
1138             var start = null;
1139
1140             for (var it = 0, len = positions.length; it < len; ++it) {
1141                 if (arrays[it].length > positions[it] &&
1142                     (start === null || start > arrays[it][positions[it]].lev) &&
1143                     !notDuplicates[arrays[it][positions[it]].fullPath]) {
1144                     start = arrays[it][positions[it]].lev;
1145                 }
1146             }
1147             return start;
1148         }
1149
1150         function mergeArrays(arrays) {
1151             var ret = [];
1152             var positions = [];
1153             var notDuplicates = {};
1154
1155             for (var x = 0, arrays_len = arrays.length; x < arrays_len; ++x) {
1156                 positions.push(0);
1157             }
1158             while (ret.length < MAX_RESULTS) {
1159                 var smallest = getSmallest(arrays, positions, notDuplicates);
1160
1161                 if (smallest === null) {
1162                     break;
1163                 }
1164                 for (x = 0; x < arrays_len && ret.length < MAX_RESULTS; ++x) {
1165                     if (arrays[x].length > positions[x] &&
1166                             arrays[x][positions[x]].lev === smallest &&
1167                             !notDuplicates[arrays[x][positions[x]].fullPath]) {
1168                         ret.push(arrays[x][positions[x]]);
1169                         notDuplicates[arrays[x][positions[x]].fullPath] = true;
1170                         positions[x] += 1;
1171                     }
1172                 }
1173             }
1174             return ret;
1175         }
1176
1177         // Split search query by ",", while respecting angle bracket nesting.
1178         // Since "<" is an alias for the Ord family of traits, it also uses
1179         // lookahead to distinguish "<"-as-less-than from "<"-as-angle-bracket.
1180         //
1181         // tokenizeQuery("A<B, C>, D") == ["A<B, C>", "D"]
1182         // tokenizeQuery("A<B, C, D") == ["A<B", "C", "D"]
1183         function tokenizeQuery(raw) {
1184             var i, matched;
1185             var l = raw.length;
1186             var depth = 0;
1187             var nextAngle = /(<|>)/g;
1188             var ret = [];
1189             var start = 0;
1190             for (i = 0; i < l; ++i) {
1191                 switch (raw[i]) {
1192                     case "<":
1193                         nextAngle.lastIndex = i + 1;
1194                         matched = nextAngle.exec(raw);
1195                         if (matched && matched[1] === '>') {
1196                             depth += 1;
1197                         }
1198                         break;
1199                     case ">":
1200                         if (depth > 0) {
1201                             depth -= 1;
1202                         }
1203                         break;
1204                     case ",":
1205                         if (depth === 0) {
1206                             ret.push(raw.substring(start, i));
1207                             start = i + 1;
1208                         }
1209                         break;
1210                 }
1211             }
1212             if (start !== i) {
1213                 ret.push(raw.substring(start, i));
1214             }
1215             return ret;
1216         }
1217
1218         var queries = tokenizeQuery(query.raw);
1219         var results = {
1220             "in_args": [],
1221             "returned": [],
1222             "others": [],
1223         };
1224
1225         for (var i = 0, len = queries.length; i < len; ++i) {
1226             query = queries[i].trim();
1227             if (query.length !== 0) {
1228                 var tmp = execQuery(getQuery(query), searchWords, filterCrates);
1229
1230                 results.in_args.push(tmp.in_args);
1231                 results.returned.push(tmp.returned);
1232                 results.others.push(tmp.others);
1233             }
1234         }
1235         if (queries.length > 1) {
1236             return {
1237                 "in_args": mergeArrays(results.in_args),
1238                 "returned": mergeArrays(results.returned),
1239                 "others": mergeArrays(results.others),
1240             };
1241         }
1242         return {
1243             "in_args": results.in_args[0],
1244             "returned": results.returned[0],
1245             "others": results.others[0],
1246         };
1247     }
1248
1249     function getFilterCrates() {
1250         var elem = document.getElementById("crate-search");
1251
1252         if (elem && elem.value !== "All crates" &&
1253             hasOwnPropertyRustdoc(rawSearchIndex, elem.value))
1254         {
1255             return elem.value;
1256         }
1257         return undefined;
1258     }
1259
1260     function search(e, forced) {
1261         var params = searchState.getQueryStringParams();
1262         var query = getQuery(searchState.input.value.trim());
1263
1264         if (e) {
1265             e.preventDefault();
1266         }
1267
1268         if (query.query.length === 0) {
1269             return;
1270         }
1271         if (!forced && query.id === currentResults) {
1272             if (query.query.length > 0) {
1273                 searchState.putBackSearch(searchState.input);
1274             }
1275             return;
1276         }
1277
1278         // Update document title to maintain a meaningful browser history
1279         searchState.title = "Results for " + query.query + " - Rust";
1280
1281         // Because searching is incremental by character, only the most
1282         // recent search query is added to the browser history.
1283         if (searchState.browserSupportsHistoryApi()) {
1284             var newURL = getNakedUrl() + "?search=" + encodeURIComponent(query.raw) +
1285                 window.location.hash;
1286             if (!history.state && !params.search) {
1287                 history.pushState(query, "", newURL);
1288             } else {
1289                 history.replaceState(query, "", newURL);
1290             }
1291         }
1292
1293         var filterCrates = getFilterCrates();
1294         showResults(execSearch(query, index, filterCrates), params.go_to_first);
1295     }
1296
1297     function buildIndex(rawSearchIndex) {
1298         searchIndex = [];
1299         var searchWords = [];
1300         var i, word;
1301         var currentIndex = 0;
1302         var id = 0;
1303
1304         for (var crate in rawSearchIndex) {
1305             if (!hasOwnPropertyRustdoc(rawSearchIndex, crate)) {
1306                 continue;
1307             }
1308
1309             var crateSize = 0;
1310
1311             searchWords.push(crate);
1312             // This object should have exactly the same set of fields as the "row"
1313             // object defined below. Your JavaScript runtime will thank you.
1314             // https://mathiasbynens.be/notes/shapes-ics
1315             var crateRow = {
1316                 crate: crate,
1317                 ty: 1, // == ExternCrate
1318                 name: crate,
1319                 path: "",
1320                 desc: rawSearchIndex[crate].doc,
1321                 parent: undefined,
1322                 type: null,
1323                 id: id,
1324                 normalizedName: crate.indexOf("_") === -1 ? crate : crate.replace(/_/g, ""),
1325             };
1326             id += 1;
1327             searchIndex.push(crateRow);
1328             currentIndex += 1;
1329
1330             // an array of (Number) item types
1331             var itemTypes = rawSearchIndex[crate].t;
1332             // an array of (String) item names
1333             var itemNames = rawSearchIndex[crate].n;
1334             // an array of (String) full paths (or empty string for previous path)
1335             var itemPaths = rawSearchIndex[crate].q;
1336             // an array of (String) descriptions
1337             var itemDescs = rawSearchIndex[crate].d;
1338             // an array of (Number) the parent path index + 1 to `paths`, or 0 if none
1339             var itemParentIdxs = rawSearchIndex[crate].i;
1340             // an array of (Object | null) the type of the function, if any
1341             var itemFunctionSearchTypes = rawSearchIndex[crate].f;
1342             // an array of [(Number) item type,
1343             //              (String) name]
1344             var paths = rawSearchIndex[crate].p;
1345             // an array of [(String) alias name
1346             //             [Number] index to items]
1347             var aliases = rawSearchIndex[crate].a;
1348
1349             // convert `rawPaths` entries into object form
1350             var len = paths.length;
1351             for (i = 0; i < len; ++i) {
1352                 paths[i] = {ty: paths[i][0], name: paths[i][1]};
1353             }
1354
1355             // convert `item*` into an object form, and construct word indices.
1356             //
1357             // before any analysis is performed lets gather the search terms to
1358             // search against apart from the rest of the data.  This is a quick
1359             // operation that is cached for the life of the page state so that
1360             // all other search operations have access to this cached data for
1361             // faster analysis operations
1362             len = itemTypes.length;
1363             var lastPath = "";
1364             for (i = 0; i < len; ++i) {
1365                 // This object should have exactly the same set of fields as the "crateRow"
1366                 // object defined above.
1367                 if (typeof itemNames[i] === "string") {
1368                     word = itemNames[i].toLowerCase();
1369                     searchWords.push(word);
1370                 } else {
1371                     word = "";
1372                     searchWords.push("");
1373                 }
1374                 var row = {
1375                     crate: crate,
1376                     ty: itemTypes[i],
1377                     name: itemNames[i],
1378                     path: itemPaths[i] ? itemPaths[i] : lastPath,
1379                     desc: itemDescs[i],
1380                     parent: itemParentIdxs[i] > 0 ? paths[itemParentIdxs[i] - 1] : undefined,
1381                     type: itemFunctionSearchTypes[i],
1382                     id: id,
1383                     normalizedName: word.indexOf("_") === -1 ? word : word.replace(/_/g, ""),
1384                 };
1385                 id += 1;
1386                 searchIndex.push(row);
1387                 lastPath = row.path;
1388                 crateSize += 1;
1389             }
1390
1391             if (aliases) {
1392                 ALIASES[crate] = {};
1393                 var j, local_aliases;
1394                 for (var alias_name in aliases) {
1395                     if (!hasOwnPropertyRustdoc(aliases, alias_name)) {
1396                         continue;
1397                     }
1398
1399                     if (!hasOwnPropertyRustdoc(ALIASES[crate], alias_name)) {
1400                         ALIASES[crate][alias_name] = [];
1401                     }
1402                     local_aliases = aliases[alias_name];
1403                     for (j = 0, len = local_aliases.length; j < len; ++j) {
1404                         ALIASES[crate][alias_name].push(local_aliases[j] + currentIndex);
1405                     }
1406                 }
1407             }
1408             currentIndex += crateSize;
1409         }
1410         return searchWords;
1411     }
1412
1413     function registerSearchEvents() {
1414         var searchAfter500ms = function() {
1415             searchState.clearInputTimeout();
1416             if (searchState.input.value.length === 0) {
1417                 if (searchState.browserSupportsHistoryApi()) {
1418                     history.replaceState("", window.currentCrate + " - Rust",
1419                         getNakedUrl() + window.location.hash);
1420                 }
1421                 searchState.hideResults();
1422             } else {
1423                 searchState.timeout = setTimeout(search, 500);
1424             }
1425         };
1426         searchState.input.onkeyup = searchAfter500ms;
1427         searchState.input.oninput = searchAfter500ms;
1428         document.getElementsByClassName("search-form")[0].onsubmit = function(e) {
1429             e.preventDefault();
1430             searchState.clearInputTimeout();
1431             search();
1432         };
1433         searchState.input.onchange = function(e) {
1434             if (e.target !== document.activeElement) {
1435                 // To prevent doing anything when it's from a blur event.
1436                 return;
1437             }
1438             // Do NOT e.preventDefault() here. It will prevent pasting.
1439             searchState.clearInputTimeout();
1440             // zero-timeout necessary here because at the time of event handler execution the
1441             // pasted content is not in the input field yet. Shouldn’t make any difference for
1442             // change, though.
1443             setTimeout(search, 0);
1444         };
1445         searchState.input.onpaste = searchState.input.onchange;
1446
1447         searchState.outputElement().addEventListener("keydown", function(e) {
1448             // We only handle unmodified keystrokes here. We don't want to interfere with,
1449             // for instance, alt-left and alt-right for history navigation.
1450             if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) {
1451                 return;
1452             }
1453             // up and down arrow select next/previous search result, or the
1454             // search box if we're already at the top.
1455             if (e.which === 38) { // up
1456                 var previous = document.activeElement.previousElementSibling;
1457                 if (previous) {
1458                     previous.focus();
1459                 } else {
1460                     searchState.focus();
1461                 }
1462                 e.preventDefault();
1463             } else if (e.which === 40) { // down
1464                 var next = document.activeElement.nextElementSibling;
1465                 if (next) {
1466                     next.focus();
1467                 }
1468                 var rect = document.activeElement.getBoundingClientRect();
1469                 if (window.innerHeight - rect.bottom < rect.height) {
1470                     window.scrollBy(0, rect.height);
1471                 }
1472                 e.preventDefault();
1473             } else if (e.which === 37) { // left
1474                 nextTab(-1);
1475                 e.preventDefault();
1476             } else if (e.which === 39) { // right
1477                 nextTab(1);
1478                 e.preventDefault();
1479             }
1480         });
1481
1482         searchState.input.addEventListener("keydown", function(e) {
1483             if (e.which === 40) { // down
1484                 focusSearchResult();
1485                 e.preventDefault();
1486             }
1487         });
1488
1489
1490         var selectCrate = document.getElementById("crate-search");
1491         if (selectCrate) {
1492             selectCrate.onchange = function() {
1493                 updateLocalStorage("rustdoc-saved-filter-crate", selectCrate.value);
1494                 // In case you "cut" the entry from the search input, then change the crate filter
1495                 // before paste back the previous search, you get the old search results without
1496                 // the filter. To prevent this, we need to remove the previous results.
1497                 currentResults = null;
1498                 search(undefined, true);
1499             };
1500         }
1501
1502         // Push and pop states are used to add search results to the browser
1503         // history.
1504         if (searchState.browserSupportsHistoryApi()) {
1505             // Store the previous <title> so we can revert back to it later.
1506             var previousTitle = document.title;
1507
1508             window.addEventListener("popstate", function(e) {
1509                 var params = searchState.getQueryStringParams();
1510                 // Revert to the previous title manually since the History
1511                 // API ignores the title parameter.
1512                 document.title = previousTitle;
1513                 // When browsing forward to search results the previous
1514                 // search will be repeated, so the currentResults are
1515                 // cleared to ensure the search is successful.
1516                 currentResults = null;
1517                 // Synchronize search bar with query string state and
1518                 // perform the search. This will empty the bar if there's
1519                 // nothing there, which lets you really go back to a
1520                 // previous state with nothing in the bar.
1521                 if (params.search && params.search.length > 0) {
1522                     searchState.input.value = params.search;
1523                     // Some browsers fire "onpopstate" for every page load
1524                     // (Chrome), while others fire the event only when actually
1525                     // popping a state (Firefox), which is why search() is
1526                     // called both here and at the end of the startSearch()
1527                     // function.
1528                     search(e);
1529                 } else {
1530                     searchState.input.value = "";
1531                     // When browsing back from search results the main page
1532                     // visibility must be reset.
1533                     searchState.hideResults();
1534                 }
1535             });
1536         }
1537
1538         // This is required in firefox to avoid this problem: Navigating to a search result
1539         // with the keyboard, hitting enter, and then hitting back would take you back to
1540         // the doc page, rather than the search that should overlay it.
1541         // This was an interaction between the back-forward cache and our handlers
1542         // that try to sync state between the URL and the search input. To work around it,
1543         // do a small amount of re-init on page show.
1544         window.onpageshow = function(){
1545             var qSearch = searchState.getQueryStringParams().search;
1546             if (searchState.input.value === "" && qSearch) {
1547                 searchState.input.value = qSearch;
1548             }
1549             search();
1550         };
1551     }
1552
1553     index = buildIndex(rawSearchIndex);
1554     registerSearchEvents();
1555     // If there's a search term in the URL, execute the search now.
1556     if (searchState.getQueryStringParams().search) {
1557         search();
1558     }
1559 };
1560
1561 if (window.searchIndex !== undefined) {
1562     initSearch(window.searchIndex);
1563 }
1564
1565 })();