diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/ContextMenuViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/ContextMenuViewModel.cs
index 02af0aa67e..cd2143200a 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/ContextMenuViewModel.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/ContextMenuViewModel.cs
@@ -5,6 +5,7 @@
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
+using ManagedCommon;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
@@ -117,36 +118,46 @@ public partial class ContextMenuViewModel : ObservableObject,
/// 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
+ /// shortcut key was pressed. In case there are duplicate keybindings, the first
+ /// one is used and the rest are ignored.
///
/// a dictionary of KeyChord -> Context commands, for all commands
/// that have a shortcut key set.
- public Dictionary Keybindings()
+ private Dictionary Keybindings()
{
- if (CurrentContextMenu is null)
+ var result = new Dictionary();
+
+ var menu = CurrentContextMenu;
+ if (menu is null)
{
- return [];
+ return result;
}
- return CurrentContextMenu
- .OfType()
- .Where(c => c.HasRequestedShortcut)
- .ToDictionary(
- c => c.RequestedShortcut ?? new KeyChord(0, 0, 0),
- c => c);
+ foreach (var item in menu)
+ {
+ if (item is CommandContextItemViewModel cmd && cmd.HasRequestedShortcut)
+ {
+ var key = cmd.RequestedShortcut ?? new KeyChord(0, 0, 0);
+ var added = result.TryAdd(key, cmd);
+ if (!added)
+ {
+ Logger.LogWarning($"Ignoring duplicate keyboard shortcut {KeyChordHelpers.FormatForDebug(key)} on command '{cmd.Title ?? cmd.Name ?? "(unknown)"}'");
+ }
+ }
+ }
+
+ return result;
}
public ContextKeybindingResult? CheckKeybinding(bool ctrl, bool alt, bool shift, bool win, VirtualKey key)
{
var keybindings = Keybindings();
- if (keybindings is not null)
+
+ // Does the pressed key match any of the keybindings?
+ var pressedKeyChord = KeyChordHelpers.FromModifiers(ctrl, alt, shift, win, key, 0);
+ if (keybindings.TryGetValue(pressedKeyChord, out var item))
{
- // Does the pressed key match any of the keybindings?
- var pressedKeyChord = KeyChordHelpers.FromModifiers(ctrl, alt, shift, win, key, 0);
- if (keybindings.TryGetValue(pressedKeyChord, out var item))
- {
- return InvokeCommand(item);
- }
+ return InvokeCommand(item);
}
return null;
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/Messages/UpdateCommandBarMessage.cs b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/Messages/UpdateCommandBarMessage.cs
index 36b7653af5..8fed920750 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/Messages/UpdateCommandBarMessage.cs
+++ b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/Messages/UpdateCommandBarMessage.cs
@@ -3,7 +3,9 @@
// See the LICENSE file in the project root for more information.
using System.ComponentModel;
+using ManagedCommon;
using Microsoft.CommandPalette.Extensions;
+using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
@@ -32,12 +34,28 @@ public interface IContextMenuContext : INotifyPropertyChanged
/// that have a shortcut key set.
public Dictionary Keybindings()
{
- return MoreCommands
- .OfType()
- .Where(c => c.HasRequestedShortcut)
- .ToDictionary(
- c => c.RequestedShortcut ?? new KeyChord(0, 0, 0),
- c => c);
+ var result = new Dictionary();
+
+ var menu = MoreCommands;
+ if (menu is null)
+ {
+ return result;
+ }
+
+ foreach (var item in menu)
+ {
+ if (item is CommandContextItemViewModel cmd && cmd.HasRequestedShortcut)
+ {
+ var key = cmd.RequestedShortcut ?? new KeyChord(0, 0, 0);
+ var added = result.TryAdd(key, cmd);
+ if (!added)
+ {
+ Logger.LogWarning($"Ignoring duplicate keyboard shortcut {KeyChordHelpers.FormatForDebug(key)} on command '{cmd.Title ?? cmd.Name ?? "(unknown)"}'");
+ }
+ }
+ }
+
+ return result;
}
}
diff --git a/src/modules/cmdpal/ext/SamplePagesExtension/EvilSamplesPage.cs b/src/modules/cmdpal/ext/SamplePagesExtension/EvilSamplesPage.cs
index 21e033f8da..80a584208a 100644
--- a/src/modules/cmdpal/ext/SamplePagesExtension/EvilSamplesPage.cs
+++ b/src/modules/cmdpal/ext/SamplePagesExtension/EvilSamplesPage.cs
@@ -219,7 +219,12 @@ public partial class EvilSamplesPage : ListPage
}
],
},
-
+ new ListItem(new EvilDuplicateRequestedShortcut())
+ {
+ Title = "Evil keyboard shortcuts",
+ Subtitle = "Two commands with the same shortcut and more...",
+ Icon = new IconInfo("\uE765"),
+ }
];
public EvilSamplesPage()
@@ -414,3 +419,42 @@ internal sealed partial class EvilFastUpdatesPage : DynamicListPage
}
}
}
+
+[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Sample code")]
+internal sealed partial class EvilDuplicateRequestedShortcut : ListPage
+{
+ private readonly IListItem[] _items =
+ [
+ new ListItem(new NoOpCommand())
+ {
+ Title = "I'm evil!",
+ Subtitle = "I have multiple commands sharing the same keyboard shortcut",
+ MoreCommands = [
+ new CommandContextItem(new AnonymousCommand(() => new ToastStatusMessage("Me too executed").Show())
+ {
+ Result = CommandResult.KeepOpen(),
+ })
+ {
+ Title = "Me too",
+ RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, vkey: VirtualKey.Number1),
+ },
+ new CommandContextItem(new AnonymousCommand(() => new ToastStatusMessage("Me three executed").Show())
+ {
+ Result = CommandResult.KeepOpen(),
+ })
+ {
+ Title = "Me three",
+ RequestedShortcut = KeyChordHelpers.FromModifiers(ctrl: true, vkey: VirtualKey.Number1),
+ },
+ ],
+ },
+ ];
+
+ public override IListItem[] GetItems() => _items;
+
+ public EvilDuplicateRequestedShortcut()
+ {
+ Icon = new IconInfo(string.Empty);
+ Name = "Open";
+ }
+}
diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/KeyChordHelpers.cs b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/KeyChordHelpers.cs
index b037941da4..0ffab0b7e4 100644
--- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/KeyChordHelpers.cs
+++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/KeyChordHelpers.cs
@@ -6,7 +6,7 @@ using Windows.System;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
-public partial class KeyChordHelpers
+public static partial class KeyChordHelpers
{
public static KeyChord FromModifiers(
bool ctrl = false,
@@ -34,4 +34,28 @@ public partial class KeyChordHelpers
{
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;
+ }
}