@@ -226,6 +226,7 @@ class InMemoryPackageIndex {
226226 packageScores,
227227 parsedQueryText,
228228 includeNameMatches: (query.offset ?? 0 ) == 0 ,
229+ textMatchExtent: query.textMatchExtent ?? TextMatchExtent .api,
229230 );
230231
231232 final nameMatches = textResults? .nameMatches;
@@ -287,7 +288,9 @@ class InMemoryPackageIndex {
287288 boundedList (indexedHits, offset: query.offset, limit: query.limit);
288289
289290 late List <PackageHit > packageHits;
290- if (textResults != null && (textResults.topApiPages? .isNotEmpty ?? false )) {
291+ if ((query.textMatchExtent ?? TextMatchExtent .api).shouldMatchApi () &&
292+ textResults != null &&
293+ (textResults.topApiPages? .isNotEmpty ?? false )) {
291294 packageHits = indexedHits.map ((ps) {
292295 final apiPages = textResults.topApiPages? [ps.index]
293296 // TODO(https://github.com/dart-lang/pub-dev/issues/7106): extract title for the page
@@ -305,6 +308,7 @@ class InMemoryPackageIndex {
305308 nameMatches: nameMatches,
306309 topicMatches: topicMatches,
307310 packageHits: packageHits,
311+ errorMessage: textResults? .errorMessage,
308312 );
309313 }
310314
@@ -332,61 +336,81 @@ class InMemoryPackageIndex {
332336 IndexedScore <String > packageScores,
333337 String ? text, {
334338 required bool includeNameMatches,
339+ required TextMatchExtent textMatchExtent,
335340 }) {
341+ if (text == null || text.isEmpty) {
342+ return null ;
343+ }
344+
336345 final sw = Stopwatch ()..start ();
337- if (text != null && text.isNotEmpty) {
338- final words = splitForQuery (text);
339- if (words.isEmpty) {
340- for (var i = 0 ; i < packageScores.length; i++ ) {
341- packageScores.setValue (i, 0 );
342- }
343- return _TextResults .empty ();
344- }
346+ final words = splitForQuery (text);
347+ if (words.isEmpty) {
348+ packageScores.fillRange (0 , packageScores.length, 0 );
349+ return _TextResults .empty ();
350+ }
345351
346- bool aborted = false ;
352+ final matchName = textMatchExtent.shouldMatchName ();
353+ if (! matchName) {
354+ packageScores.fillRange (0 , packageScores.length, 0 );
355+ return _TextResults .empty (
356+ errorMessage:
357+ 'Search index in reduced mode: unable to match query text.' );
358+ }
347359
348- bool checkAborted () {
349- if (! aborted && sw.elapsed > _textSearchTimeout) {
350- aborted = true ;
351- _logger.info (
352- '[pub-aborted-search-query] Aborted text search after ${sw .elapsedMilliseconds } ms.' );
353- }
354- return aborted;
360+ bool aborted = false ;
361+ bool checkAborted () {
362+ if (! aborted && sw.elapsed > _textSearchTimeout) {
363+ aborted = true ;
364+ _logger.info (
365+ '[pub-aborted-search-query] Aborted text search after ${sw .elapsedMilliseconds } ms.' );
355366 }
367+ return aborted;
368+ }
369+
370+ Set <String >? nameMatches;
371+ if (includeNameMatches && _documentsByName.containsKey (text)) {
372+ nameMatches ?? = < String > {};
373+ nameMatches.add (text);
374+ }
375+
376+ // Multiple words are scored separately, and then the individual scores
377+ // are multiplied. We can use a package filter that is applied after each
378+ // word to reduce the scope of the later words based on the previous results.
379+ /// However, API docs search should be filtered on the original list.
380+ final indexedPositiveList = packageScores.toIndexedPositiveList ();
356381
357- Set <String >? nameMatches;
358- if (includeNameMatches && _documentsByName.containsKey (text)) {
382+ final matchDescription = textMatchExtent.shouldMatchDescription ();
383+ final matchReadme = textMatchExtent.shouldMatchReadme ();
384+ final matchApi = textMatchExtent.shouldMatchApi ();
385+
386+ for (final word in words) {
387+ if (includeNameMatches && _documentsByName.containsKey (word)) {
359388 nameMatches ?? = < String > {};
360- nameMatches.add (text );
389+ nameMatches.add (word );
361390 }
362391
363- // Multiple words are scored separately, and then the individual scores
364- // are multiplied. We can use a package filter that is applied after each
365- // word to reduce the scope of the later words based on the previous results.
366- /// However, API docs search should be filtered on the original list.
367- final indexedPositiveList = packageScores.toIndexedPositiveList ();
368-
369- for (final word in words) {
370- if (includeNameMatches && _documentsByName.containsKey (word)) {
371- nameMatches ?? = < String > {};
372- nameMatches.add (word);
373- }
392+ _scorePool.withScore (
393+ value: 0.0 ,
394+ fn: (wordScore) {
395+ _packageNameIndex.searchWord (word,
396+ score: wordScore, filterOnNonZeros: packageScores);
374397
375- _scorePool.withScore (
376- value: 0.0 ,
377- fn: (wordScore) {
378- _packageNameIndex.searchWord (word,
379- score: wordScore, filterOnNonZeros: packageScores);
398+ if (matchDescription) {
380399 _descrIndex.searchAndAccumulate (word, score: wordScore);
400+ }
401+ if (matchReadme) {
381402 _readmeIndex.searchAndAccumulate (word,
382403 weight: 0.75 , score: wordScore);
383- packageScores.multiplyAllFrom (wordScore);
384- },
385- );
386- }
404+ }
405+ packageScores.multiplyAllFrom (wordScore);
406+ },
407+ );
408+ }
387409
388- final topApiPages =
389- List <List <MapEntry <String , double >>?>.filled (_documents.length, null );
410+ final topApiPages =
411+ List <List <MapEntry <String , double >>?>.filled (_documents.length, null );
412+
413+ if (matchApi) {
390414 const maxApiPageCount = 2 ;
391415 if (! checkAborted ()) {
392416 _apiSymbolIndex.withSearchWords (words, weight: 0.70 , (symbolPages) {
@@ -420,29 +444,28 @@ class InMemoryPackageIndex {
420444 }
421445 });
422446 }
447+ }
423448
424- // filter results based on exact phrases
425- final phrases = extractExactPhrases (text);
426- if (! aborted && phrases.isNotEmpty) {
427- for (var i = 0 ; i < packageScores.length; i++ ) {
428- if (packageScores.isNotPositive (i)) continue ;
429- final doc = _documents[i];
430- final matchedAllPhrases = phrases.every ((phrase) =>
431- doc.package.contains (phrase) ||
432- doc.description! .contains (phrase) ||
433- doc.readme! .contains (phrase));
434- if (! matchedAllPhrases) {
435- packageScores.setValue (i, 0 );
436- }
449+ // filter results based on exact phrases
450+ final phrases = extractExactPhrases (text);
451+ if (! aborted && phrases.isNotEmpty) {
452+ for (var i = 0 ; i < packageScores.length; i++ ) {
453+ if (packageScores.isNotPositive (i)) continue ;
454+ final doc = _documents[i];
455+ final matchedAllPhrases = phrases.every ((phrase) =>
456+ (matchName && doc.package.contains (phrase)) ||
457+ (matchDescription && doc.description! .contains (phrase)) ||
458+ (matchReadme && doc.readme! .contains (phrase)));
459+ if (! matchedAllPhrases) {
460+ packageScores.setValue (i, 0 );
437461 }
438462 }
439-
440- return _TextResults (
441- topApiPages,
442- nameMatches: nameMatches? .toList (),
443- );
444463 }
445- return null ;
464+
465+ return _TextResults (
466+ topApiPages,
467+ nameMatches: nameMatches? .toList (),
468+ );
446469 }
447470
448471 List <IndexedPackageHit > _rankWithValues (
@@ -521,15 +544,20 @@ class InMemoryPackageIndex {
521544class _TextResults {
522545 final List <List <MapEntry <String , double >>?>? topApiPages;
523546 final List <String >? nameMatches;
547+ final String ? errorMessage;
524548
525- factory _TextResults .empty () => _TextResults (
526- null ,
527- nameMatches: null ,
528- );
549+ factory _TextResults .empty ({String ? errorMessage}) {
550+ return _TextResults (
551+ null ,
552+ nameMatches: null ,
553+ errorMessage: errorMessage,
554+ );
555+ }
529556
530557 _TextResults (
531558 this .topApiPages, {
532559 required this .nameMatches,
560+ this .errorMessage,
533561 });
534562}
535563
0 commit comments