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