1 /* global addClass, getNakedUrl, getSettingValue */
2 /* global onEachLazy, removeClass, searchState, browserSupportsHistoryApi, exports */
7 // This mapping table should match the discriminants of
8 // `rustdoc::formats::item_type::ItemType` type in Rust.
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
: "../";
43 function hasOwnPropertyRustdoc(obj
, property
) {
44 return Object
.prototype.hasOwnProperty
.call(obj
, property
);
47 // In the search display, allows to switch between tabs.
48 function printTab(nb
) {
50 let foundCurrentTab
= false;
51 let foundCurrentResultSet
= false;
52 onEachLazy(document
.getElementById("titles").childNodes
, elem
=> {
54 addClass(elem
, "selected");
55 foundCurrentTab
= true;
57 removeClass(elem
, "selected");
62 onEachLazy(document
.getElementById("results").childNodes
, elem
=> {
64 addClass(elem
, "active");
65 foundCurrentResultSet
= true;
67 removeClass(elem
, "active");
71 if (foundCurrentTab
&& foundCurrentResultSet
) {
72 searchState
.currentTab
= nb
;
73 } else if (nb
!== 0) {
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
85 const levenshtein_row2
= [];
86 function levenshtein(s1
, s2
) {
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
;
98 c2
= s2
.charCodeAt(i2
);
102 for (i1
= 0; i1
< s1_len
; ++i1
) {
103 c
= a
+ (s1
.charCodeAt(i1
) !== c2
? 1 : 0);
105 b
= b
< a
? (b
< c
? b
+ 1 : c
) : (a
< c
? a
+ 1 : c
);
111 return s1_len
+ s2_len
;
114 function initSearch(rawSearchIndex
) {
115 const MAX_LEV_DISTANCE
= 3;
116 const MAX_RESULTS
= 200;
117 const GENERICS_DATA
= 2;
119 const INPUTS_DATA
= 0;
120 const OUTPUT_DATA
= 1;
121 const NO_TYPE_FILTER
= -1;
127 const ALIASES
= Object
.create(null);
129 function isWhitespace(c
) {
130 return " \t\n\r".indexOf(c
) !== -1;
133 function isSpecialStartCharacter(c
) {
134 return "<\"".indexOf(c
) !== -1;
137 function isEndCharacter(c
) {
138 return ",>-".indexOf(c
) !== -1;
141 function isStopCharacter(c
) {
142 return isWhitespace(c
) || isEndCharacter(c
);
145 function isErrorCharacter(c
) {
146 return "()".indexOf(c
) !== -1;
149 function itemTypeFromName(typename
) {
150 for (let i
= 0, len
= itemTypes
.length
; i
< len
; ++i
) {
151 if (itemTypes
[i
] === typename
) {
156 throw new Error("Unknown type filter `" + typename
+ "`");
160 * If we encounter a `"`, then we try to extract the string from it until we find another `"`.
162 * This function will throw an error in the following cases:
163 * * There is already another string element.
164 * * We are parsing a generic argument.
165 * * There is more than one element.
166 * * There is no closing `"`.
168 * @param {ParsedQuery} query
169 * @param {ParserState} parserState
170 * @param {boolean} isInGenerics
172 function getStringElem(query
, parserState
, isInGenerics
) {
174 throw new Error("`\"` cannot be used in generics");
175 } else if (query
.literalSearch
) {
176 throw new Error("Cannot have more than one literal search element");
177 } else if (parserState
.totalElems
- parserState
.genericsElems
> 0) {
178 throw new Error("Cannot use literal search when there is more than one element");
180 parserState
.pos
+= 1;
181 const start
= parserState
.pos
;
182 const end
= getIdentEndPosition(parserState
);
183 if (parserState
.pos
>= parserState
.length
) {
184 throw new Error("Unclosed `\"`");
185 } else if (parserState
.userQuery
[end
] !== "\"") {
186 throw new Error(`Unexpected \`${parserState.userQuery[end]}
\` in a string element`);
187 } else if (start
=== end
) {
188 throw new Error("Cannot have empty string element");
190 // To skip the quote at the end.
191 parserState
.pos
+= 1;
192 query
.literalSearch
= true;
196 * Returns `true` if the current parser position is starting with "::".
198 * @param {ParserState} parserState
202 function isPathStart(parserState
) {
203 return parserState
.userQuery
.slice(parserState
.pos
, parserState
.pos
+ 2) === "::";
207 * Returns `true` if the current parser position is starting with "->".
209 * @param {ParserState} parserState
213 function isReturnArrow(parserState
) {
214 return parserState
.userQuery
.slice(parserState
.pos
, parserState
.pos
+ 2) === "->";
218 * Returns `true` if the given `c` character is valid for an ident.
224 function isIdentCharacter(c
) {
227 (c
>= "0" && c
<= "9") ||
228 (c
>= "a" && c
<= "z") ||
229 (c
>= "A" && c
<= "Z"));
233 * Returns `true` if the given `c` character is a separator.
239 function isSeparatorCharacter(c
) {
240 return c
=== "," || isWhitespaceCharacter(c
);
244 * Returns `true` if the given `c` character is a whitespace.
250 function isWhitespaceCharacter(c
) {
251 return c
=== " " || c
=== "\t";
255 * @param {ParsedQuery} query
256 * @param {ParserState} parserState
257 * @param {string} name - Name of the query element.
258 * @param {Array<QueryElement>} generics - List of generics of this query element.
260 * @return {QueryElement} - The newly created `QueryElement`.
262 function createQueryElement(query
, parserState
, name
, generics
, isInGenerics
) {
263 if (name
=== "*" || (name
.length
=== 0 && generics
.length
=== 0)) {
266 if (query
.literalSearch
&& parserState
.totalElems
- parserState
.genericsElems
> 0) {
267 throw new Error("You cannot have more than one element if you use quotes");
269 const pathSegments
= name
.split("::");
270 if (pathSegments
.length
> 1) {
271 for (let i
= 0, len
= pathSegments
.length
; i
< len
; ++i
) {
272 const pathSegment
= pathSegments
[i
];
274 if (pathSegment
.length
=== 0) {
276 throw new Error("Paths cannot start with `::`");
277 } else if (i
+ 1 === len
) {
278 throw new Error("Paths cannot end with `::`");
280 throw new Error("Unexpected `::::`");
284 // In case we only have something like `<p>`, there is no name.
285 if (pathSegments
.length
=== 0 || (pathSegments
.length
=== 1 && pathSegments
[0] === "")) {
286 throw new Error("Found generics without a path");
288 parserState
.totalElems
+= 1;
290 parserState
.genericsElems
+= 1;
294 fullPath
: pathSegments
,
295 pathWithoutLast
: pathSegments
.slice(0, pathSegments
.length
- 1),
296 pathLast
: pathSegments
[pathSegments
.length
- 1],
302 * This function goes through all characters until it reaches an invalid ident character or the
303 * end of the query. It returns the position of the last character of the ident.
305 * @param {ParserState} parserState
309 function getIdentEndPosition(parserState
) {
310 let end
= parserState
.pos
;
311 let foundExclamation
= false;
312 while (parserState
.pos
< parserState
.length
) {
313 const c
= parserState
.userQuery
[parserState
.pos
];
314 if (!isIdentCharacter(c
)) {
316 if (foundExclamation
) {
317 throw new Error("Cannot have more than one `!` in an ident");
318 } else if (parserState
.pos
+ 1 < parserState
.length
&&
319 isIdentCharacter(parserState
.userQuery
[parserState
.pos
+ 1])
321 throw new Error("`!` can only be at the end of an ident");
323 foundExclamation
= true;
324 } else if (isErrorCharacter(c
)) {
325 throw new Error(`Unexpected \`${c}
\``);
327 isStopCharacter(c
) ||
328 isSpecialStartCharacter(c
) ||
329 isSeparatorCharacter(c
)
332 } else if (c
=== ":") { // If we allow paths ("str::string" for example).
333 if (!isPathStart(parserState
)) {
337 parserState
.pos
+= 1;
338 foundExclamation
= false;
340 throw new Error(`Unexpected \`${c}
\``);
343 parserState
.pos
+= 1;
344 end
= parserState
.pos
;
350 * @param {ParsedQuery} query
351 * @param {ParserState} parserState
352 * @param {Array<QueryElement>} elems - This is where the new {QueryElement} will be added.
353 * @param {boolean} isInGenerics
355 function getNextElem(query
, parserState
, elems
, isInGenerics
) {
358 let start
= parserState
.pos
;
360 // We handle the strings on their own mostly to make code easier to follow.
361 if (parserState
.userQuery
[parserState
.pos
] === "\"") {
363 getStringElem(query
, parserState
, isInGenerics
);
364 end
= parserState
.pos
- 1;
366 end
= getIdentEndPosition(parserState
);
368 if (parserState
.pos
< parserState
.length
&&
369 parserState
.userQuery
[parserState
.pos
] === "<"
372 throw new Error("Unexpected `<` after `<`");
373 } else if (start
>= end
) {
374 throw new Error("Found generics without a path");
376 parserState
.pos
+= 1;
377 getItemsBefore(query
, parserState
, generics
, ">");
379 if (start
>= end
&& generics
.length
=== 0) {
386 parserState
.userQuery
.slice(start
, end
),
394 * This function parses the next query element until it finds `endChar`, calling `getNextElem`
395 * to collect each element.
397 * If there is no `endChar`, this function will implicitly stop at the end without raising an
400 * @param {ParsedQuery} query
401 * @param {ParserState} parserState
402 * @param {Array<QueryElement>} elems - This is where the new {QueryElement} will be added.
403 * @param {string} endChar - This function will stop when it'll encounter this
406 function getItemsBefore(query
, parserState
, elems
, endChar
) {
407 let foundStopChar
= true;
409 while (parserState
.pos
< parserState
.length
) {
410 const c
= parserState
.userQuery
[parserState
.pos
];
413 } else if (isSeparatorCharacter(c
)) {
414 parserState
.pos
+= 1;
415 foundStopChar
= true;
417 } else if (c
=== ":" && isPathStart(parserState
)) {
418 throw new Error("Unexpected `::`: paths cannot start with `::`");
419 } else if (c
=== ":" || isEndCharacter(c
)) {
421 if (endChar
=== ">") {
423 } else if (endChar
=== "") {
426 throw new Error("Unexpected `" + c
+ "` after " + extra
);
428 if (!foundStopChar
) {
429 if (endChar
!== "") {
430 throw new Error(`Expected \`,\`, \` \` or \`${endChar}
\`, found \`${c}
\``);
432 throw new Error(`Expected \`,\` or \` \`, found \`${c}
\``);
434 const posBefore
= parserState
.pos
;
435 getNextElem(query
, parserState
, elems
, endChar
=== ">");
436 // This case can be encountered if `getNextElem` encounted a "stop character" right from
437 // the start. For example if you have `,,` or `<>`. In this case, we simply move up the
438 // current position to continue the parsing.
439 if (posBefore
=== parserState
.pos
) {
440 parserState
.pos
+= 1;
442 foundStopChar
= false;
444 // We are either at the end of the string or on the `endChar`` character, let's move forward
446 parserState
.pos
+= 1;
450 * Checks that the type filter doesn't have unwanted characters like `<>` (which are ignored
453 * @param {ParserState} parserState
455 function checkExtraTypeFilterCharacters(parserState
) {
456 const query
= parserState
.userQuery
;
458 for (let pos
= 0; pos
< parserState
.pos
; ++pos
) {
459 if (!isIdentCharacter(query
[pos
]) && !isWhitespaceCharacter(query
[pos
])) {
460 throw new Error(`Unexpected \`${query[pos]}
\` in type filter`);
466 * Parses the provided `query` input to fill `parserState`. If it encounters an error while
467 * parsing `query`, it'll throw an error.
469 * @param {ParsedQuery} query
470 * @param {ParserState} parserState
472 function parseInput(query
, parserState
) {
474 let foundStopChar
= true;
476 while (parserState
.pos
< parserState
.length
) {
477 c
= parserState
.userQuery
[parserState
.pos
];
478 if (isStopCharacter(c
)) {
479 foundStopChar
= true;
480 if (isSeparatorCharacter(c
)) {
481 parserState
.pos
+= 1;
483 } else if (c
=== "-" || c
=== ">") {
484 if (isReturnArrow(parserState
)) {
487 throw new Error(`Unexpected \`${c}
\` (did you mean \`->\`?)`);
489 throw new Error(`Unexpected \`${c}
\``);
490 } else if (c
=== ":" && !isPathStart(parserState
)) {
491 if (parserState
.typeFilter
!== null) {
492 throw new Error("Unexpected `:`");
494 if (query
.elems
.length
=== 0) {
495 throw new Error("Expected type filter before `:`");
496 } else if (query
.elems
.length
!== 1 || parserState
.totalElems
!== 1) {
497 throw new Error("Unexpected `:`");
498 } else if (query
.literalSearch
) {
499 throw new Error("You cannot use quotes on type filter");
501 checkExtraTypeFilterCharacters(parserState
);
502 // The type filter doesn't count as an element since it's a modifier.
503 parserState
.typeFilter
= query
.elems
.pop().name
;
504 parserState
.pos
+= 1;
505 parserState
.totalElems
= 0;
506 query
.literalSearch
= false;
507 foundStopChar
= true;
510 if (!foundStopChar
) {
511 if (parserState
.typeFilter
!== null) {
512 throw new Error(`Expected \`,\`, \` \` or \`->\`, found \`${c}
\``);
514 throw new Error(`Expected \`,\`, \` \`, \`:\` or \`->\`, found \`${c}
\``);
516 before
= query
.elems
.length
;
517 getNextElem(query
, parserState
, query
.elems
, false);
518 if (query
.elems
.length
=== before
) {
519 // Nothing was added, weird... Let's increase the position to not remain stuck.
520 parserState
.pos
+= 1;
522 foundStopChar
= false;
524 while (parserState
.pos
< parserState
.length
) {
525 c
= parserState
.userQuery
[parserState
.pos
];
526 if (isReturnArrow(parserState
)) {
527 parserState
.pos
+= 2;
528 // Get returned elements.
529 getItemsBefore(query
, parserState
, query
.returned
, "");
530 // Nothing can come afterward!
531 if (query
.returned
.length
=== 0) {
532 throw new Error("Expected at least one item after `->`");
536 parserState
.pos
+= 1;
542 * Takes the user search input and returns an empty `ParsedQuery`.
544 * @param {string} userQuery
546 * @return {ParsedQuery}
548 function newParsedQuery(userQuery
) {
551 userQuery
: userQuery
.toLowerCase(),
552 typeFilter
: NO_TYPE_FILTER
,
555 // Total number of "top" elements (does not include generics).
557 literalSearch
: false,
563 * Build an URL with search parameters.
565 * @param {string} search - The current search being performed.
566 * @param {string|null} filterCrates - The current filtering crate (if any).
570 function buildUrl(search
, filterCrates
) {
571 let extra
= "?search=" + encodeURIComponent(search
);
573 if (filterCrates
!== null) {
574 extra
+= "&filter-crate=" + encodeURIComponent(filterCrates
);
576 return getNakedUrl() + extra
+ window
.location
.hash
;
580 * Return the filtering crate or `null` if there is none.
582 * @return {string|null}
584 function getFilterCrates() {
585 const elem
= document
.getElementById("crate-search");
588 elem
.value
!== "All crates" &&
589 hasOwnPropertyRustdoc(rawSearchIndex
, elem
.value
)
599 * The supported syntax by this parser is as follow:
601 * ident = *(ALPHA / DIGIT / "_") [!]
602 * path = ident *(DOUBLE-COLON ident)
603 * arg = path [generics]
604 * arg-without-generic = path
605 * type-sep = COMMA/WS *(COMMA/WS)
606 * nonempty-arg-list = *(type-sep) arg *(type-sep arg) *(type-sep)
607 * nonempty-arg-list-without-generics = *(type-sep) arg-without-generic
608 * *(type-sep arg-without-generic) *(type-sep)
609 * generics = OPEN-ANGLE-BRACKET [ nonempty-arg-list-without-generics ] *(type-sep)
610 * CLOSE-ANGLE-BRACKET/EOF
611 * return-args = RETURN-ARROW *(type-sep) nonempty-arg-list
613 * exact-search = [type-filter *WS COLON] [ RETURN-ARROW ] *WS QUOTE ident QUOTE [ generics ]
614 * type-search = [type-filter *WS COLON] [ nonempty-arg-list ] [ return-args ]
616 * query = *WS (exact-search / type-search) *WS
637 * "associatedconstant" /
646 * OPEN-ANGLE-BRACKET = "<"
647 * CLOSE-ANGLE-BRACKET = ">"
649 * DOUBLE-COLON = "::"
652 * RETURN-ARROW = "->"
654 * ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
658 * @param {string} val - The user query
660 * @return {ParsedQuery} - The parsed query
662 function parseQuery(userQuery
) {
663 userQuery
= userQuery
.trim();
664 const parserState
= {
665 length
: userQuery
.length
,
667 // Total number of elements (includes generics).
671 userQuery
: userQuery
.toLowerCase(),
673 let query
= newParsedQuery(userQuery
);
676 parseInput(query
, parserState
);
677 if (parserState
.typeFilter
!== null) {
678 let typeFilter
= parserState
.typeFilter
;
679 if (typeFilter
=== "const") {
680 typeFilter
= "constant";
682 query
.typeFilter
= itemTypeFromName(typeFilter
);
685 query
= newParsedQuery(userQuery
);
686 query
.error
= err
.message
;
687 query
.typeFilter
= -1;
691 if (!query
.literalSearch
) {
692 // If there is more than one element in the query, we switch to literalSearch in any
694 query
.literalSearch
= parserState
.totalElems
> 1;
696 query
.foundElems
= query
.elems
.length
+ query
.returned
.length
;
701 * Creates the query results.
703 * @param {Array<Result>} results_in_args
704 * @param {Array<Result>} results_returned
705 * @param {Array<Result>} results_in_args
706 * @param {ParsedQuery} parsedQuery
708 * @return {ResultsTable}
710 function createQueryResults(results_in_args
, results_returned
, results_others
, parsedQuery
) {
712 "in_args": results_in_args
,
713 "returned": results_returned
,
714 "others": results_others
,
715 "query": parsedQuery
,
720 * Executes the parsed query and builds a {ResultsTable}.
722 * @param {ParsedQuery} parsedQuery - The parsed user query
723 * @param {Object} searchWords - The list of search words to query against
724 * @param {Object} [filterCrates] - Crate to search in if defined
725 * @param {Object} [currentCrate] - Current crate, to rank results from this crate higher
727 * @return {ResultsTable}
729 function execQuery(parsedQuery
, searchWords
, filterCrates
, currentCrate
) {
730 const results_others
= {}, results_in_args
= {}, results_returned
= {};
732 function transformResults(results
) {
733 const duplicates
= {};
736 for (const result
of results
) {
737 if (result
.id
> -1) {
738 const obj
= searchIndex
[result
.id
];
739 obj
.lev
= result
.lev
;
740 const res
= buildHrefAndPath(obj
);
741 obj
.displayPath
= pathSplitter(res
[0]);
742 obj
.fullPath
= obj
.displayPath
+ obj
.name
;
743 // To be sure than it some items aren't considered as duplicate.
744 obj
.fullPath
+= "|" + obj
.ty
;
746 if (duplicates
[obj
.fullPath
]) {
749 duplicates
[obj
.fullPath
] = true;
753 if (out
.length
>= MAX_RESULTS
) {
761 function sortResults(results
, isType
, preferredCrate
) {
762 const userQuery
= parsedQuery
.userQuery
;
764 for (const entry
in results
) {
765 if (hasOwnPropertyRustdoc(results
, entry
)) {
766 const result
= results
[entry
];
767 result
.word
= searchWords
[result
.id
];
768 result
.item
= searchIndex
[result
.id
] || {};
773 // if there are no results then return to default and fail
774 if (results
.length
=== 0) {
778 results
.sort((aaa
, bbb
) => {
781 // sort by exact match with regard to the last word (mismatch goes later)
782 a
= (aaa
.word
!== userQuery
);
783 b
= (bbb
.word
!== userQuery
);
788 // Sort by non levenshtein results and then levenshtein results by the distance
789 // (less changes required to match means higher rankings)
796 // sort by crate (current crate comes first)
797 a
= (aaa
.item
.crate
!== preferredCrate
);
798 b
= (bbb
.item
.crate
!== preferredCrate
);
803 // sort by item name length (longer goes later)
810 // sort by item name (lexicographically larger goes later)
814 return (a
> b
? +1 : -1);
817 // sort by index of keyword in item name (no literal occurrence goes later)
823 // (later literal occurrence, if any, goes later)
830 // special precedence for primitive and keyword pages
831 if ((aaa
.item
.ty
=== TY_PRIMITIVE
&& bbb
.item
.ty
!== TY_KEYWORD
) ||
832 (aaa
.item
.ty
=== TY_KEYWORD
&& bbb
.item
.ty
!== TY_PRIMITIVE
)) {
835 if ((bbb
.item
.ty
=== TY_PRIMITIVE
&& aaa
.item
.ty
!== TY_PRIMITIVE
) ||
836 (bbb
.item
.ty
=== TY_KEYWORD
&& aaa
.item
.ty
!== TY_KEYWORD
)) {
840 // sort by description (no description goes later)
841 a
= (aaa
.item
.desc
=== "");
842 b
= (bbb
.item
.desc
=== "");
847 // sort by type (later occurrence in `itemTypes` goes later)
854 // sort by path (lexicographically larger goes later)
858 return (a
> b
? +1 : -1);
865 let nameSplit
= null;
866 if (parsedQuery
.elems
.length
=== 1) {
867 const hasPath
= typeof parsedQuery
.elems
[0].path
=== "undefined";
868 nameSplit
= hasPath
? null : parsedQuery
.elems
[0].path
;
871 for (const result
of results
) {
872 // this validation does not make sense when searching by types
873 if (result
.dontValidate
) {
876 const name
= result
.item
.name
.toLowerCase(),
877 path
= result
.item
.path
.toLowerCase(),
878 parent
= result
.item
.parent
;
880 if (!isType
&& !validateResult(name
, path
, nameSplit
, parent
)) {
884 return transformResults(results
);
888 * This function checks if the object (`row`) generics match the given type (`elem`)
889 * generics. If there are no generics on `row`, `defaultLev` is returned.
891 * @param {Row} row - The object to check.
892 * @param {QueryElement} elem - The element from the parsed query.
893 * @param {integer} defaultLev - This is the value to return in case there are no generics.
895 * @return {integer} - Returns the best match (if any) or `MAX_LEV_DISTANCE + 1`.
897 function checkGenerics(row
, elem
, defaultLev
) {
898 if (row
.length
<= GENERICS_DATA
|| row
[GENERICS_DATA
].length
=== 0) {
899 return elem
.generics
.length
=== 0 ? defaultLev
: MAX_LEV_DISTANCE
+ 1;
900 } else if (row
[GENERICS_DATA
].length
> 0 && row
[GENERICS_DATA
][0][NAME
] === "") {
901 if (row
.length
> GENERICS_DATA
) {
902 return checkGenerics(row
[GENERICS_DATA
][0], elem
, defaultLev
);
904 return elem
.generics
.length
=== 0 ? defaultLev
: MAX_LEV_DISTANCE
+ 1;
906 // The names match, but we need to be sure that all generics kinda
909 if (elem
.generics
.length
> 0 && row
[GENERICS_DATA
].length
>= elem
.generics
.length
) {
910 const elems
= Object
.create(null);
911 for (const entry
of row
[GENERICS_DATA
]) {
912 elem_name
= entry
[NAME
];
913 if (elem_name
=== "") {
914 // Pure generic, needs to check into it.
915 if (checkGenerics(entry
, elem
, MAX_LEV_DISTANCE
+ 1) !== 0) {
916 return MAX_LEV_DISTANCE
+ 1;
920 if (elems
[elem_name
] === undefined) {
921 elems
[elem_name
] = 0;
923 elems
[elem_name
] += 1;
925 // We need to find the type that matches the most to remove it in order
927 for (const generic
of elem
.generics
) {
929 if (elems
[generic
.name
]) {
930 match
= generic
.name
;
932 for (elem_name
in elems
) {
933 if (!hasOwnPropertyRustdoc(elems
, elem_name
)) {
936 if (elem_name
=== generic
) {
942 if (match
=== null) {
943 return MAX_LEV_DISTANCE
+ 1;
946 if (elems
[match
] === 0) {
952 return MAX_LEV_DISTANCE
+ 1;
956 * This function checks if the object (`row`) matches the given type (`elem`) and its
960 * @param {QueryElement} elem - The element from the parsed query.
962 * @return {integer} - Returns a Levenshtein distance to the best match.
964 function checkIfInGenerics(row
, elem
) {
965 let lev
= MAX_LEV_DISTANCE
+ 1;
966 for (const entry
of row
[GENERICS_DATA
]) {
967 lev
= Math
.min(checkType(entry
, elem
, true), lev
);
976 * This function checks if the object (`row`) matches the given type (`elem`) and its
980 * @param {QueryElement} elem - The element from the parsed query.
981 * @param {boolean} literalSearch
983 * @return {integer} - Returns a Levenshtein distance to the best match. If there is
984 * no match, returns `MAX_LEV_DISTANCE + 1`.
986 function checkType(row
, elem
, literalSearch
) {
987 if (row
[NAME
].length
=== 0) {
988 // This is a pure "generic" search, no need to run other checks.
989 if (row
.length
> GENERICS_DATA
) {
990 return checkIfInGenerics(row
, elem
);
992 return MAX_LEV_DISTANCE
+ 1;
995 let lev
= levenshtein(row
[NAME
], elem
.name
);
998 // The name didn't match, let's try to check if the generics do.
999 if (elem
.generics
.length
=== 0) {
1000 const checkGeneric
= (row
.length
> GENERICS_DATA
&&
1001 row
[GENERICS_DATA
].length
> 0);
1002 if (checkGeneric
&& row
[GENERICS_DATA
]
1003 .findIndex(tmp_elem
=> tmp_elem
[NAME
] === elem
.name
) !== -1) {
1007 return MAX_LEV_DISTANCE
+ 1;
1008 } else if (elem
.generics
.length
> 0) {
1009 return checkGenerics(row
, elem
, MAX_LEV_DISTANCE
+ 1);
1012 } else if (row
.length
> GENERICS_DATA
) {
1013 if (elem
.generics
.length
=== 0) {
1017 // The name didn't match so we now check if the type we're looking for is inside
1019 lev
= checkIfInGenerics(row
, elem
);
1020 // Now whatever happens, the returned distance is "less good" so we should mark
1021 // it as such, and so we add 0.5 to the distance to make it "less good".
1023 } else if (lev
> MAX_LEV_DISTANCE
) {
1024 // So our item's name doesn't match at all and has generics.
1026 // Maybe it's present in a sub generic? For example "f<A<B<C>>>()", if we're
1027 // looking for "B<C>", we'll need to go down.
1028 return checkIfInGenerics(row
, elem
);
1030 // At this point, the name kinda match and we have generics to check, so
1032 const tmp_lev
= checkGenerics(row
, elem
, lev
);
1033 if (tmp_lev
> MAX_LEV_DISTANCE
) {
1034 return MAX_LEV_DISTANCE
+ 1;
1036 // We compute the median value of both checks and return it.
1037 return (tmp_lev
+ lev
) / 2;
1039 } else if (elem
.generics
.length
> 0) {
1040 // In this case, we were expecting generics but there isn't so we simply reject this
1042 return MAX_LEV_DISTANCE
+ 1;
1044 // No generics on our query or on the target type so we can return without doing
1050 * This function checks if the object (`row`) has an argument with the given type (`elem`).
1053 * @param {QueryElement} elem - The element from the parsed query.
1054 * @param {integer} typeFilter
1056 * @return {integer} - Returns a Levenshtein distance to the best match. If there is no
1057 * match, returns `MAX_LEV_DISTANCE + 1`.
1059 function findArg(row
, elem
, typeFilter
) {
1060 let lev
= MAX_LEV_DISTANCE
+ 1;
1062 if (row
&& row
.type
&& row
.type
[INPUTS_DATA
] && row
.type
[INPUTS_DATA
].length
> 0) {
1063 for (const input
of row
.type
[INPUTS_DATA
]) {
1064 if (!typePassesFilter(typeFilter
, input
[1])) {
1067 lev
= Math
.min(lev
, checkType(input
, elem
, parsedQuery
.literalSearch
));
1073 return parsedQuery
.literalSearch
? MAX_LEV_DISTANCE
+ 1 : lev
;
1077 * This function checks if the object (`row`) returns the given type (`elem`).
1080 * @param {QueryElement} elem - The element from the parsed query.
1081 * @param {integer} typeFilter
1083 * @return {integer} - Returns a Levenshtein distance to the best match. If there is no
1084 * match, returns `MAX_LEV_DISTANCE + 1`.
1086 function checkReturned(row
, elem
, typeFilter
) {
1087 let lev
= MAX_LEV_DISTANCE
+ 1;
1089 if (row
&& row
.type
&& row
.type
.length
> OUTPUT_DATA
) {
1090 let ret
= row
.type
[OUTPUT_DATA
];
1091 if (typeof ret
[0] === "string") {
1094 for (const ret_ty
of ret
) {
1095 if (!typePassesFilter(typeFilter
, ret_ty
[1])) {
1098 lev
= Math
.min(lev
, checkType(ret_ty
, elem
, parsedQuery
.literalSearch
));
1104 return parsedQuery
.literalSearch
? MAX_LEV_DISTANCE
+ 1 : lev
;
1107 function checkPath(contains
, ty
) {
1108 if (contains
.length
=== 0) {
1111 let ret_lev
= MAX_LEV_DISTANCE
+ 1;
1112 const path
= ty
.path
.split("::");
1114 if (ty
.parent
&& ty
.parent
.name
) {
1115 path
.push(ty
.parent
.name
.toLowerCase());
1118 const length
= path
.length
;
1119 const clength
= contains
.length
;
1120 if (clength
> length
) {
1121 return MAX_LEV_DISTANCE
+ 1;
1123 for (let i
= 0; i
< length
; ++i
) {
1124 if (i
+ clength
> length
) {
1128 let aborted
= false;
1129 for (let x
= 0; x
< clength
; ++x
) {
1130 const lev
= levenshtein(path
[i
+ x
], contains
[x
]);
1131 if (lev
> MAX_LEV_DISTANCE
) {
1138 ret_lev
= Math
.min(ret_lev
, Math
.round(lev_total
/ clength
));
1144 function typePassesFilter(filter
, type
) {
1145 // No filter or Exact mach
1146 if (filter
<= NO_TYPE_FILTER
|| filter
=== type
) return true;
1148 // Match related items
1149 const name
= itemTypes
[type
];
1150 switch (itemTypes
[filter
]) {
1152 return name
=== "associatedconstant";
1154 return name
=== "method" || name
=== "tymethod";
1156 return name
=== "primitive" || name
=== "associatedtype";
1158 return name
=== "traitalias";
1165 function createAliasFromItem(item
) {
1172 parent
: item
.parent
,
1178 function handleAliases(ret
, query
, filterCrates
, currentCrate
) {
1179 const lowerQuery
= query
.toLowerCase();
1180 // We separate aliases and crate aliases because we want to have current crate
1181 // aliases to be before the others in the displayed results.
1183 const crateAliases
= [];
1184 if (filterCrates
!== null) {
1185 if (ALIASES
[filterCrates
] && ALIASES
[filterCrates
][lowerQuery
]) {
1186 const query_aliases
= ALIASES
[filterCrates
][lowerQuery
];
1187 for (const alias
of query_aliases
) {
1188 aliases
.push(createAliasFromItem(searchIndex
[alias
]));
1192 Object
.keys(ALIASES
).forEach(crate
=> {
1193 if (ALIASES
[crate
][lowerQuery
]) {
1194 const pushTo
= crate
=== currentCrate
? crateAliases
: aliases
;
1195 const query_aliases
= ALIASES
[crate
][lowerQuery
];
1196 for (const alias
of query_aliases
) {
1197 pushTo
.push(createAliasFromItem(searchIndex
[alias
]));
1203 const sortFunc
= (aaa
, bbb
) => {
1204 if (aaa
.path
< bbb
.path
) {
1206 } else if (aaa
.path
=== bbb
.path
) {
1211 crateAliases
.sort(sortFunc
);
1212 aliases
.sort(sortFunc
);
1214 const pushFunc
= alias
=> {
1215 alias
.alias
= query
;
1216 const res
= buildHrefAndPath(alias
);
1217 alias
.displayPath
= pathSplitter(res
[0]);
1218 alias
.fullPath
= alias
.displayPath
+ alias
.name
;
1219 alias
.href
= res
[1];
1221 ret
.others
.unshift(alias
);
1222 if (ret
.others
.length
> MAX_RESULTS
) {
1227 aliases
.forEach(pushFunc
);
1228 crateAliases
.forEach(pushFunc
);
1232 * This function adds the given result into the provided `results` map if it matches the
1233 * following condition:
1235 * * If it is a "literal search" (`parsedQuery.literalSearch`), then `lev` must be 0.
1236 * * If it is not a "literal search", `lev` must be <= `MAX_LEV_DISTANCE`.
1238 * The `results` map contains information which will be used to sort the search results:
1240 * * `fullId` is a `string`` used as the key of the object we use for the `results` map.
1241 * * `id` is the index in both `searchWords` and `searchIndex` arrays for this element.
1242 * * `index` is an `integer`` used to sort by the position of the word in the item's name.
1243 * * `lev` is the main metric used to sort the search results.
1245 * @param {Results} results
1246 * @param {string} fullId
1247 * @param {integer} id
1248 * @param {integer} index
1249 * @param {integer} lev
1251 function addIntoResults(results
, fullId
, id
, index
, lev
) {
1252 if (lev
=== 0 || (!parsedQuery
.literalSearch
&& lev
<= MAX_LEV_DISTANCE
)) {
1253 if (results
[fullId
] !== undefined) {
1254 const result
= results
[fullId
];
1255 if (result
.dontValidate
|| result
.lev
<= lev
) {
1262 dontValidate
: parsedQuery
.literalSearch
,
1269 * This function is called in case the query is only one element (with or without generics).
1270 * This element will be compared to arguments' and returned values' items and also to items.
1272 * Other important thing to note: since there is only one element, we use levenshtein
1273 * distance for name comparisons.
1276 * @param {integer} pos - Position in the `searchIndex`.
1277 * @param {QueryElement} elem - The element from the parsed query.
1278 * @param {Results} results_others - Unqualified results (not in arguments nor in
1280 * @param {Results} results_in_args - Matching arguments results.
1281 * @param {Results} results_returned - Matching returned arguments results.
1283 function handleSingleArg(
1291 if (!row
|| (filterCrates
!== null && row
.crate
!== filterCrates
)) {
1294 let lev
, lev_add
= 0, index
= -1;
1295 const fullId
= row
.id
;
1297 const in_args
= findArg(row
, elem
, parsedQuery
.typeFilter
);
1298 const returned
= checkReturned(row
, elem
, parsedQuery
.typeFilter
);
1300 addIntoResults(results_in_args
, fullId
, pos
, index
, in_args
);
1301 addIntoResults(results_returned
, fullId
, pos
, index
, returned
);
1303 if (!typePassesFilter(parsedQuery
.typeFilter
, row
.ty
)) {
1306 const searchWord
= searchWords
[pos
];
1308 if (parsedQuery
.literalSearch
) {
1309 if (searchWord
=== elem
.name
) {
1310 addIntoResults(results_others
, fullId
, pos
, -1, 0);
1315 // No need to check anything else if it's a "pure" generics search.
1316 if (elem
.name
.length
=== 0) {
1317 if (row
.type
!== null) {
1318 lev
= checkGenerics(row
.type
, elem
, MAX_LEV_DISTANCE
+ 1);
1319 addIntoResults(results_others
, fullId
, pos
, index
, lev
);
1324 if (elem
.fullPath
.length
> 1) {
1325 lev
= checkPath(elem
.pathWithoutLast
, row
);
1326 if (lev
> MAX_LEV_DISTANCE
|| (parsedQuery
.literalSearch
&& lev
!== 0)) {
1328 } else if (lev
> 0) {
1333 if (searchWord
.indexOf(elem
.pathLast
) > -1 ||
1334 row
.normalizedName
.indexOf(elem
.pathLast
) > -1
1336 index
= row
.normalizedName
.indexOf(elem
.pathLast
);
1338 lev
= levenshtein(searchWord
, elem
.pathLast
);
1339 if (lev
> 0 && elem
.pathLast
.length
> 2 && searchWord
.indexOf(elem
.pathLast
) > -1) {
1340 if (elem
.pathLast
.length
< 6) {
1347 if (lev
> MAX_LEV_DISTANCE
) {
1349 } else if (index
!== -1 && elem
.fullPath
.length
< 2) {
1355 addIntoResults(results_others
, fullId
, pos
, index
, lev
);
1359 * This function is called in case the query has more than one element. In this case, it'll
1360 * try to match the items which validates all the elements. For `aa -> bb` will look for
1361 * functions which have a parameter `aa` and has `bb` in its returned values.
1364 * @param {integer} pos - Position in the `searchIndex`.
1365 * @param {Object} results
1367 function handleArgs(row
, pos
, results
) {
1368 if (!row
|| (filterCrates
!== null && row
.crate
!== filterCrates
)) {
1375 // If the result is too "bad", we return false and it ends this search.
1376 function checkArgs(elems
, callback
) {
1377 for (const elem
of elems
) {
1378 // There is more than one parameter to the query so all checks should be "exact"
1379 const lev
= callback(row
, elem
, NO_TYPE_FILTER
);
1389 if (!checkArgs(parsedQuery
.elems
, findArg
)) {
1392 if (!checkArgs(parsedQuery
.returned
, checkReturned
)) {
1399 const lev
= Math
.round(totalLev
/ nbLev
);
1400 addIntoResults(results
, row
.id
, pos
, 0, lev
);
1403 function innerRunQuery() {
1404 let elem
, i
, nSearchWords
, in_returned
, row
;
1406 if (parsedQuery
.foundElems
=== 1) {
1407 if (parsedQuery
.elems
.length
=== 1) {
1408 elem
= parsedQuery
.elems
[0];
1409 for (i
= 0, nSearchWords
= searchWords
.length
; i
< nSearchWords
; ++i
) {
1410 // It means we want to check for this element everywhere (in names, args and
1421 } else if (parsedQuery
.returned
.length
=== 1) {
1422 // We received one returned argument to check, so looking into returned values.
1423 elem
= parsedQuery
.returned
[0];
1424 for (i
= 0, nSearchWords
= searchWords
.length
; i
< nSearchWords
; ++i
) {
1425 row
= searchIndex
[i
];
1426 in_returned
= checkReturned(row
, elem
, parsedQuery
.typeFilter
);
1427 addIntoResults(results_others
, row
.id
, i
, -1, in_returned
);
1430 } else if (parsedQuery
.foundElems
> 0) {
1431 for (i
= 0, nSearchWords
= searchWords
.length
; i
< nSearchWords
; ++i
) {
1432 handleArgs(searchIndex
[i
], i
, results_others
);
1437 if (parsedQuery
.error
=== null) {
1441 const ret
= createQueryResults(
1442 sortResults(results_in_args
, true, currentCrate
),
1443 sortResults(results_returned
, true, currentCrate
),
1444 sortResults(results_others
, false, currentCrate
),
1446 handleAliases(ret
, parsedQuery
.original
.replace(/"/g, ""), filterCrates, currentCrate);
1447 if (parsedQuery.error !== null && ret.others.length !== 0) {
1448 // It means some doc aliases were found so let's "remove
" the error!
1449 ret.query.error = null;
1455 * Validate performs the following boolean logic. For example:
1456 * "File
::open
" will give IF A PARENT EXISTS => ("file
" && "open
")
1457 * exists in (name || path || parent) OR => ("file
" && "open
") exists in
1460 * This could be written functionally, but I wanted to minimise
1461 * functions on stack.
1463 * @param {string} name - The name of the result
1464 * @param {string} path - The path of the result
1465 * @param {string} keys - The keys to be used (["file
", "open
"])
1466 * @param {Object} parent - The parent of the result
1468 * @return {boolean} - Whether the result is valid or not
1470 function validateResult(name, path, keys, parent) {
1471 if (!keys || !keys.length) {
1474 for (const key of keys) {
1475 // each check is for validation so we negate the conditions and invalidate
1477 // check for an exact name match
1478 name.indexOf(key) > -1 ||
1479 // then an exact path match
1480 path.indexOf(key) > -1 ||
1481 // next if there is a parent, check for exact parent match
1482 (parent !== undefined && parent.name !== undefined &&
1483 parent.name.toLowerCase().indexOf(key) > -1) ||
1484 // lastly check to see if the name was a levenshtein match
1485 levenshtein(name, key) <= MAX_LEV_DISTANCE)) {
1492 function nextTab(direction) {
1493 const next = (searchState.currentTab + direction + 3) % searchState.focusedByTab.length;
1494 searchState.focusedByTab[searchState.currentTab] = document.activeElement;
1496 focusSearchResult();
1499 // Focus the first search result on the active tab, or the result that
1500 // was focused last time this tab was active.
1501 function focusSearchResult() {
1502 const target = searchState.focusedByTab[searchState.currentTab] ||
1503 document.querySelectorAll(".search
-results
.active a
").item(0) ||
1504 document.querySelectorAll("#titles
> button
").item(searchState.currentTab);
1510 function buildHrefAndPath(item) {
1513 const type = itemTypes[item.ty];
1514 const name = item.name;
1515 let path = item.path;
1517 if (type === "mod
") {
1518 displayPath = path + "::";
1519 href = ROOT_PATH + path.replace(/::/g, "/") + "/" +
1520 name + "/index
.html
";
1521 } else if (type === "import") {
1522 displayPath = item.path + "::";
1523 href = ROOT_PATH + item.path.replace(/::/g, "/") + "/index.html
#reexport
." + name;
1524 } else if (type === "primitive
" || type === "keyword
") {
1526 href = ROOT_PATH + path.replace(/::/g, "/") +
1527 "/" + type + "." + name + ".html
";
1528 } else if (type === "externcrate
") {
1530 href = ROOT_PATH + name + "/index
.html
";
1531 } else if (item.parent !== undefined) {
1532 const myparent = item.parent;
1533 let anchor = "#" + type + "." + name;
1534 const parentType = itemTypes[myparent.ty];
1535 let pageType = parentType;
1536 let pageName = myparent.name;
1538 if (parentType === "primitive
") {
1539 displayPath = myparent.name + "::";
1540 } else if (type === "structfield
" && parentType === "variant
") {
1541 // Structfields belonging to variants are special: the
1542 // final path element is the enum name.
1543 const enumNameIdx = item.path.lastIndexOf("::");
1544 const enumName = item.path.substr(enumNameIdx + 2);
1545 path = item.path.substr(0, enumNameIdx);
1546 displayPath = path + "::" + enumName + "::" + myparent.name + "::";
1547 anchor = "#variant
." + myparent.name + ".field
." + name;
1549 pageName = enumName;
1551 displayPath = path + "::" + myparent.name + "::";
1553 href = ROOT_PATH + path.replace(/::/g, "/") +
1558 displayPath = item.path + "::";
1559 href = ROOT_PATH + item.path.replace(/::/g, "/") +
1560 "/" + type + "." + name + ".html
";
1562 return [displayPath, href];
1565 function escape(content) {
1566 const h1 = document.createElement("h1
");
1567 h1.textContent = content;
1568 return h1.innerHTML;
1571 function pathSplitter(path) {
1572 const tmp = "<span
>" + path.replace(/::/g, "::</span
><span
>");
1573 if (tmp.endsWith("<span
>")) {
1574 return tmp.slice(0, tmp.length - 6);
1580 * Render a set of search results for a single tab.
1581 * @param {Array<?>} array - The search results for this tab
1582 * @param {ParsedQuery} query
1583 * @param {boolean} display - True if this is the active tab
1585 function addTab(array, query, display) {
1586 let extraClass = "";
1587 if (display === true) {
1588 extraClass = " active
";
1591 const output = document.createElement("div
");
1593 if (array.length > 0) {
1594 output.className = "search
-results
" + extraClass;
1596 array.forEach(item => {
1597 const name = item.name;
1598 const type = itemTypes[item.ty];
1603 if (type === "primitive
") {
1604 extra = " <i
>(primitive type
)</i
>";
1605 } else if (type === "keyword
") {
1606 extra = " <i
>(keyword
)</i
>";
1609 const link = document.createElement("a
");
1610 link.className = "result
-" + type;
1611 link.href = item.href;
1613 const wrapper = document.createElement("div
");
1614 const resultName = document.createElement("div
");
1615 resultName.className = "result
-name
";
1617 if (item.is_alias) {
1618 const alias = document.createElement("span
");
1619 alias.className = "alias
";
1621 const bold = document.createElement("b
");
1622 bold.innerText = item.alias;
1623 alias.appendChild(bold);
1625 alias.insertAdjacentHTML(
1627 "<span
class=\"grey
\"><i
> 
;- see
 
;</i></span>");
1629 resultName.appendChild(alias);
1631 resultName.insertAdjacentHTML(
1633 item.displayPath + "<span
class=\"" + type + "\">" + name + extra + "</span
>");
1634 wrapper.appendChild(resultName);
1636 const description = document.createElement("div
");
1637 description.className = "desc
";
1638 const spanDesc = document.createElement("span
");
1639 spanDesc.insertAdjacentHTML("beforeend
", item.desc);
1641 description.appendChild(spanDesc);
1642 wrapper.appendChild(description);
1643 link.appendChild(wrapper);
1644 output.appendChild(link);
1646 } else if (query.error === null) {
1647 output.className = "search
-failed
" + extraClass;
1648 output.innerHTML = "No results
:(<br
/>" +
1649 "Try on
<a href
=\"https
://duckduckgo.com/?q=" +
1650 encodeURIComponent("rust " + query
.userQuery
) +
1651 "\">DuckDuckGo</a>?<br/><br/>" +
1652 "Or try looking in one of these:<ul><li>The <a " +
1653 "href=\"https://doc.rust-lang.org/reference/index.html\">Rust Reference</a> " +
1654 " for technical details about the language.</li><li><a " +
1655 "href=\"https://doc.rust-lang.org/rust-by-example/index.html\">Rust By " +
1656 "Example</a> for expository code examples.</a></li><li>The <a " +
1657 "href=\"https://doc.rust-lang.org/book/index.html\">Rust Book</a> for " +
1658 "introductions to language features and the language itself.</li><li><a " +
1659 "href=\"https://docs.rs\">Docs.rs</a> for documentation of crates released on" +
1660 " <a href=\"https://crates.io/\">crates.io</a>.</li></ul>";
1662 return [output
, length
];
1665 function makeTabHeader(tabNb
, text
, nbElems
) {
1666 if (searchState
.currentTab
=== tabNb
) {
1667 return "<button class=\"selected\">" + text
+
1668 " <div class=\"count\">(" + nbElems
+ ")</div></button>";
1670 return "<button>" + text
+ " <div class=\"count\">(" + nbElems
+ ")</div></button>";
1674 * @param {ResultsTable} results
1675 * @param {boolean} go_to_first
1676 * @param {string} filterCrates
1678 function showResults(results
, go_to_first
, filterCrates
) {
1679 const search
= searchState
.outputElement();
1680 if (go_to_first
|| (results
.others
.length
=== 1
1681 && getSettingValue("go-to-only-result") === "true"
1682 // By default, the search DOM element is "empty" (meaning it has no children not
1683 // text content). Once a search has been run, it won't be empty, even if you press
1684 // ESC or empty the search input (which also "cancels" the search).
1685 && (!search
.firstChild
|| search
.firstChild
.innerText
!== searchState
.loadingText
))
1687 const elem
= document
.createElement("a");
1688 elem
.href
= results
.others
[0].href
;
1689 removeClass(elem
, "active");
1690 // For firefox, we need the element to be in the DOM so it can be clicked.
1691 document
.body
.appendChild(elem
);
1695 if (results
.query
=== undefined) {
1696 results
.query
= parseQuery(searchState
.input
.value
);
1699 currentResults
= results
.query
.userQuery
;
1701 const ret_others
= addTab(results
.others
, results
.query
, true);
1702 const ret_in_args
= addTab(results
.in_args
, results
.query
, false);
1703 const ret_returned
= addTab(results
.returned
, results
.query
, false);
1705 // Navigate to the relevant tab if the current tab is empty, like in case users search
1706 // for "-> String". If they had selected another tab previously, they have to click on
1708 let currentTab
= searchState
.currentTab
;
1709 if ((currentTab
=== 0 && ret_others
[1] === 0) ||
1710 (currentTab
=== 1 && ret_in_args
[1] === 0) ||
1711 (currentTab
=== 2 && ret_returned
[1] === 0)) {
1712 if (ret_others
[1] !== 0) {
1714 } else if (ret_in_args
[1] !== 0) {
1716 } else if (ret_returned
[1] !== 0) {
1722 const crates_list
= Object
.keys(rawSearchIndex
);
1723 if (crates_list
.length
> 1) {
1724 crates
= " in <select id=\"crate-search\"><option value=\"All crates\">" +
1725 "All crates</option>";
1726 for (const c
of crates_list
) {
1727 crates
+= `<option value="${c}" ${c === filterCrates && "selected"}>${c}</option>`;
1729 crates
+= "</select>";
1732 let typeFilter
= "";
1733 if (results
.query
.typeFilter
!== NO_TYPE_FILTER
) {
1734 typeFilter
= " (type: " + escape(itemTypes
[results
.query
.typeFilter
]) + ")";
1737 let output
= "<div id=\"search-settings\">" +
1738 `<h1 class="search-results-title">Results for ${escape(results.query.userQuery)}` +
1739 `${typeFilter}</h1>${crates}</div>`;
1740 if (results
.query
.error
!== null) {
1741 output
+= `<h3>Query parser error: "${results.query.error}".</h3>`;
1742 output
+= "<div id=\"titles\">" +
1743 makeTabHeader(0, "In Names", ret_others
[1]) +
1746 } else if (results
.query
.foundElems
<= 1 && results
.query
.returned
.length
=== 0) {
1747 output
+= "<div id=\"titles\">" +
1748 makeTabHeader(0, "In Names", ret_others
[1]) +
1749 makeTabHeader(1, "In Parameters", ret_in_args
[1]) +
1750 makeTabHeader(2, "In Return Types", ret_returned
[1]) +
1753 const signatureTabTitle
=
1754 results
.query
.elems
.length
=== 0 ? "In Function Return Types" :
1755 results
.query
.returned
.length
=== 0 ? "In Function Parameters" :
1756 "In Function Signatures";
1757 output
+= "<div id=\"titles\">" +
1758 makeTabHeader(0, signatureTabTitle
, ret_others
[1]) +
1763 const resultsElem
= document
.createElement("div");
1764 resultsElem
.id
= "results";
1765 resultsElem
.appendChild(ret_others
[0]);
1766 resultsElem
.appendChild(ret_in_args
[0]);
1767 resultsElem
.appendChild(ret_returned
[0]);
1769 search
.innerHTML
= output
;
1770 const crateSearch
= document
.getElementById("crate-search");
1772 crateSearch
.addEventListener("input", updateCrate
);
1774 search
.appendChild(resultsElem
);
1775 // Reset focused elements.
1776 searchState
.showResults(search
);
1777 const elems
= document
.getElementById("titles").childNodes
;
1778 searchState
.focusedByTab
= [];
1780 for (const elem
of elems
) {
1782 elem
.onclick
= () => printTab(j
);
1783 searchState
.focusedByTab
.push(null);
1786 printTab(currentTab
);
1790 * Perform a search based on the current state of the search input element
1791 * and display the results.
1792 * @param {Event} [e] - The event that triggered this search, if any
1793 * @param {boolean} [forced]
1795 function search(e
, forced
) {
1796 const params
= searchState
.getQueryStringParams();
1797 const query
= parseQuery(searchState
.input
.value
.trim());
1803 if (!forced
&& query
.userQuery
=== currentResults
) {
1804 if (query
.userQuery
.length
> 0) {
1810 let filterCrates
= getFilterCrates();
1812 // In case we have no information about the saved crate and there is a URL query parameter,
1813 // we override it with the URL query parameter.
1814 if (filterCrates
=== null && params
["filter-crate"] !== undefined) {
1815 filterCrates
= params
["filter-crate"];
1818 // Update document title to maintain a meaningful browser history
1819 searchState
.title
= "Results for " + query
.original
+ " - Rust";
1821 // Because searching is incremental by character, only the most
1822 // recent search query is added to the browser history.
1823 if (browserSupportsHistoryApi()) {
1824 const newURL
= buildUrl(query
.original
, filterCrates
);
1826 if (!history
.state
&& !params
.search
) {
1827 history
.pushState(null, "", newURL
);
1829 history
.replaceState(null, "", newURL
);
1834 execQuery(query
, searchWords
, filterCrates
, window
.currentCrate
),
1839 function buildIndex(rawSearchIndex
) {
1842 * @type {Array<string>}
1844 const searchWords
= [];
1846 let currentIndex
= 0;
1849 for (const crate
in rawSearchIndex
) {
1850 if (!hasOwnPropertyRustdoc(rawSearchIndex
, crate
)) {
1857 * The raw search data for a given crate. `n`, `t`, `d`, and `q`, `i`, and `f`
1858 * are arrays with the same length. n[i] contains the name of an item.
1859 * t[i] contains the type of that item (as a small integer that represents an
1860 * offset in `itemTypes`). d[i] contains the description of that item.
1862 * q[i] contains the full path of the item, or an empty string indicating
1865 * i[i], f[i] are a mystery.
1867 * `a` defines aliases with an Array of pairs: [name, offset], where `offset`
1868 * points into the n/t/d/q/i/f arrays.
1870 * `doc` contains the description of the crate.
1872 * `p` is a mystery and isn't the same length as n/t/d/q/i/f.
1882 * f: Array<Array<?>>,
1886 const crateCorpus
= rawSearchIndex
[crate
];
1888 searchWords
.push(crate
);
1889 // This object should have exactly the same set of fields as the "row"
1890 // object defined below. Your JavaScript runtime will thank you.
1891 // https://mathiasbynens.be/notes/shapes-ics
1894 ty
: 1, // == ExternCrate
1897 desc
: crateCorpus
.doc
,
1901 normalizedName
: crate
.indexOf("_") === -1 ? crate
: crate
.replace(/_
/g
, ""),
1904 searchIndex
.push(crateRow
);
1907 // an array of (Number) item types
1908 const itemTypes
= crateCorpus
.t
;
1909 // an array of (String) item names
1910 const itemNames
= crateCorpus
.n
;
1911 // an array of (String) full paths (or empty string for previous path)
1912 const itemPaths
= crateCorpus
.q
;
1913 // an array of (String) descriptions
1914 const itemDescs
= crateCorpus
.d
;
1915 // an array of (Number) the parent path index + 1 to `paths`, or 0 if none
1916 const itemParentIdxs
= crateCorpus
.i
;
1917 // an array of (Object | null) the type of the function, if any
1918 const itemFunctionSearchTypes
= crateCorpus
.f
;
1919 // an array of [(Number) item type,
1921 const paths
= crateCorpus
.p
;
1922 // an array of [(String) alias name
1923 // [Number] index to items]
1924 const aliases
= crateCorpus
.a
;
1926 // convert `rawPaths` entries into object form
1927 let len
= paths
.length
;
1928 for (i
= 0; i
< len
; ++i
) {
1929 paths
[i
] = {ty
: paths
[i
][0], name
: paths
[i
][1]};
1932 // convert `item*` into an object form, and construct word indices.
1934 // before any analysis is performed lets gather the search terms to
1935 // search against apart from the rest of the data. This is a quick
1936 // operation that is cached for the life of the page state so that
1937 // all other search operations have access to this cached data for
1938 // faster analysis operations
1939 len
= itemTypes
.length
;
1941 for (i
= 0; i
< len
; ++i
) {
1942 // This object should have exactly the same set of fields as the "crateRow"
1943 // object defined above.
1944 if (typeof itemNames
[i
] === "string") {
1945 word
= itemNames
[i
].toLowerCase();
1946 searchWords
.push(word
);
1949 searchWords
.push("");
1955 path
: itemPaths
[i
] ? itemPaths
[i
] : lastPath
,
1957 parent
: itemParentIdxs
[i
] > 0 ? paths
[itemParentIdxs
[i
] - 1] : undefined,
1958 type
: itemFunctionSearchTypes
[i
],
1960 normalizedName
: word
.indexOf("_") === -1 ? word
: word
.replace(/_
/g
, ""),
1963 searchIndex
.push(row
);
1964 lastPath
= row
.path
;
1969 ALIASES
[crate
] = Object
.create(null);
1970 for (const alias_name
in aliases
) {
1971 if (!hasOwnPropertyRustdoc(aliases
, alias_name
)) {
1975 if (!hasOwnPropertyRustdoc(ALIASES
[crate
], alias_name
)) {
1976 ALIASES
[crate
][alias_name
] = [];
1978 for (const local_alias
of aliases
[alias_name
]) {
1979 ALIASES
[crate
][alias_name
].push(local_alias
+ currentIndex
);
1983 currentIndex
+= crateSize
;
1989 * Callback for when the search form is submitted.
1990 * @param {Event} [e] - The event that triggered this call, if any
1992 function onSearchSubmit(e
) {
1994 searchState
.clearInputTimeout();
1998 function putBackSearch() {
1999 const search_input
= searchState
.input
;
2000 if (!searchState
.input
) {
2003 if (search_input
.value
!== "" && !searchState
.isDisplayed()) {
2004 searchState
.showResults();
2005 if (browserSupportsHistoryApi()) {
2006 history
.replaceState(null, "",
2007 buildUrl(search_input
.value
, getFilterCrates()));
2009 document
.title
= searchState
.title
;
2013 function registerSearchEvents() {
2014 const params
= searchState
.getQueryStringParams();
2016 // Populate search bar with query string search term when provided,
2017 // but only if the input bar is empty. This avoid the obnoxious issue
2018 // where you start trying to do a search, and the index loads, and
2019 // suddenly your search is gone!
2020 if (searchState
.input
.value
=== "") {
2021 searchState
.input
.value
= params
.search
|| "";
2024 const searchAfter500ms
= () => {
2025 searchState
.clearInputTimeout();
2026 if (searchState
.input
.value
.length
=== 0) {
2027 if (browserSupportsHistoryApi()) {
2028 history
.replaceState(null, window
.currentCrate
+ " - Rust",
2029 getNakedUrl() + window
.location
.hash
);
2031 searchState
.hideResults();
2033 searchState
.timeout
= setTimeout(search
, 500);
2036 searchState
.input
.onkeyup
= searchAfter500ms
;
2037 searchState
.input
.oninput
= searchAfter500ms
;
2038 document
.getElementsByClassName("search-form")[0].onsubmit
= onSearchSubmit
;
2039 searchState
.input
.onchange
= e
=> {
2040 if (e
.target
!== document
.activeElement
) {
2041 // To prevent doing anything when it's from a blur event.
2044 // Do NOT e.preventDefault() here. It will prevent pasting.
2045 searchState
.clearInputTimeout();
2046 // zero-timeout necessary here because at the time of event handler execution the
2047 // pasted content is not in the input field yet. Shouldn’t make any difference for
2049 setTimeout(search
, 0);
2051 searchState
.input
.onpaste
= searchState
.input
.onchange
;
2053 searchState
.outputElement().addEventListener("keydown", e
=> {
2054 // We only handle unmodified keystrokes here. We don't want to interfere with,
2055 // for instance, alt-left and alt-right for history navigation.
2056 if (e
.altKey
|| e
.ctrlKey
|| e
.shiftKey
|| e
.metaKey
) {
2059 // up and down arrow select next/previous search result, or the
2060 // search box if we're already at the top.
2061 if (e
.which
=== 38) { // up
2062 const previous
= document
.activeElement
.previousElementSibling
;
2066 searchState
.focus();
2069 } else if (e
.which
=== 40) { // down
2070 const next
= document
.activeElement
.nextElementSibling
;
2074 const rect
= document
.activeElement
.getBoundingClientRect();
2075 if (window
.innerHeight
- rect
.bottom
< rect
.height
) {
2076 window
.scrollBy(0, rect
.height
);
2079 } else if (e
.which
=== 37) { // left
2082 } else if (e
.which
=== 39) { // right
2088 searchState
.input
.addEventListener("keydown", e
=> {
2089 if (e
.which
=== 40) { // down
2090 focusSearchResult();
2095 searchState
.input
.addEventListener("focus", () => {
2099 searchState
.input
.addEventListener("blur", () => {
2100 searchState
.input
.placeholder
= searchState
.input
.origPlaceholder
;
2103 // Push and pop states are used to add search results to the browser
2105 if (browserSupportsHistoryApi()) {
2106 // Store the previous <title> so we can revert back to it later.
2107 const previousTitle
= document
.title
;
2109 window
.addEventListener("popstate", e
=> {
2110 const params
= searchState
.getQueryStringParams();
2111 // Revert to the previous title manually since the History
2112 // API ignores the title parameter.
2113 document
.title
= previousTitle
;
2114 // When browsing forward to search results the previous
2115 // search will be repeated, so the currentResults are
2116 // cleared to ensure the search is successful.
2117 currentResults
= null;
2118 // Synchronize search bar with query string state and
2119 // perform the search. This will empty the bar if there's
2120 // nothing there, which lets you really go back to a
2121 // previous state with nothing in the bar.
2122 if (params
.search
&& params
.search
.length
> 0) {
2123 searchState
.input
.value
= params
.search
;
2124 // Some browsers fire "onpopstate" for every page load
2125 // (Chrome), while others fire the event only when actually
2126 // popping a state (Firefox), which is why search() is
2127 // called both here and at the end of the startSearch()
2131 searchState
.input
.value
= "";
2132 // When browsing back from search results the main page
2133 // visibility must be reset.
2134 searchState
.hideResults();
2139 // This is required in firefox to avoid this problem: Navigating to a search result
2140 // with the keyboard, hitting enter, and then hitting back would take you back to
2141 // the doc page, rather than the search that should overlay it.
2142 // This was an interaction between the back-forward cache and our handlers
2143 // that try to sync state between the URL and the search input. To work around it,
2144 // do a small amount of re-init on page show.
2145 window
.onpageshow
= () => {
2146 const qSearch
= searchState
.getQueryStringParams().search
;
2147 if (searchState
.input
.value
=== "" && qSearch
) {
2148 searchState
.input
.value
= qSearch
;
2154 function updateCrate(ev
) {
2155 if (ev
.target
.value
=== "All crates") {
2156 // If we don't remove it from the URL, it'll be picked up again by the search.
2157 const params
= searchState
.getQueryStringParams();
2158 const query
= searchState
.input
.value
.trim();
2159 if (!history
.state
&& !params
.search
) {
2160 history
.pushState(null, "", buildUrl(query
, null));
2162 history
.replaceState(null, "", buildUrl(query
, null));
2165 // In case you "cut" the entry from the search input, then change the crate filter
2166 // before paste back the previous search, you get the old search results without
2167 // the filter. To prevent this, we need to remove the previous results.
2168 currentResults
= null;
2169 search(undefined, true);
2173 * @type {Array<string>}
2175 const searchWords
= buildIndex(rawSearchIndex
);
2176 if (typeof window
!== "undefined") {
2177 registerSearchEvents();
2178 // If there's a search term in the URL, execute the search now.
2179 if (window
.searchState
.getQueryStringParams().search
) {
2184 if (typeof exports
!== "undefined") {
2185 exports
.initSearch
= initSearch
;
2186 exports
.execQuery
= execQuery
;
2187 exports
.parseQuery
= parseQuery
;
2192 if (typeof window
!== "undefined") {
2193 window
.initSearch
= initSearch
;
2194 if (window
.searchIndex
!== undefined) {
2195 initSearch(window
.searchIndex
);
2198 // Running in Node, not a browser. Run initSearch just to produce the