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