From 1e7cc5cbda06a4b4be8fcc146ff982bc7d7f489c Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 12 Nov 2024 05:58:43 -0800 Subject: [PATCH] Spec, basic impl for `TextToSuggest` (#145) As discussed in #121. Adds a new `IListItem.TextToSuggest` property. If an extension populates that, and the user hits right-arrow when the item is selected, we'll set the `SearchText` to that text. I didn't give it a UI treatment, because 1. I'm not good at XAML 2. As noted in https://github.com/zadjii-msft/PowerToys/issues/121#issuecomment-2464704543, there's complicated edge cases here. You have to track what's actually been typed, and then also have a ghost suggestion for the very first item (before the user types/up/downs at all). I didn't think it was valuable to implement that right now before we have A Real App. * targets #144 * which targets #142 * which targets #141 and those guys are all merged as of #150 --- .../Helpers/ResultHelper.cs | 8 +++--- .../ListItemViewModel.cs | 7 +++++ .../Views/ListPage.xaml.cs | 27 +++++++++---------- .../doc/initial-sdk-spec/initial-sdk-spec.md | 21 ++++++++++++++- .../ListItem.cs | 25 +++++++++-------- .../Microsoft.CmdPal.Extensions.idl | 1 + 6 files changed, 56 insertions(+), 33 deletions(-) diff --git a/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.Registry/Helpers/ResultHelper.cs b/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.Registry/Helpers/ResultHelper.cs index a4a5fc1bd3..80f4b0e82c 100644 --- a/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.Registry/Helpers/ResultHelper.cs +++ b/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.Registry/Helpers/ResultHelper.cs @@ -42,20 +42,20 @@ internal static class ResultHelper if (entry.Exception is null && !(entry.Key is null)) { // when key contains keys or fields - // TODO: Investigate query text display (i.e. placholder text?) --> result.QueryTextDisplay = entry.Key.Name; + result.TextToSuggest = entry.Key.Name; result.Subtitle = RegistryHelper.GetSummary(entry.Key); result.Title = GetTruncatedText(entry.Key.Name, MaxTextLength.MaximumTitleLengthWithTwoSymbols); } else if (entry.Key is null && !(entry.Exception is null)) { // on error (e.g access denied) - // TODO GH #121 Investigate query text display (i.e placeholder text?) --> result.QueryTextDisplay = entry.KeyPath; + result.TextToSuggest = entry.KeyPath; result.Subtitle = GetTruncatedText(entry.Exception.Message, MaxTextLength.MaximumSubTitleLengthWithTwoSymbols, TruncateSide.OnlyFromRight); result.Title = GetTruncatedText(entry.KeyPath, MaxTextLength.MaximumTitleLengthWithTwoSymbols); } else { - // TODO GH #121 Investigate query text display (i.e placeholder text?) --> result.QueryTextDisplay = entry.KeyPath; + result.TextToSuggest = entry.KeyPath; result.Title = GetTruncatedText(entry.KeyPath, MaxTextLength.MaximumTitleLengthWithTwoSymbols); } @@ -76,7 +76,7 @@ internal static class ResultHelper return new List(0); } - List> valueList = new List>(key.ValueCount); + var valueList = new List>(key.ValueCount); var resultList = new List(); diff --git a/src/modules/cmdpal/WindowsCommandPalette/ListItemViewModel.cs b/src/modules/cmdpal/WindowsCommandPalette/ListItemViewModel.cs index bef8a5538c..ea175e33c2 100644 --- a/src/modules/cmdpal/WindowsCommandPalette/ListItemViewModel.cs +++ b/src/modules/cmdpal/WindowsCommandPalette/ListItemViewModel.cs @@ -24,6 +24,8 @@ public sealed class ListItemViewModel : INotifyPropertyChanged, IDisposable internal string Icon { get; private set; } + internal string TextToSuggest { get; private set; } + private readonly Lazy _details; internal DetailsViewModel? Details => _details.Value; @@ -105,6 +107,7 @@ public sealed class ListItemViewModel : INotifyPropertyChanged, IDisposable this.Title = model.Title; this.Subtitle = model.Subtitle; this.Icon = model.Icon.Icon; + this.TextToSuggest = model.TextToSuggest; if (model.Tags != null) { @@ -150,6 +153,10 @@ public sealed class ListItemViewModel : INotifyPropertyChanged, IDisposable this.Icon = item.Command.Icon.Icon; BubbleXamlPropertyChanged(nameof(IcoElement)); break; + case nameof(TextToSuggest): + this.TextToSuggest = item.TextToSuggest; + BubbleXamlPropertyChanged(nameof(TextToSuggest)); + break; } BubbleXamlPropertyChanged(args.PropertyName); diff --git a/src/modules/cmdpal/WindowsCommandPalette/Views/ListPage.xaml.cs b/src/modules/cmdpal/WindowsCommandPalette/Views/ListPage.xaml.cs index 790f5ed2f6..4eef23d8b4 100644 --- a/src/modules/cmdpal/WindowsCommandPalette/Views/ListPage.xaml.cs +++ b/src/modules/cmdpal/WindowsCommandPalette/Views/ListPage.xaml.cs @@ -13,7 +13,6 @@ using Microsoft.UI.Xaml.Controls.Primitives; using Microsoft.UI.Xaml.Input; using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Navigation; -using WindowsCommandPalette; namespace WindowsCommandPalette.Views; @@ -52,18 +51,7 @@ public sealed partial class ListPage : Microsoft.UI.Xaml.Controls.Page, INotifyP } } - private bool MoreCommandsAvailable - { - get - { - if (ItemsList.SelectedItem is not ListItemViewModel li) - { - return false; - } - - return li.HasMoreCommands; - } - } + private bool MoreCommandsAvailable => ItemsList.SelectedItem is not ListItemViewModel li ? false : li.HasMoreCommands; public ListPage() { @@ -173,7 +161,7 @@ public sealed partial class ListPage : Microsoft.UI.Xaml.Controls.Page, INotifyP private void MoreCommandsButton_Tapped(object sender, TappedRoutedEventArgs e) { - FlyoutShowOptions options = new FlyoutShowOptions + var options = new FlyoutShowOptions { ShowMode = FlyoutShowMode.Standard, }; @@ -283,6 +271,15 @@ public sealed partial class ListPage : Microsoft.UI.Xaml.Controls.Page, INotifyP e.Handled = true; } + else if (e.Key == Windows.System.VirtualKey.Right) + { + if (!string.IsNullOrEmpty(SelectedItem?.TextToSuggest)) + { + FilterBox.Text = SelectedItem.TextToSuggest; + FilterBox.Select(SelectedItem.TextToSuggest.Length, 0); + FilterBox.Focus(FocusState.Keyboard); + } + } else if (e.Key == Windows.System.VirtualKey.Enter /* && ItemsList.SelectedItem != null */) { if (ItemsList.SelectedItem is ListItemViewModel li) @@ -310,7 +307,7 @@ public sealed partial class ListPage : Microsoft.UI.Xaml.Controls.Page, INotifyP } // ctrl+k else if (ctrlPressed && e.Key == Windows.System.VirtualKey.K && ActionsDropdown.Items.Count > 0) { - FlyoutShowOptions options = new FlyoutShowOptions + var options = new FlyoutShowOptions { ShowMode = FlyoutShowMode.Standard, }; diff --git a/src/modules/cmdpal/doc/initial-sdk-spec/initial-sdk-spec.md b/src/modules/cmdpal/doc/initial-sdk-spec/initial-sdk-spec.md index ade1fa04da..97b9cb84b2 100644 --- a/src/modules/cmdpal/doc/initial-sdk-spec/initial-sdk-spec.md +++ b/src/modules/cmdpal/doc/initial-sdk-spec/initial-sdk-spec.md @@ -1,7 +1,7 @@ --- author: Mike Griese created on: 2024-07-19 -last updated: 2024-11-07 +last updated: 2024-11-08 issue id: n/a --- @@ -569,6 +569,7 @@ interface IListItem requires INotifyPropChanged { IDetails Details{ get; }; IFallbackHandler FallbackHandler{ get; }; String Section { get; }; + String TextToSuggest { get; }; } interface IGridProperties { @@ -703,6 +704,24 @@ actions (if it sets `Details`). false`. If the user activates the automatic "Show details" action, then the github action can then fetch the body and show it. +Each item in the list may also provide `TextToSuggest`. This serves a similar +purpose to `result.QueryTextDisplay` in the original PowerToys Run API. When +provided, DevPal will use that text to provide a suggested search query to the +user. The user can accept that suggestion with a single keypress, and DevPal +will then set the `SearchText` on the page to that `TextToSuggest`. This is +especially useful for dynamic lists, to easily populate the entire `SearchText` +as the user navigates the list. + +Consider the Windows Registry command. When the page is initially loaded, it +displays only the top-level registry keys (`HKEY_CURRENT_USER`, +`HKEY_LOCAL_MACHINE`, etc). If the user types `HKC`, the command will filter the +results down to just `HKEY_CURRENT_USER`, `HKEY_CLASSES_ROOT` and +`HKEY_CURRENT_CONFIG`. However, if the user at this point taps the right-arrow +key, DevPall will use the `TextToSuggest` from the `HKEY_CURRENT_USER` +`ListItem` to fill the `SearchText` with `"HKEY_CURRENT_USER\"`. The extension +will then be notified of this change to the search text, and can now return +results nested under `HKEY_CURRENT_USER\`. + An example list page for the Hacker News extension: ```cs diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CmdPal.Extensions.Helpers/ListItem.cs b/src/modules/cmdpal/extensionsdk/Microsoft.CmdPal.Extensions.Helpers/ListItem.cs index 171a6991da..67fa28ea2a 100644 --- a/src/modules/cmdpal/extensionsdk/Microsoft.CmdPal.Extensions.Helpers/ListItem.cs +++ b/src/modules/cmdpal/extensionsdk/Microsoft.CmdPal.Extensions.Helpers/ListItem.cs @@ -2,8 +2,6 @@ // 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.ComponentModel.DataAnnotations; - namespace Microsoft.CmdPal.Extensions.Helpers; public class ListItem : BaseObservable, IListItem @@ -17,6 +15,7 @@ public class ListItem : BaseObservable, IListItem private IContextItem[] _moreCommands = []; private IFallbackHandler? _fallbackHandler; private string _section = string.Empty; + private string _textToSuggest = string.Empty; public IconDataType? Icon { @@ -30,17 +29,7 @@ public class ListItem : BaseObservable, IListItem public string Title { - get - { - if (!string.IsNullOrEmpty(this._title)) - { - return _title; - } - else - { - return _command?.Name ?? string.Empty; - } - } + get => !string.IsNullOrEmpty(this._title) ? _title : _command?.Name ?? string.Empty; set { @@ -115,6 +104,16 @@ public class ListItem : BaseObservable, IListItem } } + public string TextToSuggest + { + get => _textToSuggest; + set + { + _textToSuggest = value; + OnPropertyChanged(nameof(TextToSuggest)); + } + } + public ListItem(ICommand command) { Command = command; diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CmdPal.Extensions/Microsoft.CmdPal.Extensions.idl b/src/modules/cmdpal/extensionsdk/Microsoft.CmdPal.Extensions/Microsoft.CmdPal.Extensions.idl index 96c031264f..26dbe41896 100644 --- a/src/modules/cmdpal/extensionsdk/Microsoft.CmdPal.Extensions/Microsoft.CmdPal.Extensions.idl +++ b/src/modules/cmdpal/extensionsdk/Microsoft.CmdPal.Extensions/Microsoft.CmdPal.Extensions.idl @@ -214,6 +214,7 @@ namespace Microsoft.CmdPal.Extensions IDetails Details{ get; }; IFallbackHandler FallbackHandler{ get; }; String Section { get; }; + String TextToSuggest { get; }; } [contract(Microsoft.CmdPal.Extensions.ExtensionsContract, 1)]