@@ -675,6 +675,18 @@ function showCopyLink(query, options) {
675
675
// create the index
676
676
var fuseIndex = undefined ;
677
677
var shownWarning = false ;
678
+
679
+ // fuse index options
680
+ const kFuseIndexOptions = {
681
+ keys : [
682
+ { name : "title" , weight : 20 } ,
683
+ { name : "section" , weight : 20 } ,
684
+ { name : "text" , weight : 10 } ,
685
+ ] ,
686
+ ignoreLocation : true ,
687
+ threshold : 0.1 ,
688
+ } ;
689
+
678
690
async function readSearchData ( ) {
679
691
// Initialize the search index on demand
680
692
if ( fuseIndex === undefined ) {
@@ -685,17 +697,7 @@ async function readSearchData() {
685
697
shownWarning = true ;
686
698
return ;
687
699
}
688
- // create fuse index
689
- const options = {
690
- keys : [
691
- { name : "title" , weight : 20 } ,
692
- { name : "section" , weight : 20 } ,
693
- { name : "text" , weight : 10 } ,
694
- ] ,
695
- ignoreLocation : true ,
696
- threshold : 0.1 ,
697
- } ;
698
- const fuse = new window . Fuse ( [ ] , options ) ;
700
+ const fuse = new window . Fuse ( [ ] , kFuseIndexOptions ) ;
699
701
700
702
// fetch the main search.json
701
703
const response = await fetch ( offsetURL ( "search.json" ) ) ;
@@ -1226,8 +1228,34 @@ function algoliaSearch(query, limit, algoliaOptions) {
1226
1228
} ) ;
1227
1229
}
1228
1230
1229
- function fuseSearch ( query , fuse , fuseOptions ) {
1230
- return fuse . search ( query , fuseOptions ) . map ( ( result ) => {
1231
+ let subSearchTerm = undefined ;
1232
+ let subSearchFuse = undefined ;
1233
+ const kFuseMaxWait = 125 ;
1234
+
1235
+ async function fuseSearch ( query , fuse , fuseOptions ) {
1236
+ let index = fuse ;
1237
+ // Fuse.js using the Bitap algorithm for text matching which runs in
1238
+ // O(nm) time (no matter the structure of the text). In our case this
1239
+ // means that long search terms mixed with large index gets very slow
1240
+ //
1241
+ // This injects a subIndex that will be used once the terms get long enough
1242
+ // Usually making this subindex is cheap since there will typically be
1243
+ // a subset of results matching the existing query
1244
+ if ( subSearchFuse !== undefined && query . startsWith ( subSearchTerm ) ) {
1245
+ // Use the existing subSearchFuse
1246
+ index = subSearchFuse ;
1247
+ } else if ( subSearchFuse !== undefined ) {
1248
+ // The term changed, discard the existing fuse
1249
+ subSearchFuse = undefined ;
1250
+ subSearchTerm = undefined ;
1251
+ }
1252
+
1253
+ // Search using the active fuse
1254
+ const then = performance . now ( ) ;
1255
+ const resultsRaw = await index . search ( query , fuseOptions ) ;
1256
+ const now = performance . now ( ) ;
1257
+
1258
+ const results = resultsRaw . map ( ( result ) => {
1231
1259
const addParam = ( url , name , value ) => {
1232
1260
const anchorParts = url . split ( "#" ) ;
1233
1261
const baseUrl = anchorParts [ 0 ] ;
@@ -1244,4 +1272,15 @@ function fuseSearch(query, fuse, fuseOptions) {
1244
1272
crumbs : result . item . crumbs ,
1245
1273
} ;
1246
1274
} ) ;
1275
+
1276
+ // If we don't have a subfuse and the query is long enough, go ahead
1277
+ // and create a subfuse to use for subsequent queries
1278
+ if ( now - then > kFuseMaxWait && subSearchFuse === undefined ) {
1279
+ subSearchTerm = query ;
1280
+ subSearchFuse = new window . Fuse ( [ ] , kFuseIndexOptions ) ;
1281
+ resultsRaw . forEach ( ( rr ) => {
1282
+ subSearchFuse . add ( rr . item ) ;
1283
+ } ) ;
1284
+ }
1285
+ return results ;
1247
1286
}
0 commit comments