diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt
index 8eba793e1b..6a2041ce18 100644
--- a/.github/actions/spell-check/expect.txt
+++ b/.github/actions/spell-check/expect.txt
@@ -273,6 +273,7 @@ CURSORINFO
cursorpos
customaction
CUSTOMACTIONTEST
+CUSTOMFORMATPLACEHOLDER
CVal
cvd
CVirtual
@@ -1005,6 +1006,7 @@ netsh
newcolor
NEWDIALOGSTYLE
NEWFILE
+NEWFILEHEADER
newitem
newpath
newplus
@@ -1299,6 +1301,7 @@ QUNS
QXZ
RAII
RAlt
+Rappl
randi
Rasterization
Rasterize
@@ -1647,6 +1650,7 @@ telephon
templatenamespace
testprocess
TEXCOORD
+TEXTBOXNEWLINE
TEXTEXTRACTOR
TEXTINCLUDE
tfopen
diff --git a/NOTICE.md b/NOTICE.md
index 83a24ef185..92bdf5fe39 100644
--- a/NOTICE.md
+++ b/NOTICE.md
@@ -75,6 +75,40 @@ OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to
```
+## Utility: Command Palette Built-in Extensions
+
+### Calculator
+
+#### Mages
+
+We use the Mages NuGet package for calculating the result of expression.
+
+**Source**: [https://github.com/FlorianRappl/Mages](https://github.com/FlorianRappl/Mages)
+
+```
+The MIT License (MIT)
+
+Copyright (c) 2016 - 2025 Florian Rappl
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+```
+
## Utility: File Explorer Add-ins
### Monaco Editor
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandBarViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandBarViewModel.cs
index 9b5be8a973..420f41f49f 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandBarViewModel.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandBarViewModel.cs
@@ -7,11 +7,15 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.UI.ViewModels.Messages;
+using Microsoft.CommandPalette.Extensions;
+using Microsoft.CommandPalette.Extensions.Toolkit;
+using Windows.System;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class CommandBarViewModel : ObservableObject,
- IRecipient
+ IRecipient,
+ IRecipient
{
public ICommandBarContext? SelectedItem
{
@@ -49,13 +53,18 @@ public partial class CommandBarViewModel : ObservableObject,
[ObservableProperty]
public partial ObservableCollection ContextCommands { get; set; } = [];
+ private Dictionary? _contextKeybindings;
+
public CommandBarViewModel()
{
WeakReferenceMessenger.Default.Register(this);
+ WeakReferenceMessenger.Default.Register(this);
}
public void Receive(UpdateCommandBarMessage message) => SelectedItem = message.ViewModel;
+ public void Receive(UpdateItemKeybindingsMessage message) => _contextKeybindings = message.Keys;
+
private void SetSelectedItem(ICommandBarContext? value)
{
if (value != null)
@@ -131,4 +140,22 @@ public partial class CommandBarViewModel : ObservableObject,
WeakReferenceMessenger.Default.Send(new(SecondaryCommand.Command.Model, SecondaryCommand.Model));
}
}
+
+ public bool CheckKeybinding(bool ctrl, bool alt, bool shift, bool win, VirtualKey key)
+ {
+ if (_contextKeybindings != null)
+ {
+ // Does the pressed key match any of the keybindings?
+ var pressedKeyChord = KeyChordHelpers.FromModifiers(ctrl, alt, shift, win, key, 0);
+ if (_contextKeybindings.TryGetValue(pressedKeyChord, out var item))
+ {
+ // TODO GH #245: This is a bit of a hack, but we need to make sure that the keybindings are updated before we send the message
+ // so that the correct item is activated.
+ WeakReferenceMessenger.Default.Send(new(item));
+ return true;
+ }
+ }
+
+ return false;
+ }
}
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandContextItemViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandContextItemViewModel.cs
index f3475fd964..dfbb1a982a 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandContextItemViewModel.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandContextItemViewModel.cs
@@ -9,12 +9,16 @@ namespace Microsoft.CmdPal.UI.ViewModels;
public partial class CommandContextItemViewModel(ICommandContextItem contextItem, WeakReference context) : CommandItemViewModel(new(contextItem), context)
{
+ private readonly KeyChord nullKeyChord = new(0, 0, 0);
+
public new ExtensionObject Model { get; } = new(contextItem);
public bool IsCritical { get; private set; }
public KeyChord? RequestedShortcut { get; private set; }
+ public bool HasRequestedShortcut => RequestedShortcut != null && (RequestedShortcut.Value != nullKeyChord);
+
public override void InitializeProperties()
{
if (IsInitialized)
@@ -31,6 +35,9 @@ public partial class CommandContextItemViewModel(ICommandContextItem contextItem
}
IsCritical = contextItem.IsCritical;
+
+ // I actually don't think this will ever actually be null, because
+ // KeyChord is a struct, which isn't nullable in WinRT
if (contextItem.RequestedShortcut != null)
{
RequestedShortcut = new(
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandItemViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandItemViewModel.cs
index 37d223b000..8634b63278 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandItemViewModel.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandItemViewModel.cs
@@ -398,6 +398,23 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
base.SafeCleanup();
Initialized |= InitializedState.CleanedUp;
}
+
+ ///
+ /// Generates a mapping of key -> command item for this particular item's
+ /// MoreCommands. (This won't include the primary Command, but it will
+ /// include the secondary one). This map can be used to quickly check if a
+ /// shortcut key was pressed
+ ///
+ /// a dictionary of KeyChord -> Context commands, for all commands
+ /// that have a shortcut key set.
+ internal Dictionary Keybindings()
+ {
+ return MoreCommands
+ .Where(c => c.HasRequestedShortcut)
+ .ToDictionary(
+ c => c.RequestedShortcut ?? new KeyChord(0, 0, 0),
+ c => c);
+ }
}
[Flags]
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ListViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ListViewModel.cs
index c448745743..b45ea08f54 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ListViewModel.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ListViewModel.cs
@@ -344,6 +344,8 @@ public partial class ListViewModel : PageViewModel, IDisposable
{
WeakReferenceMessenger.Default.Send(new(item));
+ WeakReferenceMessenger.Default.Send(new(item.Keybindings()));
+
if (ShowDetails && item.HasDetails)
{
WeakReferenceMessenger.Default.Send(new(item.Details));
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/PerformCommandMessage.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/PerformCommandMessage.cs
index 9bc0c730e8..b0e5e4829a 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/PerformCommandMessage.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/PerformCommandMessage.cs
@@ -51,6 +51,12 @@ public record PerformCommandMessage
Context = context.Unsafe;
}
+ public PerformCommandMessage(CommandContextItemViewModel contextCommand)
+ {
+ Command = contextCommand.Command.Model;
+ Context = contextCommand.Model.Unsafe;
+ }
+
public PerformCommandMessage(ConfirmResultViewModel vm)
{
Command = vm.PrimaryCommand.Model;
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/UpdateItemKeybindingsMessage.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/UpdateItemKeybindingsMessage.cs
new file mode 100644
index 0000000000..2054d3d8fd
--- /dev/null
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Messages/UpdateItemKeybindingsMessage.cs
@@ -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.
+
+using Microsoft.CommandPalette.Extensions;
+
+namespace Microsoft.CmdPal.UI.ViewModels.Messages;
+
+public record UpdateItemKeybindingsMessage(Dictionary? Keys);
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/CommandBar.xaml b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/CommandBar.xaml
index 9f7b4d4071..38ea017125 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/CommandBar.xaml
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/CommandBar.xaml
@@ -36,7 +36,7 @@
-
+
@@ -53,37 +53,16 @@
Grid.Column="1"
VerticalAlignment="Center"
Text="{x:Bind Title, Mode=OneWay}" />
-
+ Text="{x:Bind RequestedShortcut, Mode=OneWay, Converter={StaticResource KeyChordToStringConverter}}" />
-
-
-
-
-
-
-
-
-
@@ -135,9 +114,6 @@