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