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