@@ -192,10 +192,10 @@ function worker_function(documenterSearchIndex, documenterBaseURL, filters) {
192192 processTerm : ( term ) => {
193193 let word = stopWords . has ( term ) ? null : term ;
194194 if ( word ) {
195- // custom trimmer that doesn't strip @ and !, which are used in julia macro and function names
195+ // custom trimmer that doesn't strip special characters `@!+-*/^&|%<>=:.` which are used in julia macro and function names.
196196 word = word
197- . replace ( / ^ [ ^ a - z A - Z 0 - 9 @ ! ] + / , "" )
198- . replace ( / [ ^ a - z A - Z 0 - 9 @ ! ] + $ / , "" ) ;
197+ . replace ( / ^ [ ^ a - z A - Z 0 - 9 @ ! + \- / * ^ & % | < > . _ = : ] + / , "" )
198+ . replace ( / [ ^ a - z A - Z 0 - 9 @ ! + \- / * ^ & % | < > . _ = : ] + $ / , "" ) ;
199199
200200 word = word . toLowerCase ( ) ;
201201 }
@@ -204,7 +204,52 @@ function worker_function(documenterSearchIndex, documenterBaseURL, filters) {
204204 } ,
205205 // add . as a separator, because otherwise "title": "Documenter.Anchors.add!", would not
206206 // find anything if searching for "add!", only for the entire qualification
207- tokenize : ( string ) => string . split ( / [ \s \- \. ] + / ) ,
207+ tokenize : ( string ) => {
208+ const tokens = [ ] ;
209+ let remaining = string ;
210+
211+ // julia specific patterns
212+ const patterns = [
213+ // Module qualified names (e.g., Base.sort, Module.Submodule. function)
214+ / \b [ A - Z a - z 0 - 9 _ 1 * ( ? : \. [ A - Z ] [ A - Z a - z 0 - 9 _ 1 * ) * \. [ a - z _ ] [ A - Z a - z 0 - 9 _ ! ] * \b / g,
215+ // Macro calls (e.g., @time, @async)
216+ / @ [ A - Z a - z 0 - 9 _ ] * / g,
217+ // Type parameters (e.g., Array{T,N}, Vector{Int})
218+ / \b [ A - Z a - z 0 - 9 _ ] * \{ [ ^ } ] + \} / g,
219+ // Function names with module qualification (e.g., Base.+, Base.:^)
220+ / \b [ A - Z a - z 0 - 9 _ ] * \. : [ A - Z a - z 0 - 9 _ ! + \- * / ^ & | % < > = . ] + / g,
221+ // Operators as complete tokens (e.g., !=, aã, ||, ^, .=, →)
222+ / [ ! < > = + \- * / ^ & | % : . ] + / g,
223+ // Function signatures with type annotations (e.g., f(x::Int))
224+ / \b [ A - Z a - z 0 - 9 _ ! ] * \( [ ^ ) ] * : : [ ^ ) ] * \) / g,
225+ // Numbers (integers, floats, scientific notation)
226+ / \b \d + (?: \. \d + ) ? (?: [ e E ] [ + - ] ? \d + ) ? \b / g,
227+ ] ;
228+
229+ // apply patterns in order of specificity
230+ for ( const pattern of patterns ) {
231+ pattern . lastIndex = 0 ; //reset regex state
232+ let match ;
233+ while ( ( match = pattern . exec ( remaining ) ) != null ) {
234+ const token = match [ 0 ] . trim ( ) ;
235+ if ( token && ! tokens . includes ( token ) ) {
236+ tokens . push ( token ) ;
237+ }
238+ }
239+ }
240+
241+ // splitting the content if something remains
242+ const basicTokens = remaining
243+ . split ( / [ \s \- , ; ( ) [ \] { } ] + / )
244+ . filter ( ( t ) => t . trim ( ) ) ;
245+ for ( const token of basicTokens ) {
246+ if ( token && ! tokens . includes ( token ) ) {
247+ tokens . push ( token ) ;
248+ }
249+ }
250+
251+ return tokens . filter ( ( token ) => token . length > 0 ) ;
252+ } ,
208253 // options which will be applied during the search
209254 searchOptions : {
210255 prefix : true ,
@@ -327,6 +372,35 @@ function worker_function(documenterSearchIndex, documenterBaseURL, filters) {
327372 return result_div ;
328373 }
329374
375+ function calculateCustomScore ( result , query ) {
376+ const titleLower = result . title . toLowerCase ( ) ;
377+ const queryLower = query . toLowerCase ( ) ;
378+
379+ // Tier 1 : Exact title match
380+ if ( titleLower == queryLower ) {
381+ return 10000 + result . score ;
382+ }
383+
384+ // Tier 2 : Title contains exact query
385+ if ( titleLower . includes ( queryLower ) ) {
386+ const position = titleLower . indexOf ( queryLower ) ;
387+ // prefer matches at the beginning
388+ return 5000 + result . score - position * 10 ;
389+ }
390+
391+ // Tier 3 : All query words in title
392+ const queryWords = queryLower . trim ( ) . split ( / \s + / ) ;
393+ const titleWords = titleLower . trim ( ) . split ( / \s + / ) ;
394+ const allWordsInTitle = queryWords . every ( ( qw ) =>
395+ titleWords . some ( ( tw ) => tw . includes ( qw ) ) ,
396+ ) ;
397+ if ( allWordsInTitle ) {
398+ return 2000 + result . score ;
399+ }
400+
401+ return result . score ;
402+ }
403+
330404 self . onmessage = function ( e ) {
331405 let query = e . data ;
332406 let results = index . search ( query , {
@@ -337,6 +411,15 @@ function worker_function(documenterSearchIndex, documenterBaseURL, filters) {
337411 combineWith : "AND" ,
338412 } ) ;
339413
414+ // calculate custom scores for all results
415+ results = results . map ( ( result ) => ( {
416+ ...result ,
417+ customScore : calculateCustomScore ( result , query ) ,
418+ } ) ) ;
419+
420+ // sort by custom score in descending order
421+ results . sort ( ( a , b ) => b . customScore - a . customScore ) ;
422+
340423 // Pre-filter to deduplicate and limit to 200 per category to the extent
341424 // possible without knowing what the filters are.
342425 let filtered_results = [ ] ;
0 commit comments