diff --git a/src/modules/cmdpal/doc/initial-sdk-spec/generate-interface.ps1 b/src/modules/cmdpal/doc/initial-sdk-spec/generate-interface.ps1
index 79d23b24fd..38edfa6274 100644
--- a/src/modules/cmdpal/doc/initial-sdk-spec/generate-interface.ps1
+++ b/src/modules/cmdpal/doc/initial-sdk-spec/generate-interface.ps1
@@ -1,8 +1,15 @@
# The spec is next to this script:
$pathToSpec = Join-Path (Split-Path -parent $MyInvocation.MyCommand.Definition) initial-sdk-spec.md
# First, use the mistletoe library to parse the markdown file to json
+if (-not (Get-Command mistletoe -ErrorAction SilentlyContinue)) {
+ throw 'The generator requires mistletoe. Install it with python -m pip install mistletoe and add its scripts directory to PATH.'
+}
+
$jsonText = mistletoe $pathToSpec --renderer mistletoe.ast_renderer.AstRenderer
-$json = $jsonText | ConvertFrom-Json
+if ($LASTEXITCODE -ne 0) {
+ throw "mistletoe failed with exit code $LASTEXITCODE"
+}
+$json = $jsonText | ConvertFrom-Json -ErrorAction Stop
$sdkContents = ""
foreach ($item in $json.children) {
@@ -55,7 +62,11 @@ foreach ($item in $json.children) {
}
# now, write the fully formatted interface with headers and all:
-Write-Output @"
+$interface = @"
+//
+// Generated from doc/initial-sdk-spec/initial-sdk-spec.md by generate-interface.ps1.
+// Edit the Markdown spec, then regenerate this file.
+//
namespace Microsoft.CommandPalette.Extensions
{
[contractversion(1)]
@@ -115,3 +126,8 @@ namespace Microsoft.CommandPalette.Extensions
$sdkContents
}
"@
+
+# Keep generated output free of trailing whitespace and use consistent line endings.
+$interface = $interface -replace "\r\n?", "`n"
+$interface = $interface -replace "(?m)[\t ]+$", ""
+Write-Output ($interface.TrimEnd("`n") -replace "`n", [Environment]::NewLine)
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 3fa29c51dd..bdbf8ededc 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: 2026-02-05
+last updated: 2026-08-28
issue id: n/a
---
@@ -85,6 +85,9 @@ functionality.
- [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)
+ - [Addenda V: Extra content types](#addenda-v-extra-content-types)
+ - [Image content](#image-content)
+ - [Plain text content](#plain-text-content)
- [Class diagram](#class-diagram)
- [Future considerations](#future-considerations)
- [Arbitrary parameters and arguments](#arbitrary-parameters-and-arguments)
@@ -819,8 +822,21 @@ interface IListItem requires ICommandItem {
String TextToSuggest { get; };
}
-interface IGridProperties {
- Windows.Foundation.Size TileSize { get; };
+[uuid("50C6F080-1CBE-4CE4-B92F-DA2F116ED524")]
+interface IGridProperties requires INotifyPropChanged { }
+
+[uuid("05914D59-6ECB-4992-9CF2-5982B5120A26")]
+interface ISmallGridLayout requires IGridProperties { }
+
+interface IMediumGridLayout requires IGridProperties
+{
+ Boolean ShowTitle { get; };
+}
+
+interface IGalleryGridLayout requires IGridProperties
+{
+ Boolean ShowTitle { get; };
+ Boolean ShowSubtitle { get; };
}
interface IListPage requires IPage, INotifyItemsChanged {
@@ -942,9 +958,10 @@ of grouped results, they're free to have as many sections as they like.
When the `GridProperties` property is set to null, DevPal will display the items
as a simple list, grouping them by section. When the `GridProperties` property
-is set to a non-null value, DevPal will display the items as a grid, with each
-item in the grid being a `TileSize` square. Grids are useful for showing items
-that are more visual in nature, like images or icons.
+is set to a non-null value, DevPal uses the implemented layout interface:
+`ISmallGridLayout`, `IMediumGridLayout`, or `IGalleryGridLayout`. Medium and
+gallery layouts can show titles, and gallery layouts can also show subtitles.
+Grids are useful for visual content such as images or icons.
Each item in the list may also include an optional `Details` property. This
allows the extension to provide additional information about the item, like a
@@ -1125,7 +1142,7 @@ interface IFilterItem {}
[uuid("0a923c7f-5b7b-431d-9898-3c8c841d02ed")]
interface ISeparatorFilterItem requires IFilterItem {}
-interface IFilter requires IFilterItem {
+interface IFilter requires INotifyPropChanged, IFilterItem {
String Id { get; };
String Name { get; };
IIconInfo Icon { get; };
@@ -1133,7 +1150,7 @@ interface IFilter requires IFilterItem {
interface IFilters {
String CurrentFilterId { get; set; };
- IFilterItem[] Filters();
+ IFilterItem[] GetFilters();
}
```
@@ -1416,6 +1433,15 @@ interface ITag {
[uuid("6a6dd345-37a3-4a1e-914d-4f658a4d583d")]
interface IDetailsData {}
+
+[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
+enum ContentSize
+{
+ Small = 0,
+ Medium = 1,
+ Large = 2,
+};
+
interface IDetailsElement {
String Key { get; };
IDetailsData Data { get; };
@@ -1487,6 +1513,10 @@ interface IFallbackCommandItem requires ICommandItem {
String DisplayTitle { get; };
};
+interface IFallbackCommandItem2 requires IFallbackCommandItem {
+ String Id { get; };
+};
+
interface ICommandProvider requires Windows.Foundation.IClosable, INotifyItemsChanged
{
String Id { get; };
@@ -2355,6 +2385,45 @@ 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`.
+
+## Addenda V: Extra content types
+
+Extra content types for [Content Pages](#content-pages) views so we can provide extra functionality to the user.
+
+### Image content
+
+Image content is dedicated to displaying a single image. The host will attempt to display the entire
+image in the UI or a scaled down preview, while respecting the max width and height. If possible, the host will
+provide UI controls to display the image 1:1, save it or copy it to the clipboard.
+
+```csharp
+interface IImageContent requires IContent {
+ IIconInfo Image { get; };
+ Int32 MaxWidth { get; };
+ Int32 MaxHeight { get; };
+}
+```
+
+### Plain text content
+
+Developers can declare that the content is unformatted plain text and provide
+hints about how to render it, such as what font to use and whether to
+wrap words or not. Users can control the view settings.
+
+```csharp
+enum FontFamily
+{
+ UserInterface,
+ Monospace,
+};
+
+interface IPlainTextContent requires IContent {
+ String Text { get; };
+ FontFamily FontFamily { get; };
+ Boolean WrapWords { get; };
+}
+```
+
## 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.)
@@ -2447,8 +2516,17 @@ classDiagram
ITag "*" *-- IListItem
IFallbackHandler "?" *-- IListItem
- class IGridProperties {
- Windows.Foundation.Size TileSize
+ IGridProperties --|> INotifyPropChanged
+ class IGridProperties
+ ISmallGridLayout --|> IGridProperties
+ IMediumGridLayout --|> IGridProperties
+ IGalleryGridLayout --|> IGridProperties
+ class IMediumGridLayout {
+ Boolean ShowTitle
+ }
+ class IGalleryGridLayout {
+ Boolean ShowTitle
+ Boolean ShowSubtitle
}
IListPage --|> IPage
@@ -2607,19 +2685,22 @@ Is that just a `Details` object? A markdown body?
### Generating the `.idl`
-The `.idl` for this SDK can be generated directly from this file. To do so, run the following command:
+This Markdown file is the source for the SDK definitions. Do not edit
+`extensionsdk/Microsoft.CommandPalette.Extensions/Microsoft.CommandPalette.Extensions.idl`
+directly. API declarations use `csharp` or `c#` fences; implementation examples use
+`cs` fences and are not emitted by the generator.
+
+Install the generator dependency with `python -m pip install mistletoe`, and ensure
+its `mistletoe` command is on `PATH`. From `src/modules/cmdpal`, regenerate with:
```ps1
-.\generate-interface.ps1 > .\Microsoft.DevPalette.Extensions.idl
+.\doc\initial-sdk-spec\generate-interface.ps1 |
+ Set-Content -Encoding utf8 .\extensionsdk\Microsoft.CommandPalette.Extensions\Microsoft.CommandPalette.Extensions.idl
```
-(After a `pip3 install mistletoe`)
-
-Or, to generate straight to the place I'm consuming it from:
-
-```ps1
-.\doc\initial-sdk-spec\generate-interface.ps1 > .\extensionsdk\Microsoft.CommandPalette.Extensions\Microsoft.CommandPalette.Extensions.Toolkit.idl
-```
+Review the generated diff along with the Markdown changes. Existing interface
+members, UUIDs and `requires` declarations must be preserved when reconciling old
+source drift. Regenerating a second time should produce no further changes.
### Adding APIs
diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions/Microsoft.CommandPalette.Extensions.idl b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions/Microsoft.CommandPalette.Extensions.idl
index 7bcaf402bc..55d94c9317 100644
--- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions/Microsoft.CommandPalette.Extensions.idl
+++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions/Microsoft.CommandPalette.Extensions.idl
@@ -1,3 +1,7 @@
+//
+// Generated from doc/initial-sdk-spec/initial-sdk-spec.md by generate-interface.ps1.
+// Edit the Markdown spec, then regenerate this file.
+//
namespace Microsoft.CommandPalette.Extensions
{
[contractversion(1)]
@@ -60,7 +64,7 @@ namespace Microsoft.CommandPalette.Extensions
String Id{ get; };
IIconInfo Icon{ get; };
}
-
+
enum CommandResultKind {
Dismiss, // Reset the palette to the main page and dismiss
GoHome, // Go back to the main page, but keep it open
@@ -71,13 +75,13 @@ namespace Microsoft.CommandPalette.Extensions
ShowToast, // Display a transient message to the user
Confirm, // Display a confirmation dialog
};
-
+
enum NavigationMode {
Push, // Push the target page onto the navigation stack
GoBack, // Go back one page before navigating to the target page
GoHome, // Go back to the home page before navigating to the target page
};
-
+
[uuid("f9d6423b-bd5e-44bb-a204-2f5c77a72396")]
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface ICommandResultArgs{};
@@ -108,7 +112,7 @@ namespace Microsoft.CommandPalette.Extensions
ICommand PrimaryCommand { get; };
Boolean IsPrimaryCommandCritical { get; };
}
-
+
// This is a "leaf" of the UI. This is something that can be "done" by the user.
// * A ListPage
// * the MoreCommands flyout of for a ListItem or a MarkdownPage
@@ -116,29 +120,29 @@ namespace Microsoft.CommandPalette.Extensions
interface IInvokableCommand requires ICommand {
ICommandResult Invoke(Object sender);
}
-
-
+
+
[uuid("ef5db50c-d26b-4aee-9343-9f98739ab411")]
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IFilterItem {}
-
+
[uuid("0a923c7f-5b7b-431d-9898-3c8c841d02ed")]
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface ISeparatorFilterItem requires IFilterItem {}
-
+
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IFilter requires INotifyPropChanged, IFilterItem {
String Id { get; };
String Name { get; };
IIconInfo Icon { get; };
}
-
+
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IFilters {
String CurrentFilterId { get; set; };
IFilterItem[] GetFilters();
}
-
+
struct Color
{
UInt8 R;
@@ -146,13 +150,13 @@ namespace Microsoft.CommandPalette.Extensions
UInt8 B;
UInt8 A;
};
-
+
struct OptionalColor
{
Boolean HasValue;
Microsoft.CommandPalette.Extensions.Color Color;
};
-
+
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface ITag {
IIconInfo Icon { get; };
@@ -161,7 +165,7 @@ namespace Microsoft.CommandPalette.Extensions
OptionalColor Background { get; };
String ToolTip { get; };
};
-
+
[uuid("6a6dd345-37a3-4a1e-914d-4f658a4d583d")]
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IDetailsData {}
@@ -202,7 +206,7 @@ namespace Microsoft.CommandPalette.Extensions
[uuid("58070392-02bb-4e89-9beb-47ceb8c3d741")]
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IDetailsSeparator requires IDetailsData {}
-
+
enum MessageState
{
Info = 0,
@@ -210,20 +214,20 @@ namespace Microsoft.CommandPalette.Extensions
Warning,
Error,
};
-
+
enum StatusContext
{
Page,
Extension
};
-
+
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IProgressState requires INotifyPropChanged
{
Boolean IsIndeterminate { get; };
UInt32 ProgressPercent { get; };
};
-
+
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IStatusMessage requires INotifyPropChanged
{
@@ -232,35 +236,35 @@ namespace Microsoft.CommandPalette.Extensions
String Message { get; };
// TODO! Icon maybe? Work with design on this
};
-
+
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface ILogMessage
{
MessageState State { get; };
String Message { get; };
};
-
+
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IExtensionHost
{
Windows.Foundation.IAsyncAction ShowStatus(IStatusMessage message, StatusContext context);
Windows.Foundation.IAsyncAction HideStatus(IStatusMessage message);
-
+
Windows.Foundation.IAsyncAction LogMessage(ILogMessage message);
};
-
+
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IPage requires ICommand {
String Title { get; };
Boolean IsLoading { get; };
-
+
OptionalColor AccentColor { get; };
}
-
+
[uuid("c78b9851-e76b-43ee-8f76-da5ba14e69a4")]
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IContextItem {}
-
+
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface ICommandItem requires INotifyPropChanged {
ICommand Command{ get; };
@@ -269,17 +273,17 @@ namespace Microsoft.CommandPalette.Extensions
String Title{ get; };
String Subtitle{ get; };
}
-
+
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface ICommandContextItem requires ICommandItem, IContextItem {
Boolean IsCritical { get; }; // READ: "make this red"
KeyChord RequestedShortcut { get; };
}
-
+
[uuid("924a87fc-32fe-4471-9156-84b3b30275a6")]
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface ISeparatorContextItem requires IContextItem {}
-
+
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IListItem requires ICommandItem {
ITag[] Tags{ get; };
@@ -295,20 +299,20 @@ namespace Microsoft.CommandPalette.Extensions
[uuid("05914D59-6ECB-4992-9CF2-5982B5120A26")]
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface ISmallGridLayout requires IGridProperties { }
-
+
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IMediumGridLayout requires IGridProperties
{
Boolean ShowTitle { get; };
}
-
+
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IGalleryGridLayout requires IGridProperties
{
Boolean ShowTitle { get; };
Boolean ShowSubtitle { get; };
}
-
+
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IListPage requires IPage, INotifyItemsChanged {
// DevPal will be responsible for filtering the list of items, unless the
@@ -320,21 +324,21 @@ namespace Microsoft.CommandPalette.Extensions
IGridProperties GridProperties { get; };
Boolean HasMoreItems { get; };
ICommandItem EmptyContent { get; };
-
+
IListItem[] GetItems();
void LoadMore();
}
-
+
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IDynamicListPage requires IListPage {
String SearchText { set; };
}
-
+
[uuid("b64def0f-8911-4afa-8f8f-042bd778d088")]
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IContent requires INotifyPropChanged {
}
-
+
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IFormContent requires IContent {
String TemplateJson { get; };
@@ -342,55 +346,35 @@ namespace Microsoft.CommandPalette.Extensions
String StateJson { get; };
ICommandResult SubmitForm(String inputs, String data);
}
-
+
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IMarkdownContent requires IContent {
String Body { get; };
}
-
+
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface ITreeContent requires IContent, INotifyItemsChanged {
IContent RootContent { get; };
IContent[] GetChildren();
}
- enum FontFamily
- {
- UserInterface,
- Monospace,
- };
-
- [contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
- interface IPlainTextContent requires IContent {
- String Text { get; };
- FontFamily FontFamily { get; };
- Boolean WrapWords { get; };
- }
-
- [contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
- interface IImageContent requires IContent {
- IIconInfo Image { get; };
- Int32 MaxWidth { get; };
- Int32 MaxHeight { get; };
- }
-
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IContentPage requires IPage, INotifyItemsChanged {
IContent[] GetContent();
IDetails Details { get; };
IContextItem[] Commands { get; };
}
-
+
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface ICommandSettings {
IContentPage SettingsPage { get; };
};
-
+
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IFallbackHandler {
void UpdateQuery(String query);
};
-
+
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IFallbackCommandItem requires ICommandItem {
IFallbackHandler FallbackHandler{ get; };
@@ -401,7 +385,7 @@ namespace Microsoft.CommandPalette.Extensions
interface IFallbackCommandItem2 requires IFallbackCommandItem {
String Id { get; };
};
-
+
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface ICommandProvider requires Windows.Foundation.IClosable, INotifyItemsChanged
{
@@ -410,44 +394,31 @@ namespace Microsoft.CommandPalette.Extensions
IIconInfo Icon { get; };
ICommandSettings Settings { get; };
Boolean Frozen { get; };
-
+
ICommandItem[] TopLevelCommands();
IFallbackCommandItem[] FallbackCommands();
-
+
ICommand GetCommand(String id);
-
+
void InitializeWithHost(IExtensionHost host);
};
-
+
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IExtendedAttributesProvider
{
Windows.Foundation.Collections.IMap GetProperties();
};
-
+
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface ICommandProvider2 requires ICommandProvider
{
Object[] GetApiExtensionStubs();
};
- [contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
- interface ICommandProvider3 requires ICommandProvider2
- {
- ICommandItem[] GetDockBands();
- };
-
- [contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
- interface ICommandProvider4 requires ICommandProvider3
- {
- ICommandItem GetCommandItem(String id);
- };
-
- [contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
[uuid("a2590cc9-510c-4af7-b562-a6b56fe37f55")]
+ [contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IParameterRun requires INotifyPropChanged
{
-
};
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
@@ -486,4 +457,38 @@ namespace Microsoft.CommandPalette.Extensions
IParameterRun[] Parameters{ get; };
IListItem Command{ get; };
};
+
+ [contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
+ interface ICommandProvider3 requires ICommandProvider2
+ {
+ ICommandItem[] GetDockBands();
+ };
+
+ [contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
+ interface ICommandProvider4 requires ICommandProvider3
+ {
+ ICommandItem GetCommandItem(String id);
+ };
+
+ [contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
+ interface IImageContent requires IContent {
+ IIconInfo Image { get; };
+ Int32 MaxWidth { get; };
+ Int32 MaxHeight { get; };
+ }
+
+ enum FontFamily
+ {
+ UserInterface,
+ Monospace,
+ };
+
+ [contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
+ interface IPlainTextContent requires IContent {
+ String Text { get; };
+ FontFamily FontFamily { get; };
+ Boolean WrapWords { get; };
+ }
+
+
}