CmdPal: Added settings for limiting apps on top level searches (#40915)

Closes #40062

Adds a setting to limit the number of apps returned on top level
searches.

Can limit to none, 1, 5, 10, or 20.


https://github.com/user-attachments/assets/de60111f-fb02-4db6-9ae9-2f636c171b5b
This commit is contained in:
Michael Jolley
2025-08-27 10:52:39 -05:00
committed by GitHub
parent 447118ab70
commit 336cdaff9b
6 changed files with 185 additions and 19 deletions

View File

@@ -27,7 +27,9 @@ public partial class MainListPage : DynamicListPage,
private readonly IServiceProvider _serviceProvider;
private readonly TopLevelCommandManager _tlcManager;
private IEnumerable<IListItem>? _filteredItems;
private IEnumerable<Scored<IListItem>>? _filteredItems;
private IEnumerable<Scored<IListItem>>? _filteredApps;
private IEnumerable<IListItem>? _allApps;
private bool _includeApps;
private bool _filteredItemsIncludesApps;
@@ -83,7 +85,7 @@ public partial class MainListPage : DynamicListPage,
}
else
{
RaiseItemsChanged(_tlcManager.TopLevelCommands.Count);
RaiseItemsChanged();
}
}
@@ -148,7 +150,13 @@ public partial class MainListPage : DynamicListPage,
{
lock (_tlcManager.TopLevelCommands)
{
return _filteredItems?.ToArray() ?? [];
var items = Enumerable.Empty<Scored<IListItem>>()
.Concat(_filteredItems is not null ? _filteredItems : [])
.Concat(_filteredApps is not null ? _filteredApps : [])
.OrderByDescending(o => o.Score)
.Select(s => s.Item)
.ToArray();
return items;
}
}
}
@@ -167,6 +175,8 @@ public partial class MainListPage : DynamicListPage,
{
_filteredItemsIncludesApps = _includeApps;
_filteredItems = null;
_filteredApps = null;
_allApps = null;
}
}
@@ -184,6 +194,8 @@ public partial class MainListPage : DynamicListPage,
{
_filteredItemsIncludesApps = _includeApps;
_filteredItems = null;
_filteredApps = null;
_allApps = null;
RaiseItemsChanged(commands.Count);
return;
}
@@ -193,35 +205,49 @@ public partial class MainListPage : DynamicListPage,
if (!newSearch.StartsWith(oldSearch, StringComparison.CurrentCultureIgnoreCase))
{
_filteredItems = null;
_filteredApps = null;
_allApps = null;
}
// If the internal state has changed, reset _filteredItems to reset the list.
if (_filteredItemsIncludesApps != _includeApps)
{
_filteredItems = null;
_filteredApps = null;
_allApps = null;
}
var newFilteredItems = _filteredItems?.Select(s => s.Item);
// If we don't have any previous filter results to work with, start
// with a list of all our commands & apps.
if (_filteredItems is null)
if (newFilteredItems is null && _filteredApps is null)
{
_filteredItems = commands;
newFilteredItems = commands;
_filteredItemsIncludesApps = _includeApps;
if (_includeApps)
{
IEnumerable<IListItem> apps = AllAppsCommandProvider.Page.GetItems();
var appIds = apps.Select(app => app.Command.Id).ToArray();
// Remove any top level pinned apps and use the apps from AllAppsCommandProvider.Page.GetItems()
// since they contain details.
_filteredItems = _filteredItems.Where(item => item.Command is not AppCommand);
_filteredItems = _filteredItems.Concat(apps);
_allApps = AllAppsCommandProvider.Page.GetItems();
}
}
// Produce a list of everything that matches the current filter.
_filteredItems = ListHelpers.FilterList<IListItem>(_filteredItems, SearchText, ScoreTopLevelItem);
RaiseItemsChanged(_filteredItems.Count());
_filteredItems = ListHelpers.FilterListWithScores<IListItem>(newFilteredItems ?? [], SearchText, ScoreTopLevelItem);
// Produce a list of filtered apps with the appropriate limit
if (_allApps is not null)
{
_filteredApps = ListHelpers.FilterListWithScores<IListItem>(_allApps, SearchText, ScoreTopLevelItem);
var appResultLimit = AllAppsCommandProvider.TopLevelResultLimit;
if (appResultLimit >= 0)
{
_filteredApps = _filteredApps.Take(appResultLimit);
}
}
RaiseItemsChanged();
}
}

View File

@@ -3,9 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CmdPal.Ext.Apps.Programs;
using Microsoft.CmdPal.Ext.Apps.Properties;
using Microsoft.CmdPal.Ext.Apps.State;
using Microsoft.CommandPalette.Extensions;
@@ -45,6 +43,28 @@ public partial class AllAppsCommandProvider : CommandProvider
PinnedAppsManager.Instance.PinStateChanged += OnPinStateChanged;
}
public static int TopLevelResultLimit
{
get
{
var limitSetting = AllAppsSettings.Instance.SearchResultLimit;
if (limitSetting is null)
{
return -1;
}
var quantity = -1;
if (int.TryParse(limitSetting, out var result))
{
quantity = result;
}
return quantity;
}
}
public override ICommandItem[] TopLevelCommands() => [_listItem, .._page.GetPinnedApps()];
public ICommandItem? LookupApp(string displayName)

