diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/ReloadFinishedMessage.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/ReloadFinishedMessage.cs
new file mode 100644
index 0000000000..a6e2122edd
--- /dev/null
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/ReloadFinishedMessage.cs
@@ -0,0 +1,7 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace Microsoft.CmdPal.UI.ViewModels.Messages;
+
+public record ReloadFinishedMessage();
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/SettingsExtensionsViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/SettingsExtensionsViewModel.cs
new file mode 100644
index 0000000000..7f2b9e322a
--- /dev/null
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/SettingsExtensionsViewModel.cs
@@ -0,0 +1,142 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using CommunityToolkit.Mvvm.Messaging;
+
+using Microsoft.CmdPal.Core.ViewModels.Messages;
+using Microsoft.CmdPal.UI.ViewModels.Messages;
+using Microsoft.CommandPalette.Extensions.Toolkit;
+
+namespace Microsoft.CmdPal.UI.ViewModels;
+
+///
+/// Provides filtering over the list of provider settings view models.
+/// Intended to be used by the UI to bind a TextBox (SearchText) and an ItemsRepeater (FilteredProviders).
+///
+public partial class SettingsExtensionsViewModel : ObservableObject
+{
+ private readonly ObservableCollection _source;
+ private readonly TaskScheduler _uiScheduler;
+
+ public ObservableCollection FilteredProviders { get; } = [];
+
+ private string _searchText = string.Empty;
+
+ public string SearchText
+ {
+ get => _searchText;
+ set
+ {
+ if (_searchText != value)
+ {
+ _searchText = value;
+ OnPropertyChanged();
+ ApplyFilter();
+ }
+ }
+ }
+
+ public string ItemCounterText
+ {
+ get
+ {
+ var hasQuery = !string.IsNullOrWhiteSpace(_searchText);
+ var count = hasQuery ? FilteredProviders.Count : _source.Count;
+ var suffix = hasQuery ? "extensions found" : "extensions installed";
+ return $"{count} {suffix}";
+ }
+ }
+
+ private bool _showManualReloadOverlay;
+
+ public bool ShowManualReloadOverlay
+ {
+ get => _showManualReloadOverlay;
+ private set
+ {
+ if (_showManualReloadOverlay != value)
+ {
+ _showManualReloadOverlay = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ public bool ShowNoResultsPanel => !string.IsNullOrWhiteSpace(_searchText) && FilteredProviders.Count == 0;
+
+ public bool HasResults => !ShowNoResultsPanel;
+
+ public IRelayCommand OpenStoreWithExtensionCommand { get; }
+
+ public IRelayCommand ReloadExtensionsCommand { get; }
+
+ public SettingsExtensionsViewModel(ObservableCollection source, TaskScheduler uiScheduler)
+ {
+ _source = source;
+ _uiScheduler = uiScheduler;
+ _source.CollectionChanged += Source_CollectionChanged;
+ ApplyFilter();
+
+ OpenStoreWithExtensionCommand = new RelayCommand(OpenStoreWithExtension);
+ ReloadExtensionsCommand = new RelayCommand(ReloadExtensions);
+
+ WeakReferenceMessenger.Default.Register(this, (_, _) =>
+ {
+ Task.Factory.StartNew(() => ShowManualReloadOverlay = false, CancellationToken.None, TaskCreationOptions.None, _uiScheduler);
+ });
+ }
+
+ private void Source_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
+ {
+ ApplyFilter();
+ }
+
+ private void ApplyFilter()
+ {
+ var query = _searchText;
+ var filtered = ListHelpers.FilterList(_source, query, Matches);
+ ListHelpers.InPlaceUpdateList(FilteredProviders, filtered);
+ OnPropertyChanged(nameof(ItemCounterText));
+ OnPropertyChanged(nameof(HasResults));
+ OnPropertyChanged(nameof(ShowNoResultsPanel));
+ }
+
+ private static int Matches(string query, ProviderSettingsViewModel item)
+ {
+ if (string.IsNullOrWhiteSpace(query))
+ {
+ return 100;
+ }
+
+ return Contains(item.DisplayName, query)
+ || Contains(item.ExtensionName, query)
+ || Contains(item.ExtensionSubtext, query)
+ ? 100
+ : 0;
+ }
+
+ private static bool Contains(string? haystack, string needle)
+ {
+ return !string.IsNullOrEmpty(haystack) && haystack.Contains(needle, StringComparison.OrdinalIgnoreCase);
+ }
+
+ private void OpenStoreWithExtension(string? query)
+ {
+ const string ExtensionsAssocUri = "ms-windows-store://assoc/?Tags=AppExtension-com.microsoft.commandpalette";
+ ShellHelpers.OpenInShell(ExtensionsAssocUri);
+ }
+
+ private void ReloadExtensions()
+ {
+ ShowManualReloadOverlay = true;
+ WeakReferenceMessenger.Default.Send();
+ WeakReferenceMessenger.Default.Send();
+ }
+}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/SettingsViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/SettingsViewModel.cs
index 2083d35191..aa5dfe3c48 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/SettingsViewModel.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/SettingsViewModel.cs
@@ -120,6 +120,8 @@ public partial class SettingsViewModel : INotifyPropertyChanged
public ObservableCollection CommandProviders { get; } = [];
+ public SettingsExtensionsViewModel Extensions { get; }
+
public SettingsViewModel(SettingsModel settings, IServiceProvider serviceProvider, TaskScheduler scheduler)
{
_settings = settings;
@@ -135,6 +137,8 @@ public partial class SettingsViewModel : INotifyPropertyChanged
var settingsModel = new ProviderSettingsViewModel(item, providerSettings, _serviceProvider);
CommandProviders.Add(settingsModel);
}
+
+ Extensions = new SettingsExtensionsViewModel(CommandProviders, scheduler);
}
private IEnumerable GetCommandProviders()
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/TopLevelCommandManager.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/TopLevelCommandManager.cs
index f55a322792..be219ab55d 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/TopLevelCommandManager.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/TopLevelCommandManager.cs
@@ -259,6 +259,9 @@ public partial class TopLevelCommandManager : ObservableObject,
IsLoading = false;
+ // Send on the current thread; receivers should marshal to UI if needed
+ WeakReferenceMessenger.Default.Send();
+
return true;
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/ExtensionsPage.xaml b/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/ExtensionsPage.xaml
index 56ec56fe20..f9790cb274 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/ExtensionsPage.xaml
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/ExtensionsPage.xaml
@@ -14,19 +14,107 @@
xmlns:viewModels="using:Microsoft.CmdPal.UI.ViewModels"
mc:Ignorable="d">
+
+
+
+
+
+
+
+
+
-
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/ExtensionsPage.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/ExtensionsPage.xaml.cs
index e164638ebb..e99296bad9 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/ExtensionsPage.xaml.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/ExtensionsPage.xaml.cs
@@ -8,6 +8,7 @@ using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Input;
namespace Microsoft.CmdPal.UI.Settings;
@@ -35,4 +36,10 @@ public sealed partial class ExtensionsPage : Page
}
}
}
+
+ private void OnFindInvoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args)
+ {
+ SearchBox?.Focus(FocusState.Keyboard);
+ args.Handled = true;
+ }
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Strings/en-us/Resources.resw b/src/modules/cmdpal/Microsoft.CmdPal.UI/Strings/en-us/Resources.resw
index 4284031df0..c6701da9ce 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Strings/en-us/Resources.resw
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Strings/en-us/Resources.resw
@@ -441,4 +441,49 @@ Right-click to remove the key combination, thereby deactivating the shortcut.
Navigation page opened
+
+ Get more extension in the Microsoft Store
+
+
+ Opens the Microsoft Store to show available third-party extensions
+
+
+ Related links
+
+
+ Search extensions
+
+
+ No extensions found
+
+
+ Try a different search term
+
+
+ Browse Microsoft Store for Command Palette extensions
+
+
+ Press Ctrl+F to focus the search box
+
+
+ Ctrl+F
+
+
+ S
+
+
+ B
+
+
+ M
+
+
+ See more
+
+
+ Reload extensions
+
+
+ Reloading extensions…
+
\ No newline at end of file