MainListPageResultFactory.cs
1 // Copyright (c) Microsoft Corporation 2 // The Microsoft Corporation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 #pragma warning disable IDE0007 // Use implicit type 6 7 using Microsoft.CommandPalette.Extensions; 8 using Microsoft.CommandPalette.Extensions.Toolkit; 9 10 namespace Microsoft.CmdPal.UI.ViewModels.Commands; 11 12 internal static class MainListPageResultFactory 13 { 14 /// <summary> 15 /// Creates a merged and ordered array of results from multiple scored input lists, 16 /// applying an application result limit and filtering fallback items as needed. 17 /// </summary> 18 public static IListItem[] Create( 19 IList<Scored<IListItem>>? filteredItems, 20 IList<Scored<IListItem>>? scoredFallbackItems, 21 IList<Scored<IListItem>>? filteredApps, 22 IList<Scored<IListItem>>? fallbackItems, 23 int appResultLimit) 24 { 25 if (appResultLimit < 0) 26 { 27 throw new ArgumentOutOfRangeException( 28 nameof(appResultLimit), "App result limit must be non-negative."); 29 } 30 31 int len1 = filteredItems?.Count ?? 0; 32 33 // Empty fallbacks are removed prior to this merge. 34 int len2 = scoredFallbackItems?.Count ?? 0; 35 36 // Apps are pre-sorted, so we just need to take the top N, limited by appResultLimit. 37 int len3 = Math.Min(filteredApps?.Count ?? 0, appResultLimit); 38 39 int nonEmptyFallbackCount = fallbackItems?.Count ?? 0; 40 41 // Allocate the exact size of the result array. 42 // We'll add an extra slot for the fallbacks section header if needed. 43 int totalCount = len1 + len2 + len3 + nonEmptyFallbackCount + (nonEmptyFallbackCount > 0 ? 1 : 0); 44 45 var result = new IListItem[totalCount]; 46 47 // Three-way stable merge of already-sorted lists. 48 int idx1 = 0, idx2 = 0, idx3 = 0; 49 int writePos = 0; 50 51 // Merge while all three lists have items. To maintain a stable sort, the 52 // priority is: list1 > list2 > list3 when scores are equal. 53 while (idx1 < len1 && idx2 < len2 && idx3 < len3) 54 { 55 // Using null-forgiving operator as we have already checked against lengths. 56 int score1 = filteredItems![idx1].Score; 57 int score2 = scoredFallbackItems![idx2].Score; 58 int score3 = filteredApps![idx3].Score; 59 60 if (score1 >= score2 && score1 >= score3) 61 { 62 result[writePos++] = filteredItems[idx1++].Item; 63 } 64 else if (score2 >= score3) 65 { 66 result[writePos++] = scoredFallbackItems[idx2++].Item; 67 } 68 else 69 { 70 result[writePos++] = filteredApps[idx3++].Item; 71 } 72 } 73 74 // Two-way merges for remaining pairs. 75 while (idx1 < len1 && idx2 < len2) 76 { 77 if (filteredItems![idx1].Score >= scoredFallbackItems![idx2].Score) 78 { 79 result[writePos++] = filteredItems[idx1++].Item; 80 } 81 else 82 { 83 result[writePos++] = scoredFallbackItems[idx2++].Item; 84 } 85 } 86 87 while (idx1 < len1 && idx3 < len3) 88 { 89 if (filteredItems![idx1].Score >= filteredApps![idx3].Score) 90 { 91 result[writePos++] = filteredItems[idx1++].Item; 92 } 93 else 94 { 95 result[writePos++] = filteredApps[idx3++].Item; 96 } 97 } 98 99 while (idx2 < len2 && idx3 < len3) 100 { 101 if (scoredFallbackItems![idx2].Score >= filteredApps![idx3].Score) 102 { 103 result[writePos++] = scoredFallbackItems[idx2++].Item; 104 } 105 else 106 { 107 result[writePos++] = filteredApps[idx3++].Item; 108 } 109 } 110 111 // Drain remaining items from a non-empty list. 112 while (idx1 < len1) 113 { 114 result[writePos++] = filteredItems![idx1++].Item; 115 } 116 117 while (idx2 < len2) 118 { 119 result[writePos++] = scoredFallbackItems![idx2++].Item; 120 } 121 122 while (idx3 < len3) 123 { 124 result[writePos++] = filteredApps![idx3++].Item; 125 } 126 127 // Append filtered fallback items. Fallback items are added post-sort so they are 128 // always at the end of the list and are sorted by user settings. 129 if (fallbackItems is not null) 130 { 131 // Create the fallbacks section header 132 if (fallbackItems.Count > 0) 133 { 134 result[writePos++] = new Separator(Properties.Resources.fallbacks); 135 } 136 137 for (int i = 0; i < fallbackItems.Count; i++) 138 { 139 var item = fallbackItems[i].Item; 140 if (!string.IsNullOrEmpty(item.Title)) 141 { 142 result[writePos++] = item; 143 } 144 } 145 } 146 147 return result; 148 } 149 150 private static int GetNonEmptyFallbackItemsCount(IList<Scored<IListItem>>? fallbackItems) 151 { 152 int fallbackItemsCount = 0; 153 154 if (fallbackItems is not null) 155 { 156 for (int i = 0; i < fallbackItems.Count; i++) 157 { 158 if (!string.IsNullOrWhiteSpace(fallbackItems[i].Item.Title)) 159 { 160 fallbackItemsCount++; 161 } 162 } 163 } 164 165 return fallbackItemsCount; 166 } 167 } 168 #pragma warning restore IDE0007 // Use implicit type