Skip to content

Commit 6b21087

Browse files
committed
Improved the search by including a levenshtein distance calculation which enables the docs search function to be more forgiving for spelling mistakes
1 parent eda75bc commit 6b21087

File tree

1 file changed

+86
-44
lines changed

1 file changed

+86
-44
lines changed

src/librustdoc/html/static/main.js

+86-44
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
}
5959
$('#' + from)[0].scrollIntoView();
6060
$('.line-numbers span').removeClass('line-highlighted');
61-
for (i = from; i <= to; i += 1) {
61+
for (i = from; i <= to; ++i) {
6262
$('#' + i).addClass('line-highlighted');
6363
}
6464
}
@@ -99,7 +99,7 @@
9999
stripped = '',
100100
len = rootPath.match(/\.\.\//g).length + 1;
101101

102-
for (i = 0; i < len; i += 1) {
102+
for (i = 0; i < len; ++i) {
103103
match = url.match(/\/[^\/]*$/);
104104
if (i < len - 1) {
105105
stripped = match[0] + stripped;
@@ -111,9 +111,44 @@
111111

112112
document.location.href = url;
113113
});
114+
/**
115+
* Code from Stackoverflow to compute the Levenshtein distance between two strings
116+
* Written by Marco de Wit at http://stackoverflow.com/a/18514751/745719
117+
*/
118+
var levenshtein = (function() {
119+
var row2 = [];
120+
return function(s1, s2) {
121+
if (s1 === s2) {
122+
return 0;
123+
} else {
124+
var s1_len = s1.length, s2_len = s2.length;
125+
if (s1_len && s2_len) {
126+
var i1 = 0, i2 = 0, a, b, c, c2, row = row2;
127+
while (i1 < s1_len)
128+
row[i1] = ++i1;
129+
while (i2 < s2_len) {
130+
c2 = s2.charCodeAt(i2);
131+
a = i2;
132+
++i2;
133+
b = i2;
134+
for (i1 = 0; i1 < s1_len; ++i1) {
135+
c = a + (s1.charCodeAt(i1) !== c2 ? 1 : 0);
136+
a = row[i1];
137+
b = b < a ? (b < c ? b + 1 : c) : (a < c ? a + 1 : c);
138+
row[i1] = b;
139+
}
140+
}
141+
return b;
142+
} else {
143+
return s1_len + s2_len;
144+
}
145+
}
146+
};
147+
})();
114148

115149
function initSearch(rawSearchIndex) {
116150
var currentResults, index, searchIndex;
151+
var MAX_LEV_DISTANCE = 3;
117152
var params = getQueryStringParams();
118153

119154
// Populate search bar with query string search term when provided,
@@ -140,7 +175,7 @@
140175
split = valLower.split("::");
141176

142177
//remove empty keywords
143-
for (var j = 0; j < split.length; j++) {
178+
for (var j = 0; j < split.length; ++j) {
144179
split[j].toLowerCase();
145180
if (split[j] === "") {
146181
split.splice(j, 1);
@@ -153,7 +188,7 @@
153188
val.charAt(val.length - 1) === val.charAt(0))
154189
{
155190
val = val.substr(1, val.length - 2);
156-
for (var i = 0; i < nSearchWords; i += 1) {
191+
for (var i = 0; i < nSearchWords; ++i) {
157192
if (searchWords[i] === val) {
158193
// filter type: ... queries
159194
if (typeFilter < 0 || typeFilter === searchIndex[i].ty) {
@@ -167,15 +202,31 @@
167202
} else {
168203
// gather matching search results up to a certain maximum
169204
val = val.replace(/\_/g, "");
170-
for (var i = 0; i < split.length; i++) {
171-
for (var j = 0; j < nSearchWords; j += 1) {
205+
for (var i = 0; i < split.length; ++i) {
206+
for (var j = 0; j < nSearchWords; ++j) {
207+
var lev_distance;
172208
if (searchWords[j].indexOf(split[i]) > -1 ||
173209
searchWords[j].indexOf(val) > -1 ||
174210
searchWords[j].replace(/_/g, "").indexOf(val) > -1)
175211
{
176212
// filter type: ... queries
177213
if (typeFilter < 0 || typeFilter === searchIndex[j].ty) {
178-
results.push({id: j, index: searchWords[j].replace(/_/g, "").indexOf(val)});
214+
results.push({
215+
id: j,
216+
index: searchWords[j].replace(/_/g, "").indexOf(val),
217+
lev: 0,
218+
});
219+
}
220+
} else if (
221+
(lev_distance = levenshtein(searchWords[j], val)) <=
222+
MAX_LEV_DISTANCE) {
223+
if (typeFilter < 0 || typeFilter === searchIndex[j].ty) {
224+
results.push({
225+
id: j,
226+
index: 0,
227+
// we want lev results to go lower than others
228+
lev: lev_distance,
229+
});
179230
}
180231
}
181232
if (results.length === max) {
@@ -186,7 +237,7 @@
186237
}
187238

188239
var nresults = results.length;
189-
for (var i = 0; i < nresults; i += 1) {
240+
for (var i = 0; i < nresults; ++i) {
190241
results[i].word = searchWords[results[i].id];
191242
results[i].item = searchIndex[results[i].id] || {};
192243
}
@@ -198,6 +249,12 @@
198249
results.sort(function(aaa, bbb) {
199250
var a, b;
200251

252+
// Sort by non levenshtein results and then levenshtein results by the distance
253+
// (less changes required to match means higher rankings)
254+
a = (aaa.lev);
255+
b = (bbb.lev);
256+
if (a !== b) return a - b;
257+
201258
// sort by crate (non-current crate goes later)
202259
a = (aaa.item.crate !== window.currentCrate);
203260
b = (bbb.item.crate !== window.currentCrate);
@@ -255,7 +312,7 @@
255312
results[i].id = -1;
256313
}
257314
}
258-
for (var i = 0; i < results.length; i++) {
315+
for (var i = 0; i < results.length; ++i) {
259316
var result = results[i],
260317
name = result.item.name.toLowerCase(),
261318
path = result.item.path.toLowerCase(),
@@ -285,38 +342,23 @@
285342
* @return {[boolean]} [Whether the result is valid or not]
286343
*/
287344
function validateResult(name, path, keys, parent) {
288-
//initially valid
289-
var validate = true;
290-
//if there is a parent, then validate against parent
291-
if (parent !== undefined) {
292-
for (var i = 0; i < keys.length; i++) {
293-
// if previous keys are valid and current key is in the
294-
// path, name or parent
295-
if ((validate) &&
296-
(name.toLowerCase().indexOf(keys[i]) > -1 ||
297-
path.toLowerCase().indexOf(keys[i]) > -1 ||
298-
parent.name.toLowerCase().indexOf(keys[i]) > -1))
299-
{
300-
validate = true;
301-
} else {
302-
validate = false;
303-
}
304-
}
305-
} else {
306-
for (var i = 0; i < keys.length; i++) {
307-
// if previous keys are valid and current key is in the
308-
// path, name
309-
if ((validate) &&
310-
(name.toLowerCase().indexOf(keys[i]) > -1 ||
311-
path.toLowerCase().indexOf(keys[i]) > -1))
312-
{
313-
validate = true;
314-
} else {
315-
validate = false;
316-
}
345+
for (var i=0; i < keys.length; ++i) {
346+
// each check is for validation so we negate the conditions and invalidate
347+
if (!(
348+
// check for an exact name match
349+
name.toLowerCase().indexOf(keys[i]) > -1 ||
350+
// then an exact path match
351+
path.toLowerCase().indexOf(keys[i]) > -1 ||
352+
// next if there is a parent, check for exact parent match
353+
(parent !== undefined &&
354+
parent.name.toLowerCase().indexOf(keys[i]) > -1) ||
355+
// lastly check to see if the name was a levenshtein match
356+
levenshtein(name.toLowerCase(), keys[i]) <=
357+
MAX_LEV_DISTANCE)) {
358+
return false;
317359
}
318360
}
319-
return validate;
361+
return true;
320362
}
321363

322364
function getQuery() {
@@ -496,7 +538,7 @@
496538

497539
resultIndex = execQuery(query, 20000, index);
498540
len = resultIndex.length;
499-
for (i = 0; i < len; i += 1) {
541+
for (i = 0; i < len; ++i) {
500542
if (resultIndex[i].id > -1) {
501543
obj = searchIndex[resultIndex[i].id];
502544
filterdata.push([obj.name, obj.ty, obj.path, obj.desc]);
@@ -568,7 +610,7 @@
568610
// faster analysis operations
569611
var len = items.length;
570612
var lastPath = "";
571-
for (var i = 0; i < len; i += 1) {
613+
for (var i = 0; i < len; ++i) {
572614
var rawRow = items[i];
573615
var row = {crate: crate, ty: rawRow[0], name: rawRow[1],
574616
path: rawRow[2] || lastPath, desc: rawRow[3],
@@ -640,7 +682,7 @@
640682
crates.push(crate);
641683
}
642684
crates.sort();
643-
for (var i = 0; i < crates.length; i++) {
685+
for (var i = 0; i < crates.length; ++i) {
644686
var klass = 'crate';
645687
if (crates[i] == window.currentCrate) {
646688
klass += ' current';
@@ -657,10 +699,10 @@
657699
window.register_implementors = function(imp) {
658700
var list = $('#implementors-list');
659701
var libs = Object.getOwnPropertyNames(imp);
660-
for (var i = 0; i < libs.length; i++) {
702+
for (var i = 0; i < libs.length; ++i) {
661703
if (libs[i] == currentCrate) continue;
662704
var structs = imp[libs[i]];
663-
for (var j = 0; j < structs.length; j++) {
705+
for (var j = 0; j < structs.length; ++j) {
664706
var code = $('<code>').append(structs[j]);
665707
$.each(code.find('a'), function(idx, a) {
666708
var href = $(a).attr('href');

0 commit comments

Comments
 (0)