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:
Mike Griese
2026-05-15 20:31:32 -05:00
committed by GitHub
parent b99defbc0d
commit 42902eeba5
56 changed files with 7442 additions and 1941 deletions

View File

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

View File

@@ -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.)