Skip to content

Commit 16ae9e8

Browse files
authored
Improve search result by prioritizing exact matches (#1433)
I know adding 2 new phases for exact matches feels like a workaround, but without a more thorough review on the entire searching algorithm this is the best solution I have. I also added basic tests for `search.js` using `mini_racer` gem (dev dependency) to simulate JS evaluation. I chose this approach because it should be simpler than maintaining a whole set of JS dependencies and setups. Fixes #1194
1 parent 0c16330 commit 16ae9e8

File tree

3 files changed

+387
-5
lines changed

3 files changed

+387
-5
lines changed

Gemfile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,9 @@ gem 'rubocop', '>= 1.31.0'
1111
gem 'gettext'
1212
gem 'prism', '>= 0.30.0'
1313
gem 'webrick'
14+
15+
platforms :ruby do
16+
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.2')
17+
gem 'mini_racer' # For testing the searcher.js file
18+
end
19+
end

lib/rdoc/generator/template/json_index/js/searcher.js

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ Searcher.prototype = new function() {
2929

3030
var results =
3131
performSearch(_this.data, regexps, queries, highlighters, state);
32-
var hasMore = (state.limit > 0 && state.pass < 4);
32+
var hasMore = (state.limit > 0 && state.pass < 6);
3333

3434
triggerResults.call(_this, results, !hasMore);
3535
if (hasMore) {
@@ -85,6 +85,30 @@ Searcher.prototype = new function() {
8585

8686
/* ----- Mathchers ------ */
8787

88+
/*
89+
* This record matches if both the index and longIndex exactly equal queries[0]
90+
* and the record matches all of the regexps. This ensures top-level exact matches
91+
* like "String" are prioritized over nested classes like "Gem::Module::String".
92+
*/
93+
function matchPassExact(index, longIndex, queries) {
94+
return index == queries[0] && longIndex == queries[0];
95+
}
96+
97+
/*
98+
* This record matches if the index without "()" exactly equals queries[0].
99+
* This prioritizes methods like "attribute()" when searching for "attribute".
100+
*/
101+
function matchPassExactMethod(index, longIndex, queries, regexps) {
102+
var indexWithoutParens = index.replace(/\(\)$/, '');
103+
if (indexWithoutParens != queries[0]) return false;
104+
if (index === indexWithoutParens) return false; // Not a method (no parens to remove)
105+
for (var i=1, l = regexps.length; i < l; i++) {
106+
if (!index.match(regexps[i]) && !longIndex.match(regexps[i]))
107+
return false;
108+
};
109+
return true;
110+
}
111+
88112
/*
89113
* This record matches if the index starts with queries[0] and the record
90114
* matches all of the regexps
@@ -192,17 +216,26 @@ Searcher.prototype = new function() {
192216
var togo = CHUNK_SIZE;
193217
var matchFunc, hltFunc;
194218

195-
while (state.pass < 4 && state.limit > 0 && togo > 0) {
219+
var isLowercaseQuery = queries[0] === queries[0].toLowerCase();
220+
221+
while (state.pass < 6 && state.limit > 0 && togo > 0) {
222+
// When query is lowercase, prioritize methods over classes
196223
if (state.pass == 0) {
197-
matchFunc = matchPassBeginning;
224+
matchFunc = isLowercaseQuery ? matchPassExactMethod : matchPassExact;
198225
hltFunc = highlightQuery;
199226
} else if (state.pass == 1) {
200-
matchFunc = matchPassLongIndex;
227+
matchFunc = isLowercaseQuery ? matchPassExact : matchPassExactMethod;
201228
hltFunc = highlightQuery;
202229
} else if (state.pass == 2) {
203-
matchFunc = matchPassContains;
230+
matchFunc = matchPassBeginning;
204231
hltFunc = highlightQuery;
205232
} else if (state.pass == 3) {
233+
matchFunc = matchPassLongIndex;
234+
hltFunc = highlightQuery;
235+
} else if (state.pass == 4) {
236+
matchFunc = matchPassContains;
237+
hltFunc = highlightQuery;
238+
} else if (state.pass == 5) {
206239
matchFunc = matchPassRegexp;
207240
hltFunc = highlightRegexp;
208241
}

0 commit comments

Comments
 (0)