diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/MainListPage.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/MainListPage.cs index d8a64525b8..4f2f116530 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/MainListPage.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/MainListPage.cs @@ -34,11 +34,11 @@ public partial class MainListPage : DynamicListPage, private readonly AppStateModel _appStateModel; private List>? _filteredItems; private List>? _filteredApps; - private List>? _fallbackItems; // Keep as IEnumerable for deferred execution. Fallback item titles are updated // asynchronously, so scoring must happen lazily when GetItems is called. private IEnumerable>? _scoredFallbackItems; + private IEnumerable>? _fallbackItems; private bool _includeApps; private bool _filteredItemsIncludesApps; private int _appResultLimit = 10; @@ -166,7 +166,7 @@ public partial class MainListPage : DynamicListPage, _filteredItems, _scoredFallbackItems?.ToList(), _filteredApps, - _fallbackItems, + _fallbackItems?.ToList(), _appResultLimit); } } @@ -386,6 +386,13 @@ public partial class MainListPage : DynamicListPage, return; } + _scoredFallbackItems = ListHelpers.FilterListWithScores(newFallbacksForScoring ?? [], SearchText, scoreItem); + + if (token.IsCancellationRequested) + { + return; + } + Func scoreFallbackItem = (a, b) => { return ScoreFallbackItem(a, b, _settings.FallbackRanks); }; _fallbackItems = [.. ListHelpers.FilterListWithScores(newFallbacks ?? [], SearchText, scoreFallbackItem)]; diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/MainListPageResultFactory.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/MainListPageResultFactory.cs index 1490512d57..464e561640 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/MainListPageResultFactory.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/MainListPageResultFactory.cs @@ -29,13 +29,24 @@ internal static class MainListPageResultFactory } int len1 = filteredItems?.Count ?? 0; - int len2 = scoredFallbackItems?.Count ?? 0; + + // Scored or not, fallbacks may contain empty items that we want to skip. + int len2 = GetNonEmptyFallbackItemsCount(scoredFallbackItems); // Apps are pre-sorted, so we just need to take the top N, limited by appResultLimit. int len3 = Math.Min(filteredApps?.Count ?? 0, appResultLimit); + int nonEmptyFallbackCount = GetNonEmptyFallbackItemsCount(fallbackItems); + // Allocate the exact size of the result array. - int totalCount = len1 + len2 + len3 + GetNonEmptyFallbackItemsCount(fallbackItems); + int totalCount = len1 + len2 + len3 + nonEmptyFallbackCount; + + // If there are non-empty fallbacks, we'll be adding a separator "Section" + if (nonEmptyFallbackCount > 0) + { + totalCount++; + } + var result = new IListItem[totalCount]; // Three-way stable merge of already-sorted lists. @@ -122,6 +133,9 @@ internal static class MainListPageResultFactory // always at the end of the list and are sorted by user settings. if (fallbackItems is not null) { + // Create the fallbacks section header + result[writePos++] = new Separator(Properties.Resources.fallbacks); + for (int i = 0; i < fallbackItems.Count; i++) { var item = fallbackItems[i].Item; @@ -143,7 +157,7 @@ internal static class MainListPageResultFactory { for (int i = 0; i < fallbackItems.Count; i++) { - if (!string.IsNullOrEmpty(fallbackItems[i].Item.Title)) + if (!string.IsNullOrWhiteSpace(fallbackItems[i].Item.Title)) { fallbackItemsCount++; } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.Designer.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.Designer.cs index 8bc2a42a92..8d9ff6d6bb 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.Designer.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.Designer.cs @@ -205,7 +205,7 @@ namespace Microsoft.CmdPal.UI.ViewModels.Properties { } /// - /// Looks up a localized string similar to Create a new extension. + /// Looks up a localized string similar to Create extension. /// public static string builtin_create_extension_title { get { @@ -349,7 +349,7 @@ namespace Microsoft.CmdPal.UI.ViewModels.Properties { } /// - /// Looks up a localized string similar to Creates a project for a new Command Palette extension. + /// Looks up a localized string similar to Generate a new Command Palette extension project. /// public static string builtin_new_extension_subtitle { get { @@ -358,7 +358,7 @@ namespace Microsoft.CmdPal.UI.ViewModels.Properties { } /// - /// Looks up a localized string similar to Open Settings. + /// Looks up a localized string similar to Open Command Palette settings. /// public static string builtin_open_settings_name { get { @@ -366,15 +366,6 @@ namespace Microsoft.CmdPal.UI.ViewModels.Properties { } } - /// - /// Looks up a localized string similar to Open Command Palette settings. - /// - public static string builtin_open_settings_subtitle { - get { - return ResourceManager.GetString("builtin_open_settings_subtitle", resourceCulture); - } - } - /// /// Looks up a localized string similar to Exit Command Palette. /// @@ -437,5 +428,14 @@ namespace Microsoft.CmdPal.UI.ViewModels.Properties { return ResourceManager.GetString("builtin_settings_extension_n_extensions_installed", resourceCulture); } } + + /// + /// Looks up a localized string similar to Fallbacks. + /// + public static string fallbacks { + get { + return ResourceManager.GetString("fallbacks", resourceCulture); + } + } } } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.resx b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.resx index bb7637e133..9f6b68c1bd 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.resx +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.resx @@ -242,4 +242,7 @@ Pick background image + + Fallbacks + \ No newline at end of file