|
7 | 7 | * 3. Match types: |
8 | 8 | * - Namespace queries (::) and method queries (# or .) match against full_name |
9 | 9 | * - Regular queries match against unqualified name |
10 | | - * - Prefix matches rank higher than substring matches |
| 10 | + * - Prefix match (1000) > substring match (100) > fuzzy match (10) |
11 | 11 | * 4. First character determines type priority: |
12 | 12 | * - Starts with lowercase: methods first |
13 | 13 | * - Starts with uppercase: classes/modules/constants first |
|
22 | 22 | var MAX_RESULTS = 30; |
23 | 23 | var MIN_QUERY_LENGTH = 1; |
24 | 24 |
|
| 25 | +/** |
| 26 | + * Check if all characters in query appear in order in target |
| 27 | + * e.g., "addalias" fuzzy matches "add_foo_alias" |
| 28 | + */ |
| 29 | +function fuzzyMatch(target, query) { |
| 30 | + var ti = 0; |
| 31 | + for (var qi = 0; qi < query.length; qi++) { |
| 32 | + ti = target.indexOf(query[qi], ti); |
| 33 | + if (ti === -1) return false; |
| 34 | + ti++; |
| 35 | + } |
| 36 | + return true; |
| 37 | +} |
| 38 | + |
25 | 39 | /** |
26 | 40 | * Parse and normalize a search query |
27 | 41 | * @param {string} query - The raw search query |
@@ -107,18 +121,22 @@ function computeScore(entry, q) { |
107 | 121 | // For namespace queries like "Foo::Bar" or method queries like "Array#filter", |
108 | 122 | // match against full_name |
109 | 123 | if (fullNameLower.startsWith(q.normalized)) { |
110 | | - matchScore = 1000; // Prefix match on full_name |
| 124 | + matchScore = 1000; // Prefix (e.g., "Arr" matches "Array") |
111 | 125 | } else if (fullNameLower.includes(q.normalized)) { |
112 | | - matchScore = 100; // Substring match on full_name |
| 126 | + matchScore = 100; // Substring (e.g., "ray" matches "Array") |
| 127 | + } else if (fuzzyMatch(fullNameLower, q.normalized)) { |
| 128 | + matchScore = 10; // Fuzzy (e.g., "addalias" matches "add_foo_alias") |
113 | 129 | } else { |
114 | 130 | return null; |
115 | 131 | } |
116 | 132 | } else { |
117 | 133 | // For regular queries, match against unqualified name |
118 | 134 | if (nameLower.startsWith(q.normalized)) { |
119 | | - matchScore = 1000; |
| 135 | + matchScore = 1000; // Prefix |
120 | 136 | } else if (nameLower.includes(q.normalized)) { |
121 | | - matchScore = 100; |
| 137 | + matchScore = 100; // Substring |
| 138 | + } else if (fuzzyMatch(nameLower, q.normalized)) { |
| 139 | + matchScore = 10; // Fuzzy |
122 | 140 | } else { |
123 | 141 | return null; |
124 | 142 | } |
@@ -206,23 +224,26 @@ function highlightMatch(text, q) { |
206 | 224 | if (!text || !q) return text; |
207 | 225 |
|
208 | 226 | var textLower = text.toLowerCase(); |
209 | | - var queryLen = q.normalized.length; |
210 | | - var result = ''; |
211 | | - var lastIndex = 0; |
212 | | - var matchIndex = textLower.indexOf(q.normalized); |
213 | | - |
214 | | - while (matchIndex !== -1) { |
215 | | - // Add text before match |
216 | | - result += text.substring(lastIndex, matchIndex); |
217 | | - // Add highlighted match |
218 | | - result += '\u0001' + text.substring(matchIndex, matchIndex + queryLen) + '\u0002'; |
219 | | - lastIndex = matchIndex + queryLen; |
220 | | - // Find next match |
221 | | - matchIndex = textLower.indexOf(q.normalized, lastIndex); |
| 227 | + var query = q.normalized; |
| 228 | + |
| 229 | + // Try contiguous match first (prefix or substring) |
| 230 | + var matchIndex = textLower.indexOf(query); |
| 231 | + if (matchIndex !== -1) { |
| 232 | + return text.substring(0, matchIndex) + |
| 233 | + '\u0001' + text.substring(matchIndex, matchIndex + query.length) + '\u0002' + |
| 234 | + text.substring(matchIndex + query.length); |
222 | 235 | } |
223 | 236 |
|
224 | | - // Add remaining text after last match |
225 | | - result += text.substring(lastIndex); |
226 | | - |
| 237 | + // Fall back to fuzzy highlight (highlight each matched character) |
| 238 | + var result = ''; |
| 239 | + var ti = 0; |
| 240 | + for (var qi = 0; qi < query.length; qi++) { |
| 241 | + var charIndex = textLower.indexOf(query[qi], ti); |
| 242 | + if (charIndex === -1) return text; |
| 243 | + result += text.substring(ti, charIndex); |
| 244 | + result += '\u0001' + text[charIndex] + '\u0002'; |
| 245 | + ti = charIndex + 1; |
| 246 | + } |
| 247 | + result += text.substring(ti); |
227 | 248 | return result; |
228 | 249 | } |
0 commit comments