Continuing part deux

This commit is contained in:
Michael Jolley
2025-12-05 10:21:47 -06:00
parent 345aaba16c
commit c589cfa9e8
124 changed files with 8640 additions and 32 deletions

View File

@@ -69,7 +69,7 @@
This is present due to a bug in CsWinRT where WPF projects cause the analyzer to fail. This is present due to a bug in CsWinRT where WPF projects cause the analyzer to fail.
--> -->
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.2.0" /> <PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.6901" /> <PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.7175" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.8.250907003" /> <PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.8.250907003" />
<PackageVersion Include="Microsoft.WindowsAppSDK.AI" Version="1.8.37" /> <PackageVersion Include="Microsoft.WindowsAppSDK.AI" Version="1.8.37" />
<PackageVersion Include="Microsoft.WindowsAppSDK.Runtime" Version="1.8.250907003" /> <PackageVersion Include="Microsoft.WindowsAppSDK.Runtime" Version="1.8.250907003" />

View File

@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18 # Visual Studio Version 18
VisualStudioVersion = 18.0.11222.15 d18.0 VisualStudioVersion = 18.0.11222.15
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "runner", "src\runner\runner.vcxproj", "{9412D5C6-2CF2-4FC2-A601-B55508EA9B27}" Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "runner", "src\runner\runner.vcxproj", "{9412D5C6-2CF2-4FC2-A601-B55508EA9B27}"
ProjectSection(ProjectDependencies) = postProject ProjectSection(ProjectDependencies) = postProject
@@ -844,6 +844,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UI", "UI", "{4F881A97-423A-
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CommandPalette.UI", "src\modules\Deux\UI\Microsoft.CommandPalette.UI\Microsoft.CommandPalette.UI.csproj", "{9EBE6DE4-58CD-CA14-7A21-B1E9DED261DB}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CommandPalette.UI", "src\modules\Deux\UI\Microsoft.CommandPalette.UI\Microsoft.CommandPalette.UI.csproj", "{9EBE6DE4-58CD-CA14-7A21-B1E9DED261DB}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CommandPalette.UI.ViewModels", "src\modules\Deux\UI\Microsoft.CommandPalette.UI.ViewModels\Microsoft.CommandPalette.UI.ViewModels.csproj", "{9D1D4795-6E0A-44E7-9C91-61CA07421F2E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SDK", "SDK", "{ED6F2337-189A-4E98-A481-316347D319BF}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.CommandPalette.Extensions", "src\modules\Deux\SDK\Microsoft.CommandPalette.Extensions\Microsoft.CommandPalette.Extensions.vcxproj", "{7997DAD4-31D6-496B-95DB-6C028D699370}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CommandPalette.Extensions.Toolkit", "src\modules\Deux\SDK\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj", "{7FA183E0-ADB2-8F18-0E6B-A724BB0D1675}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CommandPalette.UI.Models", "src\modules\Deux\UI\Microsoft.CommandPalette.UI.Models\Microsoft.CommandPalette.UI.Models.csproj", "{D985116D-16D6-9BE7-0371-9E7EAA2FF2B5}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64 Debug|ARM64 = Debug|ARM64
@@ -3074,6 +3084,38 @@ Global
{9EBE6DE4-58CD-CA14-7A21-B1E9DED261DB}.Release|x64.ActiveCfg = Release|x64 {9EBE6DE4-58CD-CA14-7A21-B1E9DED261DB}.Release|x64.ActiveCfg = Release|x64
{9EBE6DE4-58CD-CA14-7A21-B1E9DED261DB}.Release|x64.Build.0 = Release|x64 {9EBE6DE4-58CD-CA14-7A21-B1E9DED261DB}.Release|x64.Build.0 = Release|x64
{9EBE6DE4-58CD-CA14-7A21-B1E9DED261DB}.Release|x64.Deploy.0 = Release|x64 {9EBE6DE4-58CD-CA14-7A21-B1E9DED261DB}.Release|x64.Deploy.0 = Release|x64
{9D1D4795-6E0A-44E7-9C91-61CA07421F2E}.Debug|ARM64.ActiveCfg = Debug|ARM64
{9D1D4795-6E0A-44E7-9C91-61CA07421F2E}.Debug|ARM64.Build.0 = Debug|ARM64
{9D1D4795-6E0A-44E7-9C91-61CA07421F2E}.Debug|x64.ActiveCfg = Debug|x64
{9D1D4795-6E0A-44E7-9C91-61CA07421F2E}.Debug|x64.Build.0 = Debug|x64
{9D1D4795-6E0A-44E7-9C91-61CA07421F2E}.Release|ARM64.ActiveCfg = Release|ARM64
{9D1D4795-6E0A-44E7-9C91-61CA07421F2E}.Release|ARM64.Build.0 = Release|ARM64
{9D1D4795-6E0A-44E7-9C91-61CA07421F2E}.Release|x64.ActiveCfg = Release|x64
{9D1D4795-6E0A-44E7-9C91-61CA07421F2E}.Release|x64.Build.0 = Release|x64
{7997DAD4-31D6-496B-95DB-6C028D699370}.Debug|ARM64.ActiveCfg = Debug|ARM64
{7997DAD4-31D6-496B-95DB-6C028D699370}.Debug|ARM64.Build.0 = Debug|ARM64
{7997DAD4-31D6-496B-95DB-6C028D699370}.Debug|x64.ActiveCfg = Debug|x64
{7997DAD4-31D6-496B-95DB-6C028D699370}.Debug|x64.Build.0 = Debug|x64
{7997DAD4-31D6-496B-95DB-6C028D699370}.Release|ARM64.ActiveCfg = Release|ARM64
{7997DAD4-31D6-496B-95DB-6C028D699370}.Release|ARM64.Build.0 = Release|ARM64
{7997DAD4-31D6-496B-95DB-6C028D699370}.Release|x64.ActiveCfg = Release|x64
{7997DAD4-31D6-496B-95DB-6C028D699370}.Release|x64.Build.0 = Release|x64
{7FA183E0-ADB2-8F18-0E6B-A724BB0D1675}.Debug|ARM64.ActiveCfg = Debug|ARM64
{7FA183E0-ADB2-8F18-0E6B-A724BB0D1675}.Debug|ARM64.Build.0 = Debug|ARM64
{7FA183E0-ADB2-8F18-0E6B-A724BB0D1675}.Debug|x64.ActiveCfg = Debug|x64
{7FA183E0-ADB2-8F18-0E6B-A724BB0D1675}.Debug|x64.Build.0 = Debug|x64
{7FA183E0-ADB2-8F18-0E6B-A724BB0D1675}.Release|ARM64.ActiveCfg = Release|ARM64
{7FA183E0-ADB2-8F18-0E6B-A724BB0D1675}.Release|ARM64.Build.0 = Release|ARM64
{7FA183E0-ADB2-8F18-0E6B-A724BB0D1675}.Release|x64.ActiveCfg = Release|x64
{7FA183E0-ADB2-8F18-0E6B-A724BB0D1675}.Release|x64.Build.0 = Release|x64
{D985116D-16D6-9BE7-0371-9E7EAA2FF2B5}.Debug|ARM64.ActiveCfg = Debug|ARM64
{D985116D-16D6-9BE7-0371-9E7EAA2FF2B5}.Debug|ARM64.Build.0 = Debug|ARM64
{D985116D-16D6-9BE7-0371-9E7EAA2FF2B5}.Debug|x64.ActiveCfg = Debug|x64
{D985116D-16D6-9BE7-0371-9E7EAA2FF2B5}.Debug|x64.Build.0 = Debug|x64
{D985116D-16D6-9BE7-0371-9E7EAA2FF2B5}.Release|ARM64.ActiveCfg = Release|ARM64
{D985116D-16D6-9BE7-0371-9E7EAA2FF2B5}.Release|ARM64.Build.0 = Release|ARM64
{D985116D-16D6-9BE7-0371-9E7EAA2FF2B5}.Release|x64.ActiveCfg = Release|x64
{D985116D-16D6-9BE7-0371-9E7EAA2FF2B5}.Release|x64.Build.0 = Release|x64
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -3410,6 +3452,11 @@ Global
{023C058E-537A-4AB6-900A-36437EC17410} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} {023C058E-537A-4AB6-900A-36437EC17410} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
{4F881A97-423A-4905-B219-677AD0184059} = {023C058E-537A-4AB6-900A-36437EC17410} {4F881A97-423A-4905-B219-677AD0184059} = {023C058E-537A-4AB6-900A-36437EC17410}
{9EBE6DE4-58CD-CA14-7A21-B1E9DED261DB} = {4F881A97-423A-4905-B219-677AD0184059} {9EBE6DE4-58CD-CA14-7A21-B1E9DED261DB} = {4F881A97-423A-4905-B219-677AD0184059}
{9D1D4795-6E0A-44E7-9C91-61CA07421F2E} = {4F881A97-423A-4905-B219-677AD0184059}
{ED6F2337-189A-4E98-A481-316347D319BF} = {023C058E-537A-4AB6-900A-36437EC17410}
{7997DAD4-31D6-496B-95DB-6C028D699370} = {ED6F2337-189A-4E98-A481-316347D319BF}
{7FA183E0-ADB2-8F18-0E6B-A724BB0D1675} = {ED6F2337-189A-4E98-A481-316347D319BF}
{D985116D-16D6-9BE7-0371-9E7EAA2FF2B5} = {4F881A97-423A-4905-B219-677AD0184059}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0} SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}

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 = Properties.Resources.AnonymousCommand_Invoke;
_action = action;
}
public override ICommandResult Invoke()
{
if (_action is not 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] is not null)
{
Value = payload[Key]?.GetValue<string>();
}
}
public override string ToState() => $"\"{Key}\": {JsonSerializer.Serialize(Value, JsonSerializationContext.Default.String)}";
}

