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]; }