@@ -432,7 +432,7 @@ public int RecommendedWidth {
432432 return 0 ;
433433 }
434434
435- int visibleEntryCount = Math . Min ( shownEntries . Length , ( int ) MathF . Ceiling ( ClientSize . Height / EntryHeight ) + 1 ) ;
435+ int visibleEntryCount = Math . Min ( shownEntries . Length , ( int ) MathF . Ceiling ( ClientSize . Height / ( float ) EntryHeight ) + 1 ) ;
436436 int medianGroupLen = shownEntries
437437 . Where ( ( _ , idx ) => idx + visibleEntryCount <= shownEntries . Length )
438438 . Select ( ( _ , idx ) => {
@@ -460,8 +460,8 @@ public int RecommendedWidth {
460460 private Entry [ ] shownEntries = [ ] ;
461461 private readonly ContentDrawable drawable ;
462462
463- private int TopVisibleEntry => ( int ) MathF . Floor ( ScrollPosition . Y / EntryHeight ) ;
464- private int BottomVisibleEntry => ( int ) MathF . Ceiling ( ( ScrollPosition . Y + ClientSize . Height ) / EntryHeight ) ;
463+ private int TopVisibleEntry => ( int ) MathF . Floor ( ScrollPosition . Y / ( float ) EntryHeight ) ;
464+ private int BottomVisibleEntry => ( int ) MathF . Ceiling ( ( ScrollPosition . Y + ClientSize . Height ) / ( float ) EntryHeight ) ;
465465 private IEnumerable < Entry > VisibleEntries {
466466 get {
467467 int top = TopVisibleEntry ;
@@ -471,6 +471,8 @@ private IEnumerable<Entry> VisibleEntries {
471471 }
472472 }
473473
474+ private readonly FuzzyMatcher matcher = new ( ) { IgnoreCase = true } ;
475+
474476 public PopupMenu ( ) {
475477 LoadStorage ( ) ;
476478
@@ -493,35 +495,63 @@ public void Recalc() {
493495 // Order for the categories
494496 const int frequentlyUsedThreshold = 5 ;
495497
496- const int favouriteIndex = 0 ;
497- const int frequentlyUsedIndex = favouriteIndex + 1 ;
498- const int suggestionIndex = frequentlyUsedIndex + FrequentlyUsedCategorySize ;
499- const int regularIndex = suggestionIndex + 1 ;
500-
501498 var frequentlyUsed = entries
502- . Where ( entry => ( string . IsNullOrEmpty ( entry . SearchText ) || entry . SearchText . StartsWith ( filter , StringComparison . InvariantCultureIgnoreCase ) )
503- && entry . Data is { } data && data . Usages . TryGetValue ( entry . StorageName , out uint amount ) && amount >= frequentlyUsedThreshold )
504- . OrderBy ( entry => entry . Data ! . Usages [ entry . StorageName ] ) ;
499+ . Where ( entry => entry . Data is { } data && data . Usages . TryGetValue ( entry . StorageName , out uint amount ) && amount >= frequentlyUsedThreshold )
500+ . Select ( entry => ( Entry : entry , Score : matcher . GetMatch ( entry . SearchText . AsSpan ( ) , filter . AsSpan ( ) ) ) )
501+ . Where ( pair => pair . Score != null )
502+ . OrderBy ( pair => pair . Entry . Data ! . Usages [ pair . Entry . StorageName ] * 10 + pair . Score ! . Value ) ;
503+
505504 int i = 0 ;
506- foreach ( var entry in frequentlyUsed ) {
505+ foreach ( var ( entry , _ ) in frequentlyUsed ) {
507506 entry . FrequentlyUsedIndex = i ++ ;
508507 }
509508
510509 shownEntries = entries
511- . Where ( entry => string . IsNullOrEmpty ( entry . SearchText ) || entry . SearchText . StartsWith ( filter , StringComparison . InvariantCultureIgnoreCase ) )
512- . OrderBy ( entry => {
513- if ( entry . Data is { } data && data . Favourites . Contains ( entry . StorageName ) ) {
514- return favouriteIndex ;
510+ . Select ( entry => ( Entry : entry , Score : matcher . GetMatch ( entry . SearchText . AsSpan ( ) , filter . AsSpan ( ) ) ) )
511+ . Where ( pair => pair . Score != null )
512+ . Order ( Comparer < ( Entry Entry , ushort ? Score ) > . Create ( ( lhs , rhs ) => {
513+ ushort lhsScore = lhs . Score ! . Value ;
514+ ushort rhsScore = rhs . Score ! . Value ;
515+
516+ bool lhsFavourite = lhs . Entry . Data is { } lhsData && lhsData . Favourites . Contains ( lhs . Entry . StorageName ) ;
517+ bool rhsFavourite = rhs . Entry . Data is { } rhsData && rhsData . Favourites . Contains ( rhs . Entry . StorageName ) ;
518+ if ( lhsFavourite && ! rhsFavourite ) {
519+ return - 1 ;
515520 }
516- if ( entry . FrequentlyUsedIndex is >= 0 and < FrequentlyUsedCategorySize ) {
517- return frequentlyUsedIndex + entry . FrequentlyUsedIndex ;
521+ if ( ! lhsFavourite && rhsFavourite ) {
522+ return 1 ;
518523 }
519- if ( entry . Suggestion ) {
520- return suggestionIndex ;
524+ if ( lhsFavourite && rhsFavourite ) {
525+ return lhsScore - rhsScore ;
521526 }
522527
523- return regularIndex ;
524- } )
528+ bool lhsFrequent = lhs . Entry . FrequentlyUsedIndex is >= 0 and < FrequentlyUsedCategorySize ;
529+ bool rhsFrequent = rhs . Entry . FrequentlyUsedIndex is >= 0 and < FrequentlyUsedCategorySize ;
530+ if ( lhsFrequent && ! rhsFrequent ) {
531+ return - 1 ;
532+ }
533+ if ( ! lhsFrequent && rhsFrequent ) {
534+ return 1 ;
535+ }
536+ if ( lhsFrequent && rhsFrequent ) {
537+ return ( lhs . Entry . FrequentlyUsedIndex * 10 + lhsScore ) - ( rhs . Entry . FrequentlyUsedIndex * 10 + rhsScore ) ;
538+ }
539+
540+ bool lhsSuggestion = lhs . Entry . Suggestion ;
541+ bool rhsSuggestion = rhs . Entry . Suggestion ;
542+ if ( lhsSuggestion && ! rhsSuggestion ) {
543+ return - 1 ;
544+ }
545+ if ( ! lhsSuggestion && rhsSuggestion ) {
546+ return 1 ;
547+ }
548+ if ( lhsSuggestion && rhsSuggestion ) {
549+ return lhsScore - rhsScore ;
550+ }
551+
552+ return lhsScore - rhsScore ;
553+ } ) )
554+ . Select ( pair => pair . Entry )
525555 . ToArray ( ) ;
526556
527557 if ( shownEntries . Length == 0 ) {
0 commit comments