View File

@@ -20,6 +20,16 @@ public class AllAppsSettings : JsonSettingsManager, ISettingsInterface
private static string Experimental(string propertyName) => $"{_namespace}.experimental.{propertyName}";
private static readonly List<ChoiceSetSetting.Choice> _searchResultLimitChoices =
[
new ChoiceSetSetting.Choice(Resources.limit_none, "-1"),
new ChoiceSetSetting.Choice(Resources.limit_0, "0"),
new ChoiceSetSetting.Choice(Resources.limit_1, "1"),
new ChoiceSetSetting.Choice(Resources.limit_5, "5"),
new ChoiceSetSetting.Choice(Resources.limit_10, "10"),
new ChoiceSetSetting.Choice(Resources.limit_20, "20"),
];
#pragma warning disable SA1401 // Fields should be private
internal static AllAppsSettings Instance = new();
#pragma warning restore SA1401 // Fields should be private
@@ -42,6 +52,14 @@ public class AllAppsSettings : JsonSettingsManager, ISettingsInterface
public bool EnablePathEnvironmentVariableSource => _enablePathEnvironmentVariableSource.Value;
private readonly ChoiceSetSetting _searchResultLimitSource = new(
Namespaced(nameof(SearchResultLimit)),
Resources.limit_fallback_results_source,
Resources.limit_fallback_results_source_description,
_searchResultLimitChoices);
public string SearchResultLimit => _searchResultLimitSource.Value ?? string.Empty;
private readonly ToggleSetting _enableStartMenuSource = new(
Namespaced(nameof(EnableStartMenuSource)),
Resources.enable_start_menu_source,
@@ -87,6 +105,7 @@ public class AllAppsSettings : JsonSettingsManager, ISettingsInterface
Settings.Add(_enableDesktopSource);
Settings.Add(_enableRegistrySource);
Settings.Add(_enablePathEnvironmentVariableSource);
Settings.Add(_searchResultLimitSource);
// Load settings from file upon initialization
LoadSettings();

View File

@@ -159,6 +159,78 @@ namespace Microsoft.CmdPal.Ext.Apps.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to 0.
/// </summary>
internal static string limit_0 {
get {
return ResourceManager.GetString("limit_0", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to 1.
/// </summary>
internal static string limit_1 {
get {
return ResourceManager.GetString("limit_1", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to 10.
/// </summary>
internal static string limit_10 {
get {
return ResourceManager.GetString("limit_10", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to 20.
/// </summary>
internal static string limit_20 {
get {
return ResourceManager.GetString("limit_20", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to 5.
/// </summary>
internal static string limit_5 {
get {
return ResourceManager.GetString("limit_5", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Limit the number of applications returned from the top level.
/// </summary>
internal static string limit_fallback_results_source {
get {
return ResourceManager.GetString("limit_fallback_results_source", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Limit fallback results to n apps.
/// </summary>
internal static string limit_fallback_results_source_description {
get {
return ResourceManager.GetString("limit_fallback_results_source_description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to None.
/// </summary>
internal static string limit_none {
get {
return ResourceManager.GetString("limit_none", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Open containing folder.
/// </summary>

View File

@@ -198,4 +198,28 @@
<data name="unpin_app" xml:space="preserve">
<value>Unpin</value>
</data>
</root>
<data name="limit_1" xml:space="preserve">
<value>1</value>
</data>
<data name="limit_5" xml:space="preserve">
<value>5</value>
</data>
<data name="limit_10" xml:space="preserve">
<value>10</value>
</data>
<data name="limit_20" xml:space="preserve">
<value>20</value>
</data>
<data name="limit_fallback_results_source" xml:space="preserve">
<value>Limit the number of applications returned from the top level</value>
</data>
<data name="limit_fallback_results_source_description" xml:space="preserve">
<value>Limit fallback results to n apps</value>
</data>
<data name="limit_0" xml:space="preserve">
<value>0</value>
</data>
<data name="limit_none" xml:space="preserve">
<value>Unlimited</value>
</data>
</root>

View File

@@ -43,13 +43,18 @@ public partial class ListHelpers
}
public static IEnumerable<T> FilterList<T>(IEnumerable<T> items, string query, Func<string, T, int> scoreFunction)
{
return FilterListWithScores<T>(items, query, scoreFunction)
.Select(score => score.Item);
}
public static IEnumerable<Scored<T>> FilterListWithScores<T>(IEnumerable<T> items, string query, Func<string, T, int> scoreFunction)
{
var scores = items
.Select(li => new Scored<T>() { Item = li, Score = scoreFunction(query, li) })
.Where(score => score.Score > 0)
.OrderByDescending(score => score.Score);
return scores
.Select(score => score.Item);
return scores;
}
/// <summary>