]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/html/static/js/search.js
Rollup merge of #93966 - rkuhn:patch-1, r=tmandry
[rust.git] / src / librustdoc / html / static / js / search.js
1 /* global addClass, getNakedUrl, getSettingValue */
2 /* global onEachLazy, removeClass, searchState, browserSupportsHistoryApi, exports */
3
4 "use strict";
5
6 (function() {
7 // This mapping table should match the discriminants of
8 // `rustdoc::formats::item_type::ItemType` type in Rust.
9 const itemTypes = [
10     "mod",
11     "externcrate",
12     "import",
13     "struct",
14     "enum",
15     "fn",
16     "type",
17     "static",
18     "trait",
19     "impl",
20     "tymethod",
21     "method",
22     "structfield",
23     "variant",
24     "macro",
25     "primitive",
26     "associatedtype",
27     "constant",
28     "associatedconstant",
29     "union",
30     "foreigntype",
31     "keyword",
32     "existential",
33     "attr",
34     "derive",
35     "traitalias",
36 ];
37
38 // used for special search precedence
39 const TY_PRIMITIVE = itemTypes.indexOf("primitive");
40 const TY_KEYWORD = itemTypes.indexOf("keyword");
41 const ROOT_PATH = typeof window !== "undefined" ? window.rootPath : "../";
42
43 function hasOwnPropertyRustdoc(obj, property) {
44     return Object.prototype.hasOwnProperty.call(obj, property);
45 }
46
47 // In the search display, allows to switch between tabs.
48 function printTab(nb) {
49     let iter = 0;
50     let foundCurrentTab = false;
51     let foundCurrentResultSet = false;
52     onEachLazy(document.getElementById("titles").childNodes, elem => {
53         if (nb === iter) {
54             addClass(elem, "selected");
55             foundCurrentTab = true;
56         } else {
57             removeClass(elem, "selected");
58         }
59         iter += 1;
60     });
61     iter = 0;
62     onEachLazy(document.getElementById("results").childNodes, elem => {
63         if (nb === iter) {
64             addClass(elem, "active");
65             foundCurrentResultSet = true;
66         } else {
67             removeClass(elem, "active");
68         }
69         iter += 1;
70     });
71     if (foundCurrentTab && foundCurrentResultSet) {
72         searchState.currentTab = nb;
73     } else if (nb != 0) {
74         printTab(0);
75     }
76 }
77
78 /**
79  * A function to compute the Levenshtein distance between two strings
80  * Licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported
81  * Full License can be found at http://creativecommons.org/licenses/by-sa/3.0/legalcode
82  * This code is an unmodified version of the code written by Marco de Wit
83  * and was found at https://stackoverflow.com/a/18514751/745719
84  */
85 const levenshtein_row2 = [];
86 function levenshtein(s1, s2) {
87     if (s1 === s2) {
88         return 0;
89     }
90     const s1_len = s1.length, s2_len = s2.length;
91     if (s1_len && s2_len) {
92         let i1 = 0, i2 = 0, a, b, c, c2;
93         const row = levenshtein_row2;
94         while (i1 < s1_len) {
95             row[i1] = ++i1;
96         }
97         while (i2 < s2_len) {
98             c2 = s2.charCodeAt(i2);
99             a = i2;
100             ++i2;
101             b = i2;
102             for (i1 = 0; i1 < s1_len; ++i1) {
103                 c = a + (s1.charCodeAt(i1) !== c2 ? 1 : 0);
104                 a = row[i1];
105                 b = b < a ? (b < c ? b + 1 : c) : (a < c ? a + 1 : c);
106                 row[i1] = b;
107             }
108         }
109         return b;
110     }
111     return s1_len + s2_len;
112 }
113
114 function initSearch(rawSearchIndex) {
115     const MAX_LEV_DISTANCE = 3;
116     const MAX_RESULTS = 200;
117     const GENERICS_DATA = 2;
118     const NAME = 0;
119     const INPUTS_DATA = 0;
120     const OUTPUT_DATA = 1;
121     const NO_TYPE_FILTER = -1;
122     /**
123      *  @type {Array<Row>}
124      */
125     let searchIndex;
126     let currentResults;
127     const ALIASES = Object.create(null);
128
129     function isWhitespace(c) {
130         return " \t\n\r".indexOf(c) !== -1;
131     }
132
133     function isSpecialStartCharacter(c) {
134         return "<\"".indexOf(c) !== -1;
135     }
136
137     function isEndCharacter(c) {
138         return ",>-".indexOf(c) !== -1;
139     }
140
141     function isStopCharacter(c) {
142         return isWhitespace(c) || isEndCharacter(c);
143     }
144
145     function isErrorCharacter(c) {
146         return "()".indexOf(c) !== -1;
147     }
148
149     function itemTypeFromName(typename) {
150         for (let i = 0, len = itemTypes.length; i < len; ++i) {
151             if (itemTypes[i] === typename) {
152                 return i;
153             }
154         }
155
156         throw new Error("Unknown type filter `" + typename + "`");
157     }
158
159     /**
160      * If we encounter a `"`, then we try to extract the string from it until we find another `"`.
161      *
162      * This function will throw an error in the following cases:
163      * * There is already another string element.
164      * * We are parsing a generic argument.
165      * * There is more than one element.
166      * * There is no closing `"`.
167      *
168      * @param {ParsedQuery} query
169      * @param {ParserState} parserState
170      * @param {boolean} isInGenerics
171      */
172     function getStringElem(query, parserState, isInGenerics) {
173         if (isInGenerics) {
174             throw new Error("`\"` cannot be used in generics");
175         } else if (query.literalSearch) {
176             throw new Error("Cannot have more than one literal search element");
177         } else if (parserState.totalElems - parserState.genericsElems > 0) {
178             throw new Error("Cannot use literal search when there is more than one element");
179         }
180         parserState.pos += 1;
181         const start = parserState.pos;
182         const end = getIdentEndPosition(parserState);
183         if (parserState.pos >= parserState.length) {
184             throw new Error("Unclosed `\"`");
185         } else if (parserState.userQuery[end] !== "\"") {
186             throw new Error(`Unexpected \`${parserState.userQuery[end]}\` in a string element`);
187         } else if (start === end) {
188             throw new Error("Cannot have empty string element");
189         }
190         // To skip the quote at the end.
191         parserState.pos += 1;
192         query.literalSearch = true;
193     }
194
195     /**
196      * Returns `true` if the current parser position is starting with "::".
197      *
198      * @param {ParserState} parserState
199      *
200      * @return {boolean}
201      */
202     function isPathStart(parserState) {
203         return parserState.userQuery.slice(parserState.pos, parserState.pos + 2) == "::";
204     }
205
206     /**
207      * Returns `true` if the current parser position is starting with "->".
208      *
209      * @param {ParserState} parserState
210      *
211      * @return {boolean}
212      */
213     function isReturnArrow(parserState) {
214         return parserState.userQuery.slice(parserState.pos, parserState.pos + 2) == "->";
215     }
216
217     /**
218      * Returns `true` if the given `c` character is valid for an ident.
219      *
220      * @param {string} c
221      *
222      * @return {boolean}
223      */
224     function isIdentCharacter(c) {
225         return (
226             c === "_" ||
227             (c >= "0" && c <= "9") ||
228             (c >= "a" && c <= "z") ||
229             (c >= "A" && c <= "Z"));
230     }
231
232     /**
233      * Returns `true` if the given `c` character is a separator.
234      *
235      * @param {string} c
236      *
237      * @return {boolean}
238      */
239     function isSeparatorCharacter(c) {
240         return c === "," || isWhitespaceCharacter(c);
241     }
242
243     /**
244      * Returns `true` if the given `c` character is a whitespace.
245      *
246      * @param {string} c
247      *
248      * @return {boolean}
249      */
250     function isWhitespaceCharacter(c) {
251         return c === " " || c === "\t";
252     }
253
254     /**
255      * @param {ParsedQuery} query
256      * @param {ParserState} parserState
257      * @param {string} name                  - Name of the query element.
258      * @param {Array<QueryElement>} generics - List of generics of this query element.
259      *
260      * @return {QueryElement}                - The newly created `QueryElement`.
261      */
262     function createQueryElement(query, parserState, name, generics, isInGenerics) {
263         if (name === "*" || (name.length === 0 && generics.length === 0)) {
264             return;
265         }
266         if (query.literalSearch && parserState.totalElems - parserState.genericsElems > 0) {
267             throw new Error("You cannot have more than one element if you use quotes");
268         }
269         const pathSegments = name.split("::");
270         if (pathSegments.length > 1) {
271             for (let i = 0, len = pathSegments.length; i < len; ++i) {
272                 const pathSegment = pathSegments[i];
273
274                 if (pathSegment.length === 0) {
275                     if (i === 0) {
276                         throw new Error("Paths cannot start with `::`");
277                     } else if (i + 1 === len) {
278                         throw new Error("Paths cannot end with `::`");
279                     }
280                     throw new Error("Unexpected `::::`");
281                 }
282             }
283         }
284         // In case we only have something like `<p>`, there is no name.
285         if (pathSegments.length === 0 || (pathSegments.length === 1 && pathSegments[0] === "")) {
286             throw new Error("Found generics without a path");
287         }
288         parserState.totalElems += 1;
289         if (isInGenerics) {
290             parserState.genericsElems += 1;
291         }
292         return {
293             name: name,
294             fullPath: pathSegments,
295             pathWithoutLast: pathSegments.slice(0, pathSegments.length - 1),
296             pathLast: pathSegments[pathSegments.length - 1],
297             generics: generics,
298         };
299     }
300
301     /**
302      * This function goes through all characters until it reaches an invalid ident character or the
303      * end of the query. It returns the position of the last character of the ident.
304      *
305      * @param {ParserState} parserState
306      *
307      * @return {integer}
308      */
309     function getIdentEndPosition(parserState) {
310         let end = parserState.pos;
311         let foundExclamation = false;
312         while (parserState.pos < parserState.length) {
313             const c = parserState.userQuery[parserState.pos];
314             if (!isIdentCharacter(c)) {
315                 if (c === "!") {
316                     if (foundExclamation) {
317                         throw new Error("Cannot have more than one `!` in an ident");
318                     } else if (parserState.pos + 1 < parserState.length &&
319                         isIdentCharacter(parserState.userQuery[parserState.pos + 1])
320                     ) {
321                         throw new Error("`!` can only be at the end of an ident");
322                     }
323                     foundExclamation = true;
324                 } else if (isErrorCharacter(c)) {
325                     throw new Error(`Unexpected \`${c}\``);
326                 } else if (
327                     isStopCharacter(c) ||
328                     isSpecialStartCharacter(c) ||
329                     isSeparatorCharacter(c)
330                 ) {
331                     break;
332                 } else if (c === ":") { // If we allow paths ("str::string" for example).
333                     if (!isPathStart(parserState)) {
334                         break;
335                     }
336                     // Skip current ":".
337                     parserState.pos += 1;
338                     foundExclamation = false;
339                 } else {
340                     throw new Error(`Unexpected \`${c}\``);
341                 }
342             }
343             parserState.pos += 1;
344             end = parserState.pos;
345         }
346         return end;
347     }
348
349     /**
350      * @param {ParsedQuery} query
351      * @param {ParserState} parserState
352      * @param {Array<QueryElement>} elems - This is where the new {QueryElement} will be added.
353      * @param {boolean} isInGenerics
354      */
355     function getNextElem(query, parserState, elems, isInGenerics) {
356         const generics = [];
357
358         let start = parserState.pos;
359         let end;
360         // We handle the strings on their own mostly to make code easier to follow.
361         if (parserState.userQuery[parserState.pos] === "\"") {
362             start += 1;
363             getStringElem(query, parserState, isInGenerics);
364             end = parserState.pos - 1;
365         } else {
366             end = getIdentEndPosition(parserState);
367         }
368         if (parserState.pos < parserState.length &&
369             parserState.userQuery[parserState.pos] === "<"
370         ) {
371             if (isInGenerics) {
372                 throw new Error("Unexpected `<` after `<`");
373             } else if (start >= end) {
374                 throw new Error("Found generics without a path");
375             }
376             parserState.pos += 1;
377             getItemsBefore(query, parserState, generics, ">");
378         }
379         if (start >= end && generics.length === 0) {
380             return;
381         }
382         elems.push(
383             createQueryElement(
384                 query,
385                 parserState,
386                 parserState.userQuery.slice(start, end),
387                 generics,
388                 isInGenerics
389             )
390         );
391     }
392
393     /**
394      * This function parses the next query element until it finds `endChar`, calling `getNextElem`
395      * to collect each element.
396      *
397      * If there is no `endChar`, this function will implicitly stop at the end without raising an
398      * error.
399      *
400      * @param {ParsedQuery} query
401      * @param {ParserState} parserState
402      * @param {Array<QueryElement>} elems - This is where the new {QueryElement} will be added.
403      * @param {string} endChar            - This function will stop when it'll encounter this
404      *                                      character.
405      */
406     function getItemsBefore(query, parserState, elems, endChar) {
407         let foundStopChar = true;
408
409         while (parserState.pos < parserState.length) {
410             const c = parserState.userQuery[parserState.pos];
411             if (c === endChar) {
412                 break;
413             } else if (isSeparatorCharacter(c)) {
414                 parserState.pos += 1;
415                 foundStopChar = true;
416                 continue;
417             } else if (c === ":" && isPathStart(parserState)) {
418                 throw new Error("Unexpected `::`: paths cannot start with `::`");
419             } else if (c === ":" || isEndCharacter(c)) {
420                 let extra = "";
421                 if (endChar === ">") {
422                     extra = "`<`";
423                 } else if (endChar === "") {
424                     extra = "`->`";
425                 }
426                 throw new Error("Unexpected `" + c + "` after " + extra);
427             }
428             if (!foundStopChar) {
429                 if (endChar !== "") {
430                     throw new Error(`Expected \`,\`, \` \` or \`${endChar}\`, found \`${c}\``);
431                 }
432                 throw new Error(`Expected \`,\` or \` \`, found \`${c}\``);
433             }
434             const posBefore = parserState.pos;
435             getNextElem(query, parserState, elems, endChar === ">");
436             // This case can be encountered if `getNextElem` encounted a "stop character" right from
437             // the start. For example if you have `,,` or `<>`. In this case, we simply move up the
438             // current position to continue the parsing.
439             if (posBefore === parserState.pos) {
440                 parserState.pos += 1;
441             }
442             foundStopChar = false;
443         }
444         // We are either at the end of the string or on the `endChar`` character, let's move forward
445         // in any case.
446         parserState.pos += 1;
447     }
448
449     /**
450      * Checks that the type filter doesn't have unwanted characters like `<>` (which are ignored
451      * if empty).
452      *
453      * @param {ParserState} parserState
454      */
455     function checkExtraTypeFilterCharacters(parserState) {
456         const query = parserState.userQuery;
457
458         for (let pos = 0; pos < parserState.pos; ++pos) {
459             if (!isIdentCharacter(query[pos]) && !isWhitespaceCharacter(query[pos])) {
460                 throw new Error(`Unexpected \`${query[pos]}\` in type filter`);
461             }
462         }
463     }
464
465     /**
466      * Parses the provided `query` input to fill `parserState`. If it encounters an error while
467      * parsing `query`, it'll throw an error.
468      *
469      * @param {ParsedQuery} query
470      * @param {ParserState} parserState
471      */
472     function parseInput(query, parserState) {
473         let c, before;
474         let foundStopChar = true;
475
476         while (parserState.pos < parserState.length) {
477             c = parserState.userQuery[parserState.pos];
478             if (isStopCharacter(c)) {
479                 foundStopChar = true;
480                 if (isSeparatorCharacter(c)) {
481                     parserState.pos += 1;
482                     continue;
483                 } else if (c === "-" || c === ">") {
484                     if (isReturnArrow(parserState)) {
485                         break;
486                     }
487                     throw new Error(`Unexpected \`${c}\` (did you mean \`->\`?)`);
488                 }
489                 throw new Error(`Unexpected \`${c}\``);
490             } else if (c === ":" && !isPathStart(parserState)) {
491                 if (parserState.typeFilter !== null) {
492                     throw new Error("Unexpected `:`");
493                 }
494                 if (query.elems.length === 0) {
495                     throw new Error("Expected type filter before `:`");
496                 } else if (query.elems.length !== 1 || parserState.totalElems !== 1) {
497                     throw new Error("Unexpected `:`");
498                 } else if (query.literalSearch) {
499                     throw new Error("You cannot use quotes on type filter");
500                 }
501                 checkExtraTypeFilterCharacters(parserState);
502                 // The type filter doesn't count as an element since it's a modifier.
503                 parserState.typeFilter = query.elems.pop().name;
504                 parserState.pos += 1;
505                 parserState.totalElems = 0;
506                 query.literalSearch = false;
507                 foundStopChar = true;
508                 continue;
509             }
510             if (!foundStopChar) {
511                 if (parserState.typeFilter !== null) {
512                     throw new Error(`Expected \`,\`, \` \` or \`->\`, found \`${c}\``);
513                 }
514                 throw new Error(`Expected \`,\`, \` \`, \`:\` or \`->\`, found \`${c}\``);
515             }
516             before = query.elems.length;
517             getNextElem(query, parserState, query.elems, false);
518             if (query.elems.length === before) {
519                 // Nothing was added, weird... Let's increase the position to not remain stuck.
520                 parserState.pos += 1;
521             }
522             foundStopChar = false;
523         }
524         while (parserState.pos < parserState.length) {
525             c = parserState.userQuery[parserState.pos];
526             if (isReturnArrow(parserState)) {
527                 parserState.pos += 2;
528                 // Get returned elements.
529                 getItemsBefore(query, parserState, query.returned, "");
530                 // Nothing can come afterward!
531                 if (query.returned.length === 0) {
532                     throw new Error("Expected at least one item after `->`");
533                 }
534                 break;
535             } else {
536                 parserState.pos += 1;
537             }
538         }
539     }
540
541     /**
542      * Takes the user search input and returns an empty `ParsedQuery`.
543      *
544      * @param {string} userQuery
545      *
546      * @return {ParsedQuery}
547      */
548     function newParsedQuery(userQuery) {
549         return {
550             original: userQuery,
551             userQuery: userQuery.toLowerCase(),
552             typeFilter: NO_TYPE_FILTER,
553             elems: [],
554             returned: [],
555             // Total number of "top" elements (does not include generics).
556             foundElems: 0,
557             literalSearch: false,
558             error: null,
559         };
560     }
561
562     /**
563      * Build an URL with search parameters.
564      *
565      * @param {string} search            - The current search being performed.
566      * @param {string|null} filterCrates - The current filtering crate (if any).
567      *
568      * @return {string}
569      */
570     function buildUrl(search, filterCrates) {
571         let extra = "?search=" + encodeURIComponent(search);
572
573         if (filterCrates !== null) {
574             extra += "&filter-crate=" + encodeURIComponent(filterCrates);
575         }
576         return getNakedUrl() + extra + window.location.hash;
577     }
578
579     /**
580      * Return the filtering crate or `null` if there is none.
581      *
582      * @return {string|null}
583      */
584     function getFilterCrates() {
585         const elem = document.getElementById("crate-search");
586
587         if (elem &&
588             elem.value !== "All crates" &&
589             hasOwnPropertyRustdoc(rawSearchIndex, elem.value)
590         ) {
591             return elem.value;
592         }
593         return null;
594     }
595
596     /**
597      * Parses the query.
598      *
599      * The supported syntax by this parser is as follow:
600      *
601      * ident = *(ALPHA / DIGIT / "_") [!]
602      * path = ident *(DOUBLE-COLON ident)
603      * arg = path [generics]
604      * arg-without-generic = path
605      * type-sep = COMMA/WS *(COMMA/WS)
606      * nonempty-arg-list = *(type-sep) arg *(type-sep arg) *(type-sep)
607      * nonempty-arg-list-without-generics = *(type-sep) arg-without-generic
608      *                                      *(type-sep arg-without-generic) *(type-sep)
609      * generics = OPEN-ANGLE-BRACKET [ nonempty-arg-list-without-generics ] *(type-sep)
610      *            CLOSE-ANGLE-BRACKET/EOF
611      * return-args = RETURN-ARROW *(type-sep) nonempty-arg-list
612      *
613      * exact-search = [type-filter *WS COLON] [ RETURN-ARROW ] *WS QUOTE ident QUOTE [ generics ]
614      * type-search = [type-filter *WS COLON] [ nonempty-arg-list ] [ return-args ]
615      *
616      * query = *WS (exact-search / type-search) *WS
617      *
618      * type-filter = (
619      *     "mod" /
620      *     "externcrate" /
621      *     "import" /
622      *     "struct" /
623      *     "enum" /
624      *     "fn" /
625      *     "type" /
626      *     "static" /
627      *     "trait" /
628      *     "impl" /
629      *     "tymethod" /
630      *     "method" /
631      *     "structfield" /
632      *     "variant" /
633      *     "macro" /
634      *     "primitive" /
635      *     "associatedtype" /
636      *     "constant" /
637      *     "associatedconstant" /
638      *     "union" /
639      *     "foreigntype" /
640      *     "keyword" /
641      *     "existential" /
642      *     "attr" /
643      *     "derive" /
644      *     "traitalias")
645      *
646      * OPEN-ANGLE-BRACKET = "<"
647      * CLOSE-ANGLE-BRACKET = ">"
648      * COLON = ":"
649      * DOUBLE-COLON = "::"
650      * QUOTE = %x22
651      * COMMA = ","
652      * RETURN-ARROW = "->"
653      *
654      * ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
655      * DIGIT = %x30-39
656      * WS = %x09 / " "
657      *
658      * @param  {string} val     - The user query
659      *
660      * @return {ParsedQuery}    - The parsed query
661      */
662     function parseQuery(userQuery) {
663         userQuery = userQuery.trim();
664         const parserState = {
665             length: userQuery.length,
666             pos: 0,
667             // Total number of elements (includes generics).
668             totalElems: 0,
669             genericsElems: 0,
670             typeFilter: null,
671             userQuery: userQuery.toLowerCase(),
672         };
673         let query = newParsedQuery(userQuery);
674
675         try {
676             parseInput(query, parserState);
677             if (parserState.typeFilter !== null) {
678                 let typeFilter = parserState.typeFilter;
679                 if (typeFilter === "const") {
680                     typeFilter = "constant";
681                 }
682                 query.typeFilter = itemTypeFromName(typeFilter);
683             }
684         } catch (err) {
685             query = newParsedQuery(userQuery);
686             query.error = err.message;
687             query.typeFilter = -1;
688             return query;
689         }
690
691         if (!query.literalSearch) {
692             // If there is more than one element in the query, we switch to literalSearch in any
693             // case.
694             query.literalSearch = parserState.totalElems > 1;
695         }
696         query.foundElems = query.elems.length + query.returned.length;
697         return query;
698     }
699
700     /**
701      * Creates the query results.
702      *
703      * @param {Array<Result>} results_in_args
704      * @param {Array<Result>} results_returned
705      * @param {Array<Result>} results_in_args
706      * @param {ParsedQuery} parsedQuery
707      *
708      * @return {ResultsTable}
709      */
710     function createQueryResults(results_in_args, results_returned, results_others, parsedQuery) {
711         return {
712             "in_args": results_in_args,
713             "returned": results_returned,
714             "others": results_others,
715             "query": parsedQuery,
716         };
717     }
718
719     /**
720      * Executes the parsed query and builds a {ResultsTable}.
721      *
722      * @param  {ParsedQuery} parsedQuery - The parsed user query
723      * @param  {Object} searchWords      - The list of search words to query against
724      * @param  {Object} [filterCrates]   - Crate to search in if defined
725      * @param  {Object} [currentCrate]   - Current crate, to rank results from this crate higher
726      *
727      * @return {ResultsTable}
728      */
729     function execQuery(parsedQuery, searchWords, filterCrates, currentCrate) {
730         const results_others = {}, results_in_args = {}, results_returned = {};
731
732         function transformResults(results) {
733             const duplicates = {};
734             const out = [];
735
736             for (const result of results) {
737                 if (result.id > -1) {
738                     const obj = searchIndex[result.id];
739                     obj.lev = result.lev;
740                     const res = buildHrefAndPath(obj);
741                     obj.displayPath = pathSplitter(res[0]);
742                     obj.fullPath = obj.displayPath + obj.name;
743                     // To be sure than it some items aren't considered as duplicate.
744                     obj.fullPath += "|" + obj.ty;
745
746                     if (duplicates[obj.fullPath]) {
747                         continue;
748                     }
749                     duplicates[obj.fullPath] = true;
750
751                     obj.href = res[1];
752                     out.push(obj);
753                     if (out.length >= MAX_RESULTS) {
754                         break;
755                     }
756                 }
757             }
758             return out;
759         }
760
761         function sortResults(results, isType, preferredCrate) {
762             const userQuery = parsedQuery.userQuery;
763             const ar = [];
764             for (const entry in results) {
765                 if (hasOwnPropertyRustdoc(results, entry)) {
766                     const result = results[entry];
767                     result.word = searchWords[result.id];
768                     result.item = searchIndex[result.id] || {};
769                     ar.push(result);
770                 }
771             }
772             results = ar;
773             // if there are no results then return to default and fail
774             if (results.length === 0) {
775                 return [];
776             }
777
778             results.sort((aaa, bbb) => {
779                 let a, b;
780
781                 // sort by exact match with regard to the last word (mismatch goes later)
782                 a = (aaa.word !== userQuery);
783                 b = (bbb.word !== userQuery);
784                 if (a !== b) {
785                     return a - b;
786                 }
787
788                 // Sort by non levenshtein results and then levenshtein results by the distance
789                 // (less changes required to match means higher rankings)
790                 a = (aaa.lev);
791                 b = (bbb.lev);
792                 if (a !== b) {
793                     return a - b;
794                 }
795
796                 // sort by crate (current crate comes first)
797                 a = (aaa.item.crate !== preferredCrate);
798                 b = (bbb.item.crate !== preferredCrate);
799                 if (a !== b) {
800                     return a - b;
801                 }
802
803                 // sort by item name length (longer goes later)
804                 a = aaa.word.length;
805                 b = bbb.word.length;
806                 if (a !== b) {
807                     return a - b;
808                 }
809
810                 // sort by item name (lexicographically larger goes later)
811                 a = aaa.word;
812                 b = bbb.word;
813                 if (a !== b) {
814                     return (a > b ? +1 : -1);
815                 }
816
817                 // sort by index of keyword in item name (no literal occurrence goes later)
818                 a = (aaa.index < 0);
819                 b = (bbb.index < 0);
820                 if (a !== b) {
821                     return a - b;
822                 }
823                 // (later literal occurrence, if any, goes later)
824                 a = aaa.index;
825                 b = bbb.index;
826                 if (a !== b) {
827                     return a - b;
828                 }
829
830                 // special precedence for primitive and keyword pages
831                 if ((aaa.item.ty === TY_PRIMITIVE && bbb.item.ty !== TY_KEYWORD) ||
832                     (aaa.item.ty === TY_KEYWORD && bbb.item.ty !== TY_PRIMITIVE)) {
833                     return -1;
834                 }
835                 if ((bbb.item.ty === TY_PRIMITIVE && aaa.item.ty !== TY_PRIMITIVE) ||
836                     (bbb.item.ty === TY_KEYWORD && aaa.item.ty !== TY_KEYWORD)) {
837                     return 1;
838                 }
839
840                 // sort by description (no description goes later)
841                 a = (aaa.item.desc === "");
842                 b = (bbb.item.desc === "");
843                 if (a !== b) {
844                     return a - b;
845                 }
846
847                 // sort by type (later occurrence in `itemTypes` goes later)
848                 a = aaa.item.ty;
849                 b = bbb.item.ty;
850                 if (a !== b) {
851                     return a - b;
852                 }
853
854                 // sort by path (lexicographically larger goes later)
855                 a = aaa.item.path;
856                 b = bbb.item.path;
857                 if (a !== b) {
858                     return (a > b ? +1 : -1);
859                 }
860
861                 // que sera, sera
862                 return 0;
863             });
864
865             let nameSplit = null;
866             if (parsedQuery.elems.length === 1) {
867                 const hasPath = typeof parsedQuery.elems[0].path === "undefined";
868                 nameSplit = hasPath ? null : parsedQuery.elems[0].path;
869             }
870
871             for (const result of results) {
872                 // this validation does not make sense when searching by types
873                 if (result.dontValidate) {
874                     continue;
875                 }
876                 const name = result.item.name.toLowerCase(),
877                     path = result.item.path.toLowerCase(),
878                     parent = result.item.parent;
879
880                 if (!isType && !validateResult(name, path, nameSplit, parent)) {
881                     result.id = -1;
882                 }
883             }
884             return transformResults(results);
885         }
886
887         /**
888          * This function checks if the object (`row`) generics match the given type (`elem`)
889          * generics. If there are no generics on `row`, `defaultLev` is returned.
890          *
891          * @param {Row} row            - The object to check.
892          * @param {QueryElement} elem  - The element from the parsed query.
893          * @param {integer} defaultLev - This is the value to return in case there are no generics.
894          *
895          * @return {integer}           - Returns the best match (if any) or `MAX_LEV_DISTANCE + 1`.
896          */
897         function checkGenerics(row, elem, defaultLev) {
898             if (row.length <= GENERICS_DATA || row[GENERICS_DATA].length === 0) {
899                 return elem.generics.length === 0 ? defaultLev : MAX_LEV_DISTANCE + 1;
900             } else if (row[GENERICS_DATA].length > 0 && row[GENERICS_DATA][0][NAME] === "") {
901                 if (row.length > GENERICS_DATA) {
902                     return checkGenerics(row[GENERICS_DATA][0], elem, defaultLev);
903                 }
904                 return elem.generics.length === 0 ? defaultLev : MAX_LEV_DISTANCE + 1;
905             }
906             // The names match, but we need to be sure that all generics kinda
907             // match as well.
908             let elem_name;
909             if (elem.generics.length > 0 && row[GENERICS_DATA].length >= elem.generics.length) {
910                 const elems = Object.create(null);
911                 for (const entry of row[GENERICS_DATA]) {
912                     elem_name = entry[NAME];
913                     if (elem_name === "") {
914                         // Pure generic, needs to check into it.
915                         if (checkGenerics(entry, elem, MAX_LEV_DISTANCE + 1) !== 0) {
916                             return MAX_LEV_DISTANCE + 1;
917                         }
918                         continue;
919                     }
920                     if (elems[elem_name] === undefined) {
921                         elems[elem_name] = 0;
922                     }
923                     elems[elem_name] += 1;
924                 }
925                 // We need to find the type that matches the most to remove it in order
926                 // to move forward.
927                 for (const generic of elem.generics) {
928                     let match = null;
929                     if (elems[generic.name]) {
930                         match = generic.name;
931                     } else {
932                         for (elem_name in elems) {
933                             if (!hasOwnPropertyRustdoc(elems, elem_name)) {
934                                 continue;
935                             }
936                             if (elem_name === generic) {
937                                 match = elem_name;
938                                 break;
939                             }
940                         }
941                     }
942                     if (match === null) {
943                         return MAX_LEV_DISTANCE + 1;
944                     }
945                     elems[match] -= 1;
946                     if (elems[match] === 0) {
947                         delete elems[match];
948                     }
949                 }
950                 return 0;
951             }
952             return MAX_LEV_DISTANCE + 1;
953         }
954
955         /**
956           * This function checks if the object (`row`) matches the given type (`elem`) and its
957           * generics (if any).
958           *
959           * @param {Row} row
960           * @param {QueryElement} elem    - The element from the parsed query.
961           *
962           * @return {integer} - Returns a Levenshtein distance to the best match.
963           */
964         function checkIfInGenerics(row, elem) {
965             let lev = MAX_LEV_DISTANCE + 1;
966             for (const entry of row[GENERICS_DATA]) {
967                 lev = Math.min(checkType(entry, elem, true), lev);
968                 if (lev === 0) {
969                     break;
970                 }
971             }
972             return lev;
973         }
974
975         /**
976           * This function checks if the object (`row`) matches the given type (`elem`) and its
977           * generics (if any).
978           *
979           * @param {Row} row
980           * @param {QueryElement} elem      - The element from the parsed query.
981           * @param {boolean} literalSearch
982           *
983           * @return {integer} - Returns a Levenshtein distance to the best match. If there is
984           *                     no match, returns `MAX_LEV_DISTANCE + 1`.
985           */
986         function checkType(row, elem, literalSearch) {
987             if (row[NAME].length === 0) {
988                 // This is a pure "generic" search, no need to run other checks.
989                 if (row.length > GENERICS_DATA) {
990                     return checkIfInGenerics(row, elem);
991                 }
992                 return MAX_LEV_DISTANCE + 1;
993             }
994
995             let lev = levenshtein(row[NAME], elem.name);
996             if (literalSearch) {
997                 if (lev !== 0) {
998                     // The name didn't match, let's try to check if the generics do.
999                     if (elem.generics.length === 0) {
1000                         const checkGeneric = (row.length > GENERICS_DATA &&
1001                             row[GENERICS_DATA].length > 0);
1002                         if (checkGeneric && row[GENERICS_DATA]
1003                             .findIndex(tmp_elem => tmp_elem[NAME] === elem.name) !== -1) {
1004                             return 0;
1005                         }
1006                     }
1007                     return MAX_LEV_DISTANCE + 1;
1008                 } else if (elem.generics.length > 0) {
1009                     return checkGenerics(row, elem, MAX_LEV_DISTANCE + 1);
1010                 }
1011                 return 0;
1012             } else if (row.length > GENERICS_DATA) {
1013                 if (elem.generics.length === 0) {
1014                     if (lev === 0) {
1015                         return 0;
1016                     }
1017                     // The name didn't match so we now check if the type we're looking for is inside
1018                     // the generics!
1019                     lev = checkIfInGenerics(row, elem);
1020                     // Now whatever happens, the returned distance is "less good" so we should mark
1021                     // it as such, and so we add 0.5 to the distance to make it "less good".
1022                     return lev + 0.5;
1023                 } else if (lev > MAX_LEV_DISTANCE) {
1024                     // So our item's name doesn't match at all and has generics.
1025                     //
1026                     // Maybe it's present in a sub generic? For example "f<A<B<C>>>()", if we're
1027                     // looking for "B<C>", we'll need to go down.
1028                     return checkIfInGenerics(row, elem);
1029                 } else {
1030                     // At this point, the name kinda match and we have generics to check, so
1031                     // let's go!
1032                     const tmp_lev = checkGenerics(row, elem, lev);
1033                     if (tmp_lev > MAX_LEV_DISTANCE) {
1034                         return MAX_LEV_DISTANCE + 1;
1035                     }
1036                     // We compute the median value of both checks and return it.
1037                     return (tmp_lev + lev) / 2;
1038                 }
1039             } else if (elem.generics.length > 0) {
1040                 // In this case, we were expecting generics but there isn't so we simply reject this
1041                 // one.
1042                 return MAX_LEV_DISTANCE + 1;
1043             }
1044             // No generics on our query or on the target type so we can return without doing
1045             // anything else.
1046             return lev;
1047         }
1048
1049         /**
1050          * This function checks if the object (`row`) has an argument with the given type (`elem`).
1051          *
1052          * @param {Row} row
1053          * @param {QueryElement} elem    - The element from the parsed query.
1054          * @param {integer} typeFilter
1055          *
1056          * @return {integer} - Returns a Levenshtein distance to the best match. If there is no
1057          *                      match, returns `MAX_LEV_DISTANCE + 1`.
1058          */
1059         function findArg(row, elem, typeFilter) {
1060             let lev = MAX_LEV_DISTANCE + 1;
1061
1062             if (row && row.type && row.type[INPUTS_DATA] && row.type[INPUTS_DATA].length > 0) {
1063                 for (const input of row.type[INPUTS_DATA]) {
1064                     if (!typePassesFilter(typeFilter, input[1])) {
1065                         continue;
1066                     }
1067                     lev = Math.min(lev, checkType(input, elem, parsedQuery.literalSearch));
1068                     if (lev === 0) {
1069                         return 0;
1070                     }
1071                 }
1072             }
1073             return parsedQuery.literalSearch ? MAX_LEV_DISTANCE + 1 : lev;
1074         }
1075
1076         /**
1077          * This function checks if the object (`row`) returns the given type (`elem`).
1078          *
1079          * @param {Row} row
1080          * @param {QueryElement} elem   - The element from the parsed query.
1081          * @param {integer} typeFilter
1082          *
1083          * @return {integer} - Returns a Levenshtein distance to the best match. If there is no
1084          *                      match, returns `MAX_LEV_DISTANCE + 1`.
1085          */
1086         function checkReturned(row, elem, typeFilter) {
1087             let lev = MAX_LEV_DISTANCE + 1;
1088
1089             if (row && row.type && row.type.length > OUTPUT_DATA) {
1090                 let ret = row.type[OUTPUT_DATA];
1091                 if (typeof ret[0] === "string") {
1092                     ret = [ret];
1093                 }
1094                 for (const ret_ty of ret) {
1095                     if (!typePassesFilter(typeFilter, ret_ty[1])) {
1096                         continue;
1097                     }
1098                     lev = Math.min(lev, checkType(ret_ty, elem, parsedQuery.literalSearch));
1099                     if (lev === 0) {
1100                         return 0;
1101                     }
1102                 }
1103             }
1104             return parsedQuery.literalSearch ? MAX_LEV_DISTANCE + 1 : lev;
1105         }
1106
1107         function checkPath(contains, ty) {
1108             if (contains.length === 0) {
1109                 return 0;
1110             }
1111             let ret_lev = MAX_LEV_DISTANCE + 1;
1112             const path = ty.path.split("::");
1113
1114             if (ty.parent && ty.parent.name) {
1115                 path.push(ty.parent.name.toLowerCase());
1116             }
1117
1118             const length = path.length;
1119             const clength = contains.length;
1120             if (clength > length) {
1121                 return MAX_LEV_DISTANCE + 1;
1122             }
1123             for (let i = 0; i < length; ++i) {
1124                 if (i + clength > length) {
1125                     break;
1126                 }
1127                 let lev_total = 0;
1128                 let aborted = false;
1129                 for (let x = 0; x < clength; ++x) {
1130                     const lev = levenshtein(path[i + x], contains[x]);
1131                     if (lev > MAX_LEV_DISTANCE) {
1132                         aborted = true;
1133                         break;
1134                     }
1135                     lev_total += lev;
1136                 }
1137                 if (!aborted) {
1138                     ret_lev = Math.min(ret_lev, Math.round(lev_total / clength));
1139                 }
1140             }
1141             return ret_lev;
1142         }
1143
1144         function typePassesFilter(filter, type) {
1145             // No filter or Exact mach
1146             if (filter <= NO_TYPE_FILTER || filter === type) return true;
1147
1148             // Match related items
1149             const name = itemTypes[type];
1150             switch (itemTypes[filter]) {
1151                 case "constant":
1152                     return name === "associatedconstant";
1153                 case "fn":
1154                     return name === "method" || name === "tymethod";
1155                 case "type":
1156                     return name === "primitive" || name === "associatedtype";
1157                 case "trait":
1158                     return name === "traitalias";
1159             }
1160
1161             // No match
1162             return false;
1163         }
1164
1165         function createAliasFromItem(item) {
1166             return {
1167                 crate: item.crate,
1168                 name: item.name,
1169                 path: item.path,
1170                 desc: item.desc,
1171                 ty: item.ty,
1172                 parent: item.parent,
1173                 type: item.type,
1174                 is_alias: true,
1175             };
1176         }
1177
1178         function handleAliases(ret, query, filterCrates, currentCrate) {
1179             const lowerQuery = query.toLowerCase();
1180             // We separate aliases and crate aliases because we want to have current crate
1181             // aliases to be before the others in the displayed results.
1182             const aliases = [];
1183             const crateAliases = [];
1184             if (filterCrates !== null) {
1185                 if (ALIASES[filterCrates] && ALIASES[filterCrates][lowerQuery]) {
1186                     const query_aliases = ALIASES[filterCrates][lowerQuery];
1187                     for (const alias of query_aliases) {
1188                         aliases.push(createAliasFromItem(searchIndex[alias]));
1189                     }
1190                 }
1191             } else {
1192                 Object.keys(ALIASES).forEach(crate => {
1193                     if (ALIASES[crate][lowerQuery]) {
1194                         const pushTo = crate === currentCrate ? crateAliases : aliases;
1195                         const query_aliases = ALIASES[crate][lowerQuery];
1196                         for (const alias of query_aliases) {
1197                             pushTo.push(createAliasFromItem(searchIndex[alias]));
1198                         }
1199                     }
1200                 });
1201             }
1202
1203             const sortFunc = (aaa, bbb) => {
1204                 if (aaa.path < bbb.path) {
1205                     return 1;
1206                 } else if (aaa.path === bbb.path) {
1207                     return 0;
1208                 }
1209                 return -1;
1210             };
1211             crateAliases.sort(sortFunc);
1212             aliases.sort(sortFunc);
1213
1214             const pushFunc = alias => {
1215                 alias.alias = query;
1216                 const res = buildHrefAndPath(alias);
1217                 alias.displayPath = pathSplitter(res[0]);
1218                 alias.fullPath = alias.displayPath + alias.name;
1219                 alias.href = res[1];
1220
1221                 ret.others.unshift(alias);
1222                 if (ret.others.length > MAX_RESULTS) {
1223                     ret.others.pop();
1224                 }
1225             };
1226
1227             aliases.forEach(pushFunc);
1228             crateAliases.forEach(pushFunc);
1229         }
1230
1231         /**
1232          * This function adds the given result into the provided `results` map if it matches the
1233          * following condition:
1234          *
1235          * * If it is a "literal search" (`parsedQuery.literalSearch`), then `lev` must be 0.
1236          * * If it is not a "literal search", `lev` must be <= `MAX_LEV_DISTANCE`.
1237          *
1238          * The `results` map contains information which will be used to sort the search results:
1239          *
1240          * * `fullId` is a `string`` used as the key of the object we use for the `results` map.
1241          * * `id` is the index in both `searchWords` and `searchIndex` arrays for this element.
1242          * * `index` is an `integer`` used to sort by the position of the word in the item's name.
1243          * * `lev` is the main metric used to sort the search results.
1244          *
1245          * @param {Results} results
1246          * @param {string} fullId
1247          * @param {integer} id
1248          * @param {integer} index
1249          * @param {integer} lev
1250          */
1251         function addIntoResults(results, fullId, id, index, lev) {
1252             if (lev === 0 || (!parsedQuery.literalSearch && lev <= MAX_LEV_DISTANCE)) {
1253                 if (results[fullId] !== undefined) {
1254                     const result = results[fullId];
1255                     if (result.dontValidate || result.lev <= lev) {
1256                         return;
1257                     }
1258                 }
1259                 results[fullId] = {
1260                     id: id,
1261                     index: index,
1262                     dontValidate: parsedQuery.literalSearch,
1263                     lev: lev,
1264                 };
1265             }
1266         }
1267
1268         /**
1269          * This function is called in case the query is only one element (with or without generics).
1270          * This element will be compared to arguments' and returned values' items and also to items.
1271          *
1272          * Other important thing to note: since there is only one element, we use levenshtein
1273          * distance for name comparisons.
1274          *
1275          * @param {Row} row
1276          * @param {integer} pos              - Position in the `searchIndex`.
1277          * @param {QueryElement} elem        - The element from the parsed query.
1278          * @param {Results} results_others   - Unqualified results (not in arguments nor in
1279          *                                     returned values).
1280          * @param {Results} results_in_args  - Matching arguments results.
1281          * @param {Results} results_returned - Matching returned arguments results.
1282          */
1283         function handleSingleArg(
1284             row,
1285             pos,
1286             elem,
1287             results_others,
1288             results_in_args,
1289             results_returned
1290         ) {
1291             if (!row || (filterCrates !== null && row.crate !== filterCrates)) {
1292                 return;
1293             }
1294             let lev, lev_add = 0, index = -1;
1295             const fullId = row.id;
1296
1297             const in_args = findArg(row, elem, parsedQuery.typeFilter);
1298             const returned = checkReturned(row, elem, parsedQuery.typeFilter);
1299
1300             addIntoResults(results_in_args, fullId, pos, index, in_args);
1301             addIntoResults(results_returned, fullId, pos, index, returned);
1302
1303             if (!typePassesFilter(parsedQuery.typeFilter, row.ty)) {
1304                 return;
1305             }
1306             const searchWord = searchWords[pos];
1307
1308             if (parsedQuery.literalSearch) {
1309                 if (searchWord === elem.name) {
1310                     addIntoResults(results_others, fullId, pos, -1, 0);
1311                 }
1312                 return;
1313             }
1314
1315             // No need to check anything else if it's a "pure" generics search.
1316             if (elem.name.length === 0) {
1317                 if (row.type !== null) {
1318                     lev = checkGenerics(row.type, elem, MAX_LEV_DISTANCE + 1);
1319                     addIntoResults(results_others, fullId, pos, index, lev);
1320                 }
1321                 return;
1322             }
1323
1324             if (elem.fullPath.length > 1) {
1325                 lev = checkPath(elem.pathWithoutLast, row);
1326                 if (lev > MAX_LEV_DISTANCE || (parsedQuery.literalSearch && lev !== 0)) {
1327                     return;
1328                 } else if (lev > 0) {
1329                     lev_add = lev / 10;
1330                 }
1331             }
1332
1333             if (searchWord.indexOf(elem.pathLast) > -1 ||
1334                 row.normalizedName.indexOf(elem.pathLast) > -1
1335             ) {
1336                 // filter type: ... queries
1337                 if (!results_others[fullId] !== undefined) {
1338                     index = row.normalizedName.indexOf(elem.pathLast);
1339                 }
1340             }
1341             lev = levenshtein(searchWord, elem.pathLast);
1342             if (lev > 0 && elem.pathLast.length > 2 && searchWord.indexOf(elem.pathLast) > -1) {
1343                 if (elem.pathLast.length < 6) {
1344                     lev = 1;
1345                 } else {
1346                     lev = 0;
1347                 }
1348             }
1349             lev += lev_add;
1350             if (lev > MAX_LEV_DISTANCE) {
1351                 return;
1352             } else if (index !== -1 && elem.fullPath.length < 2) {
1353                 lev -= 1;
1354             }
1355             if (lev < 0) {
1356                 lev = 0;
1357             }
1358             addIntoResults(results_others, fullId, pos, index, lev);
1359         }
1360
1361         /**
1362          * This function is called in case the query has more than one element. In this case, it'll
1363          * try to match the items which validates all the elements. For `aa -> bb` will look for
1364          * functions which have a parameter `aa` and has `bb` in its returned values.
1365          *
1366          * @param {Row} row
1367          * @param {integer} pos      - Position in the `searchIndex`.
1368          * @param {Object} results
1369          */
1370         function handleArgs(row, pos, results) {
1371             if (!row || (filterCrates !== null && row.crate !== filterCrates)) {
1372                 return;
1373             }
1374
1375             let totalLev = 0;
1376             let nbLev = 0;
1377
1378             // If the result is too "bad", we return false and it ends this search.
1379             function checkArgs(elems, callback) {
1380                 for (const elem of elems) {
1381                     // There is more than one parameter to the query so all checks should be "exact"
1382                     const lev = callback(row, elem, NO_TYPE_FILTER);
1383                     if (lev <= 1) {
1384                         nbLev += 1;
1385                         totalLev += lev;
1386                     } else {
1387                         return false;
1388                     }
1389                 }
1390                 return true;
1391             }
1392             if (!checkArgs(parsedQuery.elems, findArg)) {
1393                 return;
1394             }
1395             if (!checkArgs(parsedQuery.returned, checkReturned)) {
1396                 return;
1397             }
1398
1399             if (nbLev === 0) {
1400                 return;
1401             }
1402             const lev = Math.round(totalLev / nbLev);
1403             addIntoResults(results, row.id, pos, 0, lev);
1404         }
1405
1406         function innerRunQuery() {
1407             let elem, i, nSearchWords, in_returned, row;
1408
1409             if (parsedQuery.foundElems === 1) {
1410                 if (parsedQuery.elems.length === 1) {
1411                     elem = parsedQuery.elems[0];
1412                     for (i = 0, nSearchWords = searchWords.length; i < nSearchWords; ++i) {
1413                         // It means we want to check for this element everywhere (in names, args and
1414                         // returned).
1415                         handleSingleArg(
1416                             searchIndex[i],
1417                             i,
1418                             elem,
1419                             results_others,
1420                             results_in_args,
1421                             results_returned
1422                         );
1423                     }
1424                 } else if (parsedQuery.returned.length === 1) {
1425                     // We received one returned argument to check, so looking into returned values.
1426                     elem = parsedQuery.returned[0];
1427                     for (i = 0, nSearchWords = searchWords.length; i < nSearchWords; ++i) {
1428                         row = searchIndex[i];
1429                         in_returned = checkReturned(row, elem, parsedQuery.typeFilter);
1430                         addIntoResults(results_others, row.id, i, -1, in_returned);
1431                     }
1432                 }
1433             } else if (parsedQuery.foundElems > 0) {
1434                 for (i = 0, nSearchWords = searchWords.length; i < nSearchWords; ++i) {
1435                     handleArgs(searchIndex[i], i, results_others);
1436                 }
1437             }
1438         }
1439
1440         if (parsedQuery.error === null) {
1441             innerRunQuery();
1442         }
1443
1444         const ret = createQueryResults(
1445             sortResults(results_in_args, true, currentCrate),
1446             sortResults(results_returned, true, currentCrate),
1447             sortResults(results_others, false, currentCrate),
1448             parsedQuery);
1449         handleAliases(ret, parsedQuery.original.replace(/"/g, ""), filterCrates, currentCrate);
1450         if (parsedQuery.error !== null && ret.others.length !== 0) {
1451             // It means some doc aliases were found so let's "remove" the error!
1452             ret.query.error = null;
1453         }
1454         return ret;
1455     }
1456
1457     /**
1458      * Validate performs the following boolean logic. For example:
1459      * "File::open" will give IF A PARENT EXISTS => ("file" && "open")
1460      * exists in (name || path || parent) OR => ("file" && "open") exists in
1461      * (name || path )
1462      *
1463      * This could be written functionally, but I wanted to minimise
1464      * functions on stack.
1465      *
1466      * @param  {string} name   - The name of the result
1467      * @param  {string} path   - The path of the result
1468      * @param  {string} keys   - The keys to be used (["file", "open"])
1469      * @param  {Object} parent - The parent of the result
1470      *
1471      * @return {boolean}       - Whether the result is valid or not
1472      */
1473     function validateResult(name, path, keys, parent) {
1474         if (!keys || !keys.length) {
1475             return true;
1476         }
1477         for (const key of keys) {
1478             // each check is for validation so we negate the conditions and invalidate
1479             if (!(
1480                 // check for an exact name match
1481                 name.indexOf(key) > -1 ||
1482                 // then an exact path match
1483                 path.indexOf(key) > -1 ||
1484                 // next if there is a parent, check for exact parent match
1485                 (parent !== undefined && parent.name !== undefined &&
1486                     parent.name.toLowerCase().indexOf(key) > -1) ||
1487                 // lastly check to see if the name was a levenshtein match
1488                 levenshtein(name, key) <= MAX_LEV_DISTANCE)) {
1489                 return false;
1490             }
1491         }
1492         return true;
1493     }
1494
1495     function nextTab(direction) {
1496         const next = (searchState.currentTab + direction + 3) % searchState.focusedByTab.length;
1497         searchState.focusedByTab[searchState.currentTab] = document.activeElement;
1498         printTab(next);
1499         focusSearchResult();
1500     }
1501
1502     // Focus the first search result on the active tab, or the result that
1503     // was focused last time this tab was active.
1504     function focusSearchResult() {
1505         const target = searchState.focusedByTab[searchState.currentTab] ||
1506             document.querySelectorAll(".search-results.active a").item(0) ||
1507             document.querySelectorAll("#titles > button").item(searchState.currentTab);
1508         if (target) {
1509             target.focus();
1510         }
1511     }
1512
1513     function buildHrefAndPath(item) {
1514         let displayPath;
1515         let href;
1516         const type = itemTypes[item.ty];
1517         const name = item.name;
1518         let path = item.path;
1519
1520         if (type === "mod") {
1521             displayPath = path + "::";
1522             href = ROOT_PATH + path.replace(/::/g, "/") + "/" +
1523                 name + "/index.html";
1524         } else if (type === "import") {
1525             displayPath = item.path + "::";
1526             href = ROOT_PATH + item.path.replace(/::/g, "/") + "/index.html#reexport." + name;
1527         } else if (type === "primitive" || type === "keyword") {
1528             displayPath = "";
1529             href = ROOT_PATH + path.replace(/::/g, "/") +
1530                 "/" + type + "." + name + ".html";
1531         } else if (type === "externcrate") {
1532             displayPath = "";
1533             href = ROOT_PATH + name + "/index.html";
1534         } else if (item.parent !== undefined) {
1535             const myparent = item.parent;
1536             let anchor = "#" + type + "." + name;
1537             const parentType = itemTypes[myparent.ty];
1538             let pageType = parentType;
1539             let pageName = myparent.name;
1540
1541             if (parentType === "primitive") {
1542                 displayPath = myparent.name + "::";
1543             } else if (type === "structfield" && parentType === "variant") {
1544                 // Structfields belonging to variants are special: the
1545                 // final path element is the enum name.
1546                 const enumNameIdx = item.path.lastIndexOf("::");
1547                 const enumName = item.path.substr(enumNameIdx + 2);
1548                 path = item.path.substr(0, enumNameIdx);
1549                 displayPath = path + "::" + enumName + "::" + myparent.name + "::";
1550                 anchor = "#variant." + myparent.name + ".field." + name;
1551                 pageType = "enum";
1552                 pageName = enumName;
1553             } else {
1554                 displayPath = path + "::" + myparent.name + "::";
1555             }
1556             href = ROOT_PATH + path.replace(/::/g, "/") +
1557                 "/" + pageType +
1558                 "." + pageName +
1559                 ".html" + anchor;
1560         } else {
1561             displayPath = item.path + "::";
1562             href = ROOT_PATH + item.path.replace(/::/g, "/") +
1563                 "/" + type + "." + name + ".html";
1564         }
1565         return [displayPath, href];
1566     }
1567
1568     function escape(content) {
1569         const h1 = document.createElement("h1");
1570         h1.textContent = content;
1571         return h1.innerHTML;
1572     }
1573
1574     function pathSplitter(path) {
1575         const tmp = "<span>" + path.replace(/::/g, "::</span><span>");
1576         if (tmp.endsWith("<span>")) {
1577             return tmp.slice(0, tmp.length - 6);
1578         }
1579         return tmp;
1580     }
1581
1582     /**
1583      * Render a set of search results for a single tab.
1584      * @param {Array<?>}    array   - The search results for this tab
1585      * @param {ParsedQuery} query
1586      * @param {boolean}     display - True if this is the active tab
1587      */
1588     function addTab(array, query, display) {
1589         let extraClass = "";
1590         if (display === true) {
1591             extraClass = " active";
1592         }
1593
1594         const output = document.createElement("div");
1595         let length = 0;
1596         if (array.length > 0) {
1597             output.className = "search-results " + extraClass;
1598
1599             array.forEach(item => {
1600                 const name = item.name;
1601                 const type = itemTypes[item.ty];
1602
1603                 length += 1;
1604
1605                 let extra = "";
1606                 if (type === "primitive") {
1607                     extra = " <i>(primitive type)</i>";
1608                 } else if (type === "keyword") {
1609                     extra = " <i>(keyword)</i>";
1610                 }
1611
1612                 const link = document.createElement("a");
1613                 link.className = "result-" + type;
1614                 link.href = item.href;
1615
1616                 const wrapper = document.createElement("div");
1617                 const resultName = document.createElement("div");
1618                 resultName.className = "result-name";
1619
1620                 if (item.is_alias) {
1621                     const alias = document.createElement("span");
1622                     alias.className = "alias";
1623
1624                     const bold = document.createElement("b");
1625                     bold.innerText = item.alias;
1626                     alias.appendChild(bold);
1627
1628                     alias.insertAdjacentHTML(
1629                         "beforeend",
1630                         "<span class=\"grey\"><i>&nbsp;- see&nbsp;</i></span>");
1631
1632                     resultName.appendChild(alias);
1633                 }
1634                 resultName.insertAdjacentHTML(
1635                     "beforeend",
1636                     item.displayPath + "<span class=\"" + type + "\">" + name + extra + "</span>");
1637                 wrapper.appendChild(resultName);
1638
1639                 const description = document.createElement("div");
1640                 description.className = "desc";
1641                 const spanDesc = document.createElement("span");
1642                 spanDesc.insertAdjacentHTML("beforeend", item.desc);
1643
1644                 description.appendChild(spanDesc);
1645                 wrapper.appendChild(description);
1646                 link.appendChild(wrapper);
1647                 output.appendChild(link);
1648             });
1649         } else if (query.error === null) {
1650             output.className = "search-failed" + extraClass;
1651             output.innerHTML = "No results :(<br/>" +
1652                 "Try on <a href=\"https://duckduckgo.com/?q=" +
1653                 encodeURIComponent("rust " + query.userQuery) +
1654                 "\">DuckDuckGo</a>?<br/><br/>" +
1655                 "Or try looking in one of these:<ul><li>The <a " +
1656                 "href=\"https://doc.rust-lang.org/reference/index.html\">Rust Reference</a> " +
1657                 " for technical details about the language.</li><li><a " +
1658                 "href=\"https://doc.rust-lang.org/rust-by-example/index.html\">Rust By " +
1659                 "Example</a> for expository code examples.</a></li><li>The <a " +
1660                 "href=\"https://doc.rust-lang.org/book/index.html\">Rust Book</a> for " +
1661                 "introductions to language features and the language itself.</li><li><a " +
1662                 "href=\"https://docs.rs\">Docs.rs</a> for documentation of crates released on" +
1663                 " <a href=\"https://crates.io/\">crates.io</a>.</li></ul>";
1664         }
1665         return [output, length];
1666     }
1667
1668     function makeTabHeader(tabNb, text, nbElems) {
1669         if (searchState.currentTab === tabNb) {
1670             return "<button class=\"selected\">" + text +
1671                    " <div class=\"count\">(" + nbElems + ")</div></button>";
1672         }
1673         return "<button>" + text + " <div class=\"count\">(" + nbElems + ")</div></button>";
1674     }
1675
1676     /**
1677      * @param {ResultsTable} results
1678      * @param {boolean} go_to_first
1679      * @param {string} filterCrates
1680      */
1681     function showResults(results, go_to_first, filterCrates) {
1682         const search = searchState.outputElement();
1683         if (go_to_first || (results.others.length === 1
1684             && getSettingValue("go-to-only-result") === "true"
1685             // By default, the search DOM element is "empty" (meaning it has no children not
1686             // text content). Once a search has been run, it won't be empty, even if you press
1687             // ESC or empty the search input (which also "cancels" the search).
1688             && (!search.firstChild || search.firstChild.innerText !== searchState.loadingText))
1689         ) {
1690             const elem = document.createElement("a");
1691             elem.href = results.others[0].href;
1692             removeClass(elem, "active");
1693             // For firefox, we need the element to be in the DOM so it can be clicked.
1694             document.body.appendChild(elem);
1695             elem.click();
1696             return;
1697         }
1698         if (results.query === undefined) {
1699             results.query = parseQuery(searchState.input.value);
1700         }
1701
1702         currentResults = results.query.userQuery;
1703
1704         const ret_others = addTab(results.others, results.query, true);
1705         const ret_in_args = addTab(results.in_args, results.query, false);
1706         const ret_returned = addTab(results.returned, results.query, false);
1707
1708         // Navigate to the relevant tab if the current tab is empty, like in case users search
1709         // for "-> String". If they had selected another tab previously, they have to click on
1710         // it again.
1711         let currentTab = searchState.currentTab;
1712         if ((currentTab === 0 && ret_others[1] === 0) ||
1713                 (currentTab === 1 && ret_in_args[1] === 0) ||
1714                 (currentTab === 2 && ret_returned[1] === 0)) {
1715             if (ret_others[1] !== 0) {
1716                 currentTab = 0;
1717             } else if (ret_in_args[1] !== 0) {
1718                 currentTab = 1;
1719             } else if (ret_returned[1] !== 0) {
1720                 currentTab = 2;
1721             }
1722         }
1723
1724         let crates = "";
1725         if (window.ALL_CRATES.length > 1) {
1726             crates = " in <select id=\"crate-search\"><option value=\"All crates\">" +
1727                 "All crates</option>";
1728             for (const c of window.ALL_CRATES) {
1729                 crates += `<option value="${c}" ${c == filterCrates && "selected"}>${c}</option>`;
1730             }
1731             crates += "</select>";
1732         }
1733
1734         let typeFilter = "";
1735         if (results.query.typeFilter !== NO_TYPE_FILTER) {
1736             typeFilter = " (type: " + escape(itemTypes[results.query.typeFilter]) + ")";
1737         }
1738
1739         let output = "<div id=\"search-settings\">" +
1740             `<h1 class="search-results-title">Results for ${escape(results.query.userQuery)}` +
1741             `${typeFilter}</h1>${crates}</div>`;
1742         if (results.query.error !== null) {
1743             output += `<h3>Query parser error: "${results.query.error}".</h3>`;
1744             output += "<div id=\"titles\">" +
1745                 makeTabHeader(0, "In Names", ret_others[1]) +
1746                 "</div>";
1747             currentTab = 0;
1748         } else if (results.query.foundElems <= 1 && results.query.returned.length === 0) {
1749             output += "<div id=\"titles\">" +
1750                 makeTabHeader(0, "In Names", ret_others[1]) +
1751                 makeTabHeader(1, "In Parameters", ret_in_args[1]) +
1752                 makeTabHeader(2, "In Return Types", ret_returned[1]) +
1753                 "</div>";
1754         } else {
1755             const signatureTabTitle =
1756                 results.query.elems.length === 0 ? "In Function Return Types" :
1757                 results.query.returned.length === 0 ? "In Function Parameters" :
1758                 "In Function Signatures";
1759             output += "<div id=\"titles\">" +
1760                 makeTabHeader(0, signatureTabTitle, ret_others[1]) +
1761                 "</div>";
1762             currentTab = 0;
1763         }
1764
1765         const resultsElem = document.createElement("div");
1766         resultsElem.id = "results";
1767         resultsElem.appendChild(ret_others[0]);
1768         resultsElem.appendChild(ret_in_args[0]);
1769         resultsElem.appendChild(ret_returned[0]);
1770
1771         search.innerHTML = output;
1772         const crateSearch = document.getElementById("crate-search");
1773         if (crateSearch) {
1774             crateSearch.addEventListener("input", updateCrate);
1775         }
1776         search.appendChild(resultsElem);
1777         // Reset focused elements.
1778         searchState.showResults(search);
1779         const elems = document.getElementById("titles").childNodes;
1780         searchState.focusedByTab = [];
1781         let i = 0;
1782         for (const elem of elems) {
1783             const j = i;
1784             elem.onclick = () => printTab(j);
1785             searchState.focusedByTab.push(null);
1786             i += 1;
1787         }
1788         printTab(currentTab);
1789     }
1790
1791     /**
1792      * Perform a search based on the current state of the search input element
1793      * and display the results.
1794      * @param {Event}   [e]       - The event that triggered this search, if any
1795      * @param {boolean} [forced]
1796      */
1797     function search(e, forced) {
1798         const params = searchState.getQueryStringParams();
1799         const query = parseQuery(searchState.input.value.trim());
1800
1801         if (e) {
1802             e.preventDefault();
1803         }
1804
1805         if (!forced && query.userQuery === currentResults) {
1806             if (query.userQuery.length > 0) {
1807                 putBackSearch();
1808             }
1809             return;
1810         }
1811
1812         let filterCrates = getFilterCrates();
1813
1814         // In case we have no information about the saved crate and there is a URL query parameter,
1815         // we override it with the URL query parameter.
1816         if (filterCrates === null && params["filter-crate"] !== undefined) {
1817             filterCrates = params["filter-crate"];
1818         }
1819
1820         // Update document title to maintain a meaningful browser history
1821         searchState.title = "Results for " + query.original + " - Rust";
1822
1823         // Because searching is incremental by character, only the most
1824         // recent search query is added to the browser history.
1825         if (browserSupportsHistoryApi()) {
1826             const newURL = buildUrl(query.original, filterCrates);
1827
1828             if (!history.state && !params.search) {
1829                 history.pushState(null, "", newURL);
1830             } else {
1831                 history.replaceState(null, "", newURL);
1832             }
1833         }
1834
1835         showResults(
1836             execQuery(query, searchWords, filterCrates, window.currentCrate),
1837             params.go_to_first,
1838             filterCrates);
1839     }
1840
1841     function buildIndex(rawSearchIndex) {
1842         searchIndex = [];
1843         /**
1844          * @type {Array<string>}
1845          */
1846         const searchWords = [];
1847         let i, word;
1848         let currentIndex = 0;
1849         let id = 0;
1850
1851         for (const crate in rawSearchIndex) {
1852             if (!hasOwnPropertyRustdoc(rawSearchIndex, crate)) {
1853                 continue;
1854             }
1855
1856             let crateSize = 0;
1857
1858             /**
1859              * The raw search data for a given crate. `n`, `t`, `d`, and `q`, `i`, and `f`
1860              * are arrays with the same length. n[i] contains the name of an item.
1861              * t[i] contains the type of that item (as a small integer that represents an
1862              * offset in `itemTypes`). d[i] contains the description of that item.
1863              *
1864              * q[i] contains the full path of the item, or an empty string indicating
1865              * "same as q[i-1]".
1866              *
1867              * i[i], f[i] are a mystery.
1868              *
1869              * `a` defines aliases with an Array of pairs: [name, offset], where `offset`
1870              * points into the n/t/d/q/i/f arrays.
1871              *
1872              * `doc` contains the description of the crate.
1873              *
1874              * `p` is a mystery and isn't the same length as n/t/d/q/i/f.
1875              *
1876              * @type {{
1877              *   doc: string,
1878              *   a: Object,
1879              *   n: Array<string>,
1880              *   t: Array<Number>,
1881              *   d: Array<string>,
1882              *   q: Array<string>,
1883              *   i: Array<Number>,
1884              *   f: Array<Array<?>>,
1885              *   p: Array<Object>,
1886              * }}
1887              */
1888             const crateCorpus = rawSearchIndex[crate];
1889
1890             searchWords.push(crate);
1891             // This object should have exactly the same set of fields as the "row"
1892             // object defined below. Your JavaScript runtime will thank you.
1893             // https://mathiasbynens.be/notes/shapes-ics
1894             const crateRow = {
1895                 crate: crate,
1896                 ty: 1, // == ExternCrate
1897                 name: crate,
1898                 path: "",
1899                 desc: crateCorpus.doc,
1900                 parent: undefined,
1901                 type: null,
1902                 id: id,
1903                 normalizedName: crate.indexOf("_") === -1 ? crate : crate.replace(/_/g, ""),
1904             };
1905             id += 1;
1906             searchIndex.push(crateRow);
1907             currentIndex += 1;
1908
1909             // an array of (Number) item types
1910             const itemTypes = crateCorpus.t;
1911             // an array of (String) item names
1912             const itemNames = crateCorpus.n;
1913             // an array of (String) full paths (or empty string for previous path)
1914             const itemPaths = crateCorpus.q;
1915             // an array of (String) descriptions
1916             const itemDescs = crateCorpus.d;
1917             // an array of (Number) the parent path index + 1 to `paths`, or 0 if none
1918             const itemParentIdxs = crateCorpus.i;
1919             // an array of (Object | null) the type of the function, if any
1920             const itemFunctionSearchTypes = crateCorpus.f;
1921             // an array of [(Number) item type,
1922             //              (String) name]
1923             const paths = crateCorpus.p;
1924             // an array of [(String) alias name
1925             //             [Number] index to items]
1926             const aliases = crateCorpus.a;
1927
1928             // convert `rawPaths` entries into object form
1929             let len = paths.length;
1930             for (i = 0; i < len; ++i) {
1931                 paths[i] = {ty: paths[i][0], name: paths[i][1]};
1932             }
1933
1934             // convert `item*` into an object form, and construct word indices.
1935             //
1936             // before any analysis is performed lets gather the search terms to
1937             // search against apart from the rest of the data.  This is a quick
1938             // operation that is cached for the life of the page state so that
1939             // all other search operations have access to this cached data for
1940             // faster analysis operations
1941             len = itemTypes.length;
1942             let lastPath = "";
1943             for (i = 0; i < len; ++i) {
1944                 // This object should have exactly the same set of fields as the "crateRow"
1945                 // object defined above.
1946                 if (typeof itemNames[i] === "string") {
1947                     word = itemNames[i].toLowerCase();
1948                     searchWords.push(word);
1949                 } else {
1950                     word = "";
1951                     searchWords.push("");
1952                 }
1953                 const row = {
1954                     crate: crate,
1955                     ty: itemTypes[i],
1956                     name: itemNames[i],
1957                     path: itemPaths[i] ? itemPaths[i] : lastPath,
1958                     desc: itemDescs[i],
1959                     parent: itemParentIdxs[i] > 0 ? paths[itemParentIdxs[i] - 1] : undefined,
1960                     type: itemFunctionSearchTypes[i],
1961                     id: id,
1962                     normalizedName: word.indexOf("_") === -1 ? word : word.replace(/_/g, ""),
1963                 };
1964                 id += 1;
1965                 searchIndex.push(row);
1966                 lastPath = row.path;
1967                 crateSize += 1;
1968             }
1969
1970             if (aliases) {
1971                 ALIASES[crate] = Object.create(null);
1972                 for (const alias_name in aliases) {
1973                     if (!hasOwnPropertyRustdoc(aliases, alias_name)) {
1974                         continue;
1975                     }
1976
1977                     if (!hasOwnPropertyRustdoc(ALIASES[crate], alias_name)) {
1978                         ALIASES[crate][alias_name] = [];
1979                     }
1980                     for (const local_alias of aliases[alias_name]) {
1981                         ALIASES[crate][alias_name].push(local_alias + currentIndex);
1982                     }
1983                 }
1984             }
1985             currentIndex += crateSize;
1986         }
1987         return searchWords;
1988     }
1989
1990     /**
1991      * Callback for when the search form is submitted.
1992      * @param {Event} [e] - The event that triggered this call, if any
1993      */
1994     function onSearchSubmit(e) {
1995         e.preventDefault();
1996         searchState.clearInputTimeout();
1997         search();
1998     }
1999
2000     function putBackSearch() {
2001         const search_input = searchState.input;
2002         if (!searchState.input) {
2003             return;
2004         }
2005         if (search_input.value !== "" && !searchState.isDisplayed()) {
2006             searchState.showResults();
2007             if (browserSupportsHistoryApi()) {
2008                 history.replaceState(null, "",
2009                     buildUrl(search_input.value, getFilterCrates()));
2010             }
2011             document.title = searchState.title;
2012         }
2013     }
2014
2015     function registerSearchEvents() {
2016         const params = searchState.getQueryStringParams();
2017
2018         // Populate search bar with query string search term when provided,
2019         // but only if the input bar is empty. This avoid the obnoxious issue
2020         // where you start trying to do a search, and the index loads, and
2021         // suddenly your search is gone!
2022         if (searchState.input.value === "") {
2023             searchState.input.value = params.search || "";
2024         }
2025
2026         const searchAfter500ms = () => {
2027             searchState.clearInputTimeout();
2028             if (searchState.input.value.length === 0) {
2029                 if (browserSupportsHistoryApi()) {
2030                     history.replaceState(null, window.currentCrate + " - Rust",
2031                         getNakedUrl() + window.location.hash);
2032                 }
2033                 searchState.hideResults();
2034             } else {
2035                 searchState.timeout = setTimeout(search, 500);
2036             }
2037         };
2038         searchState.input.onkeyup = searchAfter500ms;
2039         searchState.input.oninput = searchAfter500ms;
2040         document.getElementsByClassName("search-form")[0].onsubmit = onSearchSubmit;
2041         searchState.input.onchange = e => {
2042             if (e.target !== document.activeElement) {
2043                 // To prevent doing anything when it's from a blur event.
2044                 return;
2045             }
2046             // Do NOT e.preventDefault() here. It will prevent pasting.
2047             searchState.clearInputTimeout();
2048             // zero-timeout necessary here because at the time of event handler execution the
2049             // pasted content is not in the input field yet. Shouldn’t make any difference for
2050             // change, though.
2051             setTimeout(search, 0);
2052         };
2053         searchState.input.onpaste = searchState.input.onchange;
2054
2055         searchState.outputElement().addEventListener("keydown", e => {
2056             // We only handle unmodified keystrokes here. We don't want to interfere with,
2057             // for instance, alt-left and alt-right for history navigation.
2058             if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) {
2059                 return;
2060             }
2061             // up and down arrow select next/previous search result, or the
2062             // search box if we're already at the top.
2063             if (e.which === 38) { // up
2064                 const previous = document.activeElement.previousElementSibling;
2065                 if (previous) {
2066                     previous.focus();
2067                 } else {
2068                     searchState.focus();
2069                 }
2070                 e.preventDefault();
2071             } else if (e.which === 40) { // down
2072                 const next = document.activeElement.nextElementSibling;
2073                 if (next) {
2074                     next.focus();
2075                 }
2076                 const rect = document.activeElement.getBoundingClientRect();
2077                 if (window.innerHeight - rect.bottom < rect.height) {
2078                     window.scrollBy(0, rect.height);
2079                 }
2080                 e.preventDefault();
2081             } else if (e.which === 37) { // left
2082                 nextTab(-1);
2083                 e.preventDefault();
2084             } else if (e.which === 39) { // right
2085                 nextTab(1);
2086                 e.preventDefault();
2087             }
2088         });
2089
2090         searchState.input.addEventListener("keydown", e => {
2091             if (e.which === 40) { // down
2092                 focusSearchResult();
2093                 e.preventDefault();
2094             }
2095         });
2096
2097         searchState.input.addEventListener("focus", () => {
2098             putBackSearch();
2099         });
2100
2101         searchState.input.addEventListener("blur", () => {
2102             searchState.input.placeholder = searchState.input.origPlaceholder;
2103         });
2104
2105         // Push and pop states are used to add search results to the browser
2106         // history.
2107         if (browserSupportsHistoryApi()) {
2108             // Store the previous <title> so we can revert back to it later.
2109             const previousTitle = document.title;
2110
2111             window.addEventListener("popstate", e => {
2112                 const params = searchState.getQueryStringParams();
2113                 // Revert to the previous title manually since the History
2114                 // API ignores the title parameter.
2115                 document.title = previousTitle;
2116                 // When browsing forward to search results the previous
2117                 // search will be repeated, so the currentResults are
2118                 // cleared to ensure the search is successful.
2119                 currentResults = null;
2120                 // Synchronize search bar with query string state and
2121                 // perform the search. This will empty the bar if there's
2122                 // nothing there, which lets you really go back to a
2123                 // previous state with nothing in the bar.
2124                 if (params.search && params.search.length > 0) {
2125                     searchState.input.value = params.search;
2126                     // Some browsers fire "onpopstate" for every page load
2127                     // (Chrome), while others fire the event only when actually
2128                     // popping a state (Firefox), which is why search() is
2129                     // called both here and at the end of the startSearch()
2130                     // function.
2131                     search(e);
2132                 } else {
2133                     searchState.input.value = "";
2134                     // When browsing back from search results the main page
2135                     // visibility must be reset.
2136                     searchState.hideResults();
2137                 }
2138             });
2139         }
2140
2141         // This is required in firefox to avoid this problem: Navigating to a search result
2142         // with the keyboard, hitting enter, and then hitting back would take you back to
2143         // the doc page, rather than the search that should overlay it.
2144         // This was an interaction between the back-forward cache and our handlers
2145         // that try to sync state between the URL and the search input. To work around it,
2146         // do a small amount of re-init on page show.
2147         window.onpageshow = () => {
2148             const qSearch = searchState.getQueryStringParams().search;
2149             if (searchState.input.value === "" && qSearch) {
2150                 searchState.input.value = qSearch;
2151             }
2152             search();
2153         };
2154     }
2155
2156     function updateCrate(ev) {
2157         if (ev.target.value === "All crates") {
2158             // If we don't remove it from the URL, it'll be picked up again by the search.
2159             const params = searchState.getQueryStringParams();
2160             const query = searchState.input.value.trim();
2161             if (!history.state && !params.search) {
2162                 history.pushState(null, "", buildUrl(query, null));
2163             } else {
2164                 history.replaceState(null, "", buildUrl(query, null));
2165             }
2166         }
2167         // In case you "cut" the entry from the search input, then change the crate filter
2168         // before paste back the previous search, you get the old search results without
2169         // the filter. To prevent this, we need to remove the previous results.
2170         currentResults = null;
2171         search(undefined, true);
2172     }
2173
2174     /**
2175      *  @type {Array<string>}
2176      */
2177     const searchWords = buildIndex(rawSearchIndex);
2178     if (typeof window !== "undefined") {
2179         registerSearchEvents();
2180         // If there's a search term in the URL, execute the search now.
2181         if (window.searchState.getQueryStringParams().search) {
2182             search();
2183         }
2184     }
2185
2186     if (typeof exports !== "undefined") {
2187         exports.initSearch = initSearch;
2188         exports.execQuery = execQuery;
2189         exports.parseQuery = parseQuery;
2190     }
2191     return searchWords;
2192 }
2193
2194 if (typeof window !== "undefined") {
2195     window.initSearch = initSearch;
2196     if (window.searchIndex !== undefined) {
2197         initSearch(window.searchIndex);
2198     }
2199 } else {
2200     // Running in Node, not a browser. Run initSearch just to produce the
2201     // exports.
2202     initSearch({});
2203 }
2204
2205
2206 })();