Add the Command Palette module (#37908)

Windows Command Palette ("CmdPal") is the next iteration of PowerToys Run. With extensibility at its core, the Command Palette is your one-stop launcher to start _anything_.

By default, CmdPal is bound to <kbd>Win+Alt+Space</kbd>.

![cmdpal-pr-002](https://github.com/user-attachments/assets/5077ec04-1009-478a-92d6-0a30989d44ac)
![cmdpal-pr-003](https://github.com/user-attachments/assets/63b4762a-9c19-48eb-9242-18ea48240ba0)

----

This brings the current preview version of CmdPal into the upstream PowerToys repo. There are still lots of bugs to work out, but it's reached the state we're ready to start sharing it with the world. From here, we can further collaborate with the community on the features that are important, and ensuring that we've got a most robust API to enable developers to build whatever extensions they want. 

Most of the built-in PT Run modules have already been ported to CmdPal's extension API. Those include:
* Installed apps
* Shell commands
* File search (powered by the indexer)
* Windows Registry search
* Web search
* Windows Terminal Profiles
* Windows Services
* Windows settings


There are a couple new extensions built-in
* You can now search for packages on `winget` and install them right from the palette. This also powers searching for extensions for the palette
* The calculator has an entirely new implementation. This is currently less feature complete than the original PT Run one - we're looking forward to updating it to be more complete for future ingestion in Windows
* "Bookmarks" allow you to save shortcuts to files, folders, and webpages as top-level commands in the palette. 

We've got a bunch of other samples too, in this repo and elsewhere

### PowerToys specific notes

CmdPal will eventually graduate out of PowerToys to live as its own application, which is why it's implemented just a little differently than most other modules. Enabling CmdPal will install its `msix` package. 

The CI was minorly changed to support CmdPal version numbers independent of PowerToys itself. It doesn't make sense for us to start CmdPal at v0.90, and in the future, we want to be able to rev CmdPal independently of PT itself. 


Closes #3200, closes #3600, closes #7770, closes #34273, closes #36471, closes #20976, closes #14495
  
  
-----

TODOs et al


**Blocking:**
- [ ] Images and descriptions in Settings and OOBE need to be properly defined, as mentioned before
  - [ ] Niels is on it
- [x] Doesn't start properly from PowerToys unless the fix PR is merged.
  - https://github.com/zadjii-msft/PowerToys/pull/556 merged
- [x] I seem to lose focus a lot when I press on some limits, like between the search bar and the results.
  - This is https://github.com/zadjii-msft/PowerToys/issues/427
- [x] Turned off an extension like Calculator and it was still working.
  - Need to get rid of that toggle, it doesn't do anything currently
- [x] `ListViewModel.<FetchItems>` crash
  - Pretty confident that was fixed in https://github.com/zadjii-msft/PowerToys/pull/553

**Not blocking / improvements:**
- Show the shortcut through settings, as mentioned before, or create a button that would open CmdPalette settings.
- When PowerToys starts, CmdPalette is always shown if enabled. That's weird when just starting PowerToys/ logging in to the computer with PowerToys auto-start activated. I think this should at least be a setting.
- Needing to double press a result for it to do the default action seems quirky. If one is already selected, I think just pressing should be enough for it to do the action.
  - This is currently a setting, though we're thinking of changing the setting even more: https://github.com/zadjii-msft/PowerToys/issues/392
- There's no URI extension. Was surprised when typing a URL that it only proposed a web search.
- [x] There's no System commands extension. Was expecting to be able to quickly restart the computer by typing restart but it wasn't there.
  - This is in PR https://github.com/zadjii-msft/PowerToys/pull/452  
  
---------

Co-authored-by: joadoumie <98557455+joadoumie@users.noreply.github.com>
Co-authored-by: Jordi Adoumie <jordiadoumie@microsoft.com>
Co-authored-by: Mike Griese <zadjii@gmail.com>
Co-authored-by: Niels Laute <niels.laute@live.nl>
Co-authored-by: Michael Hawker <24302614+michael-hawker@users.noreply.github.com>
Co-authored-by: Stefan Markovic <57057282+stefansjfw@users.noreply.github.com>
Co-authored-by: Seraphima <zykovas91@gmail.com>
Co-authored-by: Jaime Bernardo <jaime@janeasystems.com>
Co-authored-by: Kristen Schau <47155823+krschau@users.noreply.github.com>
Co-authored-by: Eric Johnson <ericjohnson327@gmail.com>
Co-authored-by: Ethan Fang <ethanfang@microsoft.com>
Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>
Co-authored-by: Clint Rutkas <clint@rutkas.com>
This commit is contained in:
Mike Griese
2025-03-19 03:39:57 -05:00
committed by GitHub
parent a62acf7a71
commit f68f408be3
984 changed files with 69758 additions and 277 deletions

View File

@@ -0,0 +1,28 @@
// 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.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public sealed partial class AnonymousCommand : InvokableCommand
{
private readonly Action? _action;
public ICommandResult Result { get; set; } = CommandResult.Dismiss();
public AnonymousCommand(Action? action)
{
Name = "Invoke";
_action = action;
}
public override ICommandResult Invoke()
{
if (_action != null)
{
_action();
}
return Result;
}
}

View File

@@ -0,0 +1,31 @@
// 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 Windows.Foundation;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
// TODO! We probably want to have OnPropertyChanged raise the event
// asynchronously, so as to not block the extension app while it's being
// processed in the host app.
// (also consider this for ItemsChanged in ListPage)
public partial class BaseObservable : INotifyPropChanged
{
public event TypedEventHandler<object, IPropChangedEventArgs>? PropChanged;
protected void OnPropertyChanged(string propertyName)
{
try
{
// TODO #181 - This is dangerous! If the original host goes away,
// this can crash as we try to invoke the handlers from that process.
// However, just catching it seems to still raise the event on the
// new host?
PropChanged?.Invoke(this, new PropChangedEventArgs(propertyName));
}
catch
{
}
}
}

View File

@@ -0,0 +1,75 @@
// 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.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public sealed class ChoiceSetSetting : Setting<string>
{
public partial class Choice
{
[JsonPropertyName("value")]
public string Value { get; set; }
[JsonPropertyName("title")]
public string Title { get; set; }
public Choice(string title, string value)
{
Value = value;
Title = title;
}
}
public List<Choice> Choices { get; set; }
private ChoiceSetSetting()
: base()
{
Choices = [];
}
public ChoiceSetSetting(string key, List<Choice> choices)
: base(key, choices.ElementAt(0).Value)
{
Choices = choices;
}
public ChoiceSetSetting(string key, string label, string description, List<Choice> choices)
: base(key, label, description, choices.ElementAt(0).Value)
{
Choices = choices;
}
public override Dictionary<string, object> ToDictionary()
{
return new Dictionary<string, object>
{
{ "type", "Input.ChoiceSet" },
{ "title", Label },
{ "id", Key },
{ "label", Description },
{ "choices", Choices },
{ "value", Value ?? string.Empty },
{ "isRequired", IsRequired },
{ "errorMessage", ErrorMessage },
};
}
public static ChoiceSetSetting LoadFromJson(JsonObject jsonObject) => new() { Value = jsonObject["value"]?.GetValue<string>() ?? string.Empty };
public override void Update(JsonObject payload)
{
// If the key doesn't exist in the payload, don't do anything
if (payload[Key] != null)
{
Value = payload[Key]?.GetValue<string>();
}
}
public override string ToState() => $"\"{Key}\": {JsonSerializer.Serialize(Value, JsonSerializationContext.Default.String)}";
}

View File

@@ -0,0 +1,301 @@
// 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.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
// shamelessly from https://github.com/PowerShell/PowerShell/blob/master/src/Microsoft.PowerShell.Commands.Management/commands/management/Clipboard.cs
public static partial class ClipboardHelper
{
private static readonly bool? _clipboardSupported = true;
// Used if an external clipboard is not available, e.g. if xclip is missing.
// This is useful for testing in CI as well.
private static string? _internalClipboard;
public static string GetText()
{
if (_clipboardSupported == false)
{
return _internalClipboard ?? string.Empty;
}
var tool = string.Empty;
var args = string.Empty;
var clipboardText = string.Empty;
ExecuteOnStaThread(() => GetTextImpl(out clipboardText));
return clipboardText;
}
public static void SetText(string text)
{
if (_clipboardSupported == false)
{
_internalClipboard = text;
return;
}
var tool = string.Empty;
var args = string.Empty;
ExecuteOnStaThread(() => SetClipboardData(Tuple.Create(text, CF_UNICODETEXT)));
return;
}
public static void SetRtf(string plainText, string rtfText)
{
if (s_CF_RTF == 0)
{
s_CF_RTF = RegisterClipboardFormat("Rich Text Format");
}
ExecuteOnStaThread(() => SetClipboardData(
Tuple.Create(plainText, CF_UNICODETEXT),
Tuple.Create(rtfText, s_CF_RTF)));
}
#pragma warning disable SA1310 // Field names should not contain underscore
private const uint GMEM_MOVEABLE = 0x0002;
private const uint GMEM_ZEROINIT = 0x0040;
#pragma warning restore SA1310 // Field names should not contain underscore
private const uint GHND = GMEM_MOVEABLE | GMEM_ZEROINIT;
[LibraryImport("kernel32.dll")]
private static partial IntPtr GlobalAlloc(uint flags, UIntPtr dwBytes);
[LibraryImport("kernel32.dll")]
private static partial IntPtr GlobalFree(IntPtr hMem);
[LibraryImport("kernel32.dll")]
private static partial IntPtr GlobalLock(IntPtr hMem);
[LibraryImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool GlobalUnlock(IntPtr hMem);
[LibraryImport("kernel32.dll", EntryPoint = "RtlMoveMemory")]
private static partial void CopyMemory(IntPtr dest, IntPtr src, uint count);
[LibraryImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool IsClipboardFormatAvailable(uint format);
[LibraryImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool OpenClipboard(IntPtr hWndNewOwner);
[LibraryImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool CloseClipboard();
[LibraryImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool EmptyClipboard();
[LibraryImport("user32.dll")]
private static partial IntPtr GetClipboardData(uint format);
[LibraryImport("user32.dll")]
private static partial IntPtr SetClipboardData(uint format, IntPtr data);
[LibraryImport("user32.dll", StringMarshalling = StringMarshalling.Utf16)]
private static partial uint RegisterClipboardFormat(string lpszFormat);
#pragma warning disable SA1310 // Field names should not contain underscore
private const uint CF_TEXT = 1;
private const uint CF_UNICODETEXT = 13;
#pragma warning disable SA1308 // Variable names should not be prefixed
private static uint s_CF_RTF;
#pragma warning restore SA1308 // Variable names should not be prefixed
#pragma warning restore SA1310 // Field names should not contain underscore
private static bool GetTextImpl(out string text)
{
try
{
if (IsClipboardFormatAvailable(CF_UNICODETEXT))
{
if (OpenClipboard(IntPtr.Zero))
{
var data = GetClipboardData(CF_UNICODETEXT);
if (data != IntPtr.Zero)
{
data = GlobalLock(data);
text = Marshal.PtrToStringUni(data) ?? string.Empty;
GlobalUnlock(data);
return true;
}
}
}
else if (IsClipboardFormatAvailable(CF_TEXT))
{
if (OpenClipboard(IntPtr.Zero))
{
var data = GetClipboardData(CF_TEXT);
if (data != IntPtr.Zero)
{
data = GlobalLock(data);
text = Marshal.PtrToStringAnsi(data) ?? string.Empty;
GlobalUnlock(data);
return true;
}
}
}
}
catch
{
// Ignore exceptions
}
finally
{
CloseClipboard();
}
text = string.Empty;
return false;
}
private static bool SetClipboardData(params Tuple<string, uint>[] data)
{
try
{
if (!OpenClipboard(IntPtr.Zero))
{
return false;
}
EmptyClipboard();
foreach (var d in data)
{
if (!SetSingleClipboardData(d.Item1, d.Item2))
{
return false;
}
}
}
finally
{
CloseClipboard();
}
return true;
}
private static bool SetSingleClipboardData(string text, uint format)
{
var hGlobal = IntPtr.Zero;
var data = IntPtr.Zero;
try
{
uint bytes;
if (format == s_CF_RTF || format == CF_TEXT)
{
bytes = (uint)(text.Length + 1);
data = Marshal.StringToHGlobalAnsi(text);
}
else if (format == CF_UNICODETEXT)
{
bytes = (uint)((text.Length + 1) * 2);
data = Marshal.StringToHGlobalUni(text);
}
else
{
// Not yet supported format.
return false;
}
if (data == IntPtr.Zero)
{
return false;
}
hGlobal = GlobalAlloc(GHND, (UIntPtr)bytes);
if (hGlobal == IntPtr.Zero)
{
return false;
}
var dataCopy = GlobalLock(hGlobal);
if (dataCopy == IntPtr.Zero)
{
return false;
}
CopyMemory(dataCopy, data, bytes);
GlobalUnlock(hGlobal);
if (SetClipboardData(format, hGlobal) != IntPtr.Zero)
{
// The clipboard owns this memory now, so don't free it.
hGlobal = IntPtr.Zero;
}
}
catch
{
// Ignore failures
}
finally
{
if (data != IntPtr.Zero)
{
Marshal.FreeHGlobal(data);
}
if (hGlobal != IntPtr.Zero)
{
GlobalFree(hGlobal);
}
}
return true;
}
private static void ExecuteOnStaThread(Func<bool> action)
{
const int RetryCount = 5;
var tries = 0;
if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA)
{
while (tries++ < RetryCount && !action())
{
// wait until RetryCount or action
}
return;
}
Exception? exception = null;
var thread = new Thread(() =>
{
try
{
while (tries++ < RetryCount && !action())
{
// wait until RetryCount or action
}
}
catch (Exception e)
{
exception = e;
}
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
thread.Join();
if (exception != null)
{
throw exception;
}
}
}

View File

@@ -0,0 +1,16 @@
// 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.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public sealed class ColorHelpers
{
public static OptionalColor FromArgb(byte a, byte r, byte g, byte b) => new(true, new(r, g, b, a));
public static OptionalColor FromRgb(byte r, byte g, byte b) => new(true, new(r, g, b, 255));
public static OptionalColor Transparent() => new(true, new(0, 0, 0, 0));
public static OptionalColor NoColor() => new(false, new(0, 0, 0, 0));
}

View File

@@ -0,0 +1,36 @@
// 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.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class Command : BaseObservable, ICommand
{
public virtual string Name
{
get;
set
{
field = value;
OnPropertyChanged(nameof(Name));
}
}
= string.Empty;
public virtual string Id { get; protected set; } = string.Empty;
public virtual IconInfo Icon
{
get;
set
{
field = value;
OnPropertyChanged(nameof(Icon));
}
}
= new();
IIconInfo ICommand.Icon => Icon;
}

View File

@@ -0,0 +1,41 @@
// 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.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class CommandContextItem : CommandItem, ICommandContextItem
{
public virtual bool IsCritical { get; set; }
public virtual KeyChord RequestedShortcut { get; set; }
public CommandContextItem(ICommand command)
: base(command)
{
}
public CommandContextItem(
string title,
string subtitle = "",
string name = "",
Action? action = null,
ICommandResult? result = null)
{
var c = new AnonymousCommand(action);
if (!string.IsNullOrEmpty(name))
{
c.Name = name;
}
if (result != null)
{
c.Result = result;
}
Command = c;
Title = title;
Subtitle = subtitle;
}
}

View File

@@ -0,0 +1,111 @@
// 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.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class CommandItem : BaseObservable, ICommandItem
{
private ICommand? _command;
public virtual IIconInfo? Icon
{
get => field;
set
{
field = value;
OnPropertyChanged(nameof(Icon));
}
}
public virtual string Title
{
get => !string.IsNullOrEmpty(field) ? field : _command?.Name ?? string.Empty;
set
{
field = value;
OnPropertyChanged(nameof(Title));
}
}
= string.Empty;
public virtual string Subtitle
{
get;
set
{
field = value;
OnPropertyChanged(nameof(Subtitle));
}
}
= string.Empty;
public virtual ICommand? Command
{
get => _command;
set
{
_command = value;
OnPropertyChanged(nameof(Command));
}
}
public virtual IContextItem[] MoreCommands
{
get;
set
{
field = value;
OnPropertyChanged(nameof(MoreCommands));
}
}
= [];
public CommandItem()
: this(new NoOpCommand())
{
}
public CommandItem(ICommand command)
{
Command = command;
Title = command.Name;
}
public CommandItem(ICommandItem other)
{
Command = other.Command;
Title = other.Title;
Subtitle = other.Subtitle;
Icon = (IconInfo?)other.Icon;
MoreCommands = other.MoreCommands;
}
public CommandItem(
string title,
string subtitle = "",
string name = "",
Action? action = null,
ICommandResult? result = null)
{
var c = new AnonymousCommand(action);
if (!string.IsNullOrEmpty(name))
{
c.Name = name;
}
if (result != null)
{
c.Result = result;
}
Command = c;
Title = title;
Subtitle = subtitle;
}
}

View File

@@ -0,0 +1,50 @@
// 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 Windows.Foundation;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public abstract partial class CommandProvider : ICommandProvider
{
public virtual string Id { get; protected set; } = string.Empty;
public virtual string DisplayName { get; protected set; } = string.Empty;
public virtual IconInfo Icon { get; protected set; } = new IconInfo();
public event TypedEventHandler<object, IItemsChangedEventArgs>? ItemsChanged;
public abstract ICommandItem[] TopLevelCommands();
public virtual IFallbackCommandItem[]? FallbackCommands() => null;
public virtual ICommand? GetCommand(string id) => null;
public virtual ICommandSettings? Settings { get; protected set; }
public virtual bool Frozen { get; protected set; } = true;
IIconInfo ICommandProvider.Icon => Icon;
public virtual void InitializeWithHost(IExtensionHost host) => ExtensionHost.Initialize(host);
#pragma warning disable CA1816 // Dispose methods should call SuppressFinalize
public void Dispose()
{
}
#pragma warning restore CA1816 // Dispose methods should call SuppressFinalize
protected void RaiseItemsChanged(int totalItems = -1)
{
try
{
// TODO #181 - This is the same thing that BaseObservable has to deal with.
ItemsChanged?.Invoke(this, new ItemsChangedEventArgs(totalItems));
}
catch
{
}
}
}

View File

@@ -0,0 +1,92 @@
// 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.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class CommandResult : ICommandResult
{
public ICommandResultArgs? Args { get; private set; }
public CommandResultKind Kind { get; private set; } = CommandResultKind.Dismiss;
public static CommandResult Dismiss()
{
return new CommandResult()
{
Kind = CommandResultKind.Dismiss,
};
}
public static CommandResult GoHome()
{
return new CommandResult()
{
Kind = CommandResultKind.GoHome,
Args = null,
};
}
public static CommandResult GoBack()
{
return new CommandResult()
{
Kind = CommandResultKind.GoBack,
Args = null,
};
}
public static CommandResult Hide()
{
return new CommandResult()
{
Kind = CommandResultKind.Hide,
Args = null,
};
}
public static CommandResult KeepOpen()
{
return new CommandResult()
{
Kind = CommandResultKind.KeepOpen,
Args = null,
};
}
public static CommandResult GoToPage(GoToPageArgs args)
{
return new CommandResult()
{
Kind = CommandResultKind.GoToPage,
Args = args,
};
}
public static CommandResult ShowToast(ToastArgs args)
{
return new CommandResult()
{
Kind = CommandResultKind.ShowToast,
Args = args,
};
}
public static CommandResult ShowToast(string message)
{
return new CommandResult()
{
Kind = CommandResultKind.ShowToast,
Args = new ToastArgs() { Message = message },
};
}
public static CommandResult Confirm(ConfirmationArgs args)
{
return new CommandResult()
{
Kind = CommandResultKind.Confirm,
Args = args,
};
}
}

View File

@@ -0,0 +1,16 @@
// 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.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class ConfirmationArgs : IConfirmationArgs
{
public virtual string? Title { get; set; }
public virtual string? Description { get; set; }
public virtual ICommand? PrimaryCommand { get; set; }
public virtual bool IsPrimaryCommandCritical { get; set; }
}

View File

@@ -0,0 +1,38 @@
// 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 Windows.Foundation;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public abstract partial class ContentPage : Page, IContentPage
{
public event TypedEventHandler<object, IItemsChangedEventArgs>? ItemsChanged;
public virtual IDetails? Details
{
get => field;
set
{
field = value;
OnPropertyChanged(nameof(Details));
}
}
public virtual IContextItem[] Commands { get; set; } = [];
public abstract IContent[] GetContent();
protected void RaiseItemsChanged(int totalItems = -1)
{
try
{
// TODO #181 - This is the same thing that BaseObservable has to deal with.
ItemsChanged?.Invoke(this, new ItemsChangedEventArgs(totalItems));
}
catch
{
}
}
}

View File

@@ -0,0 +1,25 @@
// 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.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class CopyTextCommand : InvokableCommand
{
public virtual string Text { get; set; }
public virtual CommandResult Result { get; set; } = CommandResult.ShowToast("Copied to clipboard");
public CopyTextCommand(string text)
{
Text = text;
Name = "Copy";
Icon = new IconInfo("\uE8C8");
}
public override ICommandResult Invoke()
{
ClipboardHelper.SetText(Text);
return Result;
}
}

View File

@@ -0,0 +1,56 @@
// 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.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class Details : BaseObservable, IDetails
{
public virtual IIconInfo HeroImage
{
get => field;
set
{
field = value;
OnPropertyChanged(nameof(HeroImage));
}
}
= new IconInfo();
public virtual string Title
{
get;
set
{
field = value;
OnPropertyChanged(nameof(Title));
}
}
= string.Empty;
public virtual string Body
{
get;
set
{
field = value;
OnPropertyChanged(nameof(Body));
}
}
= string.Empty;
public virtual IDetailsElement[] Metadata
{
get;
set
{
field = value;
OnPropertyChanged(nameof(Metadata));
}
}
= [];
}

View File

@@ -0,0 +1,10 @@
// 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.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class DetailsCommand : IDetailsCommand
{
public ICommand? Command { get; set; }
}

View File

@@ -0,0 +1,12 @@
// 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.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class DetailsElement : IDetailsElement
{
public virtual string Key { get; set; } = string.Empty;
public virtual IDetailsData? Data { get; set; }
}

View File

@@ -0,0 +1,31 @@
// 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.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class DetailsLink : IDetailsLink
{
public virtual Uri? Link { get; set; }
public virtual string Text { get; set; } = string.Empty;
public DetailsLink()
{
}
public DetailsLink(string url)
: this(url, url)
{
}
public DetailsLink(string url, string text)
{
if (Uri.TryCreate(url, default(UriCreationOptions), out var newUri))
{
Link = newUri;
}
Text = text;
}
}

View File

@@ -0,0 +1,9 @@
// 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.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class DetailsSeparator : IDetailsSeparator
{
}

View File

@@ -0,0 +1,10 @@
// 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.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class DetailsTags : IDetailsTags
{
public ITag[] Tags { get; set; } = [];
}

View File

@@ -0,0 +1,21 @@
// 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.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public abstract class DynamicListPage : ListPage, IDynamicListPage
{
public override string SearchText
{
get => base.SearchText;
set
{
var oldSearch = base.SearchText;
base.SearchText = value;
UpdateSearchText(oldSearch, value);
}
}
public abstract void UpdateSearchText(string oldSearch, string newSearch);
}

View File

@@ -0,0 +1,76 @@
// 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.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class ExtensionHost
{
public static IExtensionHost? Host { get; private set; }
public static void Initialize(IExtensionHost host) => Host = host;
/// <summary>
/// Fire-and-forget a log message to the Command Palette host app. Since
/// the host is in another process, we do this in a try/catch in a
/// background thread, as to not block the calling thread, nor explode if
/// the host app is gone.
/// </summary>
/// <param name="message">The log message to send</param>
public static void LogMessage(ILogMessage message)
{
if (Host != null)
{
_ = Task.Run(async () =>
{
try
{
await Host.LogMessage(message);
}
catch (Exception)
{
}
});
}
}
public static void LogMessage(string message)
{
var logMessage = new LogMessage() { Message = message };
LogMessage(logMessage);
}
public static void ShowStatus(IStatusMessage message, StatusContext context)
{
if (Host != null)
{
_ = Task.Run(async () =>
{
try
{
await Host.ShowStatus(message, context);
}
catch (Exception)
{
}
});
}
}
public static void HideStatus(IStatusMessage message)
{
if (Host != null)
{
_ = Task.Run(async () =>
{
try
{
await Host.HideStatus(message);
}
catch (Exception)
{
}
});
}
}
}

View File

@@ -0,0 +1,129 @@
// 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.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
using Windows.Win32;
using Windows.Win32.Foundation;
using WinRT;
namespace Microsoft.CommandPalette.Extensions;
[ComVisible(true)]
[GeneratedComClass]
internal sealed partial class ExtensionInstanceManager : IClassFactory
{
#pragma warning disable SA1310 // Field names should not contain underscore
private const int E_NOINTERFACE = unchecked((int)0x80004002);
private const int CLASS_E_NOAGGREGATION = unchecked((int)0x80040110);
private const int E_ACCESSDENIED = unchecked((int)0x80070005);
// Known constant ignored by win32metadata and cswin32 projections.
// https://github.com/microsoft/win32metadata/blob/main/generation/WinSDK/RecompiledIdlHeaders/um/processthreadsapi.h
private static readonly HANDLE CURRENT_THREAD_PSEUDO_HANDLE = (HANDLE)(IntPtr)(-6);
private static readonly Guid IID_IUnknown = Guid.Parse("00000000-0000-0000-C000-000000000046");
#pragma warning restore SA1310 // Field names should not contain underscore
private readonly Func<IExtension> _createExtension;
private readonly bool _restrictToMicrosoftExtensionHosts;
private readonly Guid _clsid;
public ExtensionInstanceManager(Func<IExtension> createExtension, bool restrictToMicrosoftExtensionHosts, Guid clsid)
{
_createExtension = createExtension;
_restrictToMicrosoftExtensionHosts = restrictToMicrosoftExtensionHosts;
_clsid = clsid;
}
public void CreateInstance(
[MarshalAs(UnmanagedType.Interface)] object pUnkOuter,
Guid riid,
out IntPtr ppvObject)
{
if (_restrictToMicrosoftExtensionHosts && !IsMicrosoftExtensionHost())
{
Marshal.ThrowExceptionForHR(E_ACCESSDENIED);
}
ppvObject = IntPtr.Zero;
if (pUnkOuter != null)
{
Marshal.ThrowExceptionForHR(CLASS_E_NOAGGREGATION);
}
if (riid == _clsid || riid == IID_IUnknown)
{
// Create the instance of the .NET object
var managed = _createExtension();
var ins = MarshalInspectable<object>.FromManaged(managed);
ppvObject = ins;
}
else
{
// The object that ppvObject points to does not support the
// interface identified by riid.
Marshal.ThrowExceptionForHR(E_NOINTERFACE);
}
}
public void LockServer([MarshalAs(UnmanagedType.Bool)] bool fLock)
{
}
private unsafe bool IsMicrosoftExtensionHost()
{
if (PInvoke.CoImpersonateClient() != 0)
{
return false;
}
uint buffer = 0;
if (PInvoke.GetPackageFamilyNameFromToken(CURRENT_THREAD_PSEUDO_HANDLE, &buffer, null) != WIN32_ERROR.ERROR_INSUFFICIENT_BUFFER)
{
return false;
}
var value = new char[buffer];
fixed (char* p = value)
{
if (PInvoke.GetPackageFamilyNameFromToken(CURRENT_THREAD_PSEUDO_HANDLE, &buffer, p) != 0)
{
return false;
}
}
if (PInvoke.CoRevertToSelf() != 0)
{
return false;
}
var valueStr = new string(value);
return valueStr switch
{
"Microsoft.Windows.CmdPal_8wekyb3d8bbwe\0" or "Microsoft.Windows.CmdPal.Canary_8wekyb3d8bbwe\0" or "Microsoft.Windows.CmdPal.Dev_8wekyb3d8bbwe\0" or "Microsoft.Windows.DevHome_8wekyb3d8bbwe\0" or "Microsoft.Windows.DevHome.Canary_8wekyb3d8bbwe\0" or "Microsoft.Windows.DevHome.Dev_8wekyb3d8bbwe\0" or "Microsoft.WindowsTerminal\0" or "Microsoft.WindowsTerminal_8wekyb3d8bbwe\0" or "WindowsTerminalDev_8wekyb3d8bbwe\0" or "Microsoft.WindowsTerminalPreview_8wekyb3d8bbwe\0" => true,
_ => false,
};
}
}
// https://docs.microsoft.com/windows/win32/api/unknwn/nn-unknwn-iclassfactory
[GeneratedComInterface]
[Guid("00000001-0000-0000-C000-000000000046")]
internal partial interface IClassFactory
{
void CreateInstance(
[MarshalAs(UnmanagedType.Interface)] object pUnkOuter,
Guid riid,
out IntPtr ppvObject);
void LockServer([MarshalAs(UnmanagedType.Bool)] bool fLock);
}

View File

@@ -0,0 +1,101 @@
// 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.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
namespace Microsoft.CommandPalette.Extensions;
public sealed partial class ExtensionServer : IDisposable
{
private readonly HashSet<int> registrationCookies = [];
private ExtensionInstanceManager? _extensionInstanceManager;
private ComWrappers? _comWrappers;
public void RegisterExtension<T>(Func<T> createExtension, bool restrictToMicrosoftExtensionHosts = false)
where T : IExtension
{
Trace.WriteLine($"Registering class object:");
Trace.Indent();
Trace.WriteLine($"CLSID: {typeof(T).GUID:B}");
Trace.WriteLine($"Type: {typeof(T)}");
int cookie;
var clsid = typeof(T).GUID;
var wrappedCallback = () => (IExtension)createExtension();
_extensionInstanceManager ??= new ExtensionInstanceManager(wrappedCallback, restrictToMicrosoftExtensionHosts, typeof(T).GUID);
_comWrappers ??= new StrategyBasedComWrappers();
var f = _comWrappers.GetOrCreateComInterfaceForObject(_extensionInstanceManager, CreateComInterfaceFlags.None);
var hr = Ole32.CoRegisterClassObject(
ref clsid,
f,
Ole32.CLSCTX_LOCAL_SERVER,
Ole32.REGCLS_MULTIPLEUSE | Ole32.REGCLS_SUSPENDED,
out cookie);
if (hr < 0)
{
Marshal.ThrowExceptionForHR(hr);
}
registrationCookies.Add(cookie);
Trace.WriteLine($"Cookie: {cookie}");
Trace.Unindent();
hr = Ole32.CoResumeClassObjects();
if (hr < 0)
{
Marshal.ThrowExceptionForHR(hr);
}
}
#pragma warning disable CA1822 // Mark members as static
public void Run() =>
// TODO : We need to handle lifetime management of the server.
// For details around ref counting and locking of out-of-proc COM servers, see
// https://docs.microsoft.com/windows/win32/com/out-of-process-server-implementation-helpers
Console.ReadLine();
public void Dispose()
{
Trace.WriteLine($"Revoking class object registrations:");
Trace.Indent();
foreach (var cookie in registrationCookies)
{
Trace.WriteLine($"Cookie: {cookie}");
var hr = Ole32.CoRevokeClassObject(cookie);
Debug.Assert(hr >= 0, $"CoRevokeClassObject failed ({hr:x}). Cookie: {cookie}");
}
Trace.Unindent();
}
private sealed class Ole32
{
#pragma warning disable SA1310 // Field names should not contain underscore
// https://docs.microsoft.com/windows/win32/api/wtypesbase/ne-wtypesbase-clsctx
public const int CLSCTX_LOCAL_SERVER = 0x4;
// https://docs.microsoft.com/windows/win32/api/combaseapi/ne-combaseapi-regcls
public const int REGCLS_MULTIPLEUSE = 1;
public const int REGCLS_SUSPENDED = 4;
#pragma warning restore SA1310 // Field names should not contain underscore
// https://docs.microsoft.com/windows/win32/api/combaseapi/nf-combaseapi-coregisterclassobject
[DllImport(nameof(Ole32))]
public static extern int CoRegisterClassObject(ref Guid guid, IntPtr obj, int context, int flags, out int register);
// https://docs.microsoft.com/windows/win32/api/combaseapi/nf-combaseapi-coresumeclassobjects
[DllImport(nameof(Ole32))]
public static extern int CoResumeClassObjects();
// https://docs.microsoft.com/windows/win32/api/combaseapi/nf-combaseapi-corevokeclassobject
[DllImport(nameof(Ole32))]
public static extern int CoRevokeClassObject(int register);
}
}

View File

@@ -0,0 +1,30 @@
// 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.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class FallbackCommandItem : CommandItem, IFallbackCommandItem, IFallbackHandler
{
private IFallbackHandler? _fallbackHandler;
public FallbackCommandItem(ICommand command, string displayTitle)
: base(command)
{
DisplayTitle = displayTitle;
if (command is IFallbackHandler f)
{
_fallbackHandler = f;
}
}
public IFallbackHandler? FallbackHandler
{
get => _fallbackHandler ?? this;
init => _fallbackHandler = value;
}
public virtual string DisplayTitle { get; }
public virtual void UpdateQuery(string query) => _fallbackHandler?.UpdateQuery(query);
}

View File

@@ -0,0 +1,44 @@
// 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.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class Filter : BaseObservable, IFilter
{
public virtual IIconInfo Icon
{
get => field;
set
{
field = value;
OnPropertyChanged(nameof(Icon));
}
}
= new IconInfo();
public virtual string Id
{
get;
set
{
field = value;
OnPropertyChanged(nameof(Id));
}
}
= string.Empty;
public virtual string Name
{
get;
set
{
field = value;
OnPropertyChanged(nameof(Name));
}
}
= string.Empty;
}

View File

@@ -0,0 +1,48 @@
// 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.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class FormContent : BaseObservable, IFormContent
{
public virtual string DataJson
{
get;
set
{
field = value;
OnPropertyChanged(nameof(DataJson));
}
}
= string.Empty;
public virtual string StateJson
{
get;
set
{
field = value;
OnPropertyChanged(nameof(StateJson));
}
}
= string.Empty;
public virtual string TemplateJson
{
get;
set
{
field = value;
OnPropertyChanged(nameof(TemplateJson));
}
}
= string.Empty;
public virtual ICommandResult SubmitForm(string inputs, string data) => SubmitForm(inputs);
public virtual ICommandResult SubmitForm(string inputs) => CommandResult.KeepOpen();
}

View File

@@ -0,0 +1,8 @@
// 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.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Usage", "CsWinRT1028:Class is not marked partial", Justification = "Type is not passed across the WinRT ABI", Scope = "type", Target = "~T:WinRT._EventSource_global__Windows_Foundation_TypedEventHandler_object__global__Microsoft_CommandPalette_Extensions_IPropChangedEventArgs_.EventState")]
[assembly: SuppressMessage("Usage", "CsWinRT1028:Class is not marked partial", Justification = "Type is not passed across the WinRT ABI", Scope = "type", Target = "~T:WinRT._EventSource_global__Windows_Foundation_TypedEventHandler_object__global__Microsoft_CommandPalette_Extensions_IItemsChangedEventArgs_.EventState")]

View File

@@ -0,0 +1,12 @@
// 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.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class GoToPageArgs : IGoToPageArgs
{
public required string PageId { get; set; }
public NavigationMode NavigationMode { get; set; } = NavigationMode.Push;
}

View File

@@ -0,0 +1,20 @@
// 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.Text.Json.Nodes;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
internal interface ISettingsForm
{
public string ToForm();
public void Update(JsonObject payload);
public Dictionary<string, object> ToDictionary();
public string ToDataIdentifier();
public string ToState();
}

View File

@@ -0,0 +1,29 @@
// 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 Windows.Storage.Streams;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class IconData : IIconData
{
public IRandomAccessStreamReference? Data { get; set; }
public string? Icon { get; set; } = string.Empty;
public IconData(string? icon)
{
Icon = icon;
}
public IconData(IRandomAccessStreamReference data)
{
Data = data;
}
internal IconData()
: this(string.Empty)
{
}
}

View File

@@ -0,0 +1,15 @@
// 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.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public sealed class IconHelpers
{
public static IconInfo FromRelativePath(string path) => new(Path.Combine(AppDomain.CurrentDomain.BaseDirectory.ToString(), path));
public static IconInfo FromRelativePaths(string lightIcon, string darkIcon) =>
new(
new(Path.Combine(AppDomain.CurrentDomain.BaseDirectory.ToString(), lightIcon)),
new(Path.Combine(AppDomain.CurrentDomain.BaseDirectory.ToString(), darkIcon)));
}

View File

@@ -0,0 +1,40 @@
// 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 Windows.Storage.Streams;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class IconInfo : IIconInfo
{
public virtual IconData Dark { get; set; } = new();
public virtual IconData Light { get; set; } = new();
IIconData IIconInfo.Dark => Dark;
IIconData IIconInfo.Light => Light;
public IconInfo(string? icon)
{
Dark = Light = new(icon);
}
public IconInfo(IconData light, IconData dark)
{
Light = light;
Dark = dark;
}
internal IconInfo()
: this(string.Empty)
{
}
public static IconInfo FromStream(IRandomAccessStream stream)
{
var data = new IconData(RandomAccessStreamReference.CreateFromStream(stream));
return new IconInfo(data, data);
}
}

View File

@@ -0,0 +1,12 @@
// 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.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public abstract partial class InvokableCommand : Command, IInvokableCommand
{
public virtual ICommandResult Invoke() => CommandResult.KeepOpen();
public virtual ICommandResult Invoke(object? sender) => Invoke();
}

View File

@@ -0,0 +1,17 @@
// 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 Windows.Foundation;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class ItemsChangedEventArgs : IItemsChangedEventArgs
{
public int TotalItems { get; protected set; }
public ItemsChangedEventArgs(int totalItems = -1)
{
TotalItems = totalItems;
}
}

View File

@@ -0,0 +1,22 @@
// 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.Text.Json.Serialization;
using static Microsoft.CommandPalette.Extensions.Toolkit.ChoiceSetSetting;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
[JsonSerializable(typeof(float))]
[JsonSerializable(typeof(int))]
[JsonSerializable(typeof(string))]
[JsonSerializable(typeof(bool))]
[JsonSerializable(typeof(Choice))]
[JsonSerializable(typeof(List<Choice>))]
[JsonSerializable(typeof(List<ChoiceSetSetting>))]
[JsonSerializable(typeof(Dictionary<string, object>), TypeInfoPropertyName = "Dictionary")]
[JsonSourceGenerationOptions(UseStringEnumConverter = true, WriteIndented = true)]
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Just used here")]
internal partial class JsonSerializationContext : JsonSerializerContext
{
}

View File

@@ -0,0 +1,100 @@
// 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.Text.Json;
using System.Text.Json.Nodes;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public abstract class JsonSettingsManager
{
public Settings Settings { get; } = new();
public string FilePath { get; init; } = string.Empty;
private static readonly JsonSerializerOptions _serializerOptions = new()
{
WriteIndented = true,
};
public virtual void LoadSettings()
{
if (string.IsNullOrEmpty(FilePath))
{
throw new InvalidOperationException($"You must set a valid {nameof(FilePath)} before calling {nameof(LoadSettings)}");
}
var filePath = FilePath;
if (!File.Exists(filePath))
{
ExtensionHost.LogMessage(new LogMessage() { Message = "The provided settings file does not exist" });
return;
}
try
{
// Read the JSON content from the file
var jsonContent = File.ReadAllText(filePath);
// Is it valid JSON?
if (JsonNode.Parse(jsonContent) is JsonObject savedSettings)
{
Settings.Update(jsonContent);
}
else
{
ExtensionHost.LogMessage(new LogMessage() { Message = "Failed to parse settings file as JsonObject." });
}
}
catch (Exception ex)
{
ExtensionHost.LogMessage(new LogMessage() { Message = ex.ToString() });
}
}
public virtual void SaveSettings()
{
if (string.IsNullOrEmpty(FilePath))
{
throw new InvalidOperationException($"You must set a valid {nameof(FilePath)} before calling {nameof(SaveSettings)}");
}
try
{
// Serialize the main dictionary to JSON and save it to the file
var settingsJson = Settings.ToJson();
// Is it valid JSON?
if (JsonNode.Parse(settingsJson) is JsonObject newSettings)
{
// Now, read the existing content from the file
var oldContent = File.Exists(FilePath) ? File.ReadAllText(FilePath) : "{}";
// Is it valid JSON?
if (JsonNode.Parse(oldContent) is JsonObject savedSettings)
{
foreach (var item in newSettings)
{
savedSettings[item.Key] = item.Value != null ? item.Value.DeepClone() : null;
}
var serialized = savedSettings.ToJsonString(_serializerOptions);
File.WriteAllText(FilePath, serialized);
}
else
{
ExtensionHost.LogMessage(new LogMessage() { Message = "Failed to parse settings file as JsonObject." });
}
}
else
{
ExtensionHost.LogMessage(new LogMessage() { Message = "Failed to parse settings file as JsonObject." });
}
}
catch (Exception ex)
{
ExtensionHost.LogMessage(new LogMessage() { Message = ex.ToString() });
}
}
}

View File

@@ -0,0 +1,21 @@
// 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 Windows.Foundation;
using Windows.System;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class KeyChordHelpers
{
public static KeyChord FromModifiers(bool ctrl, bool alt, bool shift, bool win, int vkey, int scanCode)
{
var modifiers = (ctrl ? VirtualKeyModifiers.Control : VirtualKeyModifiers.None)
| (alt ? VirtualKeyModifiers.Menu : VirtualKeyModifiers.None)
| (shift ? VirtualKeyModifiers.Shift : VirtualKeyModifiers.None)
| (win ? VirtualKeyModifiers.Windows : VirtualKeyModifiers.None)
;
return new(modifiers, vkey, scanCode);
}
}

View File

@@ -0,0 +1,149 @@
// 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.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class ListHelpers
{
// Generate a score for a list item.
public static int ScoreListItem(string query, ICommandItem listItem)
{
if (string.IsNullOrEmpty(query) || string.IsNullOrWhiteSpace(query))
{
return 1;
}
if (string.IsNullOrEmpty(listItem.Title))
{
return 0;
}
var nameMatch = StringMatcher.FuzzySearch(query, listItem.Title);
// var locNameMatch = StringMatcher.FuzzySearch(query, NameLocalized);
var descriptionMatch = StringMatcher.FuzzySearch(query, listItem.Subtitle);
// var executableNameMatch = StringMatcher.FuzzySearch(query, ExePath);
// var locExecutableNameMatch = StringMatcher.FuzzySearch(query, ExecutableNameLocalized);
// var lnkResolvedExecutableNameMatch = StringMatcher.FuzzySearch(query, LnkResolvedExecutableName);
// var locLnkResolvedExecutableNameMatch = StringMatcher.FuzzySearch(query, LnkResolvedExecutableNameLocalized);
// var score = new[] { nameMatch.Score, (descriptionMatch.Score - 4) / 2, executableNameMatch.Score }.Max();
return new[] { nameMatch.Score, (descriptionMatch.Score - 4) / 2, 0 }.Max();
}
public static IEnumerable<IListItem> FilterList(IEnumerable<IListItem> items, string query)
{
var scores = items
.Select(li => new ScoredListItem() { ListItem = li, Score = ScoreListItem(query, li) })
.Where(score => score.Score > 0)
.OrderByDescending(score => score.Score);
return scores
.Select(score => score.ListItem);
}
public static IEnumerable<T> FilterList<T>(IEnumerable<T> items, string query, Func<string, T, int> scoreFunction)
where T : class
{
var scores = items
.Select(li => new Scored<T>() { Item = li, Score = scoreFunction(query, li) })
.Where(score => score.Score > 0)
.OrderByDescending(score => score.Score);
return scores
.Select(score => score.Item);
}
/// <summary>
/// Modifies the contents of `original` in-place, to match those of
/// `newContents`. The canonical use being:
/// ```cs
/// ListHelpers.InPlaceUpdateList(FilteredItems, FilterList(ItemsToFilter, TextToFilterOn));
/// ```
/// </summary>
/// <typeparam name="T">Any type that can be compared for equality</typeparam>
/// <param name="original">Collection to modify</param>
/// <param name="newContents">The enumerable which `original` should match</param>
public static void InPlaceUpdateList<T>(IList<T> original, IEnumerable<T> newContents)
where T : class
{
// we're not changing newContents - stash this so we don't re-evaluate it every time
var numberOfNew = newContents.Count();
// Short circuit - new contents should just be empty
if (numberOfNew == 0)
{
original.Clear();
return;
}
var i = 0;
foreach (var newItem in newContents)
{
if (i >= original.Count)
{
break;
}
for (var j = i; j < original.Count; j++)
{
var og_2 = original[j];
var areEqual_2 = og_2?.Equals(newItem) ?? false;
if (areEqual_2)
{
for (var k = i; k < j; k++)
{
// This item from the original list was not in the new list. Remove it.
original.RemoveAt(i);
}
break;
}
}
var og = original[i];
var areEqual = og?.Equals(newItem) ?? false;
// Is this new item already in the list?
if (areEqual)
{
// It is already in the list
}
else
{
// it isn't. Add it.
original.Insert(i, newItem);
}
i++;
}
// Remove any extra trailing items from the destination
while (original.Count > numberOfNew)
{
// RemoveAtEnd
original.RemoveAt(original.Count - 1);
}
// Add any extra trailing items from the source
if (original.Count < numberOfNew)
{
var remaining = newContents.Skip(original.Count);
foreach (var item in remaining)
{
original.Add(item);
}
}
}
}
public struct ScoredListItem
{
public int Score;
public IListItem ListItem;
}
public struct Scored<T>
{
public int Score;
public T Item;
}

View File

@@ -0,0 +1,64 @@
// 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.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class ListItem : CommandItem, IListItem
{
private ITag[] _tags = [];
private IDetails? _details;
private string _section = string.Empty;
private string _textToSuggest = string.Empty;
public virtual ITag[] Tags
{
get => _tags;
set
{
_tags = value;
OnPropertyChanged(nameof(Tags));
}
}
public virtual IDetails? Details
{
get => _details;
set
{
_details = value;
OnPropertyChanged(nameof(Details));
}
}
public virtual string Section
{
get => _section;
set
{
_section = value;
OnPropertyChanged(nameof(Section));
}
}
public virtual string TextToSuggest
{
get => _textToSuggest;
set
{
_textToSuggest = value;
OnPropertyChanged(nameof(TextToSuggest));
}
}
public ListItem(ICommand command)
: base(command)
{
}
public ListItem(ICommandItem command)
: base(command)
{
}
}

View File

@@ -0,0 +1,108 @@
// 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 Windows.Foundation;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class ListPage : Page, IListPage
{
private string _placeholderText = string.Empty;
private string _searchText = string.Empty;
private bool _showDetails;
private bool _hasMore;
private IFilters? _filters;
private IGridProperties? _gridProperties;
private ICommandItem? _emptyContent;
public event TypedEventHandler<object, IItemsChangedEventArgs>? ItemsChanged;
public virtual string PlaceholderText
{
get => _placeholderText;
set
{
_placeholderText = value;
OnPropertyChanged(nameof(PlaceholderText));
}
}
public virtual string SearchText
{
get => _searchText;
set
{
_searchText = value;
OnPropertyChanged(nameof(SearchText));
}
}
public virtual bool ShowDetails
{
get => _showDetails;
set
{
_showDetails = value;
OnPropertyChanged(nameof(ShowDetails));
}
}
public virtual bool HasMoreItems
{
get => _hasMore;
set
{
_hasMore = value;
OnPropertyChanged(nameof(HasMoreItems));
}
}
public virtual IFilters? Filters
{
get => _filters;
set
{
_filters = value;
OnPropertyChanged(nameof(Filters));
}
}
public virtual IGridProperties? GridProperties
{
get => _gridProperties;
set
{
_gridProperties = value;
OnPropertyChanged(nameof(GridProperties));
}
}
public virtual ICommandItem? EmptyContent
{
get => _emptyContent;
set
{
_emptyContent = value;
OnPropertyChanged(nameof(EmptyContent));
}
}
public virtual IListItem[] GetItems() => [];
public virtual void LoadMore()
{
}
protected void RaiseItemsChanged(int totalItems = -1)
{
try
{
// TODO #181 - This is the same thing that BaseObservable has to deal with.
ItemsChanged?.Invoke(this, new ItemsChangedEventArgs(totalItems));
}
catch
{
}
}
}

View File

@@ -0,0 +1,37 @@
// 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.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class LogMessage : BaseObservable, ILogMessage
{
private MessageState _messageState = MessageState.Info;
private string _message = string.Empty;
public string Message
{
get => _message;
set
{
_message = value;
OnPropertyChanged(nameof(Message));
}
}
public MessageState State
{
get => _messageState;
set
{
_messageState = value;
OnPropertyChanged(nameof(State));
}
}
public LogMessage(string message = "")
{
_message = message;
}
}

View File

@@ -0,0 +1,29 @@
// 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.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class MarkdownContent : BaseObservable, IMarkdownContent
{
public virtual string Body
{
get;
set
{
field = value;
OnPropertyChanged(nameof(Body));
}
}
= string.Empty;
public MarkdownContent()
{
}
public MarkdownContent(string body)
{
Body = body;
}
}

View File

@@ -0,0 +1,27 @@
// 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.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.Plugin.Program.UnitTests")]
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class MatchOption
{
/// <summary>
/// Gets or sets prefix of match char, use for highlight
/// </summary>
[Obsolete("this is never used")]
public string Prefix { get; set; } = string.Empty;
/// <summary>
/// Gets or sets suffix of match char, use for highlight
/// </summary>
[Obsolete("this is never used")]
public string Suffix { get; set; } = string.Empty;
public bool IgnoreCase { get; set; } = true;
}

View File

@@ -0,0 +1,71 @@
// 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.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.Plugin.Program.UnitTests")]
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class MatchResult
{
public MatchResult(bool success, SearchPrecisionScore searchPrecision)
{
Success = success;
SearchPrecision = searchPrecision;
}
public MatchResult(bool success, SearchPrecisionScore searchPrecision, List<int> matchData, int rawScore)
{
Success = success;
SearchPrecision = searchPrecision;
MatchData = matchData;
RawScore = rawScore;
}
public bool Success { get; set; }
/// <summary>
/// Gets the final score of the match result with search precision filters applied.
/// </summary>
public int Score { get; private set; }
/// <summary>
/// The raw calculated search score without any search precision filtering applied.
/// </summary>
private int _rawScore;
public int RawScore
{
get => _rawScore;
set
{
_rawScore = value;
Score = ScoreAfterSearchPrecisionFilter(_rawScore);
}
}
/// <summary>
/// Gets matched data to highlight.
/// </summary>
public List<int> MatchData { get; private set; } = new();
public SearchPrecisionScore SearchPrecision { get; set; }
public bool IsSearchPrecisionScoreMet()
{
return IsSearchPrecisionScoreMet(_rawScore);
}
private bool IsSearchPrecisionScoreMet(int rawScore)
{
return rawScore >= (int)SearchPrecision;
}
private int ScoreAfterSearchPrecisionFilter(int rawScore)
{
return IsSearchPrecisionScoreMet(rawScore) ? rawScore : 0;
}
}

View File

@@ -0,0 +1,56 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\..\..\Common.Dotnet.AotCompatibility.props" />
<PropertyGroup>
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\Microsoft.CommandPalette.Extensions.Toolkit</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
<!-- MRT from windows app sdk will search for a pri file with the same name of the module before defaulting to resources.pri -->
<ProjectPriFileName>Microsoft.CommandPalette.Extensions.Toolkit.pri</ProjectPriFileName>
<PlatformTarget>AnyCPU</PlatformTarget>
<ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>None</ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>
</PropertyGroup>
<PropertyGroup>
<CsWinRTIncludes>Microsoft.CommandPalette.Extensions</CsWinRTIncludes>
<CsWinRTGeneratedFilesDir>$(OutDir)</CsWinRTGeneratedFilesDir>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<ControlFlowGuard>Guard</ControlFlowGuard>
<SpectreMitigation>Spectre</SpectreMitigation>
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.CsWin32">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.WindowsAppSDK" />
<PackageReference Include="Microsoft.Web.WebView2" />
<PackageReference Include="System.Drawing.Common" />
<!-- This line forces the WebView2 version used by Windows App SDK to be the one we expect from Directory.Packages.props . -->
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Microsoft.CommandPalette.Extensions\Microsoft.CommandPalette.Extensions.vcxproj" />
</ItemGroup>
<ItemGroup>
<Content Include="$(SolutionDir)$(Platform)\$(Configuration)\Microsoft.CommandPalette.Extensions\Microsoft.CommandPalette.Extensions.winmd" Link="Microsoft.CommandPalette.Extensions.winmd" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<PropertyGroup>
<PublishTrimmed>True</PublishTrimmed>
<PublishAot>True</PublishAot>
<!-- Suppress DynamicallyAccessedMemberTypes.PublicParameterlessConstructor in fallback code path of Windows SDK projection -->
<WarningsNotAsErrors>IL2081</WarningsNotAsErrors>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,30 @@
// 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.Runtime.InteropServices;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
internal sealed class NativeMethods
{
[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
internal static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbFileInfo, uint uFlags);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct SHFILEINFO
{
#pragma warning disable SA1307 // Accessible fields should begin with upper-case letter
public IntPtr hIcon;
public int iIcon;
public uint dwAttributes;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string szDisplayName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
public string szTypeName;
#pragma warning restore SA1307 // Accessible fields should begin with upper-case letter
}
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
internal static extern bool DestroyIcon(IntPtr hIcon);
}

View File

@@ -0,0 +1,8 @@
CoImpersonateClient
GetCurrentThread
OpenThreadToken
GetPackageFamilyNameFromToken
CoRevertToSelf
SHGetKnownFolderPath
KNOWN_FOLDER_FLAG
GetCurrentPackageId

View File

@@ -0,0 +1,10 @@
// 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.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class NoOpCommand : InvokableCommand
{
public override ICommandResult Invoke() => CommandResult.KeepOpen();
}

View File

@@ -0,0 +1,25 @@
// 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.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public sealed partial class OpenUrlCommand : InvokableCommand
{
private readonly string _target;
public CommandResult Result { get; set; } = CommandResult.KeepOpen();
public OpenUrlCommand(string target)
{
_target = target;
Name = "Open";
Icon = new IconInfo("\uE8A7");
}
public override CommandResult Invoke()
{
ShellHelpers.OpenInShell(_target);
return Result;
}
}

View File

@@ -0,0 +1,42 @@
// 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.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class Page : Command, IPage
{
private bool _loading;
private string _title = string.Empty;
private OptionalColor _accentColor;
public virtual bool IsLoading
{
get => _loading;
set
{
_loading = value;
OnPropertyChanged(nameof(IsLoading));
}
}
public virtual string Title
{
get => _title;
set
{
_title = value;
OnPropertyChanged(nameof(Title));
}
}
public virtual OptionalColor AccentColor
{
get => _accentColor;
set
{
_accentColor = value;
OnPropertyChanged(nameof(AccentColor));
}
}
}

View File

@@ -0,0 +1,32 @@
// 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.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class ProgressState : BaseObservable, IProgressState
{
private bool _isIndeterminate;
private uint _progressPercent;
public virtual bool IsIndeterminate
{
get => _isIndeterminate;
set
{
_isIndeterminate = value;
OnPropertyChanged(nameof(IsIndeterminate));
}
}
public virtual uint ProgressPercent
{
get => _progressPercent;
set
{
_progressPercent = value;
OnPropertyChanged(nameof(ProgressPercent));
}
}
}

View File

@@ -0,0 +1,17 @@
// 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 Windows.Foundation;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class PropChangedEventArgs : IPropChangedEventArgs
{
public string PropertyName { get; private set; }
public PropChangedEventArgs(string propertyName)
{
PropertyName = propertyName;
}
}

View File

@@ -0,0 +1,12 @@
// 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.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public enum SearchPrecisionScore
{
Regular = 50,
Low = 20,
None = 0,
}

View File

@@ -0,0 +1,80 @@
// 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.Text.Json;
using System.Text.Json.Nodes;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public abstract class Setting<T> : ISettingsForm
{
private static readonly JsonSerializerOptions _jsonSerializerOptions = new() { WriteIndented = true };
public T? Value { get; set; }
public string Key { get; }
public bool IsRequired { get; set; }
public string ErrorMessage { get; set; } = string.Empty;
public string Label { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
protected Setting()
{
Value = default;
Key = string.Empty;
}
public Setting(string key, T defaultValue)
{
Key = key;
Value = defaultValue;
}
public Setting(string key, string label, string description, T defaultValue)
{
Key = key;
Value = defaultValue;
Label = label;
Description = description;
}
public abstract Dictionary<string, object> ToDictionary();
public string ToDataIdentifier() => $"\"{Key}\": \"{Key}\"";
public string ToForm()
{
var bodyJson = JsonSerializer.Serialize(ToDictionary(), JsonSerializationContext.Default.Dictionary);
var dataJson = $"\"{Key}\": \"{Key}\"";
var json = $$"""
{
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.5",
"body": [
{{bodyJson}}
],
"actions": [
{
"type": "Action.Submit",
"title": "Save",
"data": {
{{dataJson}}
}
}
]
}
""";
return json;
}
public abstract void Update(JsonObject payload);
public abstract string ToState();
}

View File

@@ -0,0 +1,127 @@
// 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.Text.Json;
using System.Text.Json.Nodes;
using Windows.Foundation;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public sealed partial class Settings : ICommandSettings
{
private readonly Dictionary<string, object> _settings = [];
private static readonly JsonSerializerOptions _jsonSerializerOptions = new() { WriteIndented = true };
public event TypedEventHandler<object, Settings>? SettingsChanged;
public void Add<T>(Setting<T> s) => _settings.Add(s.Key, s);
public T? GetSetting<T>(string key) => _settings[key] is Setting<T> s ? s.Value : default;
public bool TryGetSetting<T>(string key, out T? val)
{
object? o;
if (_settings.TryGetValue(key, out o))
{
if (o is Setting<T> s)
{
val = s.Value;
return true;
}
}
val = default;
return false;
}
internal string ToFormJson()
{
var settings = _settings
.Values
.Where(s => s is ISettingsForm)
.Select(s => s as ISettingsForm)
.Where(s => s != null)
.Select(s => s!);
var bodies = string.Join(",", settings
.Select(s => JsonSerializer.Serialize(s.ToDictionary(), JsonSerializationContext.Default.Dictionary)));
var datas = string.Join(",", settings.Select(s => s.ToDataIdentifier()));
var json = $$"""
{
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.5",
"body": [
{{bodies}}
],
"actions": [
{
"type": "Action.Submit",
"title": "Save",
"data": {
{{datas}}
}
}
]
}
""";
return json;
}
public string ToJson()
{
var settings = _settings
.Values
.Where(s => s is ISettingsForm)
.Select(s => s as ISettingsForm)
.Where(s => s != null)
.Select(s => s!);
var content = string.Join(",\n", settings.Select(s => s.ToState()));
return $"{{\n{content}\n}}";
}
public void Update(string data)
{
var formInput = JsonNode.Parse(data)?.AsObject();
if (formInput == null)
{
return;
}
foreach (var key in _settings.Keys)
{
var value = _settings[key];
if (value is ISettingsForm f)
{
f.Update(formInput);
}
}
}
internal void RaiseSettingsChanged()
{
var handlers = SettingsChanged;
handlers?.Invoke(this, this);
}
private sealed partial class SettingsContentPage : ContentPage
{
private readonly Settings _settings;
public override IContent[] GetContent() => _settings.ToContent();
public SettingsContentPage(Settings settings)
{
_settings = settings;
Name = "Settings";
Icon = new IconInfo("\uE713"); // Settings icon
}
}
public IContentPage SettingsPage => new SettingsContentPage(this);
public IContent[] ToContent() => [new SettingsForm(this)];
}

View File

@@ -0,0 +1,32 @@
// 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.Text.Json.Nodes;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class SettingsForm : FormContent
{
private readonly Settings _settings;
internal SettingsForm(Settings settings)
{
_settings = settings;
TemplateJson = _settings.ToFormJson();
}
public override ICommandResult SubmitForm(string inputs, string data)
{
var formInput = JsonNode.Parse(inputs)?.AsObject();
if (formInput == null)
{
return CommandResult.KeepOpen();
}
_settings.Update(inputs);
_settings.RaiseSettingsChanged();
return CommandResult.GoHome();
}
}

View File

@@ -0,0 +1,62 @@
// 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.ComponentModel;
using System.Diagnostics;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public static class ShellHelpers
{
public static bool OpenCommandInShell(string? path, string? pattern, string? arguments, string? workingDir = null, ShellRunAsType runAs = ShellRunAsType.None, bool runWithHiddenWindow = false)
{
if (string.IsNullOrEmpty(pattern))
{
// Log.Warn($"Trying to run OpenCommandInShell with an empty pattern. The default browser definition might have issues. Path: '${path ?? string.Empty}' ; Arguments: '${arguments ?? string.Empty}' ; Working Directory: '${workingDir ?? string.Empty}'", typeof(ShellHelpers));
}
else if (pattern.Contains("%1", StringComparison.Ordinal))
{
arguments = pattern.Replace("%1", arguments);
}
return OpenInShell(path, arguments, workingDir, runAs, runWithHiddenWindow);
}
public static bool OpenInShell(string? path, string? arguments = null, string? workingDir = null, ShellRunAsType runAs = ShellRunAsType.None, bool runWithHiddenWindow = false)
{
using var process = new Process();
process.StartInfo.FileName = path;
process.StartInfo.WorkingDirectory = string.IsNullOrWhiteSpace(workingDir) ? string.Empty : workingDir;
process.StartInfo.Arguments = string.IsNullOrWhiteSpace(arguments) ? string.Empty : arguments;
process.StartInfo.WindowStyle = runWithHiddenWindow ? ProcessWindowStyle.Hidden : ProcessWindowStyle.Normal;
process.StartInfo.UseShellExecute = true;
if (runAs == ShellRunAsType.Administrator)
{
process.StartInfo.Verb = "RunAs";
}
else if (runAs == ShellRunAsType.OtherUser)
{
process.StartInfo.Verb = "RunAsUser";
}
try
{
process.Start();
return true;
}
catch (Win32Exception)
{
// Log.Exception($"Unable to open {path}: {ex.Message}", ex, MethodBase.GetCurrentMethod().DeclaringType);
return false;
}
}
public enum ShellRunAsType
{
None,
Administrator,
OtherUser,
}
}

View File

@@ -0,0 +1,39 @@
// 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.Diagnostics;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class ShowFileInFolderCommand : InvokableCommand
{
private readonly string _path;
private static readonly IconInfo Ico = new("\uE838");
public CommandResult Result { get; set; } = CommandResult.Dismiss();
public ShowFileInFolderCommand(string path)
{
_path = path;
Name = "Show in folder";
Icon = Ico;
}
public override CommandResult Invoke()
{
if (Path.Exists(_path))
{
try
{
var argument = "/select, \"" + _path + "\"";
Process.Start("explorer.exe", argument);
}
catch (Exception)
{
}
}
return Result;
}
}

View File

@@ -0,0 +1,42 @@
// 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.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class StatusMessage : BaseObservable, IStatusMessage
{
public virtual string Message
{
get;
set
{
field = value;
OnPropertyChanged(nameof(Message));
}
}
= string.Empty;
public virtual MessageState State
{
get;
set
{
field = value;
OnPropertyChanged(nameof(State));
}
}
= MessageState.Info;
public virtual IProgressState? Progress
{
get;
set
{
field = value;
OnPropertyChanged(nameof(Progress));
}
}
}

View File

@@ -0,0 +1,311 @@
// 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.Globalization;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class StringMatcher
{
private readonly MatchOption _defaultMatchOption = new();
public SearchPrecisionScore UserSettingSearchPrecision { get; set; }
// private readonly IAlphabet _alphabet;
public StringMatcher(/*IAlphabet alphabet = null*/)
{
// _alphabet = alphabet;
}
private static StringMatcher? _instance;
public static StringMatcher Instance
{
get
{
_instance ??= new StringMatcher();
return _instance;
}
set => _instance = value;
}
private static readonly char[] Separator = new[] { ' ' };
public static MatchResult FuzzySearch(string query, string stringToCompare)
{
return Instance.FuzzyMatch(query, stringToCompare);
}
public MatchResult FuzzyMatch(string query, string stringToCompare)
{
try
{
return FuzzyMatch(query, stringToCompare, _defaultMatchOption);
}
catch (IndexOutOfRangeException)
{
return new MatchResult(false, UserSettingSearchPrecision);
}
}
/// <summary>
/// Current method:
/// Character matching + substring matching;
/// 1. Query search string is split into substrings, separator is whitespace.
/// 2. Check each query substring's characters against full compare string,
/// 3. if a character in the substring is matched, loop back to verify the previous character.
/// 4. If previous character also matches, and is the start of the substring, update list.
/// 5. Once the previous character is verified, move on to the next character in the query substring.
/// 6. Move onto the next substring's characters until all substrings are checked.
/// 7. Consider success and move onto scoring if every char or substring without whitespaces matched
/// </summary>
public MatchResult FuzzyMatch(string query, string stringToCompare, MatchOption opt)
{
if (string.IsNullOrEmpty(stringToCompare))
{
return new MatchResult(false, UserSettingSearchPrecision);
}
var bestResult = new MatchResult(false, UserSettingSearchPrecision);
for (var startIndex = 0; startIndex < stringToCompare.Length; startIndex++)
{
MatchResult result = FuzzyMatch(query, stringToCompare, opt, startIndex);
if (result.Success && (!bestResult.Success || result.Score > bestResult.Score))
{
bestResult = result;
}
}
return bestResult;
}
private MatchResult FuzzyMatch(string query, string stringToCompare, MatchOption opt, int startIndex)
{
if (string.IsNullOrEmpty(stringToCompare) || string.IsNullOrEmpty(query))
{
return new MatchResult(false, UserSettingSearchPrecision);
}
ArgumentNullException.ThrowIfNull(opt);
query = query.Trim();
// if (_alphabet != null)
// {
// query = _alphabet.Translate(query);
// stringToCompare = _alphabet.Translate(stringToCompare);
// }
// Using InvariantCulture since this is internal
var fullStringToCompareWithoutCase = opt.IgnoreCase ? stringToCompare.ToUpper(CultureInfo.InvariantCulture) : stringToCompare;
var queryWithoutCase = opt.IgnoreCase ? query.ToUpper(CultureInfo.InvariantCulture) : query;
var querySubstrings = queryWithoutCase.Split(Separator, StringSplitOptions.RemoveEmptyEntries);
var currentQuerySubstringIndex = 0;
var currentQuerySubstring = querySubstrings[currentQuerySubstringIndex];
var currentQuerySubstringCharacterIndex = 0;
var firstMatchIndex = -1;
var firstMatchIndexInWord = -1;
var lastMatchIndex = 0;
var allQuerySubstringsMatched = false;
var matchFoundInPreviousLoop = false;
var allSubstringsContainedInCompareString = true;
var indexList = new List<int>();
List<int> spaceIndices = new List<int>();
for (var compareStringIndex = startIndex; compareStringIndex < fullStringToCompareWithoutCase.Length; compareStringIndex++)
{
// To maintain a list of indices which correspond to spaces in the string to compare
// To populate the list only for the first query substring
if (fullStringToCompareWithoutCase[compareStringIndex].Equals(' ') && currentQuerySubstringIndex == 0)
{
spaceIndices.Add(compareStringIndex);
}
bool compareResult;
if (opt.IgnoreCase)
{
var fullStringToCompare = fullStringToCompareWithoutCase[compareStringIndex].ToString();
var querySubstring = currentQuerySubstring[currentQuerySubstringCharacterIndex].ToString();
#pragma warning disable CA1309 // Use ordinal string comparison (We are looking for a fuzzy match here)
compareResult = string.Compare(fullStringToCompare, querySubstring, CultureInfo.CurrentCulture, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace) != 0;
#pragma warning restore CA1309 // Use ordinal string comparison
}
else
{
compareResult = fullStringToCompareWithoutCase[compareStringIndex] != currentQuerySubstring[currentQuerySubstringCharacterIndex];
}
if (compareResult)
{
matchFoundInPreviousLoop = false;
continue;
}
if (firstMatchIndex < 0)
{
// first matched char will become the start of the compared string
firstMatchIndex = compareStringIndex;
}
if (currentQuerySubstringCharacterIndex == 0)
{
// first letter of current word
matchFoundInPreviousLoop = true;
firstMatchIndexInWord = compareStringIndex;
}
else if (!matchFoundInPreviousLoop)
{
// we want to verify that there is not a better match if this is not a full word
// in order to do so we need to verify all previous chars are part of the pattern
var startIndexToVerify = compareStringIndex - currentQuerySubstringCharacterIndex;
if (AllPreviousCharsMatched(startIndexToVerify, currentQuerySubstringCharacterIndex, fullStringToCompareWithoutCase, currentQuerySubstring))
{
matchFoundInPreviousLoop = true;
// if it's the beginning character of the first query substring that is matched then we need to update start index
firstMatchIndex = currentQuerySubstringIndex == 0 ? startIndexToVerify : firstMatchIndex;
indexList = GetUpdatedIndexList(startIndexToVerify, currentQuerySubstringCharacterIndex, firstMatchIndexInWord, indexList);
}
}
lastMatchIndex = compareStringIndex + 1;
indexList.Add(compareStringIndex);
currentQuerySubstringCharacterIndex++;
// if finished looping through every character in the current substring
if (currentQuerySubstringCharacterIndex == currentQuerySubstring.Length)
{
// if any of the substrings was not matched then consider as all are not matched
allSubstringsContainedInCompareString = matchFoundInPreviousLoop && allSubstringsContainedInCompareString;
currentQuerySubstringIndex++;
allQuerySubstringsMatched = AllQuerySubstringsMatched(currentQuerySubstringIndex, querySubstrings.Length);
if (allQuerySubstringsMatched)
{
break;
}
// otherwise move to the next query substring
currentQuerySubstring = querySubstrings[currentQuerySubstringIndex];
currentQuerySubstringCharacterIndex = 0;
}
}
// proceed to calculate score if every char or substring without whitespaces matched
if (allQuerySubstringsMatched)
{
var nearestSpaceIndex = CalculateClosestSpaceIndex(spaceIndices, firstMatchIndex);
var score = CalculateSearchScore(query, stringToCompare, firstMatchIndex - nearestSpaceIndex - 1, lastMatchIndex - firstMatchIndex, allSubstringsContainedInCompareString);
return new MatchResult(true, UserSettingSearchPrecision, indexList, score);
}
return new MatchResult(false, UserSettingSearchPrecision);
}
// To get the index of the closest space which precedes the first matching index
private static int CalculateClosestSpaceIndex(List<int> spaceIndices, int firstMatchIndex)
{
if (spaceIndices.Count == 0)
{
return -1;
}
else
{
return spaceIndices.OrderBy(item => (firstMatchIndex - item)).Where(item => firstMatchIndex > item).FirstOrDefault(-1);
}
}
private static bool AllPreviousCharsMatched(int startIndexToVerify, int currentQuerySubstringCharacterIndex, string fullStringToCompareWithoutCase, string currentQuerySubstring)
{
var allMatch = true;
for (var indexToCheck = 0; indexToCheck < currentQuerySubstringCharacterIndex; indexToCheck++)
{
if (fullStringToCompareWithoutCase[startIndexToVerify + indexToCheck] !=
currentQuerySubstring[indexToCheck])
{
allMatch = false;
}
}
return allMatch;
}
private static List<int> GetUpdatedIndexList(int startIndexToVerify, int currentQuerySubstringCharacterIndex, int firstMatchIndexInWord, List<int> indexList)
{
var updatedList = new List<int>();
indexList.RemoveAll(x => x >= firstMatchIndexInWord);
updatedList.AddRange(indexList);
for (var indexToCheck = 0; indexToCheck < currentQuerySubstringCharacterIndex; indexToCheck++)
{
updatedList.Add(startIndexToVerify + indexToCheck);
}
return updatedList;
}
private static bool AllQuerySubstringsMatched(int currentQuerySubstringIndex, int querySubstringsLength)
{
return currentQuerySubstringIndex >= querySubstringsLength;
}
private static int CalculateSearchScore(string query, string stringToCompare, int firstIndex, int matchLen, bool allSubstringsContainedInCompareString)
{
// A match found near the beginning of a string is scored more than a match found near the end
// A match is scored more if the characters in the patterns are closer to each other,
// while the score is lower if they are more spread out
// The length of the match is assigned a larger weight factor.
// I.e. the length is more important than where in the string a match is found.
const int matchLenWeightFactor = 2;
var score = 100 * (query.Length + 1) * matchLenWeightFactor / ((1 + firstIndex) + (matchLenWeightFactor * (matchLen + 1)));
// A match with less characters assigning more weights
if (stringToCompare.Length - query.Length < 5)
{
score += 20;
}
else if (stringToCompare.Length - query.Length < 10)
{
score += 10;
}
if (allSubstringsContainedInCompareString)
{
var count = query.Count(c => !char.IsWhiteSpace(c));
var threshold = 4;
if (count <= threshold)
{
score += count * 10;
}
else
{
score += (threshold * 10) + ((count - threshold) * 5);
}
}
#pragma warning disable CA1309 // Use ordinal string comparison (Using CurrentCultureIgnoreCase since this relates to queries input by user)
if (string.Equals(query, stringToCompare, StringComparison.CurrentCultureIgnoreCase))
{
var bonusForExactMatch = 10;
score += bonusForExactMatch;
}
#pragma warning restore CA1309 // Use ordinal string comparison
return score;
}
}

View File

@@ -0,0 +1,75 @@
// 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.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class Tag : BaseObservable, ITag
{
private OptionalColor _foreground;
private OptionalColor _background;
private string _text = string.Empty;
public virtual OptionalColor Foreground
{
get => _foreground;
set
{
_foreground = value;
OnPropertyChanged(nameof(Foreground));
}
}
public virtual OptionalColor Background
{
get => _background;
set
{
_background = value;
OnPropertyChanged(nameof(Background));
}
}
public virtual IIconInfo Icon
{
get;
set
{
field = value;
OnPropertyChanged(nameof(Icon));
}
}
= new IconInfo();
public virtual string Text
{
get => _text;
set
{
_text = value;
OnPropertyChanged(nameof(Text));
}
}
public virtual string ToolTip
{
get;
set
{
field = value;
OnPropertyChanged(nameof(ToolTip));
}
}
= string.Empty;
public Tag()
{
}
public Tag(string text)
{
_text = text;
}
}

View File

@@ -0,0 +1,60 @@
// 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.Text.Json;
using System.Text.Json.Nodes;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class TextSetting : Setting<string>
{
public bool Multiline { get; set; }
public string Placeholder { get; set; } = string.Empty;
private TextSetting()
: base()
{
Value = string.Empty;
}
public TextSetting(string key, string defaultValue)
: base(key, defaultValue)
{
}
public TextSetting(string key, string label, string description, string defaultValue)
: base(key, label, description, defaultValue)
{
}
public override Dictionary<string, object> ToDictionary()
{
return new Dictionary<string, object>
{
{ "type", "Input.Text" },
{ "title", Label },
{ "id", Key },
{ "label", Description },
{ "value", Value ?? string.Empty },
{ "isRequired", IsRequired },
{ "errorMessage", ErrorMessage },
{ "isMultiline", Multiline },
{ "placeholder", Placeholder },
};
}
public static TextSetting LoadFromJson(JsonObject jsonObject) => new() { Value = jsonObject["value"]?.GetValue<string>() ?? string.Empty };
public override void Update(JsonObject payload)
{
// If the key doesn't exist in the payload, don't do anything
if (payload[Key] != null)
{
Value = payload[Key]?.GetValue<string>();
}
}
public override string ToState() => $"\"{Key}\": {JsonSerializer.Serialize(Value, JsonSerializationContext.Default.String)}";
}

View File

@@ -0,0 +1,98 @@
// 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.Drawing;
using System.Globalization;
using System.Runtime.InteropServices;
using Windows.Storage;
using Windows.Storage.FileProperties;
using Windows.Storage.Streams;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public class ThumbnailHelper
{
private static readonly string[] ImageExtensions =
[
".png",
".jpg",
".jpeg",
".gif",
".bmp",
".tiff",
".ico",
];
public static Task<IRandomAccessStream?> GetThumbnail(string path)
{
var extension = Path.GetExtension(path).ToLower(CultureInfo.InvariantCulture);
try
{
if (ImageExtensions.Contains(extension))
{
return GetImageThumbnailAsync(path);
}
else
{
return GetFileIconStream(path);
}
}
catch (Exception)
{
}
return Task.FromResult<IRandomAccessStream?>(null);
}
private const uint SHGFIICON = 0x000000100;
private const uint SHGFILARGEICON = 0x000000000;
private static MemoryStream GetMemoryStreamFromIcon(IntPtr hIcon)
{
var memoryStream = new MemoryStream();
// Ensure disposing the icon before freeing the handle
using (var icon = Icon.FromHandle(hIcon))
{
icon.ToBitmap().Save(memoryStream, System.Drawing.Imaging.ImageFormat.Png);
}
// Clean up the unmanaged handle without risking a use-after-free.
NativeMethods.DestroyIcon(hIcon);
memoryStream.Position = 0;
return memoryStream;
}
private static async Task<IRandomAccessStream?> GetFileIconStream(string filePath)
{
var shinfo = default(NativeMethods.SHFILEINFO);
var hr = NativeMethods.SHGetFileInfo(filePath, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), SHGFIICON | SHGFILARGEICON);
if (hr == 0 || shinfo.hIcon == 0)
{
return null;
}
var stream = new InMemoryRandomAccessStream();
using var memoryStream = GetMemoryStreamFromIcon(shinfo.hIcon);
using var outputStream = stream.GetOutputStreamAt(0);
using (var dataWriter = new DataWriter(outputStream))
{
dataWriter.WriteBytes(memoryStream.ToArray());
await dataWriter.StoreAsync();
await dataWriter.FlushAsync();
}
return stream;
}
private static async Task<IRandomAccessStream?> GetImageThumbnailAsync(string filePath)
{
var file = await StorageFile.GetFileFromPathAsync(filePath);
var thumbnail = await file.GetThumbnailAsync(ThumbnailMode.PicturesView);
return thumbnail;
}
}

View File

@@ -0,0 +1,12 @@
// 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.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class ToastArgs : IToastArgs
{
public string? Message { get; set; }
public ICommandResult? Result { get; set; } = CommandResult.Dismiss();
}

View File

@@ -0,0 +1,47 @@
// 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.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class ToastStatusMessage
{
private readonly Lock _showLock = new();
private bool _shown;
public virtual StatusMessage Message { get; init; }
public virtual int Duration { get; init; } = 2500;
public ToastStatusMessage(StatusMessage message)
{
Message = message;
}
public ToastStatusMessage(string text)
{
Message = new StatusMessage() { Message = text };
}
public void Show()
{
lock (_showLock)
{
if (!_shown)
{
ExtensionHost.ShowStatus(Message, StatusContext.Extension);
_ = Task.Run(() =>
{
Thread.Sleep(Duration);
lock (_showLock)
{
_shown = false;
ExtensionHost.HideStatus(Message);
}
});
_shown = true;
}
}
}
}

View File

@@ -0,0 +1,60 @@
// 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.Text.Json;
using System.Text.Json.Nodes;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public sealed class ToggleSetting : Setting<bool>
{
private ToggleSetting()
: base()
{
}
public ToggleSetting(string key, bool defaultValue)
: base(key, defaultValue)
{
}
public ToggleSetting(string key, string label, string description, bool defaultValue)
: base(key, label, description, defaultValue)
{
}
public override Dictionary<string, object> ToDictionary()
{
return new Dictionary<string, object>
{
{ "type", "Input.Toggle" },
{ "title", Label },
{ "id", Key },
{ "label", Description },
{ "value", JsonSerializer.Serialize(Value, JsonSerializationContext.Default.Boolean) },
{ "isRequired", IsRequired },
{ "errorMessage", ErrorMessage },
};
}
public static ToggleSetting LoadFromJson(JsonObject jsonObject) => new() { Value = jsonObject["value"]?.GetValue<bool>() ?? false };
public override void Update(JsonObject payload)
{
// If the key doesn't exist in the payload, don't do anything
if (payload[Key] != null)
{
// Adaptive cards returns boolean values as a string "true"/"false", cause of course.
var strFromJson = payload[Key]?.GetValue<string>() ?? string.Empty;
var val = strFromJson switch { "true" => true, "false" => false, _ => false };
Value = val;
}
}
public override string ToState()
{
var adaptiveCardsUsesStringsForBools = Value ? "true" : "false";
return $"\"{Key}\": \"{adaptiveCardsUsesStringsForBools}\"";
}
}

View File

@@ -0,0 +1,38 @@
// 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 Windows.Foundation;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class TreeContent : BaseObservable, ITreeContent
{
public IContent[] Children { get; set; } = [];
public virtual IContent? RootContent
{
get;
set
{
field = value;
OnPropertyChanged(nameof(RootContent));
}
}
public event TypedEventHandler<object, IItemsChangedEventArgs>? ItemsChanged;
public virtual IContent[] GetChildren() => Children;
protected void RaiseItemsChanged(int totalItems = -1)
{
try
{
// TODO #181 - This is the same thing that BaseObservable has to deal with.
ItemsChanged?.Invoke(this, new ItemsChangedEventArgs(totalItems));
}
catch
{
}
}
}

View File

@@ -0,0 +1,83 @@
// 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.Runtime.InteropServices;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.UI.Shell;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1312:Variable names should begin with lower-case letter", Justification = "This file has more than a couple Windows constants in it, which don't make sense to rename")]
public class Utilities
{
/// <summary>
/// Used to produce a path to a settings folder which your app can use.
/// If your app is running packaged, this will return the redirected local
/// app data path (Packages/{your_pfn}/LocalState). If not, it'll return
/// %LOCALAPPDATA%\{settingsFolderName}.
///
/// Does not ensure that the directory exists. Callers should call
/// CreateDirectory before writing settings files to this directory.
/// </summary>
/// <example>
/// var directory = Utilities.BaseSettingsPath("Some.Unique.String.Here");
/// Directory.CreateDirectory(directory);
/// </example>
/// <param name="settingsFolderName">A fallback directory name to use
/// inside of %LocalAppData%, in the case this app is not currently running
/// in a package context</param>
/// <returns>The path to a folder to use for storing settings.</returns>
public static string BaseSettingsPath(string settingsFolderName)
{
// KF_FLAG_FORCE_APP_DATA_REDIRECTION, when engaged, causes SHGet... to return
// the new AppModel paths (Packages/xxx/RoamingState, etc.) for standard path requests.
// Using this flag allows us to avoid Windows.Storage.ApplicationData completely.
var FOLDERID_LocalAppData = new Guid("F1B32785-6FBA-4FCF-9D55-7B8E7F157091");
var hr = PInvoke.SHGetKnownFolderPath(
FOLDERID_LocalAppData,
(uint)KNOWN_FOLDER_FLAG.KF_FLAG_FORCE_APP_DATA_REDIRECTION,
null,
out var localAppDataFolder);
if (hr.Succeeded)
{
var basePath = new string(localAppDataFolder.ToString());
if (!IsPackaged())
{
basePath = Path.Combine(basePath, settingsFolderName);
}
return basePath;
}
else
{
throw Marshal.GetExceptionForHR(hr.Value)!;
}
}
/// <summary>
/// Can be used to quickly determine if this process is running with package identity.
/// </summary>
/// <returns>true iff the process is running with package identity</returns>
public static bool IsPackaged()
{
uint bufferSize = 0;
var bytes = Array.Empty<byte>();
// CsWinRT apparently won't generate this constant
var APPMODEL_ERROR_NO_PACKAGE = (WIN32_ERROR)15700;
unsafe
{
fixed (byte* p = bytes)
{
// We don't actually need the package ID. We just need to know
// if we have a package or not, and APPMODEL_ERROR_NO_PACKAGE
// is a quick way to find out.
var win32Error = PInvoke.GetCurrentPackageId(ref bufferSize, p);
return win32Error != APPMODEL_ERROR_NO_PACKAGE;
}
}
}
}

View File

@@ -0,0 +1,3 @@
EXPORTS
DllCanUnloadNow = WINRT_CanUnloadNow PRIVATE
DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE

View File

@@ -0,0 +1,368 @@
namespace Microsoft.CommandPalette.Extensions
{
[contractversion(1)]
apicontract ExtensionsContract {}
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IExtension {
IInspectable GetProvider(ProviderType providerType);
void Dispose();
};
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
enum ProviderType {
Commands = 0,
};
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IIconData {
String Icon { get; };
Windows.Storage.Streams.IRandomAccessStreamReference Data { get; };
};
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IIconInfo {
IIconData Light { get; };
IIconData Dark { get; };
};
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
struct KeyChord
{
Windows.System.VirtualKeyModifiers Modifiers;
Int32 Vkey;
Int32 ScanCode;
};
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface INotifyPropChanged {
event Windows.Foundation.TypedEventHandler<Object, IPropChangedEventArgs> PropChanged;
};
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IPropChangedEventArgs {
String PropertyName { get; };
};
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface INotifyItemsChanged {
event Windows.Foundation.TypedEventHandler<Object, IItemsChangedEventArgs> ItemsChanged;
};
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IItemsChangedEventArgs {
Int32 TotalItems { get; };
};
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface ICommand requires INotifyPropChanged{
String Name{ get; };
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
GoBack, // Go back one level
Hide, // Keep this page open, but hide the palette.
KeepOpen, // Do nothing.
GoToPage, // Go to another page. GoToPageArgs will tell you where.
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{};
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface ICommandResult {
CommandResultKind Kind { get; };
ICommandResultArgs Args { get; };
}
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IGoToPageArgs requires ICommandResultArgs{
String PageId { get; };
NavigationMode NavigationMode { get; };
}
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IToastArgs requires ICommandResultArgs{
String Message { get; };
ICommandResult Result { get; };
}
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IConfirmationArgs requires ICommandResultArgs{
String Title { get; };
String Description { get; };
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
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
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 IFilterItem {
String Id { get; };
String Name { get; };
IIconInfo Icon { get; };
}
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IFilters {
String CurrentFilterId { get; set; };
IFilterItem[] Filters();
}
struct Color
{
UInt8 R;
UInt8 G;
UInt8 B;
UInt8 A;
};
struct OptionalColor
{
Boolean HasValue;
Microsoft.CommandPalette.Extensions.Color Color;
};
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface ITag {
IIconInfo Icon { get; };
String Text { get; };
OptionalColor Foreground { get; };
OptionalColor Background { get; };
String ToolTip { get; };
};
[uuid("6a6dd345-37a3-4a1e-914d-4f658a4d583d")]
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IDetailsData {}
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IDetailsElement {
String Key { get; };
IDetailsData Data { get; };
}
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IDetails {
IIconInfo HeroImage { get; };
String Title { get; };
String Body { get; };
IDetailsElement[] Metadata { get; };
}
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IDetailsTags requires IDetailsData {
ITag[] Tags { get; };
}
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IDetailsLink requires IDetailsData {
Windows.Foundation.Uri Link { get; };
String Text { get; };
}
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IDetailsCommand requires IDetailsData {
ICommand Command { get; };
}
[uuid("58070392-02bb-4e89-9beb-47ceb8c3d741")]
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IDetailsSeparator requires IDetailsData {}
enum MessageState
{
Info = 0,
Success,
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
{
MessageState State { get; };
IProgressState Progress { get; };
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; };
IContextItem[] MoreCommands{ get; };
IIconInfo Icon{ get; };
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; };
IDetails Details{ get; };
String Section { get; };
String TextToSuggest { get; };
}
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IGridProperties {
Windows.Foundation.Size TileSize { get; };
}
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IListPage requires IPage, INotifyItemsChanged {
// DevPal will be responsible for filtering the list of items, unless the
// class implements IDynamicListPage
String SearchText { get; };
String PlaceholderText { get; };
Boolean ShowDetails{ get; };
IFilters Filters { get; };
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; };
String DataJson { get; };
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();
}
[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; };
String DisplayTitle { get; };
};
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface ICommandProvider requires Windows.Foundation.IClosable, INotifyItemsChanged
{
String Id { get; };
String DisplayName { get; };
IIconInfo Icon { get; };
ICommandSettings Settings { get; };
Boolean Frozen { get; };
ICommandItem[] TopLevelCommands();
IFallbackCommandItem[] FallbackCommands();
ICommand GetCommand(String id);
void InitializeWithHost(IExtensionHost host);
};
}

View File

@@ -0,0 +1,191 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PathToRoot>..\..\..\..\..\</PathToRoot>
<WasdkNuget>$(PathToRoot)packages\Microsoft.WindowsAppSDK.1.6.250205002</WasdkNuget>
<CppWinRTNuget>$(PathToRoot)packages\Microsoft.Windows.CppWinRT.2.0.240111.5</CppWinRTNuget>
<WindowsSdkBuildToolsNuget>$(PathToRoot)packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.2428</WindowsSdkBuildToolsNuget>
<WebView2Nuget>$(PathToRoot)packages\Microsoft.Web.WebView2.1.0.2903.40</WebView2Nuget>
</PropertyGroup>
<Import Project="$(WasdkNuget)\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('$(WasdkNuget)\build\native\Microsoft.WindowsAppSDK.props')" />
<Import Project="$(CppWinRTNuget)\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('$(CppWinRTNuget)\build\native\Microsoft.Windows.CppWinRT.props')" />
<Import Project="$(WindowsSdkBuildToolsNuget)\build\Microsoft.Windows.SDK.BuildTools.props" Condition="Exists('$(WindowsSdkBuildToolsNuget)\build\Microsoft.Windows.SDK.BuildTools.props')" />
<PropertyGroup Label="Globals">
<CppWinRTOptimized>true</CppWinRTOptimized>
<CppWinRTRootNamespaceAutoMerge>true</CppWinRTRootNamespaceAutoMerge>
<CppWinRTGenerateWindowsMetadata>true</CppWinRTGenerateWindowsMetadata>
<MinimalCoreWin>true</MinimalCoreWin>
<ProjectGuid>{305dd37e-c85d-4b08-aafe-7381fa890463}</ProjectGuid>
<ProjectName>Microsoft.CommandPalette.Extensions</ProjectName>
<RootNamespace>Microsoft.CommandPalette.Extensions</RootNamespace>
<DefaultLanguage>en-US</DefaultLanguage>
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
<AppContainerApplication>false</AppContainerApplication>
<ApplicationType>Windows Store</ApplicationType>
<ApplicationTypeRevision>10.0</ApplicationTypeRevision>
<WindowsTargetPlatformMinVersion>10.0.19041.0</WindowsTargetPlatformMinVersion>
<WindowsTargetPlatformVersion>10.0.22621.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|ARM64">
<Configuration>Debug</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|ARM64">
<Configuration>Release</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\Microsoft.CommandPalette.Extensions\</OutDir>
<IntDir>obj\$(Platform)\$(Configuration)\</IntDir>
</PropertyGroup>
<PropertyGroup Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '16.0'">v142</PlatformToolset>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '15.0'">v141</PlatformToolset>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '14.0'">v140</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
<GenerateManifest>false</GenerateManifest>
<DesktopCompatible>true</DesktopCompatible>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<UseDebugLibraries>true</UseDebugLibraries>
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<!-- We use MultiThreadedDebug, rather than MultiThreadedDebugDLL, to avoid DLL dependencies on VCRUNTIME140d.dll and MSVCP140d.dll. -->
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
<!-- Link statically against the runtime and STL, but link dynamically against the CRT by ignoring the static CRT
lib and instead linking against the Universal CRT DLL import library. This "hybrid" linking mechanism is
supported according to the CRT maintainer. Dynamic linking against the CRT makes the binaries a bit smaller
than they would otherwise be if the CRT, runtime, and STL were all statically linked in. -->
<IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries);libucrtd.lib</IgnoreSpecificDefaultLibraries>
<AdditionalOptions>%(AdditionalOptions) /defaultlib:ucrtd.lib /profile /opt:ref /opt:icf</AdditionalOptions>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<!-- We use MultiThreaded, rather than MultiThreadedDLL, to avoid DLL dependencies on VCRUNTIME140.dll and MSVCP140.dll. -->
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
<!-- Link statically against the runtime and STL, but link dynamically against the CRT by ignoring the static CRT
lib and instead linking against the Universal CRT DLL import library. This "hybrid" linking mechanism is
supported according to the CRT maintainer. Dynamic linking against the CRT makes the binaries a bit smaller
than they would otherwise be if the CRT, runtime, and STL were all statically linked in. -->
<IgnoreSpecificDefaultLibraries>%(IgnoreSpecificDefaultLibraries);libucrt.lib</IgnoreSpecificDefaultLibraries>
<AdditionalOptions>%(AdditionalOptions) /defaultlib:ucrt.lib /profile /opt:ref /opt:icf</AdditionalOptions>
</Link>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<ItemDefinitionGroup>
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<PrecompiledHeaderOutputFile>$(IntDir)pch.pch</PrecompiledHeaderOutputFile>
<WarningLevel>Level4</WarningLevel>
<LanguageStandard>stdcpp20</LanguageStandard>
<AdditionalOptions>%(AdditionalOptions) /bigobj /Zi</AdditionalOptions>
<PreprocessorDefinitions>_WINRT_DLL;WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalUsingDirectories>$(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories)</AdditionalUsingDirectories>
<ControlFlowGuard>Guard</ControlFlowGuard>
<SpectreMitigation>Spectre</SpectreMitigation>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateWindowsMetadata>false</GenerateWindowsMetadata>
<ModuleDefinitionFile>Microsoft.CommandPalette.Extensions.def</ModuleDefinitionFile>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
</ItemGroup>
<ItemGroup>
<!-- <ClCompile Include="Generated Files\module.g.cpp" /> -->
<ClCompile Include="winrt_module.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Midl Include="Microsoft.CommandPalette.Extensions.idl" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
<None Include="Microsoft.CommandPalette.Extensions.def" />
</ItemGroup>
<ItemGroup>
<Text Include="readme.txt">
<DeploymentContent>false</DeploymentContent>
</Text>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="$(WindowsSdkBuildToolsNuget)\build\Microsoft.Windows.SDK.BuildTools.targets" Condition="Exists('$(WindowsSdkBuildToolsNuget)\build\Microsoft.Windows.SDK.BuildTools.targets')" />
<Import Project="$(CppWinRTNuget)\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(CppWinRTNuget)\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="$(WasdkNuget)\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('$(WasdkNuget)\build\native\Microsoft.WindowsAppSDK.targets')" />
<Import Project="$(WebView2Nuget)\build\native\Microsoft.Web.WebView2.targets" Condition="Exists('$(WebView2Nuget)\build\native\Microsoft.Web.WebView2.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(WindowsSdkBuildToolsNuget)\build\Microsoft.Windows.SDK.BuildTools.props')" Text="$([System.String]::Format('$(ErrorText)', '$(WindowsSdkBuildToolsNuget)\build\Microsoft.Windows.SDK.BuildTools.props'))" />
<Error Condition="!Exists('$(WindowsSdkBuildToolsNuget)\build\Microsoft.Windows.SDK.BuildTools.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(WindowsSdkBuildToolsNuget)\build\Microsoft.Windows.SDK.BuildTools.targets'))" />
<Error Condition="!Exists('$(CppWinRTNuget)\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(CppWinRTNuget)\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('$(CppWinRTNuget)\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(CppWinRTNuget)\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('$(WasdkNuget)\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '$(WasdkNuget)\build\native\Microsoft.WindowsAppSDK.props'))" />
<Error Condition="!Exists('$(WasdkNuget)\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(WasdkNuget)\build\native\Microsoft.WindowsAppSDK.targets'))" />
<Error Condition="!Exists('$(WebView2Nuget)\build\native\Microsoft.Web.WebView2.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(WebView2Nuget)\build\native\Microsoft.Web.WebView2.targets'))" />
</Target>
<ItemGroup>
<ResourceCompile Include="version.rc" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\common\version\version.vcxproj">
<Project>{cc6e41ac-8174-4e8a-8d22-85dd7f4851df}</Project>
</ProjectReference>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Web.WebView2" version="1.0.2903.40" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
<package id="Microsoft.Windows.SDK.BuildTools" version="10.0.22621.2428" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK" version="1.6.250205002" targetFramework="native" />
</packages>

View File

@@ -0,0 +1 @@
#include "pch.h"

View File

@@ -0,0 +1,4 @@
#pragma once
#include <winrt/base.h>
#include <Windows.h>
#include <til/winrt.h>

View File

@@ -0,0 +1,2 @@
The DLL that this project builds exists only to satisfy the build system, and is otherwise unused.
This Project provides winmd to the SDK project for building projections.

View File

@@ -0,0 +1,112 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
namespace til // Terminal Implementation Library. Also: "Today I Learned"
{
template<typename T>
struct property
{
explicit constexpr property(auto&&... args) :
_value{ std::forward<decltype(args)>(args)... } {}
property& operator=(const property& other) = default;
T operator()() const noexcept
{
return _value;
}
void operator()(auto&& arg)
{
_value = std::forward<decltype(arg)>(arg);
}
explicit operator bool() const noexcept
{
#ifdef WINRT_Windows_Foundation_H
if constexpr (std::is_same_v<T, winrt::hstring>)
{
return !_value.empty();
}
else
#endif
{
return _value;
}
}
bool operator==(const property& other) const noexcept
{
return _value == other._value;
}
bool operator!=(const property& other) const noexcept
{
return _value != other._value;
}
bool operator==(const T& other) const noexcept
{
return _value == other;
}
bool operator!=(const T& other) const noexcept
{
return _value != other;
}
private:
T _value;
};
#ifdef WINRT_Windows_Foundation_H
template<typename ArgsT>
struct event
{
event<ArgsT>() = default;
winrt::event_token operator()(const ArgsT& handler) { return _handlers.add(handler); }
void operator()(const winrt::event_token& token) { _handlers.remove(token); }
operator bool() const noexcept { return bool(_handlers); }
template<typename... Arg>
void raise(auto&&... args)
{
_handlers(std::forward<decltype(args)>(args)...);
}
winrt::event<ArgsT> _handlers;
};
template<typename SenderT = winrt::Windows::Foundation::IInspectable, typename ArgsT = winrt::Windows::Foundation::IInspectable>
struct typed_event
{
typed_event<SenderT, ArgsT>() = default;
winrt::event_token operator()(const winrt::Windows::Foundation::TypedEventHandler<SenderT, ArgsT>& handler) { return _handlers.add(handler); }
void operator()(const winrt::event_token& token) { _handlers.remove(token); }
operator bool() const noexcept { return bool(_handlers); }
template<typename... Arg>
void raise(Arg const&... args)
{
_handlers(std::forward<decltype(args)>(args)...);
}
winrt::event<winrt::Windows::Foundation::TypedEventHandler<SenderT, ArgsT>> _handlers;
};
#endif
#ifdef WINRT_Windows_UI_Xaml_Data_H
using property_changed_event = til::event<winrt::Windows::UI::Xaml::Data::PropertyChangedEventHandler>;
// Making a til::observable_property unfortunately doesn't seem feasible.
// It's gonna just result in more macros, which no one wants.
//
// 1. We don't know who the sender is, or would require `this` to always be
// the first parameter to one of these observable_property's.
//
// 2. We don't know what our own name is. We need to actually raise an event
// with the name of the variable as the parameter. Only way to do that is
// with something like
//
// til::observable<int> Foo(this, L"Foo", 42)
//
// which then implies the creation of:
//
// #define OBSERVABLE(type, name, ...) til::observable_property<type> name{ this, L## #name, this.PropertyChanged, __VA_ARGS__ };
//
// Which is just silly
#endif
}

View File

@@ -0,0 +1,34 @@
#include "winres.h"
#include "../../../../common/version/version.h"
VS_VERSION_INFO VERSIONINFO
FILEVERSION FILE_VERSION
PRODUCTVERSION PRODUCT_VERSION
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x40004L
FILETYPE 0x0L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", "Microsoft Corporation"
VALUE "FileDescription", "Microsoft.CommandPalette.Extensions.Toolkit"
VALUE "FileVersion", FILE_VERSION_STRING
VALUE "ProductName", "Microsoft.CommandPalette.Extensions.Toolkit"
VALUE "ProductVersion", PRODUCT_VERSION_STRING
VALUE "LegalCopyright", "Copyright (c) Microsoft Corporation"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END

View File

@@ -0,0 +1,58 @@
// TYPICALLY: This file is generated by C++/WinRT (this one was originally from
// v2.0.240111.5), and it's included in the dll by way of "Generated
// Files\module.g.cpp". However, our project doesn't have any activatable WinRT
// `runtimeclass`es in it. But we need something to appease WinRT, so this is
// the empty version of this file, without any classes in it.
//
// Should you need to add a WinRT runtime class to this project, think very
// carefully about it first. If that's the only solution, then just delete this
// file.
#include "pch.h"
#include "winrt/base.h"
bool __stdcall winrt_can_unload_now() noexcept
{
if (winrt::get_module_lock())
{
return false;
}
winrt::clear_factory_cache();
return true;
}
void* __stdcall winrt_get_activation_factory([[maybe_unused]] std::wstring_view const& name)
{
return nullptr;
}
int32_t __stdcall WINRT_CanUnloadNow() noexcept
{
#ifdef _WRL_MODULE_H_
if (!::Microsoft::WRL::Module<::Microsoft::WRL::InProc>::GetModule().Terminate())
{
return 1;
}
#endif
return winrt_can_unload_now() ? 0 : 1;
}
int32_t __stdcall WINRT_GetActivationFactory(void* classId, void** factory) noexcept try
{
std::wstring_view const name{ *reinterpret_cast<winrt::hstring*>(&classId) };
*factory = winrt_get_activation_factory(name);
if (*factory)
{
return 0;
}
#ifdef _WRL_MODULE_H_
return ::Microsoft::WRL::Module<::Microsoft::WRL::InProc>::GetModule().GetActivationFactory(static_cast<HSTRING>(classId), reinterpret_cast<::IActivationFactory**>(factory));
#else
return winrt::hresult_class_not_available(name).to_abi();
#endif
}
catch (...) { return winrt::to_hresult(); }

View File

@@ -0,0 +1,18 @@
# Command Palette Extension Toolkit
The C# toolkit for building your own extension for the Command Palette.
The quickest way to get started building an extension is to install the Command
Palette, and use the "Create a new extension" command. That will set up a
project for you, with the packaging, dependencies, and basic program structure
ready to go.
To view the full docs, you can head over to [our docs site](TODO! docs link here when we have it)
You can also add extensions to your existing packages. For detailed docs on how,
refer to [this docs page](TODO! add docs link)
There are samples of just about everything you can do in [the samples project].
Head over there to see basic usage of the APIs.
[the samples project]: https://github.com/microsoft/PowerToys/tree/main/src/modules/cmdpal/Exts/SamplePagesExtension

View File

@@ -0,0 +1,102 @@
Param(
[string]$Configuration = "release",
[string]$VersionOfSDK = "0.0.0",
[string]$BuildStep = "all",
[switch]$IsAzurePipelineBuild = $false,
[switch]$Help = $false
)
$StartTime = Get-Date
if ($Help) {
Write-Host @"
Copyright (c) Microsoft Corporation.
Licensed under the MIT License.
Syntax:
Build.cmd [options]
Description:
Builds the Command Palette SDK
Options:
-Configuration <configuration>
Only build the selected configuration(s)
Example: -Configuration Release
Example: -Configuration "Debug,Release"
-VersionOfSDK <version>
Set the version number of the build sdk nuget package
Example: -VersionOfSDK "1.0.0"
-Help
Display this usage message.
"@
Exit
}
$ErrorActionPreference = "Stop"
$buildPlatforms = "x64","arm64"
$msbuildPath = &"${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -latest -prerelease -products * -requires Microsoft.Component.MSBuild -find MSBuild\**\Bin\MSBuild.exe
if ($IsAzurePipelineBuild) {
$nugetPath = "nuget.exe";
} else {
$nugetPath = (Join-Path $PSScriptRoot "NugetWrapper.cmd")
}
if (($BuildStep -ieq "all") -Or ($BuildStep -ieq "build")) {
& $nugetPath restore (Join-Path $PSScriptRoot "..\..\..\..\..\PowerToys.sln")
Try {
foreach ($config in $Configuration.Split(",")) {
foreach ($platform in $buildPlatforms) {
$msbuildArgs = @(
("$PSScriptRoot\..\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj"),
("/p:Platform="+$platform),
("/p:Configuration="+$config),
("/binaryLogger:CmdPal.Extensions.$platform.$config.binlog"),
("/p:VersionNumber="+$VersionOfSDK)
)
& $msbuildPath $msbuildArgs
}
}
} Catch {
$formatString = "`n{0}`n`n{1}`n`n"
$fields = $_, $_.ScriptStackTrace
Write-Host ($formatString -f $fields) -ForegroundColor RED
Exit 1
}
}
if (($BuildStep -ieq "all") -Or ($BuildStep -ieq "pack")) {
foreach ($config in $Configuration.Split(",")) {
if ($config -eq "release")
{
New-Item -ItemType Directory -Force -Path "$PSScriptRoot\..\_build"
& $nugetPath pack (Join-Path $PSScriptRoot "Microsoft.CommandPalette.Extensions.SDK.nuspec") -Version $VersionOfSDK -OutputDirectory "$PSScriptRoot\..\_build"
} else {
Write-Host @"
WARNING: You are currently building as '$config' configuration.
CmdPalSDK nuget creation only supports 'release' configuration right now.
"@ -ForegroundColor YELLOW
}
}
}
if ($IsAzurePipelineBuild) {
Write-Host "##vso[task.setvariable variable=VersionOfSDK;]$VersionOfSDK"
Write-Host "##vso[task.setvariable variable=VersionOfSDK;isOutput=true;]$VersionOfSDK"
}
$TotalTime = (Get-Date)-$StartTime
$TotalMinutes = [math]::Floor($TotalTime.TotalMinutes)
$TotalSeconds = [math]::Ceiling($TotalTime.TotalSeconds)
Write-Host @"
Total Running Time:
$TotalMinutes minutes and $TotalSeconds seconds
"@ -ForegroundColor CYAN

View File

@@ -0,0 +1,41 @@
<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata minClientVersion="2.5">
<id>Microsoft.CommandPalette.Extensions</id>
<version>0.0.0</version>
<title>Command Palette Extension SDK</title>
<authors>Microsoft</authors>
<owners>Microsoft</owners>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<description>Command Palette SDK provides support for creating Command Palette extensions on Windows.</description>
<releaseNotes>Release notes are available in the Power Toys repository.</releaseNotes>
<tags>Command Palette Extension SDK</tags>
<copyright>© Microsoft Corporation. All rights reserved.</copyright>
<projectUrl>https://github.com/microsoft/powertoys</projectUrl>
<readme>docs\README.md</readme>
<dependencies>
<group targetFramework="net8.0-windows10.0.19041.0">
<dependency id="Microsoft.Windows.CsWinRT" version="2.2.0" />
</group>
</dependencies>
</metadata>
<files>
<!-- TODO : Add NOTICE.txt and LICENSE files -->
<file src="Microsoft.CommandPalette.Extensions.props" target="build\"/>
<file src="Microsoft.CommandPalette.Extensions.targets" target="build\"/>
<!-- AnyCPU Managed dlls from SDK.Lib project -->
<file src="..\Microsoft.CommandPalette.Extensions.Toolkit\x64\release\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.dll" target="lib\net8.0-windows10.0.19041.0\"/>
<file src="..\Microsoft.CommandPalette.Extensions.Toolkit\x64\release\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.deps.json" target="lib\net8.0-windows10.0.19041.0\"/>
<!-- Native dlls and winmd from SDK cpp project -->
<!-- TODO: we may not need this, since there are no implementations in the Microsoft.CommandPalette.Extensions namespace -->
<file src="..\Microsoft.CommandPalette.Extensions\x64\release\Microsoft.CommandPalette.Extensions\Microsoft.CommandPalette.Extensions.dll" target="runtimes\win-x64\native\"/>
<file src="..\Microsoft.CommandPalette.Extensions\arm64\release\Microsoft.CommandPalette.Extensions\Microsoft.CommandPalette.Extensions.dll" target="runtimes\win-arm64\native\"/>
<!-- Not putting in the following the lib folder because we don't want plugin project to directly reference the winmd -->
<file src="..\Microsoft.CommandPalette.Extensions\x64\release\Microsoft.CommandPalette.Extensions\Microsoft.CommandPalette.Extensions.winmd" target="winmd\"/>
<file src="..\README.md" target="docs\" />
</files>
</package>

View File

@@ -0,0 +1,5 @@
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<CmdPalSDKPackageDir>$([MSBuild]::NormalizeDirectory('$(MSBuildThisFileDirectory)', '..'))</CmdPalSDKPackageDir>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,7 @@
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<None Include="$(CmdPalSDKPackageDir)\winmd\*.winmd" Visible="false">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,13 @@
@echo OFF
setlocal
if "%VisualStudioVersion%" == "" set VisualStudioVersion=15.0
if not exist %TEMP%\nuget.6.4.0.exe (
echo Nuget.exe not found in the temp dir, downloading.
powershell -Command "& { Invoke-WebRequest https://dist.nuget.org/win-x86-commandline/v6.4.0/nuget.exe -outfile $env:TEMP\nuget.6.4.0.exe }"
)
%TEMP%\nuget.6.4.0.exe %*
exit /B %ERRORLEVEL%