View File

@@ -0,0 +1,298 @@
// 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;
// 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 is not 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; 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 is not null)
{
c.Result = result;
}
Command = c;
Title = title;
Subtitle = subtitle;
}
}

View File

@@ -0,0 +1,135 @@
// 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;
private WeakEventListener<CommandItem, object, IPropChangedEventArgs>? _commandListener;
private string _title = string.Empty;
public virtual IIconInfo? Icon
{
get => field;
set
{
field = value;
OnPropertyChanged(nameof(Icon));
}
}
public virtual string Title
{
get => !string.IsNullOrEmpty(_title) ? _title : _command?.Name ?? string.Empty;
set
{
_title = value;
OnPropertyChanged(nameof(Title));
}
}
public virtual string Subtitle
{
get;
set
{
field = value;
OnPropertyChanged(nameof(Subtitle));
}
}
= string.Empty;
public virtual ICommand? Command
{
get => _command;
set
{
if (_commandListener is not null)
{
_commandListener.Detach();
_commandListener = null;
}
_command = value;
if (value is not null)
{
_commandListener = new(this, OnCommandPropertyChanged, listener => value.PropChanged -= listener.OnEvent);
value.PropChanged += _commandListener.OnEvent;
}
OnPropertyChanged(nameof(Command));
if (string.IsNullOrEmpty(_title))
{
OnPropertyChanged(nameof(Title));
}
}
}
private void OnCommandPropertyChanged(CommandItem instance, object source, IPropChangedEventArgs args)
{
// command's name affects Title only if Title wasn't explicitly set
if (args.PropertyName == nameof(ICommand.Name) && string.IsNullOrEmpty(_title))
{
instance.OnPropertyChanged(nameof(Title));
}
}
public virtual IContextItem[] MoreCommands
{
get;
set
{
field = value;
OnPropertyChanged(nameof(MoreCommands));
}
}
= [];
public CommandItem()
: this(new NoOpCommand())
{
}
public CommandItem(ICommand command)
{
Command = command;
}
public CommandItem(ICommandItem other)
{
Command = other.Command;
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 is not null)
{
c.Result = result;
}
Command = c;
Title = title;
Subtitle = subtitle;
}
}

View File

