CmdPal: Add settings to control which fallbacks are enabled (#40505)

This adds settings to each provider to allow us to control if individual
fallback items are enabled or not, regardless of the provider being
enabled.

This is relevant to _all the threads where disabling fallback commands
came up_

This just adds another section to each provider's settings page, with a
list of the fallback commands.

This also has nothing to do with the "top-level apps search", which is
not really a fallback command - it's its own thing.

Ref #38288. Doesn't close that, because this only controls
enable/disable, not ranking.

From here, we should be able to add a dedicated page in the SUI that
shows all the fallbacks across all providers. That's where we'll enable
the ordering.
This commit is contained in:
Mike Griese
2025-07-09 22:01:38 -05:00
committed by GitHub
parent 5c6166bc9f
commit cfa5f75862
18 changed files with 178 additions and 26 deletions

View File

@@ -177,13 +177,13 @@ public sealed class CommandProviderWrapper
private void InitializeCommands(ICommandItem[] commands, IFallbackCommandItem[] fallbacks, IServiceProvider serviceProvider, WeakReference<IPageContext> pageContext)
{
var settings = serviceProvider.GetService<SettingsModel>()!;
var providerSettings = GetProviderSettings(settings);
Func<ICommandItem?, bool, TopLevelViewModel> makeAndAdd = (ICommandItem? i, bool fallback) =>
{
CommandItemViewModel commandItemViewModel = new(new(i), pageContext);
TopLevelViewModel topLevelViewModel = new(commandItemViewModel, fallback, ExtensionHost, ProviderId, settings, serviceProvider);
topLevelViewModel.ItemViewModel.SlowInitializeProperties();
TopLevelViewModel topLevelViewModel = new(commandItemViewModel, fallback, ExtensionHost, ProviderId, settings, providerSettings, serviceProvider);
topLevelViewModel.InitializeProperties();
return topLevelViewModel;
};

View File

@@ -13,7 +13,7 @@ internal sealed partial class FallbackLogItem : FallbackCommandItem
private readonly LogMessagesPage _logMessagesPage;
public FallbackLogItem()
: base(new LogMessagesPage(), Resources.builtin_log_subtitle)
: base(new LogMessagesPage() { Id = "com.microsoft.cmdpal.log" }, Resources.builtin_log_subtitle)
{
_logMessagesPage = (LogMessagesPage)Command!;
Title = string.Empty;

View File

@@ -12,7 +12,9 @@ internal sealed partial class FallbackReloadItem : FallbackCommandItem
private readonly ReloadExtensionsCommand _reloadCommand;
public FallbackReloadItem()
: base(new ReloadExtensionsCommand(), Properties.Resources.builtin_reload_display_title)
: base(
new ReloadExtensionsCommand() { Id = "com.microsoft.cmdpal.reload" },
Properties.Resources.builtin_reload_display_title)
{
_reloadCommand = (ReloadExtensionsCommand)Command!;
Title = string.Empty;

View File

@@ -13,6 +13,7 @@ public partial class QuitCommand : InvokableCommand, IFallbackHandler
{
public QuitCommand()
{
Id = "com.microsoft.cmdpal.quit";
Icon = new IconInfo("\uE711");
}

View File

@@ -10,6 +10,8 @@ public class ProviderSettings
{
public bool IsEnabled { get; set; } = true;
public Dictionary<string, bool> FallbackCommands { get; set; } = [];
[JsonIgnore]
public string ProviderDisplayName { get; set; } = string.Empty;
@@ -42,4 +44,14 @@ public class ProviderSettings
throw new InvalidDataException("Did you add a built-in command and forget to set the Id? Make sure you do that!");
}
}
public bool IsFallbackEnabled(TopLevelViewModel command)
{
return FallbackCommands.TryGetValue(command.Id, out var enabled) ? enabled : true;
}
public void SetFallbackEnabled(TopLevelViewModel command, bool enabled)
{
FallbackCommands[command.Id] = enabled;
}
}

View File

@@ -25,7 +25,11 @@ public partial class ProviderSettingsViewModel(
public string ExtensionName => _provider.Extension?.ExtensionDisplayName ?? "Built-in";
public string ExtensionSubtext => IsEnabled ? $"{ExtensionName}, {TopLevelCommands.Count} commands" : Resources.builtin_disabled_extension;
public string ExtensionSubtext => IsEnabled ?
HasFallbackCommands ?
$"{ExtensionName}, {TopLevelCommands.Count} commands, {FallbackCommands.Count} fallback commands" :
$"{ExtensionName}, {TopLevelCommands.Count} commands" :
Resources.builtin_disabled_extension;
[MemberNotNullWhen(true, nameof(Extension))]
public bool IsFromExtension => _provider.Extension != null;
@@ -139,6 +143,31 @@ public partial class ProviderSettingsViewModel(
return [.. providersCommands];
}
[field: AllowNull]
public List<TopLevelViewModel> FallbackCommands
{
get
{
if (field == null)
{
field = BuildFallbackViewModels();
}
return field;
}
}
public bool HasFallbackCommands => _provider.FallbackItems?.Length > 0;
private List<TopLevelViewModel> BuildFallbackViewModels()
{
var thisProvider = _provider;
var providersCommands = thisProvider.FallbackItems;
// Remember! This comes in on the UI thread!
return [.. providersCommands];
}
private void Save() => SettingsModel.SaveSettings(_settings);
private void InitializeSettingsPage()

View File

@@ -98,19 +98,28 @@ public partial class TopLevelCommandManager : ObservableObject,
await commandProvider.LoadTopLevelCommands(_serviceProvider, weakSelf);
var settings = _serviceProvider.GetService<SettingsModel>()!;
var commands = await Task.Factory.StartNew(
() =>
{
List<TopLevelViewModel> commands = [];
foreach (var item in commandProvider.TopLevelItems)
{
TopLevelCommands.Add(item);
}
List<TopLevelViewModel> commands = [];
foreach (var item in commandProvider.FallbackItems)
{
if (item.IsEnabled)
{
TopLevelCommands.Add(item);
}
}
foreach (var item in commandProvider.TopLevelItems)
{
commands.Add(item);
}
foreach (var item in commandProvider.FallbackItems)
{
commands.Add(item);
}
return commands;
},
CancellationToken.None,
TaskCreationOptions.None,
_taskScheduler);
commandProvider.CommandsChanged -= CommandProvider_CommandsChanged;
commandProvider.CommandsChanged += CommandProvider_CommandsChanged;
@@ -176,7 +185,10 @@ public partial class TopLevelCommandManager : ObservableObject,
foreach (var i in sender.FallbackItems)
{
newItems.Add(i);
if (i.IsEnabled)
{
newItems.Add(i);
}
}
// Slice out the old commands

View File

@@ -4,7 +4,9 @@
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using ManagedCommon;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Settings;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
@@ -17,6 +19,7 @@ namespace Microsoft.CmdPal.UI.ViewModels;
public sealed partial class TopLevelViewModel : ObservableObject, IListItem
{
private readonly SettingsModel _settings;
private readonly ProviderSettings _providerSettings;
private readonly IServiceProvider _serviceProvider;
private readonly CommandItemViewModel _commandItemViewModel;
@@ -77,6 +80,9 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
////// INotifyPropChanged
public event TypedEventHandler<object, IPropChangedEventArgs>? PropChanged;
// Fallback items
public string DisplayTitle { get; private set; } = string.Empty;
public HotkeySettings? Hotkey
{
get => _hotkey;
@@ -133,16 +139,32 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
}
}
public bool IsEnabled
{
get => _providerSettings.IsFallbackEnabled(this);
set
{
if (value != IsEnabled)
{
_providerSettings.SetFallbackEnabled(this, value);
Save();
WeakReferenceMessenger.Default.Send<ReloadCommandsMessage>(new());
}
}
}
public TopLevelViewModel(
CommandItemViewModel item,
bool isFallback,
CommandPaletteHost extensionHost,
string commandProviderId,
SettingsModel settings,
ProviderSettings providerSettings,
IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
_settings = settings;
_providerSettings = providerSettings;
_commandProviderId = commandProviderId;
_commandItemViewModel = item;
@@ -156,6 +178,22 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
// UpdateTags();
}
internal void InitializeProperties()
{
ItemViewModel.SlowInitializeProperties();
if (IsFallback)
{
var model = _commandItemViewModel.Model.Unsafe;
// RPC to check type
if (model is IFallbackCommandItem fallback)
{
DisplayTitle = fallback.DisplayTitle;
}
}
}
private void Item_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (!string.IsNullOrEmpty(e.PropertyName))
@@ -240,7 +278,7 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
{
// Use WyHash64 to generate stable ID hashes.
// manually seeding with 0, so that the hash is stable across launches
var result = WyHash64.ComputeHash64(_commandProviderId + Title + Subtitle, seed: 0);
var result = WyHash64.ComputeHash64(_commandProviderId + DisplayTitle + Title + Subtitle, seed: 0);
_generatedId = $"{_commandProviderId}{result}";
}
@@ -263,6 +301,11 @@ public sealed partial class TopLevelViewModel : ObservableObject, IListItem
return false;
}
if (!IsEnabled)
{
return false;
}
try
{
return UnsafeUpdateFallbackSynchronous(newQuery);