From 456ace19e710184f134753c25fc37cd256b48c58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Pol=C3=A1=C5=A1ek?= Date: Fri, 29 Aug 2025 01:00:41 +0200 Subject: [PATCH] Initial prototype for extension list search MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a rough implementation that appears to work, but it hasn’t been tested. Don’t rely on it yet—I’m too tired to test properly or write a polished commit message. --- .../Messages/ReloadFinishedMessage.cs | 7 + .../SettingsExtensionsViewModel.cs | 142 ++++++++++++++++++ .../SettingsViewModel.cs | 4 + .../TopLevelCommandManager.cs | 3 + .../Settings/ExtensionsPage.xaml | 102 ++++++++++++- .../Settings/ExtensionsPage.xaml.cs | 7 + .../Strings/en-us/Resources.resw | 45 ++++++ 7 files changed, 308 insertions(+), 2 deletions(-) create mode 100644 src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/ReloadFinishedMessage.cs create mode 100644 src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/SettingsExtensionsViewModel.cs 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