@@ -0,0 +1,72 @@
// 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, ICommandProvider2
{
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 virtual 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
{
}
}
/// <summary>
/// This is used to manually populate the WinRT type cache in CmdPal with
/// any interfaces that might not follow a straight linear path of requires.
///
/// You don't need to call this as an extension author.
/// </summary>
/// <returns>an array of objects that implement all the leaf interfaces we support</returns>
public object[] GetApiExtensionStubs()
{
return [new SupportCommandsWithProperties()];
}
/// <summary>
/// A stub class which implements IExtendedAttributesProvider. Just marshalling this
/// across the ABI will be enough for CmdPal to store IExtendedAttributesProvider in
/// its type cache.
/// </summary>
private sealed partial class SupportCommandsWithProperties : IExtendedAttributesProvider
{
public IDictionary<string, object>? GetProperties() => null;
}
}

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,155 @@
// 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.CodeAnalysis;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Common.Commands;
public sealed partial class ConfirmableCommand : InvokableCommand
{
private readonly IInvokableCommand? _command;
public Func<bool>? IsConfirmationRequired { get; init; }
public required string ConfirmationTitle { get; init; }
public required string ConfirmationMessage { get; init; }
public required IInvokableCommand Command
{
get => _command!;
init
{
if (_command is INotifyPropChanged oldNotifier)
{
oldNotifier.PropChanged -= InnerCommand_PropChanged;
}
_command = value;
if (_command is INotifyPropChanged notifier)
{
notifier.PropChanged += InnerCommand_PropChanged;
}
OnPropertyChanged(nameof(Name));
OnPropertyChanged(nameof(Id));
OnPropertyChanged(nameof(Icon));
}
}
public override string Name
{
get => (_command as Command)?.Name ?? base.Name;
set
{
if (_command is Command cmd)
{
cmd.Name = value;
}
else
{
base.Name = value;
}
}
}
public override string Id
{
get => (_command as Command)?.Id ?? base.Id;
set
{
var previous = Id;
if (_command is Command cmd)
{
cmd.Id = value;
}
else
{
base.Id = value;
}
if (previous != Id)
{
OnPropertyChanged(nameof(Id));
}
}
}
public override IconInfo Icon
{
get => (_command as Command)?.Icon ?? base.Icon;
set
{
if (_command is Command cmd)
{
cmd.Icon = value;
}
else
{
base.Icon = value;
}
}
}
public ConfirmableCommand()
{
// Allow init-only construction
}
[SetsRequiredMembers]
public ConfirmableCommand(IInvokableCommand command, string confirmationTitle, string confirmationMessage, Func<bool>? isConfirmationRequired = null)
{
ArgumentNullException.ThrowIfNull(command);
ArgumentException.ThrowIfNullOrWhiteSpace(confirmationMessage);
ArgumentNullException.ThrowIfNull(confirmationMessage);
IsConfirmationRequired = isConfirmationRequired;
ConfirmationTitle = confirmationTitle;
ConfirmationMessage = confirmationMessage;
Command = command;
}
private void InnerCommand_PropChanged(object sender, IPropChangedEventArgs args)
{
var property = args.PropertyName;
if (string.IsNullOrEmpty(property) || property == nameof(Name))
{
OnPropertyChanged(nameof(Name));
}
if (string.IsNullOrEmpty(property) || property == nameof(Id))
{
OnPropertyChanged(nameof(Id));
}
if (string.IsNullOrEmpty(property) || property == nameof(Icon))
{
OnPropertyChanged(nameof(Icon));
}
}
public override ICommandResult Invoke()
{
var showConfirmationDialog = IsConfirmationRequired?.Invoke() ?? true;
if (showConfirmationDialog)
{
return CommandResult.Confirm(new ConfirmationArgs
{
Title = ConfirmationTitle,
Description = ConfirmationMessage,
PrimaryCommand = Command,
IsPrimaryCommandCritical = true,
});
}
else
{
return Command.Invoke(this) ?? 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.
using System.Globalization;
using System.Text;
using Microsoft.CommandPalette.Extensions.Toolkit.Properties;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class CopyPathCommand : InvokableCommand
{
internal static IconInfo CopyPath { get; } = new("\uE8c8"); // Copy
private static readonly CompositeFormat CopyFailedFormat = CompositeFormat.Parse(Resources.copy_failed);
private readonly string _path;
public CommandResult Result { get; set; } = CommandResult.ShowToast(Resources.CopyPathTextCommand_Result);
public CopyPathCommand(string fullPath)
{
this._path = fullPath;
this.Name = Resources.CopyPathTextCommand_Name;
this.Icon = CopyPath;
}
public override CommandResult Invoke()
{
try
{
ClipboardHelper.SetText(_path);
}
catch (Exception ex)
{
ExtensionHost.LogMessage(new LogMessage("Copy failed: " + ex.Message) { State = MessageState.Error });
return CommandResult.ShowToast(
new ToastArgs
{
Message = string.Format(CultureInfo.CurrentCulture, CopyFailedFormat, ex.Message),
Result = CommandResult.KeepOpen(),
});
}
return Result;
}
}

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(Properties.Resources.CopyTextCommand_CopiedToClipboard);
public CopyTextCommand(string text)
{
Text = text;
Name = Properties.Resources.CopyTextCommand_Copy;
Icon = new IconInfo("\uE8C8");
}
public override ICommandResult Invoke()
{
ClipboardHelper.SetText(Text);
return Result;
}
}

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,45 @@
// 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;
using Microsoft.CommandPalette.Extensions.Toolkit.Properties;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class OpenFileCommand : InvokableCommand
{
internal static IconInfo OpenFile { get; } = new("\uE8E5"); // OpenFile
private readonly string _fullPath;
public CommandResult Result { get; set; } = CommandResult.Dismiss();
public OpenFileCommand(string fullPath)
{
this._fullPath = fullPath;
this.Name = Resources.OpenFileCommand_Name;
this.Icon = OpenFile;
}
public override CommandResult Invoke()
{
using (var process = new Process())
{
process.StartInfo.FileName = _fullPath;
process.StartInfo.UseShellExecute = true;
try
{
process.Start();
}
catch (Win32Exception ex)
{
ExtensionHost.LogMessage($"Unable to open {_fullPath}\n{ex}");
}
}
return Result;
}
}

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.
using System.ComponentModel;
using System.Diagnostics;
using Microsoft.CommandPalette.Extensions.Toolkit.Properties;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class OpenInConsoleCommand : InvokableCommand
{
internal static IconInfo OpenInConsoleIcon { get; } = new("\uE756"); // "CommandPrompt"
private readonly string _path;
private bool _isDirectory;
public OpenInConsoleCommand(string fullPath)
{
this._path = fullPath;
this.Name = Resources.OpenInConsoleCommand_Name;
this.Icon = OpenInConsoleIcon;
}
public static OpenInConsoleCommand FromDirectory(string directory) => new(directory) { _isDirectory = true };
public static OpenInConsoleCommand FromFile(string file) => new(file);
public override CommandResult Invoke()
{
using (var process = new Process())
{
process.StartInfo.WorkingDirectory = _isDirectory ? _path : Path.GetDirectoryName(_path);
process.StartInfo.FileName = "cmd.exe";
try
{
process.Start();
}
catch (Win32Exception ex)
{
ExtensionHost.LogMessage(new LogMessage($"Unable to open '{_path}'\n{ex.Message}\n{ex.StackTrace}") { State = MessageState.Error });
}
}
return CommandResult.Dismiss();
}
}

View File

@@ -0,0 +1,63 @@
// 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 ManagedCsWin32;
using Microsoft.CommandPalette.Extensions.Toolkit.Properties;
using Windows.Win32.UI.WindowsAndMessaging;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class OpenPropertiesCommand : InvokableCommand
{
internal static IconInfo OpenPropertiesIcon { get; } = new("\uE90F");
private readonly string _path;
private static unsafe bool ShowFileProperties(string filename)
{
var propertiesPtr = Marshal.StringToHGlobalUni("properties");
var filenamePtr = Marshal.StringToHGlobalUni(filename);
try
{
var info = new Shell32.SHELLEXECUTEINFOW
{
CbSize = (uint)sizeof(Shell32.SHELLEXECUTEINFOW),
LpVerb = propertiesPtr,
LpFile = filenamePtr,
Show = (int)SHOW_WINDOW_CMD.SW_SHOW,
FMask = global::Windows.Win32.PInvoke.SEE_MASK_INVOKEIDLIST,
};
return Shell32.ShellExecuteEx(ref info);
}
finally
{
Marshal.FreeHGlobal(filenamePtr);
Marshal.FreeHGlobal(propertiesPtr);
}
}
public OpenPropertiesCommand(string fullPath)
{
this._path = fullPath;
this.Name = Resources.OpenPropertiesCommand_Name;
this.Icon = OpenPropertiesIcon;
}
public override CommandResult Invoke()
{
try
{
ShowFileProperties(_path);
}
catch (Exception ex)
{
ExtensionHost.LogMessage(new LogMessage($"Error showing file properties '{_path}'\n{ex.Message}\n{ex.StackTrace}") { State = MessageState.Error });
}
return CommandResult.Dismiss();
}
}

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 OpenUrlCommand : InvokableCommand
{
private readonly string _target;
public CommandResult Result { get; set; } = CommandResult.KeepOpen();
public OpenUrlCommand(string target)
{
_target = target;
Name = Properties.Resources.OpenUrlCommand_Open;
Icon = new IconInfo("\uE8A7");
}
public override CommandResult Invoke()
{
ShellHelpers.OpenInShell(_target);
return Result;
}
}

View File

@@ -0,0 +1,57 @@
// 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 ManagedCsWin32;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.CommandPalette.Extensions.Toolkit.Properties;
using Windows.Win32.UI.WindowsAndMessaging;
namespace Microsoft.CmdPal.Core.Common.Commands;
public partial class OpenWithCommand : InvokableCommand
{
internal static IconInfo OpenWithIcon { get; } = new("\uE7AC");
private readonly string _path;
private static unsafe bool OpenWith(string filename)
{
var filenamePtr = Marshal.StringToHGlobalUni(filename);
var verbPtr = Marshal.StringToHGlobalUni("openas");
try
{
var info = new Shell32.SHELLEXECUTEINFOW
{
CbSize = (uint)sizeof(Shell32.SHELLEXECUTEINFOW),
LpVerb = verbPtr,
LpFile = filenamePtr,
Show = (int)SHOW_WINDOW_CMD.SW_SHOWNORMAL,
FMask = global::Windows.Win32.PInvoke.SEE_MASK_INVOKEIDLIST,
};
return Shell32.ShellExecuteEx(ref info);
}
finally
{
Marshal.FreeHGlobal(filenamePtr);
Marshal.FreeHGlobal(verbPtr);
}
}
public OpenWithCommand(string fullPath)
{
this._path = fullPath;
this.Name = Resources.OpenWithCommand_Name;
this.Icon = OpenWithIcon;
}
public override CommandResult Invoke()
{
OpenWith(_path);
return CommandResult.GoHome();
}
}

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 = Properties.Resources.ShowFileInFolderCommand_ShowInFolder;
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,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,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 DetailsCommands : IDetailsCommands
{
public ICommand[]? Commands { 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;
SetSearchNoUpdate(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 is not 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 is not null)
{
_ = Task.Run(async () =>
{
try
{
await Host.ShowStatus(message, context);
}
catch (Exception)
{
}
});
}
}
public static void HideStatus(IStatusMessage message)
{
if (Host is not 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 is not 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,35 @@
// 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 readonly IFallbackHandler? _fallbackHandler;
public FallbackCommandItem(string displayTitle)
{
DisplayTitle = displayTitle;
}
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,23 @@
// 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 Filters : BaseObservable, IFilters
{
public string CurrentFilterId
{
get => field;
set
{
field = value;
OnPropertyChanged(nameof(CurrentFilterId));
}
}
= string.Empty;
// This method should be overridden in derived classes to provide the actual filters.
public abstract IFilterItem[] GetFilters();
}

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 Windows.Foundation.Collections;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
/// <summary>
/// Represents an icon that is a font glyph.
/// This is used for icons that are defined by a specific font face,
/// such as Wingdings.
///
/// Note that Command Palette will default to using the Segoe Fluent Icons,
/// Segoe MDL2 Assets font for glyphs in the Segoe UI Symbol range, or Segoe
/// UI for any other glyphs. This class is only needed if you want a non-Segoe
/// font icon.
/// </summary>
public partial class FontIconData : IconData, IExtendedAttributesProvider
{
public string FontFamily { get; set; }
public FontIconData(string glyph, string fontFamily)
: base(glyph)
{
FontFamily = fontFamily;
}
public IDictionary<string, object>? GetProperties() => new ValueSet()
{
{ "FontFamily", FontFamily },
};
}

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,182 @@
// 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;
// Inspired by the fuzzy.rs from edit.exe
public static class FuzzyStringMatcher
{
private const int NOMATCH = 0;
public static int ScoreFuzzy(string needle, string haystack, bool allowNonContiguousMatches = true)
{
var (s, _) = ScoreFuzzyWithPositions(needle, haystack, allowNonContiguousMatches);
return s;
}
public static (int Score, List<int> Positions) ScoreFuzzyWithPositions(string needle, string haystack, bool allowNonContiguousMatches)
{
if (string.IsNullOrEmpty(haystack) || string.IsNullOrEmpty(needle))
{
return (NOMATCH, new List<int>());
}
var target = haystack.ToCharArray();
var query = needle.ToCharArray();
if (target.Length < query.Length)
{
return (NOMATCH, new List<int>());
}
var targetUpper = FoldCase(haystack);
var queryUpper = FoldCase(needle);
var targetUpperChars = targetUpper.ToCharArray();
var queryUpperChars = queryUpper.ToCharArray();
var area = query.Length * target.Length;
var scores = new int[area];
var matches = new int[area];
for (var qi = 0; qi < query.Length; qi++)
{
var qiOffset = qi * target.Length;
var qiPrevOffset = qi > 0 ? (qi - 1) * target.Length : 0;
for (var ti = 0; ti < target.Length; ti++)
{
var currentIndex = qiOffset + ti;
var diagIndex = (qi > 0 && ti > 0) ? qiPrevOffset + ti - 1 : 0;
var leftScore = ti > 0 ? scores[currentIndex - 1] : 0;
var diagScore = (qi > 0 && ti > 0) ? scores[diagIndex] : 0;
var matchSeqLen = (qi > 0 && ti > 0) ? matches[diagIndex] : 0;
var score = (diagScore == 0 && qi != 0) ? 0 :
ComputeCharScore(
query[qi],
queryUpperChars[qi],
ti != 0 ? target[ti - 1] : null,
target[ti],
targetUpperChars[ti],
matchSeqLen);
var isValidScore = score != 0 && diagScore + score >= leftScore &&
(allowNonContiguousMatches || qi > 0 ||
targetUpperChars.Skip(ti).Take(queryUpperChars.Length).SequenceEqual(queryUpperChars));
if (isValidScore)
{
matches[currentIndex] = matchSeqLen + 1;
scores[currentIndex] = diagScore + score;
}
else
{
matches[currentIndex] = NOMATCH;
scores[currentIndex] = leftScore;
}
}
}
var positions = new List<int>();
if (query.Length > 0 && target.Length > 0)
{
var qi = query.Length - 1;
var ti = target.Length - 1;
while (true)
{
var index = (qi * target.Length) + ti;
if (matches[index] == NOMATCH)
{
if (ti == 0)
{
break;
}
ti--;
}
else
{
positions.Add(ti);
if (qi == 0 || ti == 0)
{
break;
}
qi--;
ti--;
}
}
positions.Reverse();
}
return (scores[area - 1], positions);
}
private static string FoldCase(string input)
{
return input.ToUpperInvariant();
}
private static int ComputeCharScore(
char query,
char queryLower,
char? targetPrev,
char targetCurr,
char targetLower,
int matchSeqLen)
{
if (!ConsiderAsEqual(queryLower, targetLower))
{
return 0;
}
var score = 1; // Character match bonus
if (matchSeqLen > 0)
{
score += matchSeqLen * 5; // Consecutive match bonus
}
if (query == targetCurr)
{
score += 1; // Same case bonus
}
if (targetPrev.HasValue)
{
var sepBonus = ScoreSeparator(targetPrev.Value);
if (sepBonus > 0)
{
score += sepBonus;
}
else if (char.IsUpper(targetCurr) && matchSeqLen == 0)
{
score += 2; // CamelCase bonus
}
}
else
{
score += 8; // Start of word bonus
}
return score;
}
private static bool ConsiderAsEqual(char a, char b)
{
return a == b || (a == '/' && b == '\\') || (a == '\\' && b == '/');
}
private static int ScoreSeparator(char ch)
{
return ch switch
{
'/' or '\\' => 5,
'_' or '-' or '.' or ' ' or '\'' or '"' or ':' => 4,
_ => 0,
};
}
}

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 GalleryGridLayout : BaseObservable, IGalleryGridLayout
{
public virtual bool ShowTitle
{
get => field;
set
{
field = value;
OnPropertyChanged(nameof(ShowTitle));
}
}
= true;
public virtual bool ShowSubtitle
{
get => field;
set
{
field = value;
OnPropertyChanged(nameof(ShowSubtitle));
}
}
= true;
}

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,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.Diagnostics.CodeAnalysis;
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,46 @@
// 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;
}
public IconInfo(IconData icon)
{
Light = icon;
Dark = icon;
}
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")]
[JsonSerializable(typeof(List<Dictionary<string, object>>))]
[JsonSourceGenerationOptions(UseStringEnumConverter = true, WriteIndented = true)]
internal sealed 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 is not 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,61 @@
// 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.System;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public static partial class KeyChordHelpers
{
public static KeyChord FromModifiers(
bool ctrl = false,
bool alt = false,
bool shift = false,
bool win = false,
int vkey = 0,
int scanCode = 0)
{
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);
}
public static KeyChord FromModifiers(
bool ctrl = false,
bool alt = false,
bool shift = false,
bool win = false,
VirtualKey vkey = VirtualKey.None,
int scanCode = 0)
{
return FromModifiers(ctrl, alt, shift, win, (int)vkey, scanCode);
}
public static string FormatForDebug(KeyChord value)
{
var result = string.Empty;
if (value.Modifiers.HasFlag(VirtualKeyModifiers.Control))
{
result += "Ctrl+";
}
if (value.Modifiers.HasFlag(VirtualKeyModifiers.Shift))
{
result += "Shift+";
}
if (value.Modifiers.HasFlag(VirtualKeyModifiers.Menu))
{
result += "Alt+";
}
result += (VirtualKey)value.Vkey;
return result;
}
}

View File

@@ -0,0 +1,175 @@
// 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 nameMatchScore = FuzzyStringMatcher.ScoreFuzzy(query, listItem.Title);
// var locNameMatch = StringMatcher.FuzzySearch(query, NameLocalized);
var descriptionMatchScore = FuzzyStringMatcher.ScoreFuzzy(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[] { nameMatchScore, (descriptionMatchScore - 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)
{
return FilterListWithScores<T>(items, query, scoreFunction)
.Select(score => score.Item);
}
public static IEnumerable<Scored<T>> FilterListWithScores<T>(IEnumerable<T> items, string query, Func<string, T, int> scoreFunction)
{
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;
}
/// <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
{
InPlaceUpdateList(original, newContents, out _);
}
/// <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>
/// <param name="removedItems">List of items that were removed from the original collection</param>
public static void InPlaceUpdateList<T>(IList<T> original, IEnumerable<T> newContents, out List<T> removedItems)
where T : class
{
removedItems = [];
// 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)
{
removedItems.AddRange(original);
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.
removedItems.Add(original[i]);
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
removedItems.Add(original[original.Count - 1]);
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,69 @@
// 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)
{
}
public ListItem()
: base()
{
}
}

View File

@@ -0,0 +1,113 @@
// 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
{
}
}
protected void SetSearchNoUpdate(string newSearchText)
{
_searchText = newSearchText;
}
}

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,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.
using System;
using System.Runtime.InteropServices;
namespace ManagedCsWin32;
internal static partial class Shell32
{
[LibraryImport("SHELL32.dll", EntryPoint = "ShellExecuteExW", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool ShellExecuteEx(ref SHELLEXECUTEINFOW lpExecInfo);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct SHELLEXECUTEINFOW
{
public uint CbSize;
public uint FMask;
public IntPtr Hwnd;
public IntPtr LpVerb;
public IntPtr LpFile;
public IntPtr LpParameters;
public IntPtr LpDirectory;
public int Show;
public IntPtr HInstApp;
public IntPtr LpIDList;
public IntPtr LpClass;
public IntPtr HkeyClass;
public uint DwHotKey;
public IntPtr HIconOrMonitor;
public IntPtr Process;
}
}

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,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.
using System;
using System.Runtime.CompilerServices;
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,69 @@
// 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;
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,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.
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class MediumGridLayout : BaseObservable, IMediumGridLayout
{
public virtual bool ShowTitle
{
get => field;
set
{
field = value;
OnPropertyChanged(nameof(ShowTitle));
}
}
= true;
}

View File

@@ -0,0 +1,78 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\..\..\Common.Dotnet.AotCompatibility.props" />
<PropertyGroup>
<WindowsSdkPackageVersion>10.0.26100.57</WindowsSdkPackageVersion>
<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 Condition="'$(CIBuild)'=='true'">
<SignAssembly>true</SignAssembly>
<DelaySign>true</DelaySign>
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)..\..\..\..\..\.pipelines\272MSSharedLibSN2048.snk</AssemblyOriginatorKeyFile>
</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="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>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<PropertyGroup>
<PublishTrimmed>True</PublishTrimmed>
<PublishAot>True</PublishAot>
<!-- Suppress DynamicallyAccessedMemberTypes.PublicParameterlessConstructor in fallback code path of Windows SDK projection -->
<WarningsNotAsErrors>IL2081;$(WarningsNotAsErrors)</WarningsNotAsErrors>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,102 @@
// 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 static partial class NativeMethods
{
[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
internal static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbFileInfo, uint uFlags);
[DllImport("shell32.dll", CharSet = CharSet.Auto)]
internal static extern IntPtr SHGetFileInfo(IntPtr pidl, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbSizeFileInfo, uint uFlags);
[DllImport("shell32.dll")]
internal static extern int SHParseDisplayName([MarshalAs(UnmanagedType.LPWStr)] string pszName, IntPtr pbc, out IntPtr ppidl, uint sfgaoIn, out uint psfgaoOut);
[DllImport("ole32.dll")]
internal static extern void CoTaskMemFree(IntPtr pv);
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
internal static extern int SHLoadIndirectString(string pszSource, System.Text.StringBuilder pszOutBuf, int cchOutBuf, IntPtr ppvReserved);
[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);
[DllImport("Shell32.dll", CharSet = CharSet.Unicode)]
internal static extern int SHGetImageList(int iImageList, ref Guid riid, out IntPtr ppv);
[DllImport("comctl32.dll", SetLastError = true)]
internal static extern int ImageList_GetIcon(IntPtr himl, int i, int flags);
[LibraryImport("shlwapi.dll", StringMarshalling = StringMarshalling.Utf16, SetLastError = false)]
internal static unsafe partial int AssocQueryStringW(
AssocF flags,
AssocStr str,
string pszAssoc,
string? pszExtra,
char* pszOut,
ref uint pcchOut);
// SHDefExtractIconW lets us ask for specific sizes (incl. 256)
// nIconSize: HIWORD = large size, LOWORD = small size
[LibraryImport("shell32.dll", StringMarshalling = StringMarshalling.Utf16, SetLastError = false)]
internal static partial int SHDefExtractIconW(
string pszIconFile,
int iIndex,
uint uFlags,
out nint phiconLarge,
out nint phiconSmall,
int nIconSize);
[Flags]
public enum AssocF : uint
{
None = 0,
IsProtocol = 0x00001000,
}
public enum AssocStr
{
Command = 1,
Executable,
FriendlyDocName,
FriendlyAppName,
NoOpen,
ShellNewValue,
DDECommand,
DDEIfExec,
DDEApplication,
DDETopic,
InfoTip,
QuickTip,
TileInfo,
ContentType,
DefaultIcon,
ShellExtension,
DropTarget,
DelegateExecute,
SupportedUriProtocols,
ProgId,
AppId,
AppPublisher,
AppIconReference, // sometimes present, but DefaultIcon is most common
}
}

View File

@@ -0,0 +1,11 @@
CoImpersonateClient
GetCurrentThread
OpenThreadToken
GetPackageFamilyNameFromToken
CoRevertToSelf
SHGetKnownFolderPath
KNOWN_FOLDER_FLAG
GetCurrentPackageId
SHOW_WINDOW_CMD
SEE_MASK_INVOKEIDLIST

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,180 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Microsoft.CommandPalette.Extensions.Toolkit.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.CommandPalette.Extensions.Toolkit.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to Invoke.
/// </summary>
internal static string AnonymousCommand_Invoke {
get {
return ResourceManager.GetString("AnonymousCommand_Invoke", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Copy failed ({0}). Please try again..
/// </summary>
internal static string copy_failed {
get {
return ResourceManager.GetString("copy_failed", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Copy path.
/// </summary>
internal static string CopyPathTextCommand_Name {
get {
return ResourceManager.GetString("CopyPathTextCommand_Name", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Copied path to clipboard.
/// </summary>
internal static string CopyPathTextCommand_Result {
get {
return ResourceManager.GetString("CopyPathTextCommand_Result", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Copied to clipboard.
/// </summary>
internal static string CopyTextCommand_CopiedToClipboard {
get {
return ResourceManager.GetString("CopyTextCommand_CopiedToClipboard", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Copy.
/// </summary>
internal static string CopyTextCommand_Copy {
get {
return ResourceManager.GetString("CopyTextCommand_Copy", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Open.
/// </summary>
internal static string OpenFileCommand_Name {
get {
return ResourceManager.GetString("OpenFileCommand_Name", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Open path in console.
/// </summary>
internal static string OpenInConsoleCommand_Name {
get {
return ResourceManager.GetString("OpenInConsoleCommand_Name", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Properties.
/// </summary>
internal static string OpenPropertiesCommand_Name {
get {
return ResourceManager.GetString("OpenPropertiesCommand_Name", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Open.
/// </summary>
internal static string OpenUrlCommand_Open {
get {
return ResourceManager.GetString("OpenUrlCommand_Open", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Open with.
/// </summary>
internal static string OpenWithCommand_Name {
get {
return ResourceManager.GetString("OpenWithCommand_Name", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Settings.
/// </summary>
internal static string Settings {
get {
return ResourceManager.GetString("Settings", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Show in folder.
/// </summary>
internal static string ShowFileInFolderCommand_ShowInFolder {
get {
return ResourceManager.GetString("ShowFileInFolderCommand_ShowInFolder", resourceCulture);
}
}
}
}

View File

@@ -0,0 +1,160 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="AnonymousCommand_Invoke" xml:space="preserve">
<value>Invoke</value>
</data>
<data name="CopyTextCommand_Copy" xml:space="preserve">
<value>Copy</value>
</data>
<data name="CopyTextCommand_CopiedToClipboard" xml:space="preserve">
<value>Copied to clipboard</value>
</data>
<data name="CopyPathTextCommand_Name" xml:space="preserve">
<value>Copy path</value>
</data>
<data name="CopyPathTextCommand_Result" xml:space="preserve">
<value>Copied path to clipboard</value>
</data>
<data name="OpenUrlCommand_Open" xml:space="preserve">
<value>Open</value>
</data>
<data name="Settings" xml:space="preserve">
<value>Settings</value>
</data>
<data name="ShowFileInFolderCommand_ShowInFolder" xml:space="preserve">
<value>Show in folder</value>
</data>
<data name="OpenInConsoleCommand_Name" xml:space="preserve">
<value>Open path in console</value>
</data>
<data name="OpenPropertiesCommand_Name" xml:space="preserve">
<value>Properties</value>
</data>
<data name="OpenFileCommand_Name" xml:space="preserve">
<value>Open</value>
</data>
<data name="OpenWithCommand_Name" xml:space="preserve">
<value>Open with</value>
</data>
<data name="copy_failed" xml:space="preserve">
<value>Copy failed ({0}). Please try again.</value>
<comment>{0} is the error message</comment>
</data>
</root>

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,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 Separator : ISeparatorContextItem, ISeparatorFilterItem
{
}

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,131 @@
// 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 is not 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 is not 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 is 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 = Properties.Resources.Settings;
Icon = new IconInfo("\uE713"); // Settings icon
// When our settings change, make sure to let CmdPal know to
// retrieve the new forms
_settings.SettingsChanged += (s, e) => RaiseItemsChanged();
}
}
public IContentPage SettingsPage => new SettingsContentPage(this);
public IContent[] ToContent() => [new SettingsForm(this)];
}

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 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 is null)
{
return CommandResult.KeepOpen();
}
// Re-render the current value of the settings to a card. The
// SettingsContentPage will raise an ItemsChanged in its own
// SettingsChange handler, so we need to be prepared to return the
// current settings value.
TemplateJson = _settings.ToFormJson();
_settings.Update(inputs);
_settings.RaiseSettingsChanged();
return CommandResult.GoHome();
}
}

View File

@@ -0,0 +1,281 @@
// 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;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Win32;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public static class ShellHelpers
{
/// <summary>
/// These are the executable file extensions that Windows Shell recognizes. Unlike CMD/PowerShell,
/// Shell does not use PATHEXT, but has a magic fixed list.
/// </summary>
public static string[] ExecutableExtensions { get; } = [".PIF", ".COM", ".EXE", ".BAT", ".CMD"];
/// <summary>
/// Determines whether the specified file name represents an executable file
/// by examining its extension against the known list of Windows Shell
/// executable extensions (a fixed list that does not honor PATHEXT).
/// </summary>
/// <param name="fileName">The file name (with or without path) whose extension will be evaluated.</param>
/// <returns>
/// True if the file name has an extension that matches one of the recognized executable
/// extensions; otherwise, false. Returns false for null, empty, or whitespace input.
/// </returns>
public static bool IsExecutableFile(string fileName)
{
if (string.IsNullOrWhiteSpace(fileName))
{
return false;
}
var fileExtension = Path.GetExtension(fileName);
return IsExecutableExtension(fileExtension);
}
/// <summary>
/// Determines whether the provided file extension (including the leading dot)
/// is one of the Windows Shell recognized executable extensions.
/// </summary>
/// <param name="fileExtension">The file extension to test. Should include the leading dot (e.g. ".exe").</param>
/// <returns>
/// True if the extension matches (case-insensitive) one of the known executable
/// extensions; false if it does not match or if the input is null/whitespace.
/// </returns>
public static bool IsExecutableExtension(string fileExtension)
{
if (string.IsNullOrWhiteSpace(fileExtension))
{
// Shell won't execute app with a filename without an extension
return false;
}
foreach (var extension in ExecutableExtensions)
{
if (string.Equals(fileExtension, extension, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
}
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,
}
/// <summary>
/// Parses the input string to extract the executable and its arguments.
/// </summary>
public static void ParseExecutableAndArgs(string input, out string executable, out string arguments)
{
input = input.Trim();
executable = string.Empty;
arguments = string.Empty;
if (string.IsNullOrEmpty(input))
{
return;
}
if (input.StartsWith("\"", System.StringComparison.InvariantCultureIgnoreCase))
{
// Find the closing quote
var closingQuoteIndex = input.IndexOf('\"', 1);
if (closingQuoteIndex > 0)
{
executable = input.Substring(1, closingQuoteIndex - 1);
if (closingQuoteIndex + 1 < input.Length)
{
arguments = input.Substring(closingQuoteIndex + 1).TrimStart();
}
}
}
else
{
// Executable ends at first space
var firstSpaceIndex = input.IndexOf(' ');
if (firstSpaceIndex > 0)
{
executable = input.Substring(0, firstSpaceIndex);
arguments = input[(firstSpaceIndex + 1)..].TrimStart();
}
else
{
executable = input;
}
}
}
/// <summary>
/// Checks if a file exists somewhere in the PATH.
/// If it exists, returns the full path to the file in the out parameter.
/// If it does not exist, returns false and the out parameter is set to an empty string.
/// <param name="filename">The name of the file to check.</param>
/// <param name="fullPath">The full path to the file if it exists; otherwise an empty string.</param>
/// <param name="token">An optional cancellation token to cancel the operation.</param>
/// <returns>True if the file exists in the PATH; otherwise false.</returns>
/// </summary>
public static bool FileExistInPath(string filename, out string fullPath, CancellationToken? token = null)
{
fullPath = string.Empty;
if (File.Exists(filename))
{
token?.ThrowIfCancellationRequested();
fullPath = Path.GetFullPath(filename);
return true;
}
else
{
var values = Environment.GetEnvironmentVariable("PATH");
if (values is not null)
{
foreach (var path in values.Split(Path.PathSeparator))
{
var path1 = Path.Combine(path, filename);
if (File.Exists(path1))
{
fullPath = Path.GetFullPath(path1);
return true;
}
token?.ThrowIfCancellationRequested();
var path2 = Path.Combine(path, filename + ".exe");
if (File.Exists(path2))
{
fullPath = Path.GetFullPath(path2);
return true;
}
token?.ThrowIfCancellationRequested();
}
}
return false;
}
}
private static bool TryResolveFromAppPaths(string name, [NotNullWhen(true)] out string? fullPath)
{
try
{
fullPath = TryHiveView(RegistryHive.CurrentUser, RegistryView.Registry64) ??
TryHiveView(RegistryHive.CurrentUser, RegistryView.Registry32) ??
TryHiveView(RegistryHive.LocalMachine, RegistryView.Registry64) ??
TryHiveView(RegistryHive.LocalMachine, RegistryView.Registry32) ?? string.Empty;
return !string.IsNullOrEmpty(fullPath);
string? TryHiveView(RegistryHive hive, RegistryView view)
{
using var baseKey = RegistryKey.OpenBaseKey(hive, view);
using var k1 = baseKey.OpenSubKey($@"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\{name}.exe");
var val = (k1?.GetValue(null) as string)?.Trim('"');
if (!string.IsNullOrEmpty(val))
{
return val;
}
// Some vendors create keys without .exe in the subkey name; check that too.
using var k2 = baseKey.OpenSubKey($@"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\{name}");
return (k2?.GetValue(null) as string)?.Trim('"');
}
}
catch (Exception)
{
fullPath = null;
return false;
}
}
/// <summary>
/// Mimics Windows Shell behavior to resolve an executable name to a full path.
/// </summary>
/// <param name="name"></param>
/// <param name="fullPath"></param>
/// <returns></returns>
public static bool TryResolveExecutableAsShell(string name, out string fullPath)
{
// First check if we can find the file in the registry
if (TryResolveFromAppPaths(name, out var path))
{
fullPath = path;
return true;
}
// If the name does not have an extension, try adding common executable extensions
// this order mimics Windows Shell behavior
// Note: HasExtension check follows Shell behavior, but differs from the
// Start Menu search results, which will offer file name with extensions + ".exe"
var nameHasExtension = Path.HasExtension(name);
if (!nameHasExtension)
{
foreach (var ext in ExecutableExtensions)
{
var nameWithExt = name + ext;
if (FileExistInPath(nameWithExt, out fullPath))
{
return true;
}
}
}
fullPath = string.Empty;
return false;
}
}

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 SmallGridLayout : BaseObservable, ISmallGridLayout
{
}

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,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] is not null)
{
Value = payload[Key]?.GetValue<string>();
}
}
public override string ToState() => $"\"{Key}\": {JsonSerializer.Serialize(Value, JsonSerializationContext.Default.String)}";
}

View File

@@ -0,0 +1,435 @@
// 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;
using System.Drawing;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Text;
using Windows.Storage;
using Windows.Storage.FileProperties;
using Windows.Storage.Streams;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public static class ThumbnailHelper
{
private static readonly string[] ImageExtensions =
[
".png",
".jpg",
".jpeg",
".gif",
".bmp",
".tiff",
".ico",
];
public static async Task<IRandomAccessStream?> GetThumbnail(string path, bool jumbo = false)
{
var extension = Path.GetExtension(path).ToLower(CultureInfo.InvariantCulture);
var isImage = ImageExtensions.Contains(extension);
if (isImage)
{
try
{
var result = await GetImageThumbnailAsync(path, jumbo);
if (result is not null)
{
return result;
}
}
catch (Exception)
{
// ignore and fall back to icon
}
}
try
{
return await GetFileIconStream(path, jumbo);
}
catch (Exception)
{
// ignore and return null
}
return null;
}
// these are windows constants and mangling them is goofy
#pragma warning disable SA1310 // Field names should not contain underscore
#pragma warning disable SA1306 // Field names should begin with lower-case letter
private const uint SHGFI_ICON = 0x000000100;
private const uint SHGFI_LARGEICON = 0x000000000;
private const uint SHGFI_SHELLICONSIZE = 0x000000004;
private const uint SHGFI_SYSICONINDEX = 0x000004000;
private const uint SHGFI_PIDL = 0x000000008;
private const int SHIL_JUMBO = 4;
private const int ILD_TRANSPARENT = 1;
#pragma warning restore SA1306 // Field names should begin with lower-case letter
#pragma warning restore SA1310 // Field names should not contain underscore
// This will call DestroyIcon on the hIcon passed in.
// Duplicate it if you need it again after this.
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, bool jumbo)
{
return await TryExtractUsingPIDL(filePath, jumbo)
?? await GetFileIconStreamUsingFilePath(filePath, jumbo);
}
private static async Task<IRandomAccessStream?> TryExtractUsingPIDL(string shellPath, bool jumbo)
{
IntPtr pidl = 0;
try
{
var hr = NativeMethods.SHParseDisplayName(shellPath, IntPtr.Zero, out pidl, 0, out _);
if (hr != 0 || pidl == IntPtr.Zero)
{
return null;
}
nint hIcon = 0;
if (jumbo)
{
hIcon = GetLargestIcon(pidl);
}
if (hIcon == 0)
{
var shinfo = default(NativeMethods.SHFILEINFO);
var fileInfoResult = NativeMethods.SHGetFileInfo(pidl, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), SHGFI_ICON | SHGFI_SHELLICONSIZE | SHGFI_LARGEICON | SHGFI_PIDL);
if (fileInfoResult != IntPtr.Zero && shinfo.hIcon != IntPtr.Zero)
{
hIcon = shinfo.hIcon;
}
}
if (hIcon == 0)
{
return null;
}
return await FromHIconToStream(hIcon);
}
catch (Exception)
{
return null;
}
finally
{
if (pidl != IntPtr.Zero)
{
NativeMethods.CoTaskMemFree(pidl);
}
}
}
private static async Task<IRandomAccessStream?> GetFileIconStreamUsingFilePath(string filePath, bool jumbo)
{
nint hIcon = 0;
// If requested, look up the Jumbo icon
if (jumbo)
{
hIcon = GetLargestIcon(filePath);
}
// If we didn't want the JUMBO icon, or didn't find it, fall back to
// the normal icon lookup
if (hIcon == 0)
{
var shinfo = default(NativeMethods.SHFILEINFO);
var hr = NativeMethods.SHGetFileInfo(filePath, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), SHGFI_ICON | SHGFI_SHELLICONSIZE);
if (hr == 0 || shinfo.hIcon == 0)
{
return null;
}
hIcon = shinfo.hIcon;
}
if (hIcon == 0)
{
return null;
}
return await FromHIconToStream(hIcon);
}
private static async Task<IRandomAccessStream?> GetImageThumbnailAsync(string filePath, bool jumbo)
{
var file = await StorageFile.GetFileFromPathAsync(filePath);
var thumbnail = await file.GetThumbnailAsync(
jumbo ? ThumbnailMode.SingleItem : ThumbnailMode.ListView,
jumbo ? 64u : 20u);
return thumbnail;
}
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Win32 Naming/Private")]
private static readonly Guid IID_IImageList = new Guid("46EB5926-582E-4017-9FDF-E8998DAA0950");
private static nint GetLargestIcon(string path)
{
var shinfo = default(NativeMethods.SHFILEINFO);
NativeMethods.SHGetFileInfo(path, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), SHGFI_SYSICONINDEX);
var hIcon = IntPtr.Zero;
var iID_IImageList = IID_IImageList;
if (NativeMethods.SHGetImageList(SHIL_JUMBO, ref iID_IImageList, out var imageListPtr) == 0 && imageListPtr != IntPtr.Zero)
{
hIcon = NativeMethods.ImageList_GetIcon(imageListPtr, shinfo.iIcon, ILD_TRANSPARENT);
}
return hIcon;
}
private static nint GetLargestIcon(IntPtr pidl)
{
var shinfo = default(NativeMethods.SHFILEINFO);
NativeMethods.SHGetFileInfo(pidl, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), SHGFI_SYSICONINDEX | SHGFI_PIDL);
var hIcon = IntPtr.Zero;
var iID_IImageList = IID_IImageList;
if (NativeMethods.SHGetImageList(SHIL_JUMBO, ref iID_IImageList, out var imageListPtr) == 0 && imageListPtr != IntPtr.Zero)
{
hIcon = NativeMethods.ImageList_GetIcon(imageListPtr, shinfo.iIcon, ILD_TRANSPARENT);
}
return hIcon;
}
/// <summary>
/// Get an icon stream for a registered URI protocol (e.g. "mailto:", "http:", "steam:").
/// </summary>
public static async Task<IRandomAccessStream?> GetProtocolIconStream(string protocol, bool jumbo)
{
// 1) Ask the shell for the protocol's default icon "path,index"
var iconRef = QueryProtocolIconReference(protocol);
if (string.IsNullOrWhiteSpace(iconRef))
{
return null;
}
// Indirect reference:
if (iconRef.StartsWith('@'))
{
if (TryLoadIndirectString(iconRef, out var expanded) && !string.IsNullOrWhiteSpace(expanded))
{
iconRef = expanded;
}
}
// 2) Handle image files from a store app
if (File.Exists(iconRef))
{
try
{
var file = await StorageFile.GetFileFromPathAsync(iconRef);
var thumbnail = await file.GetThumbnailAsync(
jumbo ? ThumbnailMode.SingleItem : ThumbnailMode.ListView,
jumbo ? 64u : 20u);
return thumbnail;
}
catch (Exception)
{
return null;
}
}
// 3) Parse "path,index" (index can be negative)
if (!TryParseIconReference(iconRef, out var path, out var index))
{
return null;
}
// if it's and .exe and without a path, let's find on path:
if (Path.GetExtension(path).Equals(".exe", StringComparison.OrdinalIgnoreCase) && !Path.IsPathRooted(path))
{
var paths = Environment.GetEnvironmentVariable("PATH")?.Split(';') ?? [];
foreach (var p in paths)
{
var candidate = Path.Combine(p, path);
if (File.Exists(candidate))
{
path = candidate;
break;
}
}
}
// 3) Extract an HICON, preferably ~256px when jumbo==true
var hIcon = ExtractIconHandle(path, index, jumbo);
if (hIcon == 0)
{
return null;
}
return await FromHIconToStream(hIcon);
}
private static bool TryLoadIndirectString(string input, out string? output)
{
var outBuffer = new StringBuilder(1024);
var hr = NativeMethods.SHLoadIndirectString(input, outBuffer, outBuffer.Capacity, IntPtr.Zero);
if (hr == 0)
{
output = outBuffer.ToString();
return !string.IsNullOrWhiteSpace(output);
}
output = null;
return false;
}
private static async Task<IRandomAccessStream?> FromHIconToStream(IntPtr hIcon)
{
var stream = new InMemoryRandomAccessStream();
using var memoryStream = GetMemoryStreamFromIcon(hIcon); // this will DestroyIcon 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 string? QueryProtocolIconReference(string protocol)
{
// First try DefaultIcon (most widely populated for protocols)
// If you want to try AppIconReference as a fallback, you can repeat with AssocStr.AppIconReference.
var iconReference = AssocQueryStringSafe(NativeMethods.AssocStr.DefaultIcon, protocol);
if (!string.IsNullOrWhiteSpace(iconReference))
{
return iconReference;
}
// Optional fallback some registrations use AppIconReference:
iconReference = AssocQueryStringSafe(NativeMethods.AssocStr.AppIconReference, protocol);
return iconReference;
static unsafe string? AssocQueryStringSafe(NativeMethods.AssocStr what, string protocol)
{
uint cch = 0;
// First call: get required length (incl. null)
_ = NativeMethods.AssocQueryStringW(NativeMethods.AssocF.IsProtocol, what, protocol, null, null, ref cch);
if (cch == 0)
{
return null;
}
// Small buffers on stack; large on heap
var span = cch <= 512 ? stackalloc char[(int)cch] : new char[(int)cch];
fixed (char* p = span)
{
var hr = NativeMethods.AssocQueryStringW(NativeMethods.AssocF.IsProtocol, what, protocol, null, p, ref cch);
if (hr != 0 || cch == 0)
{
return null;
}
// cch includes the null terminator; slice it off
var len = (int)cch - 1;
if (len < 0)
{
len = 0;
}
return new string(span.Slice(0, len));
}
}
}
private static bool TryParseIconReference(string iconRef, out string path, out int index)
{
// Typical shapes:
// "C:\Program Files\Outlook\OUTLOOK.EXE,-1"
// "shell32.dll,21"
// "\"C:\Some Path\app.dll\",-325"
// If there's no comma, assume ",0"
index = 0;
path = iconRef.Trim();
// Split only on the last comma so paths with commas still work
var lastComma = path.LastIndexOf(',');
if (lastComma >= 0)
{
var idxPart = path[(lastComma + 1)..].Trim();
path = path[..lastComma].Trim();
_ = int.TryParse(idxPart, out index);
}
// Trim quotes around path
path = path.Trim('"');
if (path.Length > 1 && path[0] == '"' && path[^1] == '"')
{
path = path.Substring(1, path.Length - 2);
}
// Basic sanity
return !string.IsNullOrWhiteSpace(path);
}
private static nint ExtractIconHandle(string path, int index, bool jumbo)
{
// Request sizes: LOWORD=small, HIWORD=large.
// Ask for 256 when jumbo, else fall back to 32/16.
var small = jumbo ? 256 : 16;
var large = jumbo ? 256 : 32;
var sizeParam = (large << 16) | (small & 0xFFFF);
var hr = NativeMethods.SHDefExtractIconW(path, index, 0, out var hLarge, out var hSmall, sizeParam);
if (hr == 0 && hLarge != 0)
{
return hLarge;
}
if (hr == 0 && hSmall != 0)
{
return hSmall;
}
// Final fallback: try 32/16 explicitly in case the resource cant upscale
sizeParam = (32 << 16) | 16;
hr = NativeMethods.SHDefExtractIconW(path, index, 0, out hLarge, out hSmall, sizeParam);
if (hr == 0 && hLarge != 0)
{
return hLarge;
}
if (hr == 0 && hSmall != 0)
{
return hSmall;
}
return 0;
}
}

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,114 @@
// 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()
{
var items = new List<Dictionary<string, object>>();
if (!string.IsNullOrEmpty(Label))
{
items.Add(
new()
{
{ "type", "TextBlock" },
{ "text", Label },
{ "wrap", true },
});
}
if (!(string.IsNullOrEmpty(Description) || string.Equals(Description, Label, StringComparison.OrdinalIgnoreCase)))
{
items.Add(
new()
{
{ "type", "TextBlock" },
{ "text", Description },
{ "isSubtle", true },
{ "size", "Small" },
{ "spacing", "Small" },
{ "wrap", true },
});
}
return new()
{
{ "type", "ColumnSet" },
{
"columns", new List<Dictionary<string, object>>
{
new()
{
{ "type", "Column" },
{ "width", "20px" },
{
"items", new List<Dictionary<string, object>>
{
new()
{
{ "type", "Input.Toggle" },
{ "title", " " },
{ "id", Key },
{ "value", JsonSerializer.Serialize(Value, JsonSerializationContext.Default.Boolean) },
{ "isRequired", IsRequired },
{ "errorMessage", ErrorMessage },
},
}
},
{ "verticalContentAlignment", "Center" },
},
new()
{
{ "type", "Column" },
{ "width", "stretch" },
{ "items", items },
{ "verticalContentAlignment", "Center" },
},
}
},
{ "spacing", "Medium" },
};
}
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] is not 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,
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,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.ComponentModel;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
/// <summary>
/// Implements a weak event listener that allows the owner to be garbage
/// collected if its only remaining link is an event handler.
/// </summary>
/// <typeparam name="TInstance">Type of instance listening for the event.</typeparam>
/// <typeparam name="TSource">Type of source for the event.</typeparam>
/// <typeparam name="TEventArgs">Type of event arguments for the event.</typeparam>
[EditorBrowsable(EditorBrowsableState.Never)]
internal sealed class WeakEventListener<TInstance, TSource, TEventArgs>
where TInstance : class
{
/// <summary>
/// WeakReference to the instance listening for the event.
/// </summary>
private readonly WeakReference<TInstance> _weakInstance;
/// <summary>
/// Initializes a new instance of the <see cref="WeakEventListener{TInstance, TSource, TEventArgs}"/> class.
/// </summary>
/// <param name="instance">Instance subscribing to the event.</param>
/// <param name="onEventAction">Event handler executed when event is raised.</param>
/// <param name="onDetachAction">Action to execute when instance was collected.</param>
public WeakEventListener(
TInstance instance,
Action<TInstance, TSource, TEventArgs>? onEventAction = null,
Action<WeakEventListener<TInstance, TSource, TEventArgs>>? onDetachAction = null)
{
ArgumentNullException.ThrowIfNull(instance);
_weakInstance = new(instance);
OnEventAction = onEventAction;
OnDetachAction = onDetachAction;
}
/// <summary>
/// Gets or sets the method to call when the event fires.
/// </summary>
public Action<TInstance, TSource, TEventArgs>? OnEventAction { get; set; }
/// <summary>
/// Gets or sets the method to call when detaching from the event.
/// </summary>
public Action<WeakEventListener<TInstance, TSource, TEventArgs>>? OnDetachAction { get; set; }
/// <summary>
/// Handler for the subscribed event calls OnEventAction to handle it.
/// </summary>
/// <param name="source">Event source.</param>
/// <param name="eventArgs">Event arguments.</param>
public void OnEvent(TSource source, TEventArgs eventArgs)
{
if (_weakInstance.TryGetTarget(out var target))
{
// Call registered action
OnEventAction?.Invoke(target, source, eventArgs);
}
else
{
// Detach from event
Detach();
}
}
/// <summary>
/// Detaches from the subscribed event.
/// </summary>
public void Detach()
{
OnDetachAction?.Invoke(this);
OnDetachAction = null;
}
}

View File

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

View File

@@ -0,0 +1,396 @@
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 INotifyPropChanged, IFilterItem {
String Id { get; };
String Name { get; };
IIconInfo Icon { get; };
}
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IFilters {
String CurrentFilterId { get; set; };
IFilterItem[] GetFilters();
}
struct Color
{
UInt8 R;
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 IDetailsCommands requires IDetailsData {
ICommand[] Commands { 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; };
}
[uuid("50C6F080-1CBE-4CE4-B92F-DA2F116ED524")]
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IGridProperties requires INotifyPropChanged { }
[uuid("05914D59-6ECB-4992-9CF2-5982B5120A26")]
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface ISmallGridLayout requires IGridProperties { }
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IMediumGridLayout requires IGridProperties
{
Boolean ShowTitle { get; };
}
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IGalleryGridLayout requires IGridProperties
{
Boolean ShowTitle { get; };
Boolean ShowSubtitle { get; };
}
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IListPage requires IPage, INotifyItemsChanged {
// DevPal will be responsible for filtering the list of items, unless the
// 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);
};
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IExtendedAttributesProvider
{
Windows.Foundation.Collections.IMap<String, Object> GetProperties();
};
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface ICommandProvider2 requires ICommandProvider
{
Object[] GetApiExtensionStubs();
};
}

View File

@@ -0,0 +1,183 @@
<?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.8.250907003</WasdkNuget>
<CppWinRTNuget>$(PathToRoot)packages\Microsoft.Windows.CppWinRT.2.0.240111.5</CppWinRTNuget>
<WindowsSdkBuildToolsNuget>$(PathToRoot)packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188</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>{7997DAD4-31D6-496B-95DB-6C028D699370}</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.26100.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>
</Project>

View File

@@ -0,0 +1,17 @@
<?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.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK" version="1.8.250907003" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Base" version="1.8.250831001" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Foundation" version="1.8.250906002" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.WinUI" version="1.8.250906003" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Runtime" version="1.8.250907003" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.DWrite" version="1.8.25090401" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.InteractiveExperiences" version="1.8.250906004" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Widgets" version="1.8.250904007" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.AI" version="1.8.37" targetFramework="native" />
<package id="Microsoft.Windows.SDK.BuildTools" version="10.0.26100.4188" targetFramework="native" />
<package id="Microsoft.Windows.SDK.BuildTools.MSIX" version="1.7.20250829.1" 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,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,58 @@
// 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 CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.CommandPalette.UI.Models;
using Microsoft.Extensions.Logging;
using Windows.Foundation;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class AppStateModel : ObservableObject
{
private static string _filePath;
public event TypedEventHandler<AppStateModel, object?>? StateChanged;
///////////////////////////////////////////////////////////////////////////
// STATE HERE
// Make sure that you make the setters public (JsonSerializer.Deserialize will fail silently otherwise)!
// Make sure that any new types you add are added to JsonSerializationContext!
public List<string> RunHistory { get; set; } = [];
// END STATE
///////////////////////////////////////////////////////////////////////////
static AppStateModel()
{
_filePath = PersistenceService.SettingsJsonPath("state.json");
}
public static AppStateModel LoadState(ILogger logger)
{
return PersistenceService.LoadObject<AppStateModel>(_filePath, JsonSerializationContext.Default.AppStateModel!, logger);
}
public static void SaveState(AppStateModel model, ILogger logger)
{
try
{
PersistenceService.SaveObject(
model,
_filePath,
JsonSerializationContext.Default.AppStateModel!,
JsonSerializationContext.Default.AppStateModel!.Options,
beforeWriteMutation: null,
afterWriteCallback: m => m.StateChanged?.Invoke(m, null),
logger);
}
catch (Exception ex)
{
Log_SaveStateFailure(logger, _filePath, ex);
}
}
[LoggerMessage(Level = LogLevel.Error, Message = "Failed to save application state to '{filePath}'.")]
static partial void Log_SaveStateFailure(ILogger logger, string filePath, Exception exception);
}

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.
namespace Microsoft.CommandPalette.UI.Models.Helpers;
public static class LayoutMapHelper
{
private static readonly global::PowerToys.Interop.LayoutMapManaged LayoutMap = new();
public static string GetKeyName(uint key)
{
return LayoutMap.GetKeyName(key);
}
public static uint GetKeyValue(string key)
{
return LayoutMap.GetKeyValue(key);
}
public static readonly uint VirtualKeyWindows = global::PowerToys.Interop.Constants.VK_WIN_BOTH;
}

View File

@@ -0,0 +1,231 @@
// 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;
using System.Text;
using System.Text.Json.Serialization;
using Microsoft.CommandPalette.UI.Models.Helpers;
namespace Microsoft.CommandPalette.UI.Models;
public record HotkeySettings
{
private const int VKTAB = 0x09;
public HotkeySettings()
{
Win = false;
Ctrl = false;
Alt = false;
Shift = false;
Code = 0;
}
/// <summary>
/// Initializes a new instance of the <see cref="HotkeySettings"/> class.
/// </summary>
/// <param name="win">Should Windows key be used</param>
/// <param name="ctrl">Should Ctrl key be used</param>
/// <param name="alt">Should Alt key be used</param>
/// <param name="shift">Should Shift key be used</param>
/// <param name="code">Go to https://learn.microsoft.com/windows/win32/inputdev/virtual-key-codes to see list of v-keys</param>
public HotkeySettings(bool win, bool ctrl, bool alt, bool shift, int code)
{
Win = win;
Ctrl = ctrl;
Alt = alt;
Shift = shift;
Code = code;
}
[JsonPropertyName("win")]
public bool Win { get; set; }
[JsonPropertyName("ctrl")]
public bool Ctrl { get; set; }
[JsonPropertyName("alt")]
public bool Alt { get; set; }
[JsonPropertyName("shift")]
public bool Shift { get; set; }
[JsonPropertyName("code")]
public int Code { get; set; }
// This is currently needed for FancyZones, we need to unify these two objects
// see src\common\settings_objects.h
[JsonPropertyName("key")]
public string Key { get; set; } = string.Empty;
public override string ToString()
{
var output = new StringBuilder();
if (Win)
{
output.Append("Win + ");
}
if (Ctrl)
{
output.Append("Ctrl + ");
}
if (Alt)
{
output.Append("Alt + ");
}
if (Shift)
{
output.Append("Shift + ");
}
if (Code > 0)
{
var localKey = LayoutMapHelper.GetKeyName((uint)Code);
output.Append(localKey);
}
else if (output.Length >= 2)
{
output.Remove(output.Length - 2, 2);
}
return output.ToString();
}
public List<object> GetKeysList()
{
var shortcutList = new List<object>();
if (Win)
{
shortcutList.Add(92); // The Windows key or button.
}
if (Ctrl)
{
shortcutList.Add("Ctrl");
}
if (Alt)
{
shortcutList.Add("Alt");
}
if (Shift)
{
shortcutList.Add("Shift");
// shortcutList.Add(16); // The Shift key or button.
}
if (Code > 0)
{
switch (Code)
{
// https://learn.microsoft.com/uwp/api/windows.system.virtualkey?view=winrt-20348
case 38: // The Up Arrow key or button.
case 40: // The Down Arrow key or button.
case 37: // The Left Arrow key or button.
case 39: // The Right Arrow key or button.
// case 8: // The Back key or button.
// case 13: // The Enter key or button.
shortcutList.Add(Code);
break;
default:
var localKey = LayoutMapHelper.GetKeyName((uint)Code);
shortcutList.Add(localKey);
break;
}
}
return shortcutList;
}
public bool IsValid()
{
return IsAccessibleShortcut() ? false : (Alt || Ctrl || Win || Shift) && Code != 0;
}
public bool IsEmpty()
{
return !Alt && !Ctrl && !Win && !Shift && Code == 0;
}
public bool IsAccessibleShortcut()
{
// Shift+Tab and Tab are accessible shortcuts
return (!Alt && !Ctrl && !Win && Shift && Code == VKTAB)
|| (!Alt && !Ctrl && !Win && !Shift && Code == VKTAB);
}
public static bool TryParseFromCmd(string cmd, out object? result)
{
bool win = false, ctrl = false, alt = false, shift = false;
var code = 0;
var parts = cmd.Split('+');
foreach (var part in parts)
{
switch (part.Trim().ToLower(CultureInfo.InvariantCulture))
{
case "win":
win = true;
break;
case "ctrl":
ctrl = true;
break;
case "alt":
alt = true;
break;
case "shift":
shift = true;
break;
default:
if (!TryParseKeyCode(part, out code))
{
result = null;
return false;
}
break;
}
}
result = new HotkeySettings(win, ctrl, alt, shift, code);
return true;
}
private static bool TryParseKeyCode(string key, out int keyCode)
{
// ASCII symbol
if (key.Length == 1 && char.IsLetterOrDigit(key[0]))
{
keyCode = char.ToUpper(key[0], CultureInfo.InvariantCulture);
return true;
}
// VK code
else if (key.Length == 4 && key.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
{
return int.TryParse(key.AsSpan(2), NumberStyles.HexNumber, null, out keyCode);
}
// Alias
else
{
keyCode = (int)LayoutMapHelper.GetKeyValue(key);
return keyCode != 0;
}
}
public bool TryToCmdRepresentable(out string result)
{
result = ToString();
result = result.Replace(" ", null);
return true;
}
}

View File

@@ -0,0 +1,23 @@
// 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 Microsoft.CmdPal.UI.ViewModels;
namespace Microsoft.CommandPalette.UI.Models;
[JsonSerializable(typeof(float))]
[JsonSerializable(typeof(int))]
[JsonSerializable(typeof(string))]
[JsonSerializable(typeof(bool))]
[JsonSerializable(typeof(SettingsModel))]
[JsonSerializable(typeof(WindowPosition))]
[JsonSerializable(typeof(AppStateModel))]
[JsonSerializable(typeof(List<string>), TypeInfoPropertyName = "StringList")]
[JsonSerializable(typeof(Dictionary<string, object>), TypeInfoPropertyName = "Dictionary")]
[JsonSourceGenerationOptions(UseStringEnumConverter = true, WriteIndented = true, IncludeFields = true, PropertyNameCaseInsensitive = true, AllowTrailingCommas = true)]
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Just used here")]
internal sealed partial class JsonSerializationContext : JsonSerializerContext
{
}

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.CmdPal.UI.Models.Messages;
public record HotkeySummonMessage(string CommandId, IntPtr Hwnd)
{
}

Some files were not shown because too many files have changed in this diff Show More