From f34735edeba708f4696faec61c9c53239cdb57ae Mon Sep 17 00:00:00 2001 From: Michael Jolley Date: Tue, 8 Jul 2025 10:05:02 -0500 Subject: [PATCH] Adding fallback command for Windows Settings extension (#40331) Two changes: - Added a new fallback command for Windows Settings extension. If only one setting or one exact match for the query was found, display that setting in the list and open that setting on . If more than one setting was found, display a message to open Windows Settings to see the search results for your query. ![image](https://github.com/user-attachments/assets/bd5708a5-b1d5-466e-9c62-cd1cd7bb1f74) ![image](https://github.com/user-attachments/assets/98f4ac20-efe1-4782-8133-30afa17e3b7d) ![image](https://github.com/user-attachments/assets/e5da90e1-f89b-480c-bd26-214c68ac013a) - Modified the titles/subtitles of the extension to pull from Resources to aid in internationalization. Closes: #38548 and possibly #40308 --- .../Helpers/ResultHelper.cs | 3 +- .../Helpers/ScoringHelper.cs | 84 +++++++++++++++++ .../Pages/FallbackWindowsSettingsItem.cs | 89 +++++++++++++++++++ .../Pages/WindowsSettingsListPage.cs | 88 ++++-------------- .../Properties/Resources.Designer.cs | 36 ++++++++ .../Properties/Resources.resx | 12 +++ .../WindowsSettingsCommandsProvider.cs | 10 ++- 7 files changed, 245 insertions(+), 77 deletions(-) create mode 100644 src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Helpers/ScoringHelper.cs create mode 100644 src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Pages/FallbackWindowsSettingsItem.cs diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Helpers/ResultHelper.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Helpers/ResultHelper.cs index 70d6c48ac2..ab457a7956 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Helpers/ResultHelper.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Helpers/ResultHelper.cs @@ -22,8 +22,7 @@ namespace Microsoft.CmdPal.Ext.WindowsSettings; internal static class ResultHelper { internal static List GetResultList( - in IEnumerable list, - string query) + in IEnumerable list) { var resultList = new List(list.Count()); diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Helpers/ScoringHelper.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Helpers/ScoringHelper.cs new file mode 100644 index 0000000000..bf720e01cd --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Helpers/ScoringHelper.cs @@ -0,0 +1,84 @@ +// 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.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CmdPal.Ext.WindowsSettings.Classes; + +namespace Microsoft.CmdPal.Ext.WindowsSettings.Helpers; + +internal static class ScoringHelper +{ + // Rank settings by how they matched the search query. Order is: + // 1. Exact Name (10 points) + // 2. Name Starts With (8 points) + // 3. Name (5 points) + // 4. Area (4 points) + // 5. AltName (2 points) + // 6. Settings path (1 point) + internal static (WindowsSetting Setting, int Score) SearchScoringPredicate(string query, WindowsSetting setting) + { + if (string.IsNullOrWhiteSpace(query)) + { + // If no search string is entered skip query comparison. + return (setting, 0); + } + + if (string.Equals(setting.Name, query, StringComparison.OrdinalIgnoreCase)) + { + return (setting, 10); + } + + if (setting.Name.StartsWith(query, StringComparison.CurrentCultureIgnoreCase)) + { + return (setting, 8); + } + + if (setting.Name.Contains(query, StringComparison.CurrentCultureIgnoreCase)) + { + return (setting, 5); + } + + if (!(setting.Areas is null)) + { + foreach (var area in setting.Areas) + { + // Search for areas on normal queries. + if (area.Contains(query, StringComparison.CurrentCultureIgnoreCase)) + { + return (setting, 4); + } + + // Search for Area only on queries with action char. + if (area.Contains(query.Replace(":", string.Empty), StringComparison.CurrentCultureIgnoreCase) + && query.EndsWith(":", StringComparison.CurrentCultureIgnoreCase)) + { + return (setting, 4); + } + } + } + + if (!(setting.AltNames is null)) + { + foreach (var altName in setting.AltNames) + { + if (altName.Contains(query, StringComparison.CurrentCultureIgnoreCase)) + { + return (setting, 2); + } + } + } + + // Search by key char '>' for app name and settings path + if (query.Contains('>') && ResultHelper.FilterBySettingsPath(setting, query)) + { + return (setting, 1); + } + + return (setting, 0); + } +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Pages/FallbackWindowsSettingsItem.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Pages/FallbackWindowsSettingsItem.cs new file mode 100644 index 0000000000..a7a7291e91 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Pages/FallbackWindowsSettingsItem.cs @@ -0,0 +1,89 @@ +// 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.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Xml.Linq; +using Microsoft.CmdPal.Ext.WindowsSettings.Classes; +using Microsoft.CmdPal.Ext.WindowsSettings.Commands; +using Microsoft.CmdPal.Ext.WindowsSettings.Helpers; +using Microsoft.CmdPal.Ext.WindowsSettings.Properties; +using Microsoft.CommandPalette.Extensions.Toolkit; + +namespace Microsoft.CmdPal.Ext.WindowsSettings.Pages; + +internal sealed partial class FallbackWindowsSettingsItem : FallbackCommandItem +{ + private readonly Classes.WindowsSettings _windowsSettings; + + private readonly string _title = Resources.settings_fallback_title; + private readonly string _subtitle = Resources.settings_fallback_subtitle; + + public FallbackWindowsSettingsItem(Classes.WindowsSettings windowsSettings) + : base(new NoOpCommand(), Resources.settings_title) + { + Icon = IconHelpers.FromRelativePath("Assets\\WindowsSettings.svg"); + _windowsSettings = windowsSettings; + } + + public override void UpdateQuery(string query) + { + Command = new NoOpCommand(); + Title = string.Empty; + Subtitle = string.Empty; + Icon = null; + MoreCommands = null; + + if (string.IsNullOrWhiteSpace(query) || + _windowsSettings?.Settings is null) + { + return; + } + + var filteredList = _windowsSettings.Settings + .Select(setting => ScoringHelper.SearchScoringPredicate(query, setting)) + .Where(scoredSetting => scoredSetting.Score > 0) + .OrderByDescending(scoredSetting => scoredSetting.Score); + + if (!filteredList.Any()) + { + return; + } + + if (filteredList.Count() == 1 || + filteredList.Any(a => a.Score == 10)) + { + var setting = filteredList.First().Setting; + + Title = setting.Name; + Subtitle = setting.JoinedFullSettingsPath; + Icon = IconHelpers.FromRelativePath("Assets\\WindowsSettings.svg"); + Command = new OpenSettingsCommand(setting) + { + Icon = IconHelpers.FromRelativePath("Assets\\WindowsSettings.svg"), + Name = setting.Name, + }; + + // There is a case with MMC snap-ins where we don't have .msc files fort them. Then we need to show the note for this results in subtitle too. + // These results have mmc.exe as command and their note property is filled. + if (setting.Command == "mmc.exe" && !string.IsNullOrEmpty(setting.Note)) + { + Subtitle += $"\u0020\u0020\u002D\u0020\u0020{Resources.Note}: {setting.Note}"; // "\u0020\u0020\u002D\u0020\u0020" = "" + } + + return; + } + + // We found more than one result. Make our command take + // us to the Windows Settings search page, prepopulated with this search. + var settingsPage = new WindowsSettingsListPage(_windowsSettings, query); + Title = string.Format(CultureInfo.CurrentCulture, _title, query); + Icon = IconHelpers.FromRelativePath("Assets\\WindowsSettings.svg"); + Subtitle = _subtitle; + Command = settingsPage; + + return; + } +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Pages/WindowsSettingsListPage.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Pages/WindowsSettingsListPage.cs index 3f5a6c6217..3ac04005e2 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Pages/WindowsSettingsListPage.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Pages/WindowsSettingsListPage.cs @@ -6,6 +6,8 @@ using System; using System.Collections.Generic; using System.Linq; using Microsoft.CmdPal.Ext.WindowsSettings.Classes; +using Microsoft.CmdPal.Ext.WindowsSettings.Helpers; +using Microsoft.CmdPal.Ext.WindowsSettings.Properties; using Microsoft.CommandPalette.Extensions; using Microsoft.CommandPalette.Extensions.Toolkit; @@ -18,11 +20,17 @@ internal sealed partial class WindowsSettingsListPage : DynamicListPage public WindowsSettingsListPage(Classes.WindowsSettings windowsSettings) { Icon = IconHelpers.FromRelativePath("Assets\\WindowsSettings.svg"); - Name = "Windows Settings"; + Name = Resources.settings_title; Id = "com.microsoft.cmdpal.windowsSettings"; _windowsSettings = windowsSettings; } + public WindowsSettingsListPage(Classes.WindowsSettings windowsSettings, string query) + : this(windowsSettings) + { + SearchText = query; + } + public List Query(string query) { if (_windowsSettings?.Settings is null) @@ -31,87 +39,21 @@ internal sealed partial class WindowsSettingsListPage : DynamicListPage } var filteredList = _windowsSettings.Settings - .Select(SearchScoringPredicate) + .Select(setting => ScoringHelper.SearchScoringPredicate(query, setting)) .Where(scoredSetting => scoredSetting.Score > 0) .OrderByDescending(scoredSetting => scoredSetting.Score) .Select(scoredSetting => scoredSetting.Setting); - var newList = ResultHelper.GetResultList(filteredList, query); + var newList = ResultHelper.GetResultList(filteredList); return newList; - - // Rank settings by how they matched the search query. Order is: - // 1. Exact Name (10 points) - // 2. Name Starts With (8 points) - // 3. Name (5 points) - // 4. Area (4 points) - // 5. AltName (2 points) - // 6. Settings path (1 point) - (WindowsSetting Setting, int Score) SearchScoringPredicate(WindowsSetting setting) - { - if (string.IsNullOrWhiteSpace(query)) - { - // If no search string is entered skip query comparison. - return (setting, 0); - } - - if (string.Equals(setting.Name, query, StringComparison.OrdinalIgnoreCase)) - { - return (setting, 10); - } - - if (setting.Name.StartsWith(query, StringComparison.CurrentCultureIgnoreCase)) - { - return (setting, 8); - } - - if (setting.Name.Contains(query, StringComparison.CurrentCultureIgnoreCase)) - { - return (setting, 5); - } - - if (!(setting.Areas is null)) - { - foreach (var area in setting.Areas) - { - // Search for areas on normal queries. - if (area.Contains(query, StringComparison.CurrentCultureIgnoreCase)) - { - return (setting, 4); - } - - // Search for Area only on queries with action char. - if (area.Contains(query.Replace(":", string.Empty), StringComparison.CurrentCultureIgnoreCase) - && query.EndsWith(":", StringComparison.CurrentCultureIgnoreCase)) - { - return (setting, 4); - } - } - } - - if (!(setting.AltNames is null)) - { - foreach (var altName in setting.AltNames) - { - if (altName.Contains(query, StringComparison.CurrentCultureIgnoreCase)) - { - return (setting, 2); - } - } - } - - // Search by key char '>' for app name and settings path - if (query.Contains('>') && ResultHelper.FilterBySettingsPath(setting, query)) - { - return (setting, 1); - } - - return (setting, 0); - } } public override void UpdateSearchText(string oldSearch, string newSearch) { - RaiseItemsChanged(0); + if (oldSearch != newSearch) + { + RaiseItemsChanged(0); + } } public override IListItem[] GetItems() diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Properties/Resources.Designer.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Properties/Resources.Designer.cs index 0a421820e8..0d5ce2cede 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Properties/Resources.Designer.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Properties/Resources.Designer.cs @@ -3840,6 +3840,42 @@ namespace Microsoft.CmdPal.Ext.WindowsSettings.Properties { } } + /// + /// Looks up a localized string similar to Search Windows settings for this device. + /// + internal static string settings_fallback_subtitle { + get { + return ResourceManager.GetString("settings_fallback_subtitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Search for "{0}" in Windows settings. + /// + internal static string settings_fallback_title { + get { + return ResourceManager.GetString("settings_fallback_title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Navigate to specific Windows settings. + /// + internal static string settings_subtitle { + get { + return ResourceManager.GetString("settings_subtitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Windows Settings. + /// + internal static string settings_title { + get { + return ResourceManager.GetString("settings_title", resourceCulture); + } + } + /// /// Looks up a localized string similar to Settings app. /// diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Properties/Resources.resx b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Properties/Resources.resx index c097ed0841..467defe1e8 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Properties/Resources.resx +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/Properties/Resources.resx @@ -2085,4 +2085,16 @@ Windows Settings + + Windows Settings + + + Navigate to specific Windows settings + + + Search for "{0}" in Windows settings + + + Search Windows settings for this device + \ No newline at end of file diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/WindowsSettingsCommandsProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/WindowsSettingsCommandsProvider.cs index bf4ccfb36e..25559b394e 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/WindowsSettingsCommandsProvider.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsSettings/WindowsSettingsCommandsProvider.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using Microsoft.CmdPal.Ext.WindowsSettings.Helpers; +using Microsoft.CmdPal.Ext.WindowsSettings.Pages; using Microsoft.CmdPal.Ext.WindowsSettings.Properties; using Microsoft.CommandPalette.Extensions; using Microsoft.CommandPalette.Extensions.Toolkit; @@ -17,6 +18,8 @@ public partial class WindowsSettingsCommandsProvider : CommandProvider private readonly WindowsSettings.Classes.WindowsSettings? _windowsSettings; #pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. + private readonly FallbackWindowsSettingsItem _fallback; + public WindowsSettingsCommandsProvider() { Id = "Windows.Settings"; @@ -26,9 +29,10 @@ public partial class WindowsSettingsCommandsProvider : CommandProvider _windowsSettings = JsonSettingsListHelper.ReadAllPossibleSettings(); _searchSettingsListItem = new CommandItem(new WindowsSettingsListPage(_windowsSettings)) { - Title = "Windows Settings", - Subtitle = "Navigate to specific Windows settings", + Title = Resources.settings_title, + Subtitle = Resources.settings_subtitle, }; + _fallback = new(_windowsSettings); UnsupportedSettingsHelper.FilterByBuild(_windowsSettings); @@ -42,4 +46,6 @@ public partial class WindowsSettingsCommandsProvider : CommandProvider _searchSettingsListItem ]; } + + public override IFallbackCommandItem[] FallbackCommands() => [_fallback]; }