]> git.proxmox.com Git - rustc.git/blame - src/librustdoc/html/static/js/search.js
New upstream version 1.65.0+dfsg1
[rustc.git] / src / librustdoc / html / static / js / search.js
CommitLineData
923072b8
FG
1/* global addClass, getNakedUrl, getSettingValue */
2/* global onEachLazy, removeClass, searchState, browserSupportsHistoryApi, exports */
04454e1e
FG
3
4"use strict";
17df50a5 5
cdc7bbd5
XL
6(function() {
7// This mapping table should match the discriminants of
04454e1e
FG
8// `rustdoc::formats::item_type::ItemType` type in Rust.
9const itemTypes = [
a2a8927a
XL
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];
cdc7bbd5
XL
37
38// used for special search precedence
04454e1e
FG
39const TY_PRIMITIVE = itemTypes.indexOf("primitive");
40const TY_KEYWORD = itemTypes.indexOf("keyword");
923072b8
FG
41const ROOT_PATH = typeof window !== "undefined" ? window.rootPath : "../";
42
43function hasOwnPropertyRustdoc(obj, property) {
44 return Object.prototype.hasOwnProperty.call(obj, property);
45}
cdc7bbd5
XL
46
47// In the search display, allows to switch between tabs.
48function printTab(nb) {
04454e1e
FG
49 let iter = 0;
50 let foundCurrentTab = false;
51 let foundCurrentResultSet = false;
52 onEachLazy(document.getElementById("titles").childNodes, elem => {
53 if (nb === iter) {
cdc7bbd5 54 addClass(elem, "selected");
04454e1e 55 foundCurrentTab = true;
cdc7bbd5
XL
56 } else {
57 removeClass(elem, "selected");
58 }
04454e1e 59 iter += 1;
cdc7bbd5 60 });
04454e1e
FG
61 iter = 0;
62 onEachLazy(document.getElementById("results").childNodes, elem => {
63 if (nb === iter) {
17df50a5 64 addClass(elem, "active");
04454e1e 65 foundCurrentResultSet = true;
cdc7bbd5 66 } else {
17df50a5 67 removeClass(elem, "active");
cdc7bbd5 68 }
04454e1e 69 iter += 1;
cdc7bbd5 70 });
04454e1e
FG
71 if (foundCurrentTab && foundCurrentResultSet) {
72 searchState.currentTab = nb;
923072b8 73 } else if (nb !== 0) {
04454e1e 74 printTab(0);
cdc7bbd5
XL
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
136023e0 83 * and was found at https://stackoverflow.com/a/18514751/745719
cdc7bbd5 84 */
04454e1e 85const levenshtein_row2 = [];
cdc7bbd5
XL
86function levenshtein(s1, s2) {
87 if (s1 === s2) {
88 return 0;
89 }
04454e1e 90 const s1_len = s1.length, s2_len = s2.length;
cdc7bbd5 91 if (s1_len && s2_len) {
04454e1e
FG
92 let i1 = 0, i2 = 0, a, b, c, c2;
93 const row = levenshtein_row2;
cdc7bbd5
XL
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
923072b8 114function initSearch(rawSearchIndex) {
04454e1e
FG
115 const MAX_LEV_DISTANCE = 3;
116 const MAX_RESULTS = 200;
04454e1e 117 const NO_TYPE_FILTER = -1;
a2a8927a
XL
118 /**
119 * @type {Array<Row>}
120 */
04454e1e
FG
121 let searchIndex;
122 let currentResults;
123 const ALIASES = Object.create(null);
cdc7bbd5 124
04454e1e
FG
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) {
923072b8 199 return parserState.userQuery.slice(parserState.pos, parserState.pos + 2) === "::";
04454e1e
FG
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) {
923072b8 210 return parserState.userQuery.slice(parserState.pos, parserState.pos + 2) === "->";
04454e1e
FG
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 &&
923072b8
FG
315 isIdentCharacter(parserState.userQuery[parserState.pos + 1])
316 ) {
04454e1e
FG
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) ||
923072b8
FG
325 isSeparatorCharacter(c)
326 ) {
04454e1e 327 break;
923072b8 328 } else if (c === ":") { // If we allow paths ("str::string" for example).
04454e1e
FG
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 &&
923072b8
FG
365 parserState.userQuery[parserState.pos] === "<"
366 ) {
04454e1e
FG
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 === ">");
f2b60f7d
FG
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.
04454e1e
FG
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
5099ac24
FG
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).
04454e1e 563 *
5099ac24
FG
564 * @return {string}
565 */
566 function buildUrl(search, filterCrates) {
04454e1e 567 let extra = "?search=" + encodeURIComponent(search);
5099ac24
FG
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() {
04454e1e 581 const elem = document.getElementById("crate-search");
5099ac24
FG
582
583 if (elem &&
f2b60f7d 584 elem.value !== "all crates" &&
923072b8
FG
585 hasOwnPropertyRustdoc(rawSearchIndex, elem.value)
586 ) {
5099ac24
FG
587 return elem.value;
588 }
589 return null;
590 }
591
cdc7bbd5 592 /**
04454e1e
FG
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
cdc7bbd5 657 */
04454e1e
FG
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";
cdc7bbd5 677 }
04454e1e 678 query.typeFilter = itemTypeFromName(typeFilter);
cdc7bbd5 679 }
04454e1e
FG
680 } catch (err) {
681 query = newParsedQuery(userQuery);
682 query.error = err.message;
683 query.typeFilter = -1;
684 return query;
cdc7bbd5
XL
685 }
686
04454e1e
FG
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 }
cdc7bbd5 714
04454e1e
FG
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
923072b8 721 * @param {Object} [currentCrate] - Current crate, to rank results from this crate higher
04454e1e
FG
722 *
723 * @return {ResultsTable}
724 */
923072b8 725 function execQuery(parsedQuery, searchWords, filterCrates, currentCrate) {
04454e1e 726 const results_others = {}, results_in_args = {}, results_returned = {};
cdc7bbd5 727
17df50a5 728 function transformResults(results) {
04454e1e
FG
729 const duplicates = {};
730 const out = [];
a2a8927a 731
04454e1e 732 for (const result of results) {
a2a8927a 733 if (result.id > -1) {
04454e1e 734 const obj = searchIndex[result.id];
a2a8927a 735 obj.lev = result.lev;
04454e1e 736 const res = buildHrefAndPath(obj);
17df50a5
XL
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;
a2a8927a
XL
741
742 if (duplicates[obj.fullPath]) {
743 continue;
744 }
745 duplicates[obj.fullPath] = true;
746
17df50a5
XL
747 obj.href = res[1];
748 out.push(obj);
749 if (out.length >= MAX_RESULTS) {
750 break;
cdc7bbd5
XL
751 }
752 }
753 }
754 return out;
755 }
756
923072b8 757 function sortResults(results, isType, preferredCrate) {
04454e1e
FG
758 const userQuery = parsedQuery.userQuery;
759 const ar = [];
760 for (const entry in results) {
17df50a5 761 if (hasOwnPropertyRustdoc(results, entry)) {
04454e1e 762 const result = results[entry];
a2a8927a
XL
763 result.word = searchWords[result.id];
764 result.item = searchIndex[result.id] || {};
765 ar.push(result);
cdc7bbd5
XL
766 }
767 }
768 results = ar;
cdc7bbd5
XL
769 // if there are no results then return to default and fail
770 if (results.length === 0) {
771 return [];
772 }
773
04454e1e
FG
774 results.sort((aaa, bbb) => {
775 let a, b;
cdc7bbd5
XL
776
777 // sort by exact match with regard to the last word (mismatch goes later)
04454e1e
FG
778 a = (aaa.word !== userQuery);
779 b = (bbb.word !== userQuery);
923072b8
FG
780 if (a !== b) {
781 return a - b;
782 }
cdc7bbd5
XL
783
784 // Sort by non levenshtein results and then levenshtein results by the distance
785 // (less changes required to match means higher rankings)
786 a = (aaa.lev);
787 b = (bbb.lev);
923072b8
FG
788 if (a !== b) {
789 return a - b;
790 }
cdc7bbd5 791
923072b8
FG
792 // sort by crate (current crate comes first)
793 a = (aaa.item.crate !== preferredCrate);
794 b = (bbb.item.crate !== preferredCrate);
795 if (a !== b) {
796 return a - b;
797 }
cdc7bbd5
XL
798
799 // sort by item name length (longer goes later)
800 a = aaa.word.length;
801 b = bbb.word.length;
923072b8
FG
802 if (a !== b) {
803 return a - b;
804 }
cdc7bbd5
XL
805
806 // sort by item name (lexicographically larger goes later)
807 a = aaa.word;
808 b = bbb.word;
923072b8
FG
809 if (a !== b) {
810 return (a > b ? +1 : -1);
811 }
cdc7bbd5
XL
812
813 // sort by index of keyword in item name (no literal occurrence goes later)
814 a = (aaa.index < 0);
815 b = (bbb.index < 0);
923072b8
FG
816 if (a !== b) {
817 return a - b;
818 }
cdc7bbd5
XL
819 // (later literal occurrence, if any, goes later)
820 a = aaa.index;
821 b = bbb.index;
923072b8
FG
822 if (a !== b) {
823 return a - b;
824 }
cdc7bbd5
XL
825
826 // special precedence for primitive and keyword pages
827 if ((aaa.item.ty === TY_PRIMITIVE && bbb.item.ty !== TY_KEYWORD) ||
828 (aaa.item.ty === TY_KEYWORD && bbb.item.ty !== TY_PRIMITIVE)) {
829 return -1;
830 }
831 if ((bbb.item.ty === TY_PRIMITIVE && aaa.item.ty !== TY_PRIMITIVE) ||
832 (bbb.item.ty === TY_KEYWORD && aaa.item.ty !== TY_KEYWORD)) {
833 return 1;
834 }
835
836 // sort by description (no description goes later)
837 a = (aaa.item.desc === "");
838 b = (bbb.item.desc === "");
923072b8
FG
839 if (a !== b) {
840 return a - b;
841 }
cdc7bbd5
XL
842
843 // sort by type (later occurrence in `itemTypes` goes later)
844 a = aaa.item.ty;
845 b = bbb.item.ty;
923072b8
FG
846 if (a !== b) {
847 return a - b;
848 }
cdc7bbd5
XL
849
850 // sort by path (lexicographically larger goes later)
851 a = aaa.item.path;
852 b = bbb.item.path;
923072b8
FG
853 if (a !== b) {
854 return (a > b ? +1 : -1);
855 }
cdc7bbd5
XL
856
857 // que sera, sera
858 return 0;
859 });
860
04454e1e
FG
861 let nameSplit = null;
862 if (parsedQuery.elems.length === 1) {
863 const hasPath = typeof parsedQuery.elems[0].path === "undefined";
864 nameSplit = hasPath ? null : parsedQuery.elems[0].path;
865 }
cdc7bbd5 866
04454e1e 867 for (const result of results) {
cdc7bbd5
XL
868 // this validation does not make sense when searching by types
869 if (result.dontValidate) {
870 continue;
871 }
04454e1e 872 const name = result.item.name.toLowerCase(),
cdc7bbd5
XL
873 path = result.item.path.toLowerCase(),
874 parent = result.item.parent;
875
04454e1e 876 if (!isType && !validateResult(name, path, nameSplit, parent)) {
cdc7bbd5
XL
877 result.id = -1;
878 }
879 }
880 return transformResults(results);
881 }
882
04454e1e
FG
883 /**
884 * This function checks if the object (`row`) generics match the given type (`elem`)
885 * generics. If there are no generics on `row`, `defaultLev` is returned.
886 *
887 * @param {Row} row - The object to check.
888 * @param {QueryElement} elem - The element from the parsed query.
889 * @param {integer} defaultLev - This is the value to return in case there are no generics.
890 *
891 * @return {integer} - Returns the best match (if any) or `MAX_LEV_DISTANCE + 1`.
892 */
893 function checkGenerics(row, elem, defaultLev) {
064997fb 894 if (row.generics.length === 0) {
04454e1e 895 return elem.generics.length === 0 ? defaultLev : MAX_LEV_DISTANCE + 1;
064997fb
FG
896 } else if (row.generics.length > 0 && row.generics[0].name === null) {
897 return checkGenerics(row.generics[0], elem, defaultLev);
cdc7bbd5 898 }
cdc7bbd5
XL
899 // The names match, but we need to be sure that all generics kinda
900 // match as well.
04454e1e 901 let elem_name;
064997fb 902 if (elem.generics.length > 0 && row.generics.length >= elem.generics.length) {
04454e1e 903 const elems = Object.create(null);
064997fb
FG
904 for (const entry of row.generics) {
905 elem_name = entry.name;
04454e1e
FG
906 if (elem_name === "") {
907 // Pure generic, needs to check into it.
908 if (checkGenerics(entry, elem, MAX_LEV_DISTANCE + 1) !== 0) {
909 return MAX_LEV_DISTANCE + 1;
136023e0 910 }
04454e1e 911 continue;
cdc7bbd5 912 }
04454e1e
FG
913 if (elems[elem_name] === undefined) {
914 elems[elem_name] = 0;
915 }
916 elems[elem_name] += 1;
917 }
918 // We need to find the type that matches the most to remove it in order
919 // to move forward.
920 for (const generic of elem.generics) {
921 let match = null;
922 if (elems[generic.name]) {
923 match = generic.name;
924 } else {
925 for (elem_name in elems) {
926 if (!hasOwnPropertyRustdoc(elems, elem_name)) {
927 continue;
cdc7bbd5 928 }
04454e1e
FG
929 if (elem_name === generic) {
930 match = elem_name;
931 break;
cdc7bbd5 932 }
cdc7bbd5
XL
933 }
934 }
04454e1e
FG
935 if (match === null) {
936 return MAX_LEV_DISTANCE + 1;
937 }
938 elems[match] -= 1;
939 if (elems[match] === 0) {
940 delete elems[match];
941 }
cdc7bbd5 942 }
04454e1e 943 return 0;
cdc7bbd5
XL
944 }
945 return MAX_LEV_DISTANCE + 1;
946 }
947
a2a8927a 948 /**
04454e1e
FG
949 * This function checks if the object (`row`) matches the given type (`elem`) and its
950 * generics (if any).
951 *
952 * @param {Row} row
953 * @param {QueryElement} elem - The element from the parsed query.
954 *
955 * @return {integer} - Returns a Levenshtein distance to the best match.
956 */
957 function checkIfInGenerics(row, elem) {
958 let lev = MAX_LEV_DISTANCE + 1;
064997fb 959 for (const entry of row.generics) {
04454e1e
FG
960 lev = Math.min(checkType(entry, elem, true), lev);
961 if (lev === 0) {
962 break;
963 }
964 }
965 return lev;
966 }
967
968 /**
969 * This function checks if the object (`row`) matches the given type (`elem`) and its
a2a8927a
XL
970 * generics (if any).
971 *
04454e1e
FG
972 * @param {Row} row
973 * @param {QueryElement} elem - The element from the parsed query.
a2a8927a
XL
974 * @param {boolean} literalSearch
975 *
976 * @return {integer} - Returns a Levenshtein distance to the best match. If there is
977 * no match, returns `MAX_LEV_DISTANCE + 1`.
978 */
04454e1e 979 function checkType(row, elem, literalSearch) {
064997fb 980 if (row.name === null) {
04454e1e 981 // This is a pure "generic" search, no need to run other checks.
064997fb 982 if (row.generics.length > 0) {
04454e1e
FG
983 return checkIfInGenerics(row, elem);
984 }
985 return MAX_LEV_DISTANCE + 1;
986 }
cdc7bbd5 987
064997fb 988 let lev = levenshtein(row.name, elem.name);
04454e1e
FG
989 if (literalSearch) {
990 if (lev !== 0) {
991 // The name didn't match, let's try to check if the generics do.
992 if (elem.generics.length === 0) {
064997fb
FG
993 const checkGeneric = row.generics.length > 0;
994 if (checkGeneric && row.generics
995 .findIndex(tmp_elem => tmp_elem.name === elem.name) !== -1) {
a2a8927a 996 return 0;
cdc7bbd5
XL
997 }
998 }
04454e1e
FG
999 return MAX_LEV_DISTANCE + 1;
1000 } else if (elem.generics.length > 0) {
1001 return checkGenerics(row, elem, MAX_LEV_DISTANCE + 1);
cdc7bbd5 1002 }
04454e1e 1003 return 0;
064997fb 1004 } else if (row.generics.length > 0) {
04454e1e
FG
1005 if (elem.generics.length === 0) {
1006 if (lev === 0) {
1007 return 0;
1008 }
1009 // The name didn't match so we now check if the type we're looking for is inside
1010 // the generics!
1011 lev = checkIfInGenerics(row, elem);
1012 // Now whatever happens, the returned distance is "less good" so we should mark
1013 // it as such, and so we add 0.5 to the distance to make it "less good".
1014 return lev + 0.5;
1015 } else if (lev > MAX_LEV_DISTANCE) {
1016 // So our item's name doesn't match at all and has generics.
1017 //
1018 // Maybe it's present in a sub generic? For example "f<A<B<C>>>()", if we're
1019 // looking for "B<C>", we'll need to go down.
1020 return checkIfInGenerics(row, elem);
1021 } else {
1022 // At this point, the name kinda match and we have generics to check, so
1023 // let's go!
1024 const tmp_lev = checkGenerics(row, elem, lev);
1025 if (tmp_lev > MAX_LEV_DISTANCE) {
1026 return MAX_LEV_DISTANCE + 1;
3c0e092e 1027 }
04454e1e
FG
1028 // We compute the median value of both checks and return it.
1029 return (tmp_lev + lev) / 2;
cdc7bbd5 1030 }
04454e1e
FG
1031 } else if (elem.generics.length > 0) {
1032 // In this case, we were expecting generics but there isn't so we simply reject this
1033 // one.
1034 return MAX_LEV_DISTANCE + 1;
cdc7bbd5 1035 }
04454e1e
FG
1036 // No generics on our query or on the target type so we can return without doing
1037 // anything else.
1038 return lev;
cdc7bbd5
XL
1039 }
1040
a2a8927a 1041 /**
04454e1e 1042 * This function checks if the object (`row`) has an argument with the given type (`elem`).
a2a8927a 1043 *
04454e1e
FG
1044 * @param {Row} row
1045 * @param {QueryElement} elem - The element from the parsed query.
a2a8927a
XL
1046 * @param {integer} typeFilter
1047 *
1048 * @return {integer} - Returns a Levenshtein distance to the best match. If there is no
1049 * match, returns `MAX_LEV_DISTANCE + 1`.
1050 */
04454e1e
FG
1051 function findArg(row, elem, typeFilter) {
1052 let lev = MAX_LEV_DISTANCE + 1;
1053
064997fb
FG
1054 if (row && row.type && row.type.inputs && row.type.inputs.length > 0) {
1055 for (const input of row.type.inputs) {
1056 if (!typePassesFilter(typeFilter, input.ty)) {
cdc7bbd5
XL
1057 continue;
1058 }
04454e1e
FG
1059 lev = Math.min(lev, checkType(input, elem, parsedQuery.literalSearch));
1060 if (lev === 0) {
a2a8927a 1061 return 0;
cdc7bbd5 1062 }
cdc7bbd5
XL
1063 }
1064 }
04454e1e 1065 return parsedQuery.literalSearch ? MAX_LEV_DISTANCE + 1 : lev;
cdc7bbd5
XL
1066 }
1067
04454e1e
FG
1068 /**
1069 * This function checks if the object (`row`) returns the given type (`elem`).
1070 *
1071 * @param {Row} row
1072 * @param {QueryElement} elem - The element from the parsed query.
1073 * @param {integer} typeFilter
1074 *
1075 * @return {integer} - Returns a Levenshtein distance to the best match. If there is no
1076 * match, returns `MAX_LEV_DISTANCE + 1`.
1077 */
1078 function checkReturned(row, elem, typeFilter) {
1079 let lev = MAX_LEV_DISTANCE + 1;
cdc7bbd5 1080
064997fb
FG
1081 if (row && row.type && row.type.output.length > 0) {
1082 const ret = row.type.output;
04454e1e 1083 for (const ret_ty of ret) {
064997fb 1084 if (!typePassesFilter(typeFilter, ret_ty.ty)) {
cdc7bbd5
XL
1085 continue;
1086 }
04454e1e
FG
1087 lev = Math.min(lev, checkType(ret_ty, elem, parsedQuery.literalSearch));
1088 if (lev === 0) {
a2a8927a 1089 return 0;
cdc7bbd5 1090 }
cdc7bbd5
XL
1091 }
1092 }
04454e1e 1093 return parsedQuery.literalSearch ? MAX_LEV_DISTANCE + 1 : lev;
cdc7bbd5
XL
1094 }
1095
04454e1e 1096 function checkPath(contains, ty) {
cdc7bbd5
XL
1097 if (contains.length === 0) {
1098 return 0;
1099 }
04454e1e
FG
1100 let ret_lev = MAX_LEV_DISTANCE + 1;
1101 const path = ty.path.split("::");
cdc7bbd5
XL
1102
1103 if (ty.parent && ty.parent.name) {
1104 path.push(ty.parent.name.toLowerCase());
1105 }
1106
04454e1e
FG
1107 const length = path.length;
1108 const clength = contains.length;
cdc7bbd5
XL
1109 if (clength > length) {
1110 return MAX_LEV_DISTANCE + 1;
1111 }
04454e1e 1112 for (let i = 0; i < length; ++i) {
cdc7bbd5
XL
1113 if (i + clength > length) {
1114 break;
1115 }
04454e1e
FG
1116 let lev_total = 0;
1117 let aborted = false;
1118 for (let x = 0; x < clength; ++x) {
1119 const lev = levenshtein(path[i + x], contains[x]);
cdc7bbd5
XL
1120 if (lev > MAX_LEV_DISTANCE) {
1121 aborted = true;
1122 break;
1123 }
1124 lev_total += lev;
1125 }
17df50a5 1126 if (!aborted) {
cdc7bbd5
XL
1127 ret_lev = Math.min(ret_lev, Math.round(lev_total / clength));
1128 }
1129 }
1130 return ret_lev;
1131 }
1132
1133 function typePassesFilter(filter, type) {
5099ac24
FG
1134 // No filter or Exact mach
1135 if (filter <= NO_TYPE_FILTER || filter === type) return true;
cdc7bbd5
XL
1136
1137 // Match related items
04454e1e 1138 const name = itemTypes[type];
cdc7bbd5
XL
1139 switch (itemTypes[filter]) {
1140 case "constant":
1141 return name === "associatedconstant";
1142 case "fn":
1143 return name === "method" || name === "tymethod";
1144 case "type":
1145 return name === "primitive" || name === "associatedtype";
1146 case "trait":
1147 return name === "traitalias";
1148 }
1149
1150 // No match
1151 return false;
1152 }
1153
1154 function createAliasFromItem(item) {
1155 return {
1156 crate: item.crate,
1157 name: item.name,
1158 path: item.path,
1159 desc: item.desc,
1160 ty: item.ty,
1161 parent: item.parent,
1162 type: item.type,
1163 is_alias: true,
1164 };
1165 }
1166
923072b8 1167 function handleAliases(ret, query, filterCrates, currentCrate) {
04454e1e 1168 const lowerQuery = query.toLowerCase();
cdc7bbd5
XL
1169 // We separate aliases and crate aliases because we want to have current crate
1170 // aliases to be before the others in the displayed results.
04454e1e
FG
1171 const aliases = [];
1172 const crateAliases = [];
5099ac24 1173 if (filterCrates !== null) {
04454e1e
FG
1174 if (ALIASES[filterCrates] && ALIASES[filterCrates][lowerQuery]) {
1175 const query_aliases = ALIASES[filterCrates][lowerQuery];
1176 for (const alias of query_aliases) {
1177 aliases.push(createAliasFromItem(searchIndex[alias]));
cdc7bbd5
XL
1178 }
1179 }
1180 } else {
04454e1e
FG
1181 Object.keys(ALIASES).forEach(crate => {
1182 if (ALIASES[crate][lowerQuery]) {
923072b8 1183 const pushTo = crate === currentCrate ? crateAliases : aliases;
04454e1e
FG
1184 const query_aliases = ALIASES[crate][lowerQuery];
1185 for (const alias of query_aliases) {
1186 pushTo.push(createAliasFromItem(searchIndex[alias]));
cdc7bbd5
XL
1187 }
1188 }
1189 });
1190 }
1191
04454e1e 1192 const sortFunc = (aaa, bbb) => {
cdc7bbd5
XL
1193 if (aaa.path < bbb.path) {
1194 return 1;
1195 } else if (aaa.path === bbb.path) {
1196 return 0;
1197 }
1198 return -1;
1199 };
1200 crateAliases.sort(sortFunc);
1201 aliases.sort(sortFunc);
1202
04454e1e
FG
1203 const pushFunc = alias => {
1204 alias.alias = query;
1205 const res = buildHrefAndPath(alias);
cdc7bbd5
XL
1206 alias.displayPath = pathSplitter(res[0]);
1207 alias.fullPath = alias.displayPath + alias.name;
1208 alias.href = res[1];
1209
1210 ret.others.unshift(alias);
1211 if (ret.others.length > MAX_RESULTS) {
1212 ret.others.pop();
1213 }
1214 };
923072b8
FG
1215
1216 aliases.forEach(pushFunc);
1217 crateAliases.forEach(pushFunc);
cdc7bbd5
XL
1218 }
1219
a2a8927a 1220 /**
04454e1e 1221 * This function adds the given result into the provided `results` map if it matches the
a2a8927a
XL
1222 * following condition:
1223 *
04454e1e 1224 * * If it is a "literal search" (`parsedQuery.literalSearch`), then `lev` must be 0.
a2a8927a
XL
1225 * * If it is not a "literal search", `lev` must be <= `MAX_LEV_DISTANCE`.
1226 *
04454e1e 1227 * The `results` map contains information which will be used to sort the search results:
a2a8927a 1228 *
04454e1e 1229 * * `fullId` is a `string`` used as the key of the object we use for the `results` map.
a2a8927a
XL
1230 * * `id` is the index in both `searchWords` and `searchIndex` arrays for this element.
1231 * * `index` is an `integer`` used to sort by the position of the word in the item's name.
1232 * * `lev` is the main metric used to sort the search results.
1233 *
04454e1e 1234 * @param {Results} results
a2a8927a
XL
1235 * @param {string} fullId
1236 * @param {integer} id
1237 * @param {integer} index
1238 * @param {integer} lev
1239 */
04454e1e
FG
1240 function addIntoResults(results, fullId, id, index, lev) {
1241 if (lev === 0 || (!parsedQuery.literalSearch && lev <= MAX_LEV_DISTANCE)) {
1242 if (results[fullId] !== undefined) {
1243 const result = results[fullId];
a2a8927a
XL
1244 if (result.dontValidate || result.lev <= lev) {
1245 return;
1246 }
1247 }
04454e1e 1248 results[fullId] = {
a2a8927a
XL
1249 id: id,
1250 index: index,
04454e1e 1251 dontValidate: parsedQuery.literalSearch,
a2a8927a
XL
1252 lev: lev,
1253 };
1254 }
1255 }
1256
04454e1e
FG
1257 /**
1258 * This function is called in case the query is only one element (with or without generics).
1259 * This element will be compared to arguments' and returned values' items and also to items.
1260 *
1261 * Other important thing to note: since there is only one element, we use levenshtein
1262 * distance for name comparisons.
1263 *
1264 * @param {Row} row
1265 * @param {integer} pos - Position in the `searchIndex`.
1266 * @param {QueryElement} elem - The element from the parsed query.
1267 * @param {Results} results_others - Unqualified results (not in arguments nor in
1268 * returned values).
1269 * @param {Results} results_in_args - Matching arguments results.
1270 * @param {Results} results_returned - Matching returned arguments results.
1271 */
1272 function handleSingleArg(
1273 row,
1274 pos,
1275 elem,
1276 results_others,
1277 results_in_args,
1278 results_returned
1279 ) {
1280 if (!row || (filterCrates !== null && row.crate !== filterCrates)) {
1281 return;
1282 }
1283 let lev, lev_add = 0, index = -1;
1284 const fullId = row.id;
1285
1286 const in_args = findArg(row, elem, parsedQuery.typeFilter);
1287 const returned = checkReturned(row, elem, parsedQuery.typeFilter);
1288
1289 addIntoResults(results_in_args, fullId, pos, index, in_args);
1290 addIntoResults(results_returned, fullId, pos, index, returned);
1291
1292 if (!typePassesFilter(parsedQuery.typeFilter, row.ty)) {
1293 return;
1294 }
1295 const searchWord = searchWords[pos];
1296
1297 if (parsedQuery.literalSearch) {
1298 if (searchWord === elem.name) {
1299 addIntoResults(results_others, fullId, pos, -1, 0);
cdc7bbd5 1300 }
04454e1e
FG
1301 return;
1302 }
cdc7bbd5 1303
04454e1e
FG
1304 // No need to check anything else if it's a "pure" generics search.
1305 if (elem.name.length === 0) {
1306 if (row.type !== null) {
1307 lev = checkGenerics(row.type, elem, MAX_LEV_DISTANCE + 1);
1308 addIntoResults(results_others, fullId, pos, index, lev);
1309 }
1310 return;
1311 }
cdc7bbd5 1312
04454e1e
FG
1313 if (elem.fullPath.length > 1) {
1314 lev = checkPath(elem.pathWithoutLast, row);
1315 if (lev > MAX_LEV_DISTANCE || (parsedQuery.literalSearch && lev !== 0)) {
1316 return;
1317 } else if (lev > 0) {
1318 lev_add = lev / 10;
cdc7bbd5
XL
1319 }
1320 }
04454e1e
FG
1321
1322 if (searchWord.indexOf(elem.pathLast) > -1 ||
923072b8
FG
1323 row.normalizedName.indexOf(elem.pathLast) > -1
1324 ) {
1325 index = row.normalizedName.indexOf(elem.pathLast);
04454e1e
FG
1326 }
1327 lev = levenshtein(searchWord, elem.pathLast);
923072b8 1328 if (lev > 0 && elem.pathLast.length > 2 && searchWord.indexOf(elem.pathLast) > -1) {
04454e1e
FG
1329 if (elem.pathLast.length < 6) {
1330 lev = 1;
1331 } else {
1332 lev = 0;
cdc7bbd5 1333 }
04454e1e
FG
1334 }
1335 lev += lev_add;
1336 if (lev > MAX_LEV_DISTANCE) {
1337 return;
1338 } else if (index !== -1 && elem.fullPath.length < 2) {
1339 lev -= 1;
1340 }
1341 if (lev < 0) {
1342 lev = 0;
1343 }
1344 addIntoResults(results_others, fullId, pos, index, lev);
1345 }
cdc7bbd5 1346
04454e1e
FG
1347 /**
1348 * This function is called in case the query has more than one element. In this case, it'll
1349 * try to match the items which validates all the elements. For `aa -> bb` will look for
1350 * functions which have a parameter `aa` and has `bb` in its returned values.
1351 *
1352 * @param {Row} row
1353 * @param {integer} pos - Position in the `searchIndex`.
1354 * @param {Object} results
1355 */
1356 function handleArgs(row, pos, results) {
1357 if (!row || (filterCrates !== null && row.crate !== filterCrates)) {
1358 return;
1359 }
cdc7bbd5 1360
04454e1e
FG
1361 let totalLev = 0;
1362 let nbLev = 0;
1363
1364 // If the result is too "bad", we return false and it ends this search.
1365 function checkArgs(elems, callback) {
1366 for (const elem of elems) {
1367 // There is more than one parameter to the query so all checks should be "exact"
1368 const lev = callback(row, elem, NO_TYPE_FILTER);
1369 if (lev <= 1) {
1370 nbLev += 1;
1371 totalLev += lev;
17df50a5 1372 } else {
04454e1e 1373 return false;
cdc7bbd5
XL
1374 }
1375 }
04454e1e
FG
1376 return true;
1377 }
1378 if (!checkArgs(parsedQuery.elems, findArg)) {
1379 return;
1380 }
1381 if (!checkArgs(parsedQuery.returned, checkReturned)) {
1382 return;
1383 }
cdc7bbd5 1384
04454e1e
FG
1385 if (nbLev === 0) {
1386 return;
1387 }
1388 const lev = Math.round(totalLev / nbLev);
1389 addIntoResults(results, row.id, pos, 0, lev);
1390 }
1391
1392 function innerRunQuery() {
1393 let elem, i, nSearchWords, in_returned, row;
1394
1395 if (parsedQuery.foundElems === 1) {
1396 if (parsedQuery.elems.length === 1) {
1397 elem = parsedQuery.elems[0];
1398 for (i = 0, nSearchWords = searchWords.length; i < nSearchWords; ++i) {
1399 // It means we want to check for this element everywhere (in names, args and
1400 // returned).
1401 handleSingleArg(
1402 searchIndex[i],
1403 i,
1404 elem,
1405 results_others,
1406 results_in_args,
1407 results_returned
1408 );
cdc7bbd5 1409 }
04454e1e
FG
1410 } else if (parsedQuery.returned.length === 1) {
1411 // We received one returned argument to check, so looking into returned values.
1412 elem = parsedQuery.returned[0];
1413 for (i = 0, nSearchWords = searchWords.length; i < nSearchWords; ++i) {
1414 row = searchIndex[i];
1415 in_returned = checkReturned(row, elem, parsedQuery.typeFilter);
1416 addIntoResults(results_others, row.id, i, -1, in_returned);
cdc7bbd5 1417 }
04454e1e
FG
1418 }
1419 } else if (parsedQuery.foundElems > 0) {
1420 for (i = 0, nSearchWords = searchWords.length; i < nSearchWords; ++i) {
1421 handleArgs(searchIndex[i], i, results_others);
cdc7bbd5
XL
1422 }
1423 }
1424 }
1425
04454e1e
FG
1426 if (parsedQuery.error === null) {
1427 innerRunQuery();
1428 }
1429
1430 const ret = createQueryResults(
923072b8
FG
1431 sortResults(results_in_args, true, currentCrate),
1432 sortResults(results_returned, true, currentCrate),
1433 sortResults(results_others, false, currentCrate),
04454e1e 1434 parsedQuery);
923072b8 1435 handleAliases(ret, parsedQuery.original.replace(/"/g, ""), filterCrates, currentCrate);
04454e1e
FG
1436 if (parsedQuery.error !== null && ret.others.length !== 0) {
1437 // It means some doc aliases were found so let's "remove" the error!
1438 ret.query.error = null;
1439 }
cdc7bbd5
XL
1440 return ret;
1441 }
1442
1443 /**
1444 * Validate performs the following boolean logic. For example:
1445 * "File::open" will give IF A PARENT EXISTS => ("file" && "open")
1446 * exists in (name || path || parent) OR => ("file" && "open") exists in
1447 * (name || path )
1448 *
1449 * This could be written functionally, but I wanted to minimise
1450 * functions on stack.
1451 *
a2a8927a
XL
1452 * @param {string} name - The name of the result
1453 * @param {string} path - The path of the result
1454 * @param {string} keys - The keys to be used (["file", "open"])
1455 * @param {Object} parent - The parent of the result
04454e1e 1456 *
a2a8927a 1457 * @return {boolean} - Whether the result is valid or not
cdc7bbd5
XL
1458 */
1459 function validateResult(name, path, keys, parent) {
04454e1e
FG
1460 if (!keys || !keys.length) {
1461 return true;
1462 }
1463 for (const key of keys) {
cdc7bbd5
XL
1464 // each check is for validation so we negate the conditions and invalidate
1465 if (!(
1466 // check for an exact name match
04454e1e 1467 name.indexOf(key) > -1 ||
cdc7bbd5 1468 // then an exact path match
04454e1e 1469 path.indexOf(key) > -1 ||
cdc7bbd5
XL
1470 // next if there is a parent, check for exact parent match
1471 (parent !== undefined && parent.name !== undefined &&
04454e1e 1472 parent.name.toLowerCase().indexOf(key) > -1) ||
cdc7bbd5 1473 // lastly check to see if the name was a levenshtein match
04454e1e 1474 levenshtein(name, key) <= MAX_LEV_DISTANCE)) {
cdc7bbd5
XL
1475 return false;
1476 }
1477 }
1478 return true;
1479 }
1480
17df50a5 1481 function nextTab(direction) {
04454e1e 1482 const next = (searchState.currentTab + direction + 3) % searchState.focusedByTab.length;
17df50a5
XL
1483 searchState.focusedByTab[searchState.currentTab] = document.activeElement;
1484 printTab(next);
1485 focusSearchResult();
1486 }
cdc7bbd5 1487
17df50a5
XL
1488 // Focus the first search result on the active tab, or the result that
1489 // was focused last time this tab was active.
1490 function focusSearchResult() {
04454e1e 1491 const target = searchState.focusedByTab[searchState.currentTab] ||
17df50a5
XL
1492 document.querySelectorAll(".search-results.active a").item(0) ||
1493 document.querySelectorAll("#titles > button").item(searchState.currentTab);
1494 if (target) {
1495 target.focus();
1496 }
cdc7bbd5
XL
1497 }
1498
1499 function buildHrefAndPath(item) {
04454e1e
FG
1500 let displayPath;
1501 let href;
1502 const type = itemTypes[item.ty];
1503 const name = item.name;
1504 let path = item.path;
cdc7bbd5
XL
1505
1506 if (type === "mod") {
1507 displayPath = path + "::";
923072b8
FG
1508 href = ROOT_PATH + path.replace(/::/g, "/") + "/" +
1509 name + "/index.html";
04454e1e
FG
1510 } else if (type === "import") {
1511 displayPath = item.path + "::";
923072b8 1512 href = ROOT_PATH + item.path.replace(/::/g, "/") + "/index.html#reexport." + name;
cdc7bbd5
XL
1513 } else if (type === "primitive" || type === "keyword") {
1514 displayPath = "";
923072b8
FG
1515 href = ROOT_PATH + path.replace(/::/g, "/") +
1516 "/" + type + "." + name + ".html";
cdc7bbd5
XL
1517 } else if (type === "externcrate") {
1518 displayPath = "";
923072b8 1519 href = ROOT_PATH + name + "/index.html";
cdc7bbd5 1520 } else if (item.parent !== undefined) {
04454e1e
FG
1521 const myparent = item.parent;
1522 let anchor = "#" + type + "." + name;
1523 const parentType = itemTypes[myparent.ty];
1524 let pageType = parentType;
1525 let pageName = myparent.name;
cdc7bbd5
XL
1526
1527 if (parentType === "primitive") {
1528 displayPath = myparent.name + "::";
1529 } else if (type === "structfield" && parentType === "variant") {
1530 // Structfields belonging to variants are special: the
1531 // final path element is the enum name.
04454e1e
FG
1532 const enumNameIdx = item.path.lastIndexOf("::");
1533 const enumName = item.path.substr(enumNameIdx + 2);
cdc7bbd5
XL
1534 path = item.path.substr(0, enumNameIdx);
1535 displayPath = path + "::" + enumName + "::" + myparent.name + "::";
1536 anchor = "#variant." + myparent.name + ".field." + name;
1537 pageType = "enum";
1538 pageName = enumName;
1539 } else {
1540 displayPath = path + "::" + myparent.name + "::";
1541 }
923072b8
FG
1542 href = ROOT_PATH + path.replace(/::/g, "/") +
1543 "/" + pageType +
1544 "." + pageName +
1545 ".html" + anchor;
cdc7bbd5
XL
1546 } else {
1547 displayPath = item.path + "::";
923072b8
FG
1548 href = ROOT_PATH + item.path.replace(/::/g, "/") +
1549 "/" + type + "." + name + ".html";
cdc7bbd5
XL
1550 }
1551 return [displayPath, href];
1552 }
1553
cdc7bbd5 1554 function pathSplitter(path) {
04454e1e 1555 const tmp = "<span>" + path.replace(/::/g, "::</span><span>");
cdc7bbd5
XL
1556 if (tmp.endsWith("<span>")) {
1557 return tmp.slice(0, tmp.length - 6);
1558 }
1559 return tmp;
1560 }
1561
a2a8927a
XL
1562 /**
1563 * Render a set of search results for a single tab.
1564 * @param {Array<?>} array - The search results for this tab
1565 * @param {ParsedQuery} query
1566 * @param {boolean} display - True if this is the active tab
1567 */
cdc7bbd5 1568 function addTab(array, query, display) {
04454e1e 1569 let extraClass = "";
17df50a5
XL
1570 if (display === true) {
1571 extraClass = " active";
cdc7bbd5
XL
1572 }
1573
04454e1e
FG
1574 const output = document.createElement("div");
1575 let length = 0;
cdc7bbd5 1576 if (array.length > 0) {
17df50a5 1577 output.className = "search-results " + extraClass;
cdc7bbd5 1578
04454e1e
FG
1579 array.forEach(item => {
1580 const name = item.name;
1581 const type = itemTypes[item.ty];
17df50a5 1582
cdc7bbd5
XL
1583 length += 1;
1584
04454e1e 1585 let extra = "";
17df50a5
XL
1586 if (type === "primitive") {
1587 extra = " <i>(primitive type)</i>";
1588 } else if (type === "keyword") {
1589 extra = " <i>(keyword)</i>";
1590 }
1591
04454e1e 1592 const link = document.createElement("a");
17df50a5
XL
1593 link.className = "result-" + type;
1594 link.href = item.href;
1595
04454e1e
FG
1596 const wrapper = document.createElement("div");
1597 const resultName = document.createElement("div");
17df50a5
XL
1598 resultName.className = "result-name";
1599
1600 if (item.is_alias) {
04454e1e 1601 const alias = document.createElement("span");
17df50a5
XL
1602 alias.className = "alias";
1603
04454e1e 1604 const bold = document.createElement("b");
17df50a5
XL
1605 bold.innerText = item.alias;
1606 alias.appendChild(bold);
1607
1608 alias.insertAdjacentHTML(
1609 "beforeend",
1610 "<span class=\"grey\"><i>&nbsp;- see&nbsp;</i></span>");
1611
1612 resultName.appendChild(alias);
1613 }
1614 resultName.insertAdjacentHTML(
1615 "beforeend",
1616 item.displayPath + "<span class=\"" + type + "\">" + name + extra + "</span>");
1617 wrapper.appendChild(resultName);
1618
04454e1e 1619 const description = document.createElement("div");
17df50a5 1620 description.className = "desc";
04454e1e 1621 const spanDesc = document.createElement("span");
17df50a5
XL
1622 spanDesc.insertAdjacentHTML("beforeend", item.desc);
1623
1624 description.appendChild(spanDesc);
1625 wrapper.appendChild(description);
1626 link.appendChild(wrapper);
1627 output.appendChild(link);
cdc7bbd5 1628 });
04454e1e 1629 } else if (query.error === null) {
17df50a5
XL
1630 output.className = "search-failed" + extraClass;
1631 output.innerHTML = "No results :(<br/>" +
cdc7bbd5 1632 "Try on <a href=\"https://duckduckgo.com/?q=" +
04454e1e 1633 encodeURIComponent("rust " + query.userQuery) +
cdc7bbd5
XL
1634 "\">DuckDuckGo</a>?<br/><br/>" +
1635 "Or try looking in one of these:<ul><li>The <a " +
1636 "href=\"https://doc.rust-lang.org/reference/index.html\">Rust Reference</a> " +
1637 " for technical details about the language.</li><li><a " +
1638 "href=\"https://doc.rust-lang.org/rust-by-example/index.html\">Rust By " +
1639 "Example</a> for expository code examples.</a></li><li>The <a " +
1640 "href=\"https://doc.rust-lang.org/book/index.html\">Rust Book</a> for " +
1641 "introductions to language features and the language itself.</li><li><a " +
1642 "href=\"https://docs.rs\">Docs.rs</a> for documentation of crates released on" +
17df50a5 1643 " <a href=\"https://crates.io/\">crates.io</a>.</li></ul>";
cdc7bbd5
XL
1644 }
1645 return [output, length];
1646 }
1647
1648 function makeTabHeader(tabNb, text, nbElems) {
1649 if (searchState.currentTab === tabNb) {
1650 return "<button class=\"selected\">" + text +
1651 " <div class=\"count\">(" + nbElems + ")</div></button>";
1652 }
1653 return "<button>" + text + " <div class=\"count\">(" + nbElems + ")</div></button>";
1654 }
1655
04454e1e
FG
1656 /**
1657 * @param {ResultsTable} results
1658 * @param {boolean} go_to_first
1659 * @param {string} filterCrates
1660 */
5099ac24 1661 function showResults(results, go_to_first, filterCrates) {
04454e1e 1662 const search = searchState.outputElement();
136023e0 1663 if (go_to_first || (results.others.length === 1
cdc7bbd5
XL
1664 && getSettingValue("go-to-only-result") === "true"
1665 // By default, the search DOM element is "empty" (meaning it has no children not
1666 // text content). Once a search has been run, it won't be empty, even if you press
1667 // ESC or empty the search input (which also "cancels" the search).
923072b8
FG
1668 && (!search.firstChild || search.firstChild.innerText !== searchState.loadingText))
1669 ) {
04454e1e 1670 const elem = document.createElement("a");
cdc7bbd5 1671 elem.href = results.others[0].href;
17df50a5 1672 removeClass(elem, "active");
cdc7bbd5
XL
1673 // For firefox, we need the element to be in the DOM so it can be clicked.
1674 document.body.appendChild(elem);
1675 elem.click();
1676 return;
1677 }
04454e1e
FG
1678 if (results.query === undefined) {
1679 results.query = parseQuery(searchState.input.value);
1680 }
cdc7bbd5 1681
04454e1e 1682 currentResults = results.query.userQuery;
cdc7bbd5 1683
04454e1e
FG
1684 const ret_others = addTab(results.others, results.query, true);
1685 const ret_in_args = addTab(results.in_args, results.query, false);
1686 const ret_returned = addTab(results.returned, results.query, false);
cdc7bbd5
XL
1687
1688 // Navigate to the relevant tab if the current tab is empty, like in case users search
1689 // for "-> String". If they had selected another tab previously, they have to click on
1690 // it again.
04454e1e 1691 let currentTab = searchState.currentTab;
cdc7bbd5
XL
1692 if ((currentTab === 0 && ret_others[1] === 0) ||
1693 (currentTab === 1 && ret_in_args[1] === 0) ||
1694 (currentTab === 2 && ret_returned[1] === 0)) {
1695 if (ret_others[1] !== 0) {
1696 currentTab = 0;
1697 } else if (ret_in_args[1] !== 0) {
1698 currentTab = 1;
1699 } else if (ret_returned[1] !== 0) {
1700 currentTab = 2;
1701 }
1702 }
1703
5099ac24 1704 let crates = "";
923072b8
FG
1705 const crates_list = Object.keys(rawSearchIndex);
1706 if (crates_list.length > 1) {
f2b60f7d
FG
1707 crates = " in&nbsp;<div id=\"crate-search-div\"><select id=\"crate-search\">" +
1708 "<option value=\"all crates\">all crates</option>";
923072b8
FG
1709 for (const c of crates_list) {
1710 crates += `<option value="${c}" ${c === filterCrates && "selected"}>${c}</option>`;
5099ac24 1711 }
f2b60f7d 1712 crates += "</select></div>";
04454e1e
FG
1713 }
1714
f2b60f7d 1715 let output = `<h1 class="search-results-title">Results${crates}</h1>`;
04454e1e
FG
1716 if (results.query.error !== null) {
1717 output += `<h3>Query parser error: "${results.query.error}".</h3>`;
1718 output += "<div id=\"titles\">" +
1719 makeTabHeader(0, "In Names", ret_others[1]) +
1720 "</div>";
1721 currentTab = 0;
1722 } else if (results.query.foundElems <= 1 && results.query.returned.length === 0) {
1723 output += "<div id=\"titles\">" +
1724 makeTabHeader(0, "In Names", ret_others[1]) +
1725 makeTabHeader(1, "In Parameters", ret_in_args[1]) +
1726 makeTabHeader(2, "In Return Types", ret_returned[1]) +
1727 "</div>";
1728 } else {
1729 const signatureTabTitle =
1730 results.query.elems.length === 0 ? "In Function Return Types" :
1731 results.query.returned.length === 0 ? "In Function Parameters" :
1732 "In Function Signatures";
1733 output += "<div id=\"titles\">" +
1734 makeTabHeader(0, signatureTabTitle, ret_others[1]) +
1735 "</div>";
1736 currentTab = 0;
1737 }
1738
1739 const resultsElem = document.createElement("div");
17df50a5
XL
1740 resultsElem.id = "results";
1741 resultsElem.appendChild(ret_others[0]);
1742 resultsElem.appendChild(ret_in_args[0]);
1743 resultsElem.appendChild(ret_returned[0]);
cdc7bbd5
XL
1744
1745 search.innerHTML = output;
04454e1e 1746 const crateSearch = document.getElementById("crate-search");
5099ac24
FG
1747 if (crateSearch) {
1748 crateSearch.addEventListener("input", updateCrate);
1749 }
17df50a5
XL
1750 search.appendChild(resultsElem);
1751 // Reset focused elements.
cdc7bbd5 1752 searchState.showResults(search);
04454e1e
FG
1753 const elems = document.getElementById("titles").childNodes;
1754 searchState.focusedByTab = [];
1755 let i = 0;
1756 for (const elem of elems) {
1757 const j = i;
923072b8 1758 elem.onclick = () => printTab(j);
04454e1e
FG
1759 searchState.focusedByTab.push(null);
1760 i += 1;
cdc7bbd5 1761 }
04454e1e 1762 printTab(currentTab);
cdc7bbd5
XL
1763 }
1764
a2a8927a
XL
1765 /**
1766 * Perform a search based on the current state of the search input element
1767 * and display the results.
1768 * @param {Event} [e] - The event that triggered this search, if any
1769 * @param {boolean} [forced]
1770 */
cdc7bbd5 1771 function search(e, forced) {
04454e1e
FG
1772 const params = searchState.getQueryStringParams();
1773 const query = parseQuery(searchState.input.value.trim());
cdc7bbd5
XL
1774
1775 if (e) {
1776 e.preventDefault();
1777 }
1778
04454e1e
FG
1779 if (!forced && query.userQuery === currentResults) {
1780 if (query.userQuery.length > 0) {
5099ac24 1781 putBackSearch();
cdc7bbd5
XL
1782 }
1783 return;
1784 }
1785
04454e1e 1786 let filterCrates = getFilterCrates();
5099ac24
FG
1787
1788 // In case we have no information about the saved crate and there is a URL query parameter,
1789 // we override it with the URL query parameter.
1790 if (filterCrates === null && params["filter-crate"] !== undefined) {
1791 filterCrates = params["filter-crate"];
1792 }
1793
cdc7bbd5 1794 // Update document title to maintain a meaningful browser history
04454e1e 1795 searchState.title = "Results for " + query.original + " - Rust";
cdc7bbd5
XL
1796
1797 // Because searching is incremental by character, only the most
1798 // recent search query is added to the browser history.
04454e1e
FG
1799 if (browserSupportsHistoryApi()) {
1800 const newURL = buildUrl(query.original, filterCrates);
5099ac24 1801
cdc7bbd5 1802 if (!history.state && !params.search) {
5099ac24 1803 history.pushState(null, "", newURL);
cdc7bbd5 1804 } else {
5099ac24 1805 history.replaceState(null, "", newURL);
cdc7bbd5
XL
1806 }
1807 }
1808
04454e1e 1809 showResults(
923072b8 1810 execQuery(query, searchWords, filterCrates, window.currentCrate),
04454e1e
FG
1811 params.go_to_first,
1812 filterCrates);
cdc7bbd5
XL
1813 }
1814
064997fb
FG
1815 /**
1816 * Convert a list of RawFunctionType / ID to object-based FunctionType.
1817 *
1818 * Crates often have lots of functions in them, and it's common to have a large number of
1819 * functions that operate on a small set of data types, so the search index compresses them
1820 * by encoding function parameter and return types as indexes into an array of names.
1821 *
1822 * Even when a general-purpose compression algorithm is used, this is still a win. I checked.
1823 * https://github.com/rust-lang/rust/pull/98475#issue-1284395985
1824 *
1825 * The format for individual function types is encoded in
1826 * librustdoc/html/render/mod.rs: impl Serialize for RenderType
1827 *
1828 * @param {null|Array<RawFunctionType>} types
1829 * @param {Array<{name: string, ty: number}>} lowercasePaths
1830 *
1831 * @return {Array<FunctionSearchType>}
1832 */
1833 function buildItemSearchTypeAll(types, lowercasePaths) {
1834 const PATH_INDEX_DATA = 0;
1835 const GENERICS_DATA = 1;
1836 return types.map(type => {
1837 let pathIndex, generics;
1838 if (typeof type === "number") {
1839 pathIndex = type;
1840 generics = [];
1841 } else {
1842 pathIndex = type[PATH_INDEX_DATA];
1843 generics = buildItemSearchTypeAll(type[GENERICS_DATA], lowercasePaths);
1844 }
1845 return {
1846 // `0` is used as a sentinel because it's fewer bytes than `null`
1847 name: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].name,
1848 ty: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].ty,
1849 generics: generics,
1850 };
1851 });
1852 }
1853
1854 /**
1855 * Convert from RawFunctionSearchType to FunctionSearchType.
1856 *
1857 * Crates often have lots of functions in them, and function signatures are sometimes complex,
1858 * so rustdoc uses a pretty tight encoding for them. This function converts it to a simpler,
1859 * object-based encoding so that the actual search code is more readable and easier to debug.
1860 *
1861 * The raw function search type format is generated using serde in
1862 * librustdoc/html/render/mod.rs: impl Serialize for IndexItemFunctionType
1863 *
1864 * @param {RawFunctionSearchType} functionSearchType
1865 * @param {Array<{name: string, ty: number}>} lowercasePaths
1866 *
1867 * @return {null|FunctionSearchType}
1868 */
1869 function buildFunctionSearchType(functionSearchType, lowercasePaths) {
1870 const INPUTS_DATA = 0;
1871 const OUTPUT_DATA = 1;
1872 // `0` is used as a sentinel because it's fewer bytes than `null`
1873 if (functionSearchType === 0) {
1874 return null;
1875 }
1876 let inputs, output;
1877 if (typeof functionSearchType[INPUTS_DATA] === "number") {
1878 const pathIndex = functionSearchType[INPUTS_DATA];
1879 inputs = [{
1880 name: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].name,
1881 ty: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].ty,
1882 generics: [],
1883 }];
1884 } else {
1885 inputs = buildItemSearchTypeAll(functionSearchType[INPUTS_DATA], lowercasePaths);
1886 }
1887 if (functionSearchType.length > 1) {
1888 if (typeof functionSearchType[OUTPUT_DATA] === "number") {
1889 const pathIndex = functionSearchType[OUTPUT_DATA];
1890 output = [{
1891 name: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].name,
1892 ty: pathIndex === 0 ? null : lowercasePaths[pathIndex - 1].ty,
1893 generics: [],
1894 }];
1895 } else {
1896 output = buildItemSearchTypeAll(functionSearchType[OUTPUT_DATA], lowercasePaths);
1897 }
1898 } else {
1899 output = [];
1900 }
1901 return {
1902 inputs, output,
1903 };
1904 }
1905
cdc7bbd5
XL
1906 function buildIndex(rawSearchIndex) {
1907 searchIndex = [];
a2a8927a
XL
1908 /**
1909 * @type {Array<string>}
1910 */
04454e1e
FG
1911 const searchWords = [];
1912 let i, word;
1913 let currentIndex = 0;
1914 let id = 0;
cdc7bbd5 1915
04454e1e 1916 for (const crate in rawSearchIndex) {
17df50a5
XL
1917 if (!hasOwnPropertyRustdoc(rawSearchIndex, crate)) {
1918 continue;
1919 }
cdc7bbd5 1920
04454e1e 1921 let crateSize = 0;
cdc7bbd5 1922
a2a8927a
XL
1923 /**
1924 * The raw search data for a given crate. `n`, `t`, `d`, and `q`, `i`, and `f`
1925 * are arrays with the same length. n[i] contains the name of an item.
1926 * t[i] contains the type of that item (as a small integer that represents an
1927 * offset in `itemTypes`). d[i] contains the description of that item.
1928 *
1929 * q[i] contains the full path of the item, or an empty string indicating
1930 * "same as q[i-1]".
1931 *
064997fb
FG
1932 * i[i] contains an item's parent, usually a module. For compactness,
1933 * it is a set of indexes into the `p` array.
1934 *
1935 * f[i] contains function signatures, or `0` if the item isn't a function.
1936 * Functions are themselves encoded as arrays. The first item is a list of
1937 * types representing the function's inputs, and the second list item is a list
1938 * of types representing the function's output. Tuples are flattened.
1939 * Types are also represented as arrays; the first item is an index into the `p`
1940 * array, while the second is a list of types representing any generic parameters.
a2a8927a
XL
1941 *
1942 * `a` defines aliases with an Array of pairs: [name, offset], where `offset`
1943 * points into the n/t/d/q/i/f arrays.
1944 *
1945 * `doc` contains the description of the crate.
1946 *
064997fb 1947 * `p` is a list of path/type pairs. It is used for parents and function parameters.
a2a8927a
XL
1948 *
1949 * @type {{
1950 * doc: string,
1951 * a: Object,
1952 * n: Array<string>,
1953 * t: Array<Number>,
1954 * d: Array<string>,
1955 * q: Array<string>,
1956 * i: Array<Number>,
064997fb 1957 * f: Array<RawFunctionSearchType>,
a2a8927a
XL
1958 * p: Array<Object>,
1959 * }}
1960 */
04454e1e 1961 const crateCorpus = rawSearchIndex[crate];
a2a8927a 1962
cdc7bbd5 1963 searchWords.push(crate);
cdc7bbd5
XL
1964 // This object should have exactly the same set of fields as the "row"
1965 // object defined below. Your JavaScript runtime will thank you.
1966 // https://mathiasbynens.be/notes/shapes-ics
04454e1e 1967 const crateRow = {
cdc7bbd5
XL
1968 crate: crate,
1969 ty: 1, // == ExternCrate
1970 name: crate,
1971 path: "",
a2a8927a 1972 desc: crateCorpus.doc,
cdc7bbd5
XL
1973 parent: undefined,
1974 type: null,
1975 id: id,
17df50a5 1976 normalizedName: crate.indexOf("_") === -1 ? crate : crate.replace(/_/g, ""),
cdc7bbd5
XL
1977 };
1978 id += 1;
1979 searchIndex.push(crateRow);
1980 currentIndex += 1;
1981
1982 // an array of (Number) item types
04454e1e 1983 const itemTypes = crateCorpus.t;
cdc7bbd5 1984 // an array of (String) item names
04454e1e 1985 const itemNames = crateCorpus.n;
cdc7bbd5 1986 // an array of (String) full paths (or empty string for previous path)
04454e1e 1987 const itemPaths = crateCorpus.q;
cdc7bbd5 1988 // an array of (String) descriptions
04454e1e 1989 const itemDescs = crateCorpus.d;
cdc7bbd5 1990 // an array of (Number) the parent path index + 1 to `paths`, or 0 if none
04454e1e 1991 const itemParentIdxs = crateCorpus.i;
cdc7bbd5 1992 // an array of (Object | null) the type of the function, if any
04454e1e 1993 const itemFunctionSearchTypes = crateCorpus.f;
cdc7bbd5
XL
1994 // an array of [(Number) item type,
1995 // (String) name]
04454e1e 1996 const paths = crateCorpus.p;
94222f64 1997 // an array of [(String) alias name
cdc7bbd5 1998 // [Number] index to items]
04454e1e 1999 const aliases = crateCorpus.a;
cdc7bbd5 2000
064997fb
FG
2001 // an array of [{name: String, ty: Number}]
2002 const lowercasePaths = [];
2003
cdc7bbd5 2004 // convert `rawPaths` entries into object form
064997fb 2005 // generate normalizedPaths for function search mode
04454e1e 2006 let len = paths.length;
cdc7bbd5 2007 for (i = 0; i < len; ++i) {
064997fb 2008 lowercasePaths.push({ty: paths[i][0], name: paths[i][1].toLowerCase()});
cdc7bbd5
XL
2009 paths[i] = {ty: paths[i][0], name: paths[i][1]};
2010 }
2011
2012 // convert `item*` into an object form, and construct word indices.
2013 //
2014 // before any analysis is performed lets gather the search terms to
2015 // search against apart from the rest of the data. This is a quick
2016 // operation that is cached for the life of the page state so that
2017 // all other search operations have access to this cached data for
2018 // faster analysis operations
2019 len = itemTypes.length;
04454e1e 2020 let lastPath = "";
cdc7bbd5
XL
2021 for (i = 0; i < len; ++i) {
2022 // This object should have exactly the same set of fields as the "crateRow"
2023 // object defined above.
2024 if (typeof itemNames[i] === "string") {
2025 word = itemNames[i].toLowerCase();
2026 searchWords.push(word);
2027 } else {
2028 word = "";
2029 searchWords.push("");
2030 }
04454e1e 2031 const row = {
cdc7bbd5
XL
2032 crate: crate,
2033 ty: itemTypes[i],
2034 name: itemNames[i],
2035 path: itemPaths[i] ? itemPaths[i] : lastPath,
2036 desc: itemDescs[i],
2037 parent: itemParentIdxs[i] > 0 ? paths[itemParentIdxs[i] - 1] : undefined,
064997fb 2038 type: buildFunctionSearchType(itemFunctionSearchTypes[i], lowercasePaths),
cdc7bbd5 2039 id: id,
17df50a5 2040 normalizedName: word.indexOf("_") === -1 ? word : word.replace(/_/g, ""),
cdc7bbd5
XL
2041 };
2042 id += 1;
2043 searchIndex.push(row);
2044 lastPath = row.path;
2045 crateSize += 1;
2046 }
2047
2048 if (aliases) {
04454e1e
FG
2049 ALIASES[crate] = Object.create(null);
2050 for (const alias_name in aliases) {
17df50a5
XL
2051 if (!hasOwnPropertyRustdoc(aliases, alias_name)) {
2052 continue;
2053 }
cdc7bbd5 2054
17df50a5 2055 if (!hasOwnPropertyRustdoc(ALIASES[crate], alias_name)) {
cdc7bbd5
XL
2056 ALIASES[crate][alias_name] = [];
2057 }
04454e1e
FG
2058 for (const local_alias of aliases[alias_name]) {
2059 ALIASES[crate][alias_name].push(local_alias + currentIndex);
cdc7bbd5
XL
2060 }
2061 }
2062 }
2063 currentIndex += crateSize;
2064 }
2065 return searchWords;
2066 }
2067
a2a8927a
XL
2068 /**
2069 * Callback for when the search form is submitted.
2070 * @param {Event} [e] - The event that triggered this call, if any
2071 */
2072 function onSearchSubmit(e) {
2073 e.preventDefault();
2074 searchState.clearInputTimeout();
2075 search();
2076 }
2077
5099ac24 2078 function putBackSearch() {
04454e1e 2079 const search_input = searchState.input;
5099ac24
FG
2080 if (!searchState.input) {
2081 return;
2082 }
04454e1e
FG
2083 if (search_input.value !== "" && !searchState.isDisplayed()) {
2084 searchState.showResults();
2085 if (browserSupportsHistoryApi()) {
5099ac24
FG
2086 history.replaceState(null, "",
2087 buildUrl(search_input.value, getFilterCrates()));
2088 }
2089 document.title = searchState.title;
2090 }
2091 }
2092
cdc7bbd5 2093 function registerSearchEvents() {
923072b8
FG
2094 const params = searchState.getQueryStringParams();
2095
2096 // Populate search bar with query string search term when provided,
2097 // but only if the input bar is empty. This avoid the obnoxious issue
2098 // where you start trying to do a search, and the index loads, and
2099 // suddenly your search is gone!
2100 if (searchState.input.value === "") {
2101 searchState.input.value = params.search || "";
2102 }
2103
04454e1e 2104 const searchAfter500ms = () => {
cdc7bbd5
XL
2105 searchState.clearInputTimeout();
2106 if (searchState.input.value.length === 0) {
04454e1e 2107 if (browserSupportsHistoryApi()) {
5099ac24 2108 history.replaceState(null, window.currentCrate + " - Rust",
cdc7bbd5
XL
2109 getNakedUrl() + window.location.hash);
2110 }
2111 searchState.hideResults();
2112 } else {
2113 searchState.timeout = setTimeout(search, 500);
2114 }
2115 };
2116 searchState.input.onkeyup = searchAfter500ms;
2117 searchState.input.oninput = searchAfter500ms;
a2a8927a 2118 document.getElementsByClassName("search-form")[0].onsubmit = onSearchSubmit;
04454e1e 2119 searchState.input.onchange = e => {
cdc7bbd5
XL
2120 if (e.target !== document.activeElement) {
2121 // To prevent doing anything when it's from a blur event.
2122 return;
2123 }
2124 // Do NOT e.preventDefault() here. It will prevent pasting.
2125 searchState.clearInputTimeout();
2126 // zero-timeout necessary here because at the time of event handler execution the
2127 // pasted content is not in the input field yet. Shouldn’t make any difference for
2128 // change, though.
2129 setTimeout(search, 0);
2130 };
2131 searchState.input.onpaste = searchState.input.onchange;
2132
04454e1e 2133 searchState.outputElement().addEventListener("keydown", e => {
17df50a5
XL
2134 // We only handle unmodified keystrokes here. We don't want to interfere with,
2135 // for instance, alt-left and alt-right for history navigation.
2136 if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) {
2137 return;
2138 }
2139 // up and down arrow select next/previous search result, or the
2140 // search box if we're already at the top.
2141 if (e.which === 38) { // up
04454e1e 2142 const previous = document.activeElement.previousElementSibling;
17df50a5
XL
2143 if (previous) {
2144 previous.focus();
2145 } else {
2146 searchState.focus();
2147 }
2148 e.preventDefault();
2149 } else if (e.which === 40) { // down
04454e1e 2150 const next = document.activeElement.nextElementSibling;
17df50a5
XL
2151 if (next) {
2152 next.focus();
2153 }
04454e1e 2154 const rect = document.activeElement.getBoundingClientRect();
17df50a5
XL
2155 if (window.innerHeight - rect.bottom < rect.height) {
2156 window.scrollBy(0, rect.height);
2157 }
2158 e.preventDefault();
2159 } else if (e.which === 37) { // left
2160 nextTab(-1);
2161 e.preventDefault();
2162 } else if (e.which === 39) { // right
2163 nextTab(1);
2164 e.preventDefault();
2165 }
2166 });
2167
04454e1e 2168 searchState.input.addEventListener("keydown", e => {
17df50a5
XL
2169 if (e.which === 40) { // down
2170 focusSearchResult();
2171 e.preventDefault();
2172 }
2173 });
2174
04454e1e 2175 searchState.input.addEventListener("focus", () => {
5099ac24
FG
2176 putBackSearch();
2177 });
17df50a5 2178
04454e1e 2179 searchState.input.addEventListener("blur", () => {
5099ac24
FG
2180 searchState.input.placeholder = searchState.input.origPlaceholder;
2181 });
cdc7bbd5
XL
2182
2183 // Push and pop states are used to add search results to the browser
2184 // history.
04454e1e 2185 if (browserSupportsHistoryApi()) {
cdc7bbd5 2186 // Store the previous <title> so we can revert back to it later.
04454e1e 2187 const previousTitle = document.title;
cdc7bbd5 2188
04454e1e
FG
2189 window.addEventListener("popstate", e => {
2190 const params = searchState.getQueryStringParams();
cdc7bbd5
XL
2191 // Revert to the previous title manually since the History
2192 // API ignores the title parameter.
2193 document.title = previousTitle;
2194 // When browsing forward to search results the previous
2195 // search will be repeated, so the currentResults are
2196 // cleared to ensure the search is successful.
2197 currentResults = null;
2198 // Synchronize search bar with query string state and
2199 // perform the search. This will empty the bar if there's
2200 // nothing there, which lets you really go back to a
2201 // previous state with nothing in the bar.
2202 if (params.search && params.search.length > 0) {
2203 searchState.input.value = params.search;
2204 // Some browsers fire "onpopstate" for every page load
2205 // (Chrome), while others fire the event only when actually
2206 // popping a state (Firefox), which is why search() is
2207 // called both here and at the end of the startSearch()
2208 // function.
2209 search(e);
2210 } else {
2211 searchState.input.value = "";
2212 // When browsing back from search results the main page
2213 // visibility must be reset.
2214 searchState.hideResults();
2215 }
2216 });
2217 }
2218
2219 // This is required in firefox to avoid this problem: Navigating to a search result
2220 // with the keyboard, hitting enter, and then hitting back would take you back to
2221 // the doc page, rather than the search that should overlay it.
2222 // This was an interaction between the back-forward cache and our handlers
2223 // that try to sync state between the URL and the search input. To work around it,
2224 // do a small amount of re-init on page show.
04454e1e
FG
2225 window.onpageshow = () => {
2226 const qSearch = searchState.getQueryStringParams().search;
cdc7bbd5
XL
2227 if (searchState.input.value === "" && qSearch) {
2228 searchState.input.value = qSearch;
2229 }
2230 search();
2231 };
2232 }
2233
5099ac24 2234 function updateCrate(ev) {
f2b60f7d 2235 if (ev.target.value === "all crates") {
5099ac24 2236 // If we don't remove it from the URL, it'll be picked up again by the search.
04454e1e
FG
2237 const params = searchState.getQueryStringParams();
2238 const query = searchState.input.value.trim();
5099ac24
FG
2239 if (!history.state && !params.search) {
2240 history.pushState(null, "", buildUrl(query, null));
2241 } else {
2242 history.replaceState(null, "", buildUrl(query, null));
2243 }
2244 }
2245 // In case you "cut" the entry from the search input, then change the crate filter
2246 // before paste back the previous search, you get the old search results without
2247 // the filter. To prevent this, we need to remove the previous results.
2248 currentResults = null;
2249 search(undefined, true);
2250 }
2251
04454e1e
FG
2252 /**
2253 * @type {Array<string>}
2254 */
2255 const searchWords = buildIndex(rawSearchIndex);
923072b8
FG
2256 if (typeof window !== "undefined") {
2257 registerSearchEvents();
5099ac24 2258 // If there's a search term in the URL, execute the search now.
923072b8 2259 if (window.searchState.getQueryStringParams().search) {
5099ac24
FG
2260 search();
2261 }
cdc7bbd5 2262 }
5099ac24 2263
923072b8
FG
2264 if (typeof exports !== "undefined") {
2265 exports.initSearch = initSearch;
2266 exports.execQuery = execQuery;
2267 exports.parseQuery = parseQuery;
2268 }
2269 return searchWords;
2270}
cdc7bbd5 2271
923072b8
FG
2272if (typeof window !== "undefined") {
2273 window.initSearch = initSearch;
2274 if (window.searchIndex !== undefined) {
2275 initSearch(window.searchIndex);
2276 }
2277} else {
2278 // Running in Node, not a browser. Run initSearch just to produce the
2279 // exports.
2280 initSearch({});
cdc7bbd5
XL
2281}
2282
923072b8 2283
cdc7bbd5 2284})();