mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-05-18 05:05:25 +02:00
CmdPal: Add support for pages with parameters (redux) (#47826)
(this PR is an updated version of #43784) This PR adds a new type of page to Command Palette: The `ParametersPage`. This allows extensions to create commands that require a set of parameters before invoking the command. Previously, extensions could create commands with a form page to use an adaptive card for parameter input, but that was a relatively heavyweight UX. Instead, the `ParametersPage` allows extensions to define a set of lightweight inputs, which allows for a more streamlined experience. The parameters page is made up of a set of "runs". Each run represents a single element in the search box. Runs can be either: * A label run: a static piece of text * An value run: some input for the user to provide a value. These fall into several categories: * String input * Command Input * `IInvokableCommand`s become buttons in the search box * `IListPage`s become a list input to pick from (**these will be added in a follow-up PR**) There are a ton of samples included. I also added all my draft notes in the drafts folder, to see how we got here. I'd skip reviewing those. Furthermore, I added the "dumb" token support, where an extension can opt in to having tokens in the search box, delimited by ZWSP characters. The XAML styling was fixed by Niels a few months back Closes #40948 --------- Co-authored-by: Niels Laute <niels.laute@live.nl>
This commit is contained in:
@@ -60,7 +60,7 @@ A markdown page is a page inside of command palette that displays markdown conte
|
||||
|
||||
```csharp
|
||||
interface IMarkdownPage requires IPage {
|
||||
String[] Bodies(); // TODO! should this be an IBody, so we can make it observable?
|
||||
String[] Bodies();
|
||||
IDetails Details();
|
||||
IContextItem[] Commands { get; };
|
||||
}
|
||||
|
||||
@@ -76,6 +76,13 @@ functionality.
|
||||
- [Status messages](#status-messages)
|
||||
- [Rendering of ICommandItems in Lists and Menus](#rendering-of-icommanditems-in-lists-and-menus)
|
||||
- [Addenda I: API additions (ICommandProvider2)](#addenda-i-api-additions-icommandprovider2)
|
||||
- [Addenda II: Commands with Parameters](#addenda-ii-commands-with-parameters)
|
||||
- [String parameters](#string-parameters)
|
||||
- [Command parameters - Invokable Commands](#command-parameters---invokable-commands)
|
||||
- [Command parameters - List Commands](#command-parameters---list-commands)
|
||||
- [Examples](#examples)
|
||||
- [Addenda III: Rich Search (DRAFT)](#addenda-iii-rich-search-draft)
|
||||
- [Nov 2025 status](#nov-2025-status)
|
||||
- [Addenda IV: Dock bands](#addenda-iv-dock-bands)
|
||||
- [Pinning nested commands to the dock (and top level)](#pinning-nested-commands-to-the-dock-and-top-level)
|
||||
- [Class diagram](#class-diagram)
|
||||
@@ -2048,6 +2055,183 @@ Fortunately, we can put all of that (`GetApiExtensionStubs`,
|
||||
developers won't have to do anything. The toolkit will just do the right thing
|
||||
for them.
|
||||
|
||||
## Addenda II: Commands with Parameters
|
||||
|
||||
Extensions will often want to provide commands that accept parameters from the
|
||||
user.
|
||||
|
||||
To support this, we're adding a new page type. The `IParametersPage` is a page
|
||||
that allows an extension to define a set of parameters that the user can fill.
|
||||
These parameters can be of different types, such as:
|
||||
* Labels: static text that provides context or instructions.
|
||||
* String parameters: text input fields where the user can type a string.
|
||||
* Command parameters: interactive fields that allow the user to select from a
|
||||
list of predefined commands, or just press a button to select an input.
|
||||
|
||||
Interleaving labels with parameters allows extensions to create rich, guided
|
||||
input forms for their commands. These are a more lightweight solution than the
|
||||
current adaptive card content.
|
||||
|
||||
```csharp
|
||||
[uuid("a2590cc9-510c-4af7-b562-a6b56fe37f55")]
|
||||
interface IParameterRun requires INotifyPropChanged
|
||||
{
|
||||
};
|
||||
|
||||
interface ILabelRun requires IParameterRun
|
||||
{
|
||||
String Text{ get; };
|
||||
};
|
||||
|
||||
interface IParameterValueRun requires IParameterRun
|
||||
{
|
||||
String PlaceholderText{ get; };
|
||||
Boolean NeedsValue{ get; }; // TODO! name is weird
|
||||
};
|
||||
|
||||
interface IStringParameterRun requires IParameterValueRun
|
||||
{
|
||||
String Text{ get; set; };
|
||||
|
||||
// TODO! do we need a way to validate string inputs?
|
||||
};
|
||||
|
||||
interface ICommandParameterRun requires IParameterValueRun
|
||||
{
|
||||
String DisplayText{ get; };
|
||||
ICommand GetSelectValueCommand(UInt64 hostHwnd);
|
||||
IIconInfo Icon{ get; }; // ? maybe
|
||||
|
||||
};
|
||||
|
||||
interface IParametersPage requires IPage
|
||||
{
|
||||
IParameterRun[] Parameters{ get; };
|
||||
IListItem Command{ get; };
|
||||
};
|
||||
```
|
||||
|
||||
When we open a `IParametersPage`, we will render the `Parameters` in the search
|
||||
box. We'll move focus to the first `IParameterRun` that is not a `ILabelRun`.
|
||||
What those interactions looks like depends on the type of `IParameterRun`.
|
||||
|
||||
There are three basic types of inputs: strings, invokable commands, and lists.
|
||||
Strings are a special case that doesn't require a command to set the value.
|
||||
Lists and invokable commands are picked based on the type of the
|
||||
`SelectValueCommand`. Each of these are detailed below.
|
||||
|
||||
When all the parameters have `NeedsValue` set to `false`, we will display a
|
||||
single item to the user - the `Command` item.
|
||||
|
||||
### String parameters
|
||||
|
||||
These are rendered as a text box within the search box. The user can type into
|
||||
it. Focus is moved to the next parameter when the user presses Enter or tab.
|
||||
|
||||
### Command parameters - Invokable Commands
|
||||
|
||||
These are used when the `SelectValueCommand` is an `IInvokableCommand`.
|
||||
|
||||
These are rendered as a button within the search box. The button text is
|
||||
`DisplayText` if it is set. If it is not, we will display the
|
||||
`PlaceholderText`. If the user clicks the button, we invoke the
|
||||
`SelectValueCommand` (and ignore the `CommandResult`).
|
||||
|
||||
This is good for file pickers, date pickers, color pickers, etc. Anything that
|
||||
requires a custom UI to pick a value.
|
||||
|
||||
When the extension has picked a value, it should set the `NeedsValue` to false.
|
||||
The extension can also set the `DisplayText` and `Icon` to reflect the chosen value.
|
||||
|
||||
When the user presses enter with the button focused, we will also invoke the
|
||||
`SelectValueCommand`.
|
||||
|
||||
When the user presses tab, we will move focus to the next parameter.
|
||||
|
||||
If the `NeedsValue` property is changed to `false` while it's focused, we will
|
||||
move focus to the next parameter.
|
||||
|
||||
### Command parameters - List Commands
|
||||
|
||||
These are used when the `SelectValueCommand` is an `IListPage` - both static and
|
||||
dynamic lists work similarly.
|
||||
|
||||
These are rendered as a text box within the search box. When the user focuses
|
||||
the text box, we will display the items from the `IListPage` in the body of
|
||||
CmdPal. The user can then type to filter the list. This filtering will work the
|
||||
same way as any other list page in CmdPal - CmdPal will filter static lists, or
|
||||
pass the query to a dynamic list.
|
||||
|
||||
The items in this list should all be `IListItem` objects with
|
||||
`IInvokableCommands`. Putting a `IPage` into one of these items will cause the
|
||||
user to navigate away from the parameters page, which would probably be
|
||||
unexpected.
|
||||
|
||||
When the user picks an item from the list, the extension should handle that
|
||||
command by bubbling an event up to the `CommandRun`, and setting the `Value`,
|
||||
`DisplayText`, and `Icon` properties, and setting `NeedsValue` to false.
|
||||
|
||||
When the user presses enter with the text box focused, we will invoke the
|
||||
command of the selected item in the list.
|
||||
|
||||
When the user presses tab, we will move focus to the next parameter.
|
||||
|
||||
If the `NeedsValue` property is changed to `false` while it's focused, we will
|
||||
move focus to the next parameter.
|
||||
|
||||
### Examples
|
||||
|
||||
Lets say you had a command like "Create a note \${title} in \${folder}".
|
||||
`title` is a string input, and `folder` is a static list of folders.
|
||||
|
||||
The extension author can then define a `IParametersPage` with four runs in it:
|
||||
* A `ILabelRun` for "Create a note"
|
||||
* A `IStringParameterRun` for the `title`
|
||||
* A `ILabelRun` for "in"
|
||||
* A `ICommandParameterRun` for the `folder`. The `Command` will be a
|
||||
`IListPage`, where the items are possible folders
|
||||
|
||||
In this example, the user can pick the "create note" command, then type the
|
||||
title, hit enter/tab, and then pick a folder from the list, then hit enter to
|
||||
run the command.
|
||||
|
||||
Samples for the parameters page are implemented over in
|
||||
[the sample extension](../../ext/SamplePagesExtension/Pages/ParameterSamples.cs)
|
||||
|
||||
|
||||
## Addenda III: Rich Search (DRAFT)
|
||||
|
||||
> [!NOTE]
|
||||
> _Mike_: Rich search and parameters were prototyped together, but ultimately we used two different solutions.
|
||||
>
|
||||
> Currently, we have a dummy implementation of draft C (ZWSP tokens), but without full API changes. Detailed [below](#nov-2025-status).
|
||||
|
||||
Extensions will often want to provide rich search experiences for their users.
|
||||
|
||||
This addenda is broken into multiple draft specs currently. These represent
|
||||
different approaches to the same goals.
|
||||
|
||||
* **A**: [Rich Search Box](./drafts/RichSearchBox-draft-A.md)
|
||||
* **B**: [Prefix Search](./drafts/PrefixSearch-draft-B.md)
|
||||
* **C**: [ZWSP tokens](./drafts/PlainRichSearch-draft-C.md)
|
||||
|
||||
### Nov 2025 status
|
||||
|
||||
As of Nov 2025, we're implementing a simple version of draft C in the host.
|
||||
|
||||
In this version, if the extension implements `IDynamicListPage`, and also
|
||||
implements `IExtendedAttributesProvider`, then they can set the `TokenSearch`
|
||||
property. This will enlighten CmdPal to treat ZWSP-separated tokens in the
|
||||
search text specially.
|
||||
|
||||
For an example, see
|
||||
[this sample implementation](../../ext/SamplePagesExtension/Pages/SampleSuggestionsPage.cs).
|
||||
|
||||
In my head, I am still leaning towards a more full-featured version of draft C,
|
||||
but with full CommandItem's in the `ISearchUpdateArgs` instead of just strings.
|
||||
We'd almost need a new page type to support that, where the extension can add
|
||||
`ICommandItem`s to the search box directly.
|
||||
|
||||
## Addenda IV: Dock bands
|
||||
|
||||
The "dock" is another way to surface commands to the user. This is a
|
||||
@@ -2158,7 +2342,6 @@ because that method is was designed for two main purposes:
|
||||
In neither of those scenarios was the full "display" of the item needed. In
|
||||
pinning scenarios, however, we need everything that the user would see in the UI
|
||||
for that item, which is all in the `ICommandItem`.
|
||||
|
||||
## Class diagram
|
||||
|
||||
This is a diagram attempting to show the relationships between the various types we've defined for the SDK. Some elements are omitted for clarity. (Notably, `IconData` and `IPropChanged`, which are used in many places.)
|
||||
|
||||
Reference in New Issue
Block a user