`, there is no name but it remains valid.
+ if (paths.length === 0) {
+ paths = [""];
+ }
+ elems.push({
+ isExact: isExact,
+ name: val,
+ fullPath: paths,
+ pathWithoutLast: paths.slice(0, paths.length - 1),
+ pathLast: paths[paths.length - 1],
+ generics: generics,
+ });
+ }
+ function getNextElem(query, elems) {
+ var isExact = false;
+ var generics = [];
+
+ skipStopCharacters(query);
+ var start = query.pos;
+ var end = start;
+ // We handle the strings on their own mostly to make code easier to follow.
+ if (query.val[query.pos] === "\"") {
+ isExact = true;
+ start += 1;
+ getStringElem(query);
+ end = query.pos - 1;
+ skipWhitespaces(query);
+ } else {
+ while (query.pos < query.length) {
+ var c = query.val[query.pos];
+ if (isStopCharacter(c) || isSpecialStartCharacter(c)) {
+ break;
+ }
+ // If we allow paths ("str::string" for example).
+ else if (c === ":") {
+ if (!isPathStart(query)) {
+ break;
+ }
+ // Skip current ":".
+ query.pos += 1;
+ }
+ query.pos += 1;
+ end = query.pos;
+ skipWhitespaces(query);
+ }
+ }
+ if (query.pos < query.length && query.val[query.pos] === "<") {
+ getItemsBefore(query, generics, ">");
+ }
+ if (start >= end && generics.length === 0) {
+ return;
+ }
+ createQueryElement(elems, query.val.slice(start, end), generics, isExact);
+ }
+ function getItemsBefore(query, elems, limit) {
+ var c;
+
+ while (query.pos < query.length) {
+ c = query.val[query.pos];
+ if (c === limit) {
+ break;
+ } else if (isSpecialStartCharacter(c) || c === ":") {
+ // Something weird is going on in here. Ignoring it!
+ query.pos += 1;
+ }
+ getNextElem(query, elems);
+ }
+ // We skip the "limit".
+ query.pos += 1;
+ }
+ function parseInput(query) {
+ var c, before;
+
+ while (query.pos < query.length) {
+ c = query.val[query.pos];
+ if (isStopCharacter(c)) {
+ if (c === ",") {
+ query.pos += 1;
+ continue;
+ } else if (c === "-" && isReturnArrow(query)) {
+ break;
+ }
+ } else if (c == "(") {
+ break;
+ } else if (c === ":" && query.typeFilter === null && !isPathStart(query) &&
+ query.elems.length === 1)
+ {
+ // The type filter doesn't count as an element since it's a modifier.
+ query.typeFilter = query.elems.pop().name;
+ query.pos += 1;
+ continue;
+ }
+ before = query.elems.length;
+ getNextElem(query, query.elems);
+ if (query.elems.length === before) {
+ // Nothing was added, let's check it's not because of a solo ":"!
+ if (query.pos >= query.length || query.val[query.pos] !== ":") {
+ break;
+ }
+ query.pos += 1;
+ }
+ }
+ while (query.pos < query.length) {
+ c = query.val[query.pos];
+ if (query.args.length === 0 && c === "(") {
+ if (query.elemName === null && query.elems.length === 1) {
+ query.elemName = query.elems.pop();
+ }
+ // Check for function/method arguments.
+ getItemsBefore(query, query.args, ")");
+ } else if (isReturnArrow(query)) {
+ // Get returned elements.
+ getItemsBefore(query, query.returned, "");
+ // Nothing can come afterward!
+ break;
+ } else {
+ query.pos += 1;
+ }
+ }
+ }
function itemTypeFromName(typename) {
for (var i = 0, len = itemTypes.length; i < len; ++i) {
if (itemTypes[i] === typename) {
@@ -187,13 +358,62 @@ window.initSearch = function(rawSearchIndex) {
return NO_TYPE_FILTER;
}
- var valLower = query.query.toLowerCase(),
- val = valLower,
- typeFilter = itemTypeFromName(query.type),
- results = {}, results_in_args = {}, results_returned = {},
- split = valLower.split("::");
+ val = val.trim();
+ var query = {
+ original: val,
+ val: val.toLowerCase(),
+ length: val.length,
+ pos: 0,
+ typeFilter: null,
+ elems: [],
+ elemName: null,
+ args: [],
+ returned: [],
+ foundElems: 0,
+ // This field is used to check if it's needed to re-run a search or not.
+ id: "",
+ // This field is used in `sortResults`.
+ nameSplit: null,
+ };
+ parseInput(query);
+ query.foundElems = query.elems.length + query.args.length + query.returned.length;
+ if (query.elemName !== null) {
+ query.foundElems += 1;
+ }
+ if (query.foundElems === 0 && val.length !== 0) {
+ // In this case, we'll simply keep whatever was entered by the user...
+ createQueryElement(query.elems, val, [], false);
+ query.foundElems += 1;
+ }
+ if (query.typeFilter !== null) {
+ query.typeFilter = query.typeFilter.replace(/^const$/, "constant");
+ query.typeFilter = itemTypeFromName(query.typeFilter);
+ } else {
+ query.typeFilter = NO_TYPE_FILTER;
+ }
+ query.id = val;
+ // In case we only have one argument, we move it back to `elems` to keep things simple.
+ if (query.foundElems === 1 && query.elemName !== null) {
+ query.elems.push(query.elemName);
+ query.elemName = null;
+ }
+ if (query.elemName !== null || query.elems.length === 1) {
+ val = query.elemName || query.elems[0];
+ query.nameSplit = typeof val.path === "undefined" ? null : val.path;
+ }
+ return query;
+ }
- removeEmptyStringsFromArray(split);
+ /**
+ * Executes the query and builds an index of results
+ * @param {[Object]} query [The user query]
+ * @param {[type]} searchWords [The list of search words to query
+ * against]
+ * @param {[type]} filterCrates [Crate to search in if defined]
+ * @return {[type]} [A search index of results]
+ */
+ function execQuery(queryInfo, searchWords, filterCrates) {
+ var results_others = {}, results_in_args = {}, results_returned = {};
function transformResults(results) {
var duplicates = {};
@@ -227,6 +447,8 @@ window.initSearch = function(rawSearchIndex) {
}
function sortResults(results, isType) {
+ var nameSplit = queryInfo.nameSplit;
+ var query = queryInfo.val;
var ar = [];
for (var entry in results) {
if (hasOwnPropertyRustdoc(results, entry)) {
@@ -246,8 +468,8 @@ window.initSearch = function(rawSearchIndex) {
var a, b;
// sort by exact match with regard to the last word (mismatch goes later)
- a = (aaa.word !== val);
- b = (bbb.word !== val);
+ a = (aaa.word !== query);
+ b = (bbb.word !== query);
if (a !== b) { return a - b; }
// Sort by non levenshtein results and then levenshtein results by the distance
@@ -320,77 +542,65 @@ window.initSearch = function(rawSearchIndex) {
path = result.item.path.toLowerCase(),
parent = result.item.parent;
- if (!isType && !validateResult(name, path, split, parent)) {
+ if (!isType && !validateResult(name, path, nameSplit, parent)) {
result.id = -1;
}
}
return transformResults(results);
}
- function extractGenerics(val) {
- val = val.toLowerCase();
- if (val.indexOf("<") !== -1) {
- var values = val.substring(val.indexOf("<") + 1, val.lastIndexOf(">"));
- return {
- name: val.substring(0, val.indexOf("<")),
- generics: values.split(/\s*,\s*/),
- };
+ /**
+ * This function checks if the object (`obj`) generics match the given type (`val`)
+ * generics. If there are no generics on `obj`, `defaultLev` is returned.
+ *
+ * @param {Object} obj - The object to check.
+ * @param {integer} defaultLev - This is the value to return in case there are no generics.
+ *
+ * @return {integer} - Returns the best match (if any) or `MAX_LEV_DISTANCE + 1`.
+ */
+ function checkGenerics(obj, val, defaultLev) {
+ if (obj.length <= GENERICS_DATA || obj[GENERICS_DATA].length === 0) {
+ return val.generics.length === 0 ? defaultLev : MAX_LEV_DISTANCE + 1;
}
- return {
- name: val,
- generics: [],
- };
- }
-
- function checkGenerics(obj, val) {
// The names match, but we need to be sure that all generics kinda
// match as well.
- var tmp_lev, elem_name;
- if (val.generics.length > 0) {
- if (obj.length > GENERICS_DATA &&
- obj[GENERICS_DATA].length >= val.generics.length) {
- var elems = Object.create(null);
- var elength = obj[GENERICS_DATA].length;
- for (var x = 0; x < elength; ++x) {
- if (!elems[obj[GENERICS_DATA][x][NAME]]) {
- elems[obj[GENERICS_DATA][x][NAME]] = 0;
- }
- elems[obj[GENERICS_DATA][x][NAME]] += 1;
+ var elem_name;
+ if (val.generics.length > 0 && obj[GENERICS_DATA].length >= val.generics.length) {
+ var elems = {};
+ for (var x = 0, length = obj[GENERICS_DATA].length; x < length; ++x) {
+ elem_name = obj[GENERICS_DATA][x][NAME];
+ if (!elems[elem_name]) {
+ elems[elem_name] = 0;
}
- var total = 0;
- var done = 0;
- // We need to find the type that matches the most to remove it in order
- // to move forward.
- var vlength = val.generics.length;
- for (x = 0; x < vlength; ++x) {
- var lev = MAX_LEV_DISTANCE + 1;
- var firstGeneric = val.generics[x];
- var match = null;
- if (elems[firstGeneric]) {
- match = firstGeneric;
- lev = 0;
- } else {
- for (elem_name in elems) {
- tmp_lev = levenshtein(elem_name, firstGeneric);
- if (tmp_lev < lev) {
- lev = tmp_lev;
- match = elem_name;
- }
- }
- }
- if (match !== null) {
- elems[match] -= 1;
- if (elems[match] == 0) {
- delete elems[match];
- }
- total += lev;
- done += 1;
- } else {
- return MAX_LEV_DISTANCE + 1;
- }
- }
- return Math.ceil(total / done);
+ elems[elem_name] += 1;
}
+ // We need to find the type that matches the most to remove it in order
+ // to move forward.
+ for (x = 0, length = val.generics.length; x < length; ++x) {
+ var generic = val.generics[x];
+ var match = null;
+ if (elems[generic.name]) {
+ match = generic.name;
+ } else {
+ for (elem_name in elems) {
+ if (!hasOwnPropertyRustdoc(elems, elem_name)) {
+ continue;
+ }
+ if (elem_name === generic) {
+ match = elem_name;
+ break;
+ }
+ }
+ }
+ if (match === null) {
+ return MAX_LEV_DISTANCE + 1;
+ }
+ elems[match] -= 1;
+ if (elems[match] === 0) {
+ delete elems[match];
+ }
+ }
+ return 0;
}
return MAX_LEV_DISTANCE + 1;
}
@@ -400,132 +610,136 @@ window.initSearch = function(rawSearchIndex) {
* generics (if any).
*
* @param {Object} obj
- * @param {string} val
- * @param {boolean} literalSearch
+ * @param {Object} val
+ *
+ * @return {integer} - Returns a Levenshtein distance to the best match.
+ */
+ function checkIfInGenerics(obj, val) {
+ var lev = MAX_LEV_DISTANCE + 1;
+ for (var x = 0, length = obj[GENERICS_DATA].length; x < length && lev !== 0; ++x) {
+ lev = Math.min(
+ checkType(obj[GENERICS_DATA][x], val),
+ lev
+ );
+ }
+ return lev;
+ }
+
+ /**
+ * This function checks if the object (`obj`) matches the given type (`val`) and its
+ * generics (if any).
+ *
+ * @param {Object} obj
+ * @param {Object} val
*
* @return {integer} - Returns a Levenshtein distance to the best match. If there is
* no match, returns `MAX_LEV_DISTANCE + 1`.
*/
- function checkType(obj, val, literalSearch) {
- var lev_distance = MAX_LEV_DISTANCE + 1;
- var tmp_lev = MAX_LEV_DISTANCE + 1;
- var len, x, firstGeneric;
- if (obj[NAME] === val.name) {
- if (literalSearch) {
- if (val.generics && val.generics.length !== 0) {
- if (obj.length > GENERICS_DATA &&
- obj[GENERICS_DATA].length > 0) {
- var elems = Object.create(null);
- len = obj[GENERICS_DATA].length;
- for (x = 0; x < len; ++x) {
- if (!elems[obj[GENERICS_DATA][x][NAME]]) {
- elems[obj[GENERICS_DATA][x][NAME]] = 0;
- }
- elems[obj[GENERICS_DATA][x][NAME]] += 1;
- }
+ function checkType(obj, val) {
+ if (val.name.length === 0 || obj[NAME].length === 0) {
+ // This is a pure "generic" search, no need to run other checks.
+ if (obj.length > GENERICS_DATA) {
+ return checkIfInGenerics(obj, val);
+ }
+ return MAX_LEV_DISTANCE + 1;
+ }
- len = val.generics.length;
- for (x = 0; x < len; ++x) {
- firstGeneric = val.generics[x];
- if (elems[firstGeneric]) {
- elems[firstGeneric] -= 1;
- } else {
- // Something wasn't found and this is a literal search so
- // abort and return a "failing" distance.
- return MAX_LEV_DISTANCE + 1;
- }
- }
- // Everything was found, success!
+ var lev = levenshtein(obj[NAME], val.name);
+ if (val.isExact) {
+ if (lev !== 0) {
+ // The name didn't match, let's try to check if the generics do.
+ if (val.generics.length === 0) {
+ var checkGeneric = (obj.length > GENERICS_DATA &&
+ obj[GENERICS_DATA].length > 0);
+ if (checkGeneric && obj[GENERICS_DATA].findIndex(function(elem) {
+ return elem[NAME] === val.name;
+ }) !== -1) {
return 0;
}
+ }
+ return MAX_LEV_DISTANCE + 1;
+ } else if (val.generics.length > 0) {
+ return checkGenerics(obj, val, MAX_LEV_DISTANCE + 1);
+ }
+ return 0;
+ } else if (obj.length > GENERICS_DATA) {
+ if (val.generics.length === 0) {
+ if (lev === 0) {
+ return 0;
+ }
+ // The name didn't match so we now check if the type we're looking for is inside
+ // the generics!
+ lev = checkIfInGenerics(obj, val);
+ // Now whatever happens, the returned distance is "less good" so we should mark
+ // it as such, and so we add 0.5 to the distance to make it "less good".
+ return lev + 0.5;
+ } else if (lev > MAX_LEV_DISTANCE) {
+ // So our item's name doesn't match at all and has generics.
+ //
+ // Maybe it's present in a sub generic? For example "f>>()", if we're
+ // looking for "B
" +
"Try on DuckDuckGo?
" +
"Or try looking in one of these:Results for ${escape(query.query)} ` +
- (query.type ? " (type: " + escape(query.type) + ")" : "") + "
" +
- crates +
- `Results for ${escape(results.query.val)}$` +
+ `${typeFilter}
in ${crates}