]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/html/static/js/search.js
rustdoc: get rid of CSS/DOM `div.desc span`, which isn't really needed
[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` encountered a "stop character" right
433             // from the start. For example if you have `,,` or `<>`. In this case, we simply move up
434             // the 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 pathSplitter(path) {
1555         const tmp = "<span>" + path.replace(/::/g, "::</span><span>");
1556         if (tmp.endsWith("<span>")) {
1557             return tmp.slice(0, tmp.length - 6);
1558         }
1559         return tmp;
1560     }
1561
1562     /**
1563      * Render a set of search results for a single tab.
1564      * @param {Array<?>}    array   - The search results for this tab
1565      * @param {ParsedQuery} query
1566      * @param {boolean}     display - True if this is the active tab
1567      */
1568     function addTab(array, query, display) {
1569         let extraClass = "";
1570         if (display === true) {
1571             extraClass = " active";
1572         }
1573
1574         const output = document.createElement("div");
1575         let length = 0;
1576         if (array.length > 0) {
1577             output.className = "search-results " + extraClass;
1578
1579             array.forEach(item => {
1580                 const name = item.name;
1581                 const type = itemTypes[item.ty];
1582
1583                 length += 1;
1584
1585                 let extra = "";
1586                 if (type === "primitive") {
1587                     extra = " <i>(primitive type)</i>";
1588                 } else if (type === "keyword") {
1589                     extra = " <i>(keyword)</i>";
1590                 }
1591
1592                 const link = document.createElement("a");
1593                 link.className = "result-" + type;
1594                 link.href = item.href;
1595
1596                 const resultName = document.createElement("div");
1597                 resultName.className = "result-name";
1598
1599                 if (item.is_alias) {
1600                     const alias = document.createElement("span");
1601                     alias.className = "alias";
1602
1603                     const bold = document.createElement("b");
1604                     bold.innerText = item.alias;
1605                     alias.appendChild(bold);
1606
1607                     alias.insertAdjacentHTML(
1608                         "beforeend",
1609                         "<span class=\"grey\"><i>&nbsp;- see&nbsp;</i></span>");
1610
1611                     resultName.appendChild(alias);
1612                 }
1613                 resultName.insertAdjacentHTML(
1614                     "beforeend",
1615                     item.displayPath + "<span class=\"" + type + "\">" + name + extra + "</span>");
1616                 link.appendChild(resultName);
1617
1618                 const description = document.createElement("div");
1619                 description.className = "desc";
1620                 description.insertAdjacentHTML("beforeend", item.desc);
1621
1622                 link.appendChild(description);
1623                 output.appendChild(link);
1624             });
1625         } else if (query.error === null) {
1626             output.className = "search-failed" + extraClass;
1627             output.innerHTML = "No results :(<br/>" +
1628                 "Try on <a href=\"https://duckduckgo.com/?q=" +
1629                 encodeURIComponent("rust " + query.userQuery) +
1630                 "\">DuckDuckGo</a>?<br/><br/>" +
1631                 "Or try looking in one of these:<ul><li>The <a " +
1632                 "href=\"https://doc.rust-lang.org/reference/index.html\">Rust Reference</a> " +
1633                 " for technical details about the language.</li><li><a " +
1634                 "href=\"https://doc.rust-lang.org/rust-by-example/index.html\">Rust By " +
1635                 "Example</a> for expository code examples.</a></li><li>The <a " +
1636                 "href=\"https://doc.rust-lang.org/book/index.html\">Rust Book</a> for " +
1637                 "introductions to language features and the language itself.</li><li><a " +
1638                 "href=\"https://docs.rs\">Docs.rs</a> for documentation of crates released on" +
1639                 " <a href=\"https://crates.io/\">crates.io</a>.</li></ul>";
1640         }
1641         return [output, length];
1642     }
1643
1644     function makeTabHeader(tabNb, text, nbElems) {
1645         if (searchState.currentTab === tabNb) {
1646             return "<button class=\"selected\">" + text +
1647                    " <div class=\"count\">(" + nbElems + ")</div></button>";
1648         }
1649         return "<button>" + text + " <div class=\"count\">(" + nbElems + ")</div></button>";
1650     }
1651
1652     /**
1653      * @param {ResultsTable} results
1654      * @param {boolean} go_to_first
1655      * @param {string} filterCrates
1656      */
1657     function showResults(results, go_to_first, filterCrates) {
1658         const search = searchState.outputElement();
1659         if (go_to_first || (results.others.length === 1
1660             && getSettingValue("go-to-only-result") === "true"
1661             // By default, the search DOM element is "empty" (meaning it has no children not
1662             // text content). Once a search has been run, it won't be empty, even if you press
1663             // ESC or empty the search input (which also "cancels" the search).
1664             && (!search.firstChild || search.firstChild.innerText !== searchState.loadingText))
1665         ) {
1666             const elem = document.createElement("a");
1667             elem.href = results.others[0].href;
1668             removeClass(elem, "active");
1669             // For firefox, we need the element to be in the DOM so it can be clicked.
1670             document.body.appendChild(elem);
1671             elem.click();
1672             return;
1673         }
1674         if (results.query === undefined) {
1675             results.query = parseQuery(searchState.input.value);
1676         }
1677
1678         currentResults = results.query.userQuery;
1679
1680         const ret_others = addTab(results.others, results.query, true);
1681         const ret_in_args = addTab(results.in_args, results.query, false);
1682         const ret_returned = addTab(results.returned, results.query, false);
1683
1684         // Navigate to the relevant tab if the current tab is empty, like in case users search
1685         // for "-> String". If they had selected another tab previously, they have to click on
1686         // it again.
1687         let currentTab = searchState.currentTab;
1688         if ((currentTab === 0 && ret_others[1] === 0) ||
1689                 (currentTab === 1 && ret_in_args[1] === 0) ||
1690                 (currentTab === 2 && ret_returned[1] === 0)) {
1691             if (ret_others[1] !== 0) {
1692                 currentTab = 0;
1693             } else if (ret_in_args[1] !== 0) {
1694                 currentTab = 1;
1695             } else if (ret_returned[1] !== 0) {
1696                 currentTab = 2;
1697             }
1698         }
1699
1700         let crates = "";
1701         const crates_list = Object.keys(rawSearchIndex);
1702         if (crates_list.length > 1) {
1703             crates = " in&nbsp;<div id=\"crate-search-div\"><select id=\"crate-search\">" +
1704                 "<option value=\"all crates\">all crates</option>";
1705             for (const c of crates_list) {
1706                 crates += `<option value="${c}" ${c === filterCrates && "selected"}>${c}</option>`;
1707             }
1708             crates += "</select></div>";
1709         }
1710
1711         let output = `<h1 class="search-results-title">Results${crates}</h1>`;
1712         if (results.query.error !== null) {
1713             output += `<h3>Query parser error: "${results.query.error}".</h3>`;
1714             output += "<div id=\"titles\">" +
1715                 makeTabHeader(0, "In Names", ret_others[1]) +
1716                 "</div>";
1717             currentTab = 0;
1718         } else if (results.query.foundElems <= 1 && results.query.returned.length === 0) {
1719             output += "<div id=\"titles\">" +
1720                 makeTabHeader(0, "In Names", ret_others[1]) +
1721                 makeTabHeader(1, "In Parameters", ret_in_args[1]) +
1722                 makeTabHeader(2, "In Return Types", ret_returned[1]) +
1723                 "</div>";
1724         } else {
1725             const signatureTabTitle =
1726                 results.query.elems.length === 0 ? "In Function Return Types" :
1727                 results.query.returned.length === 0 ? "In Function Parameters" :
1728                 "In Function Signatures";
1729             output += "<div id=\"titles\">" +
1730                 makeTabHeader(0, signatureTabTitle, ret_others[1]) +
1731                 "</div>";
1732             currentTab = 0;
1733         }
1734
1735         const resultsElem = document.createElement("div");
1736         resultsElem.id = "results";
1737         resultsElem.appendChild(ret_others[0]);
1738         resultsElem.appendChild(ret_in_args[0]);
1739         resultsElem.appendChild(ret_returned[0]);
1740
1741         search.innerHTML = output;
1742         const crateSearch = document.getElementById("crate-search");
1743         if (crateSearch) {
1744             crateSearch.addEventListener("input", updateCrate);
1745         }
1746         search.appendChild(resultsElem);
1747         // Reset focused elements.
1748         searchState.showResults(search);
1749         const elems = document.getElementById("titles").childNodes;
1750         searchState.focusedByTab = [];
1751         let i = 0;
1752         for (const elem of elems) {
1753             const j = i;
1754             elem.onclick = () => printTab(j);
1755             searchState.focusedByTab.push(null);
1756             i += 1;
1757         }
1758         printTab(currentTab);
1759     }
1760
1761     /**
1762      * Perform a search based on the current state of the search input element
1763      * and display the results.
1764      * @param {Event}   [e]       - The event that triggered this search, if any
1765      * @param {boolean} [forced]
1766      */
1767     function search(e, forced) {
1768         const params = searchState.getQueryStringParams();
1769         const query = parseQuery(searchState.input.value.trim());
1770
1771         if (e) {
1772             e.preventDefault();
1773         }
1774
1775         if (!forced && query.userQuery === currentResults) {
1776             if (query.userQuery.length > 0) {
1777                 putBackSearch();
1778             }
1779             return;
1780         }
1781
1782         let filterCrates = getFilterCrates();
1783
1784         // In case we have no information about the saved crate and there is a URL query parameter,
1785         // we override it with the URL query parameter.
1786         if (filterCrates === null && params["filter-crate"] !== undefined) {
1787             filterCrates = params["filter-crate"];
1788         }
1789
1790         // Update document title to maintain a meaningful browser history
1791         searchState.title = "Results for " + query.original + " - Rust";
1792
1793         // Because searching is incremental by character, only the most
1794         // recent search query is added to the browser history.
1795         if (browserSupportsHistoryApi()) {
1796             const newURL = buildUrl(query.original, filterCrates);
1797
1798             if (!history.state && !params.search) {
1799                 history.pushState(null, "", newURL);
1800             } else {
1801                 history.replaceState(null, "", newURL);
1802             }
1803         }
1804
1805         showResults(
1806             execQuery(query, searchWords, filterCrates, window.currentCrate),
1807             params.go_to_first,
1808             filterCrates);
1809     }
1810
1811     /**
1812      * Convert a list of RawFunctionType / ID to object-based FunctionType.
1813      *
1814      * Crates often have lots of functions in them, and it's common to have a large number of
1815      * functions that operate on a small set of data types, so the search index compresses them
1816      * by encoding function parameter and return types as indexes into an array of names.
1817      *
1818      * Even when a general-purpose compression algorithm is used, this is still a win. I checked.
1819      * https://github.com/rust-lang/rust/pull/98475#issue-1284395985
1820      *
1821      * The format for individual function types is encoded in
1822      * librustdoc/html/render/mod.rs: impl Serialize for RenderType
1823      *
1824      * @param {null|Array<RawFunctionType>} types
1825      * @param {Array<{name: string, ty: number}>} lowercasePaths
1826      *
1827      * @return {Array<FunctionSearchType>}
1828      */
1829     function buildItemSearchTypeAll(types, lowercasePaths) {
1830         const PATH_INDEX_DATA = 0;
1831         const GENERICS_DATA = 1;
1832         return types.map(type => {
1833             let pathIndex, generics;
1834             if (typeof type === "number") {
1835                 pathIndex = type;
1836                 generics = [];
1837             } else {
1838                 pathIndex = type[PATH_INDEX_DATA];
1839                 generics = buildItemSearchTypeAll(type[GENERICS_DATA], lowercasePaths);
1840             }
1841             return {
1842                 // `0` is used as a sentinel because it's fewer bytes than `null`
1843                 name: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].name,
1844                 ty: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].ty,
1845                 generics: generics,
1846             };
1847         });
1848     }
1849
1850     /**
1851      * Convert from RawFunctionSearchType to FunctionSearchType.
1852      *
1853      * Crates often have lots of functions in them, and function signatures are sometimes complex,
1854      * so rustdoc uses a pretty tight encoding for them. This function converts it to a simpler,
1855      * object-based encoding so that the actual search code is more readable and easier to debug.
1856      *
1857      * The raw function search type format is generated using serde in
1858      * librustdoc/html/render/mod.rs: impl Serialize for IndexItemFunctionType
1859      *
1860      * @param {RawFunctionSearchType} functionSearchType
1861      * @param {Array<{name: string, ty: number}>} lowercasePaths
1862      *
1863      * @return {null|FunctionSearchType}
1864      */
1865     function buildFunctionSearchType(functionSearchType, lowercasePaths) {
1866         const INPUTS_DATA = 0;
1867         const OUTPUT_DATA = 1;
1868         // `0` is used as a sentinel because it's fewer bytes than `null`
1869         if (functionSearchType === 0) {
1870             return null;
1871         }
1872         let inputs, output;
1873         if (typeof functionSearchType[INPUTS_DATA] === "number") {
1874             const pathIndex = functionSearchType[INPUTS_DATA];
1875             inputs = [{
1876                 name: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].name,
1877                 ty: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].ty,
1878                 generics: [],
1879             }];
1880         } else {
1881             inputs = buildItemSearchTypeAll(functionSearchType[INPUTS_DATA], lowercasePaths);
1882         }
1883         if (functionSearchType.length > 1) {
1884             if (typeof functionSearchType[OUTPUT_DATA] === "number") {
1885                 const pathIndex = functionSearchType[OUTPUT_DATA];
1886                 output = [{
1887                     name: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].name,
1888                     ty: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].ty,
1889                     generics: [],
1890                 }];
1891             } else {
1892                 output = buildItemSearchTypeAll(functionSearchType[OUTPUT_DATA], lowercasePaths);
1893             }
1894         } else {
1895             output = [];
1896         }
1897         return {
1898             inputs, output,
1899         };
1900     }
1901
1902     function buildIndex(rawSearchIndex) {
1903         searchIndex = [];
1904         /**
1905          * @type {Array<string>}
1906          */
1907         const searchWords = [];
1908         let i, word;
1909         let currentIndex = 0;
1910         let id = 0;
1911
1912         for (const crate in rawSearchIndex) {
1913             if (!hasOwnPropertyRustdoc(rawSearchIndex, crate)) {
1914                 continue;
1915             }
1916
1917             let crateSize = 0;
1918
1919             /**
1920              * The raw search data for a given crate. `n`, `t`, `d`, and `q`, `i`, and `f`
1921              * are arrays with the same length. n[i] contains the name of an item.
1922              * t[i] contains the type of that item (as a small integer that represents an
1923              * offset in `itemTypes`). d[i] contains the description of that item.
1924              *
1925              * q[i] contains the full path of the item, or an empty string indicating
1926              * "same as q[i-1]".
1927              *
1928              * i[i] contains an item's parent, usually a module. For compactness,
1929              * it is a set of indexes into the `p` array.
1930              *
1931              * f[i] contains function signatures, or `0` if the item isn't a function.
1932              * Functions are themselves encoded as arrays. The first item is a list of
1933              * types representing the function's inputs, and the second list item is a list
1934              * of types representing the function's output. Tuples are flattened.
1935              * Types are also represented as arrays; the first item is an index into the `p`
1936              * array, while the second is a list of types representing any generic parameters.
1937              *
1938              * `a` defines aliases with an Array of pairs: [name, offset], where `offset`
1939              * points into the n/t/d/q/i/f arrays.
1940              *
1941              * `doc` contains the description of the crate.
1942              *
1943              * `p` is a list of path/type pairs. It is used for parents and function parameters.
1944              *
1945              * @type {{
1946              *   doc: string,
1947              *   a: Object,
1948              *   n: Array<string>,
1949              *   t: Array<Number>,
1950              *   d: Array<string>,
1951              *   q: Array<string>,
1952              *   i: Array<Number>,
1953              *   f: Array<RawFunctionSearchType>,
1954              *   p: Array<Object>,
1955              * }}
1956              */
1957             const crateCorpus = rawSearchIndex[crate];
1958
1959             searchWords.push(crate);
1960             // This object should have exactly the same set of fields as the "row"
1961             // object defined below. Your JavaScript runtime will thank you.
1962             // https://mathiasbynens.be/notes/shapes-ics
1963             const crateRow = {
1964                 crate: crate,
1965                 ty: 1, // == ExternCrate
1966                 name: crate,
1967                 path: "",
1968                 desc: crateCorpus.doc,
1969                 parent: undefined,
1970                 type: null,
1971                 id: id,
1972                 normalizedName: crate.indexOf("_") === -1 ? crate : crate.replace(/_/g, ""),
1973             };
1974             id += 1;
1975             searchIndex.push(crateRow);
1976             currentIndex += 1;
1977
1978             // an array of (Number) item types
1979             const itemTypes = crateCorpus.t;
1980             // an array of (String) item names
1981             const itemNames = crateCorpus.n;
1982             // an array of (String) full paths (or empty string for previous path)
1983             const itemPaths = crateCorpus.q;
1984             // an array of (String) descriptions
1985             const itemDescs = crateCorpus.d;
1986             // an array of (Number) the parent path index + 1 to `paths`, or 0 if none
1987             const itemParentIdxs = crateCorpus.i;
1988             // an array of (Object | null) the type of the function, if any
1989             const itemFunctionSearchTypes = crateCorpus.f;
1990             // an array of [(Number) item type,
1991             //              (String) name]
1992             const paths = crateCorpus.p;
1993             // an array of [(String) alias name
1994             //             [Number] index to items]
1995             const aliases = crateCorpus.a;
1996
1997             // an array of [{name: String, ty: Number}]
1998             const lowercasePaths = [];
1999
2000             // convert `rawPaths` entries into object form
2001             // generate normalizedPaths for function search mode
2002             let len = paths.length;
2003             for (i = 0; i < len; ++i) {
2004                 lowercasePaths.push({ty: paths[i][0], name: paths[i][1].toLowerCase()});
2005                 paths[i] = {ty: paths[i][0], name: paths[i][1]};
2006             }
2007
2008             // convert `item*` into an object form, and construct word indices.
2009             //
2010             // before any analysis is performed lets gather the search terms to
2011             // search against apart from the rest of the data.  This is a quick
2012             // operation that is cached for the life of the page state so that
2013             // all other search operations have access to this cached data for
2014             // faster analysis operations
2015             len = itemTypes.length;
2016             let lastPath = "";
2017             for (i = 0; i < len; ++i) {
2018                 // This object should have exactly the same set of fields as the "crateRow"
2019                 // object defined above.
2020                 if (typeof itemNames[i] === "string") {
2021                     word = itemNames[i].toLowerCase();
2022                     searchWords.push(word);
2023                 } else {
2024                     word = "";
2025                     searchWords.push("");
2026                 }
2027                 const row = {
2028                     crate: crate,
2029                     ty: itemTypes[i],
2030                     name: itemNames[i],
2031                     path: itemPaths[i] ? itemPaths[i] : lastPath,
2032                     desc: itemDescs[i],
2033                     parent: itemParentIdxs[i] > 0 ? paths[itemParentIdxs[i] - 1] : undefined,
2034                     type: buildFunctionSearchType(itemFunctionSearchTypes[i], lowercasePaths),
2035                     id: id,
2036                     normalizedName: word.indexOf("_") === -1 ? word : word.replace(/_/g, ""),
2037                 };
2038                 id += 1;
2039                 searchIndex.push(row);
2040                 lastPath = row.path;
2041                 crateSize += 1;
2042             }
2043
2044             if (aliases) {
2045                 ALIASES[crate] = Object.create(null);
2046                 for (const alias_name in aliases) {
2047                     if (!hasOwnPropertyRustdoc(aliases, alias_name)) {
2048                         continue;
2049                     }
2050
2051                     if (!hasOwnPropertyRustdoc(ALIASES[crate], alias_name)) {
2052                         ALIASES[crate][alias_name] = [];
2053                     }
2054                     for (const local_alias of aliases[alias_name]) {
2055                         ALIASES[crate][alias_name].push(local_alias + currentIndex);
2056                     }
2057                 }
2058             }
2059             currentIndex += crateSize;
2060         }
2061         return searchWords;
2062     }
2063
2064     /**
2065      * Callback for when the search form is submitted.
2066      * @param {Event} [e] - The event that triggered this call, if any
2067      */
2068     function onSearchSubmit(e) {
2069         e.preventDefault();
2070         searchState.clearInputTimeout();
2071         search();
2072     }
2073
2074     function putBackSearch() {
2075         const search_input = searchState.input;
2076         if (!searchState.input) {
2077             return;
2078         }
2079         if (search_input.value !== "" && !searchState.isDisplayed()) {
2080             searchState.showResults();
2081             if (browserSupportsHistoryApi()) {
2082                 history.replaceState(null, "",
2083                     buildUrl(search_input.value, getFilterCrates()));
2084             }
2085             document.title = searchState.title;
2086         }
2087     }
2088
2089     function registerSearchEvents() {
2090         const params = searchState.getQueryStringParams();
2091
2092         // Populate search bar with query string search term when provided,
2093         // but only if the input bar is empty. This avoid the obnoxious issue
2094         // where you start trying to do a search, and the index loads, and
2095         // suddenly your search is gone!
2096         if (searchState.input.value === "") {
2097             searchState.input.value = params.search || "";
2098         }
2099
2100         const searchAfter500ms = () => {
2101             searchState.clearInputTimeout();
2102             if (searchState.input.value.length === 0) {
2103                 if (browserSupportsHistoryApi()) {
2104                     history.replaceState(null, window.currentCrate + " - Rust",
2105                         getNakedUrl() + window.location.hash);
2106                 }
2107                 searchState.hideResults();
2108             } else {
2109                 searchState.timeout = setTimeout(search, 500);
2110             }
2111         };
2112         searchState.input.onkeyup = searchAfter500ms;
2113         searchState.input.oninput = searchAfter500ms;
2114         document.getElementsByClassName("search-form")[0].onsubmit = onSearchSubmit;
2115         searchState.input.onchange = e => {
2116             if (e.target !== document.activeElement) {
2117                 // To prevent doing anything when it's from a blur event.
2118                 return;
2119             }
2120             // Do NOT e.preventDefault() here. It will prevent pasting.
2121             searchState.clearInputTimeout();
2122             // zero-timeout necessary here because at the time of event handler execution the
2123             // pasted content is not in the input field yet. Shouldn’t make any difference for
2124             // change, though.
2125             setTimeout(search, 0);
2126         };
2127         searchState.input.onpaste = searchState.input.onchange;
2128
2129         searchState.outputElement().addEventListener("keydown", e => {
2130             // We only handle unmodified keystrokes here. We don't want to interfere with,
2131             // for instance, alt-left and alt-right for history navigation.
2132             if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) {
2133                 return;
2134             }
2135             // up and down arrow select next/previous search result, or the
2136             // search box if we're already at the top.
2137             if (e.which === 38) { // up
2138                 const previous = document.activeElement.previousElementSibling;
2139                 if (previous) {
2140                     previous.focus();
2141                 } else {
2142                     searchState.focus();
2143                 }
2144                 e.preventDefault();
2145             } else if (e.which === 40) { // down
2146                 const next = document.activeElement.nextElementSibling;
2147                 if (next) {
2148                     next.focus();
2149                 }
2150                 const rect = document.activeElement.getBoundingClientRect();
2151                 if (window.innerHeight - rect.bottom < rect.height) {
2152                     window.scrollBy(0, rect.height);
2153                 }
2154                 e.preventDefault();
2155             } else if (e.which === 37) { // left
2156                 nextTab(-1);
2157                 e.preventDefault();
2158             } else if (e.which === 39) { // right
2159                 nextTab(1);
2160                 e.preventDefault();
2161             }
2162         });
2163
2164         searchState.input.addEventListener("keydown", e => {
2165             if (e.which === 40) { // down
2166                 focusSearchResult();
2167                 e.preventDefault();
2168             }
2169         });
2170
2171         searchState.input.addEventListener("focus", () => {
2172             putBackSearch();
2173         });
2174
2175         searchState.input.addEventListener("blur", () => {
2176             searchState.input.placeholder = searchState.input.origPlaceholder;
2177         });
2178
2179         // Push and pop states are used to add search results to the browser
2180         // history.
2181         if (browserSupportsHistoryApi()) {
2182             // Store the previous <title> so we can revert back to it later.
2183             const previousTitle = document.title;
2184
2185             window.addEventListener("popstate", e => {
2186                 const params = searchState.getQueryStringParams();
2187                 // Revert to the previous title manually since the History
2188                 // API ignores the title parameter.
2189                 document.title = previousTitle;
2190                 // When browsing forward to search results the previous
2191                 // search will be repeated, so the currentResults are
2192                 // cleared to ensure the search is successful.
2193                 currentResults = null;
2194                 // Synchronize search bar with query string state and
2195                 // perform the search. This will empty the bar if there's
2196                 // nothing there, which lets you really go back to a
2197                 // previous state with nothing in the bar.
2198                 if (params.search && params.search.length > 0) {
2199                     searchState.input.value = params.search;
2200                     // Some browsers fire "onpopstate" for every page load
2201                     // (Chrome), while others fire the event only when actually
2202                     // popping a state (Firefox), which is why search() is
2203                     // called both here and at the end of the startSearch()
2204                     // function.
2205                     search(e);
2206                 } else {
2207                     searchState.input.value = "";
2208                     // When browsing back from search results the main page
2209                     // visibility must be reset.
2210                     searchState.hideResults();
2211                 }
2212             });
2213         }
2214
2215         // This is required in firefox to avoid this problem: Navigating to a search result
2216         // with the keyboard, hitting enter, and then hitting back would take you back to
2217         // the doc page, rather than the search that should overlay it.
2218         // This was an interaction between the back-forward cache and our handlers
2219         // that try to sync state between the URL and the search input. To work around it,
2220         // do a small amount of re-init on page show.
2221         window.onpageshow = () => {
2222             const qSearch = searchState.getQueryStringParams().search;
2223             if (searchState.input.value === "" && qSearch) {
2224                 searchState.input.value = qSearch;
2225             }
2226             search();
2227         };
2228     }
2229
2230     function updateCrate(ev) {
2231         if (ev.target.value === "all crates") {
2232             // If we don't remove it from the URL, it'll be picked up again by the search.
2233             const params = searchState.getQueryStringParams();
2234             const query = searchState.input.value.trim();
2235             if (!history.state && !params.search) {
2236                 history.pushState(null, "", buildUrl(query, null));
2237             } else {
2238                 history.replaceState(null, "", buildUrl(query, null));
2239             }
2240         }
2241         // In case you "cut" the entry from the search input, then change the crate filter
2242         // before paste back the previous search, you get the old search results without
2243         // the filter. To prevent this, we need to remove the previous results.
2244         currentResults = null;
2245         search(undefined, true);
2246     }
2247
2248     /**
2249      *  @type {Array<string>}
2250      */
2251     const searchWords = buildIndex(rawSearchIndex);
2252     if (typeof window !== "undefined") {
2253         registerSearchEvents();
2254         // If there's a search term in the URL, execute the search now.
2255         if (window.searchState.getQueryStringParams().search) {
2256             search();
2257         }
2258     }
2259
2260     if (typeof exports !== "undefined") {
2261         exports.initSearch = initSearch;
2262         exports.execQuery = execQuery;
2263         exports.parseQuery = parseQuery;
2264     }
2265     return searchWords;
2266 }
2267
2268 if (typeof window !== "undefined") {
2269     window.initSearch = initSearch;
2270     if (window.searchIndex !== undefined) {
2271         initSearch(window.searchIndex);
2272     }
2273 } else {
2274     // Running in Node, not a browser. Run initSearch just to produce the
2275     // exports.
2276     initSearch({});
2277 }
2278
2279
2280 })();