rustdoc-search: match path components on words

Since the length of a path is treated as sorting criteria, and
every path that contains the query without exactly matching it
must be longer, exact matches will always sort first if they exist.
This commit is contained in:
Michael Howell
2026-04-03 21:28:18 -07:00
parent 2972b5e59f
commit f1d240c040
4 changed files with 128 additions and 22 deletions
+34 -19
View File
@@ -2799,6 +2799,8 @@ class DocSearch {
result_list.sort((aaa, bbb) => {
const aai = aaa.item;
const bbi = bbb.item;
const ap = aai.modulePath !== undefined ? aai.modulePath : "";
const bp = bbi.modulePath !== undefined ? bbi.modulePath : "";
/** @type {number} */
let a;
/** @type {number} */
@@ -2829,14 +2831,25 @@ class DocSearch {
if (a !== b) {
return a - b;
}
}
// Sort by distance in the path part, if specified
// (less changes required to match means higher rankings)
a = Number(aaa.path_dist);
b = Number(bbb.path_dist);
if (a !== b) {
return a - b;
if (parsedQuery.elems[0] &&
parsedQuery.elems[0].pathWithoutLast.length !== 0
) {
// Sort by distance in the path part, if specified
// (less changes required to match means higher rankings)
a = Number(aaa.path_dist);
b = Number(bbb.path_dist);
if (a !== b) {
return a - b;
}
// sort by path (longer goes later)
a = ap.length + (aai.parent ? aai.parent.name.length + 2 : 0);
b = bp.length + (bbi.parent ? bbi.parent.name.length + 2 : 0);
if (a !== b) {
return a - b;
}
}
}
// (later literal occurrence, if any, goes later)
@@ -2890,8 +2903,8 @@ class DocSearch {
}
// sort by item name (lexicographically larger goes later)
let aw = aai.normalizedName;
let bw = bbi.normalizedName;
const aw = aai.normalizedName;
const bw = bbi.normalizedName;
if (aw !== bw) {
return (aw > bw ? +1 : -1);
}
@@ -2914,12 +2927,8 @@ class DocSearch {
}
// sort by path (lexicographically larger goes later)
const ap = aai.modulePath;
const bp = bbi.modulePath;
aw = ap === undefined ? "" : ap;
bw = bp === undefined ? "" : bp;
if (aw !== bw) {
return (aw > bw ? +1 : -1);
if (ap !== bp) {
return (ap > bp ? +1 : -1);
}
// que sera, sera
@@ -3848,13 +3857,19 @@ class DocSearch {
let dist_total = 0;
for (let x = 0; x < clength; ++x) {
const [p, c] = [path[i + x], contains[x]];
const indexOf = p.indexOf(c);
if (parsedQuery.literalSearch && p !== c) {
continue pathiter;
} else if (Math.floor((p.length - c.length) / 3) <= maxPathEditDistance &&
p.indexOf(c) !== -1
) {
} else if (indexOf !== -1) {
// discount distance on substring match
dist_total += Math.floor((p.length - c.length) / 3);
// if component is surrounded by underscores or edges,
// count the distance as zero
if (
(indexOf !== 0 && p[indexOf - 1] !== "_") ||
(indexOf + c.length !== p.length && p[indexOf + c.length] !== "_")
) {
dist_total += Math.floor((p.length - c.length) / 3);
}
} else {
const dist = editDistance(p, c, maxPathEditDistance);
if (dist > maxPathEditDistance) {
+12 -3
View File
@@ -10,15 +10,24 @@ const EXPECTED = [
query: 'vec::iter',
others: [
// std::net::ToSocketAttrs::iter should not show up here
{ 'path': 'std::collections::vec_deque', 'name': 'Iter' },
{ 'path': 'std::collections::VecDeque', 'name': 'iter' },
{ 'path': 'std::collections::vec_deque', 'name': 'IterMut' },
{ 'path': 'std::collections::VecDeque', 'name': 'iter_mut' },
{ 'path': 'std::vec::Vec', 'name': 'from_iter' },
{ 'path': 'std::vec', 'name': 'IntoIter' },
{ 'path': 'std::vec::Vec', 'name': 'from_iter' },
{ 'path': 'std::vec::Vec', 'name': 'into_iter' },
{ 'path': 'std::vec::ExtractIf', 'name': 'into_iter' },
{ 'path': 'std::vec::Drain', 'name': 'into_iter' },
{ 'path': 'std::vec::IntoIter', 'name': 'into_iter' },
{ 'path': 'std::vec::Splice', 'name': 'into_iter' },
{ 'path': 'std::vec::IntoIter', 'name': 'into_iter' },
{ 'path': 'std::vec::ExtractIf', 'name': 'into_iter' },
{ 'path': 'std::collections::vec_deque', 'name': 'IntoIter' },
{ 'path': 'std::collections::vec_deque::Iter', 'name': 'into_iter' },
{ 'path': 'std::collections::vec_deque::Drain', 'name': 'into_iter' },
{ 'path': 'std::collections::vec_deque::Splice', 'name': 'into_iter' },
{ 'path': 'std::collections::vec_deque::IterMut', 'name': 'into_iter' },
{ 'path': 'std::collections::vec_deque::IntoIter', 'name': 'into_iter' },
{ 'path': 'std::collections::vec_deque::ExtractIf', 'name': 'into_iter' },
{ 'path': 'std::collections::VecDeque', 'name': 'from_iter' },
{ 'path': 'std::collections::VecDeque', 'name': 'into_iter' },
],
+75
View File
@@ -0,0 +1,75 @@
// exact-check
// ignore-tidy-linelength
const EXPECTED = [
// should match (substring)
{
'query': 'struct:now::Country',
'others': [
{ 'path': 'x::now_is_the_time_for_all_good_men_to_come_to_the_aid_of_their', 'name': 'Country' },
],
},
{
'query': 'struct:is::Country',
'others': [
{ 'path': 'x::now_is_the_time_for_all_good_men_to_come_to_the_aid_of_their', 'name': 'Country' },
],
},
{
'query': 'struct:is_the::Country',
'others': [
{ 'path': 'x::now_is_the_time_for_all_good_men_to_come_to_the_aid_of_their', 'name': 'Country' },
],
},
{
'query': 'struct:the::Country',
'others': [
{ 'path': 'x::now_is_the_time_for_all_good_men_to_come_to_the_aid_of_their', 'name': 'Country' },
],
},
{
'query': 'struct:their::Country',
'others': [
{ 'path': 'x::now_is_the_time_for_all_good_men_to_come_to_the_aid_of_their', 'name': 'Country' },
],
},
// should not match
{
'query': 'struct:ood::Country',
'others': [],
},
{
'query': 'struct:goo::Country',
'others': [],
},
{
'query': 'struct:he::Country',
'others': [],
},
{
'query': 'struct:heir::Country',
'others': [],
},
{
'query': 'struct:hei::Country',
'others': [],
},
{
'query': 'struct:no::Country',
'others': [],
},
// should match (edit distance)
{
'query': 'struct:nowisthetimeforallgoodmentocometotheaidoftheir::Country',
'others': [
{ 'path': 'x::nowisthetimeforallgoodmentocometotheaidoftheir', 'name': 'Country' },
{ 'path': 'x::now_is_the_time_for_all_good_men_to_come_to_the_aid_of_their', 'name': 'Country' },
],
},
{
'query': 'struct:now_is_the_time_for_all_good_men_to_come_to_the_aid_of_their::Country',
'others': [
{ 'path': 'x::now_is_the_time_for_all_good_men_to_come_to_the_aid_of_their', 'name': 'Country' },
{ 'path': 'x::nowisthetimeforallgoodmentocometotheaidoftheir', 'name': 'Country' },
],
},
];
+7
View File
@@ -0,0 +1,7 @@
#![crate_name = "x"]
pub mod now_is_the_time_for_all_good_men_to_come_to_the_aid_of_their {
pub struct Country;
}
pub mod nowisthetimeforallgoodmentocometotheaidoftheir {
pub struct Country;
}