diff --git a/Keybinding.Test/ExecuteChordTests.cs b/Keybinding.Test/ExecuteChordTests.cs
index 5368849..dde8f22 100644
--- a/Keybinding.Test/ExecuteChordTests.cs
+++ b/Keybinding.Test/ExecuteChordTests.cs
@@ -58,6 +58,35 @@ public void ExecuteChord_OnlyBindingUnregistered_ReturnsNull()
Assert.IsNull(_service.ExecuteChord(chord));
}
+ [TestMethod]
+ public void FindCommandByChord_FirstBindingUnregistered_AgreesWithExecuteChord()
+ {
+ Chord chord = Chord.Parse("Ctrl+S");
+ _service.BindChord("a", chord);
+ _service.BindChord("b", chord);
+
+ _registry.UnregisterCommand("a");
+
+ Assert.AreEqual("b", _service.FindCommandByChord(chord), "A stale binding should not hide the command that will run.");
+ Assert.AreEqual("b", _service.FindCommandByChord("p", chord));
+ Assert.AreEqual(_service.ExecuteChord(chord), _service.FindCommandByChord(chord));
+ Assert.AreEqual(_service.ExecuteChord("p", chord), _service.FindCommandByChord("p", chord));
+ }
+
+ [TestMethod]
+ public void FindCommandByChord_OnlyBindingUnregistered_ReturnsNull()
+ {
+ Chord chord = Chord.Parse("Ctrl+S");
+ _service.BindChord("a", chord);
+
+ _registry.UnregisterCommand("a");
+
+ Assert.IsNull(_service.FindCommandByChord(chord), "A chord bound only to an unregistered command does nothing.");
+ Assert.IsNull(_service.FindCommandByChord("p", chord));
+ Assert.AreEqual(_service.ExecuteChord(chord), _service.FindCommandByChord(chord));
+ Assert.AreEqual(_service.ExecuteChord("p", chord), _service.FindCommandByChord("p", chord));
+ }
+
[TestMethod]
public void ExecuteChord_UnknownProfile_ReturnsNull()
{
diff --git a/Keybinding.Test/ProfileChordAccessTests.cs b/Keybinding.Test/ProfileChordAccessTests.cs
index 3f54cb4..e90a37e 100644
--- a/Keybinding.Test/ProfileChordAccessTests.cs
+++ b/Keybinding.Test/ProfileChordAccessTests.cs
@@ -82,6 +82,6 @@ public void FindAndExecuteChord_UseTheLockedSnapshot()
registry.UnregisterCommand("b");
Assert.IsNull(service.ExecuteChord(CtrlB), "A binding whose command is unregistered is skipped");
- Assert.AreEqual("b", service.FindCommandByChord(CtrlB), "FindCommandByChord does not filter by registration");
+ Assert.IsNull(service.FindCommandByChord(CtrlB), "FindCommandByChord skips it too, matching ExecuteChord");
}
}
diff --git a/Keybinding/Contracts/IKeybindingService.cs b/Keybinding/Contracts/IKeybindingService.cs
index 4f1431b..3dc7966 100644
--- a/Keybinding/Contracts/IKeybindingService.cs
+++ b/Keybinding/Contracts/IKeybindingService.cs
@@ -169,14 +169,16 @@ public interface IKeybindingService
public bool HasChordBinding(string profileId, string commandId);
///
- /// Finds the command ID bound to a specific chord in the active profile
+ /// Finds the command ID bound to a specific chord in the active profile, skipping bindings whose command is
+ /// no longer registered, so the result is the command would run
///
/// The chord to search for
/// The command ID if found, null otherwise
public string? FindCommandByChord(Chord chord);
///
- /// Finds the command ID bound to a specific chord in a specific profile
+ /// Finds the command ID bound to a specific chord in a specific profile, skipping bindings whose command is
+ /// no longer registered, so the result is the command would run
///
/// The profile ID
/// The chord to search for
diff --git a/Keybinding/Services/KeybindingService.cs b/Keybinding/Services/KeybindingService.cs
index 647bc27..4f78001 100644
--- a/Keybinding/Services/KeybindingService.cs
+++ b/Keybinding/Services/KeybindingService.cs
@@ -243,11 +243,7 @@ public Chord ParseChord(string chordString)
return null;
}
- // A chord can be bound to more than one command, and unregistering a command leaves its
- // bindings in place, so skip bindings whose command is no longer registered rather than
- // giving up on the first match.
- Profile? profile = _profileManager.GetProfile(profileId);
- string? commandId = profile?.FindCommand(chord, _commandRegistry.IsCommandRegistered);
+ string? commandId = FindRegisteredCommand(profileId, chord);
// In a real implementation, this would trigger command execution
// For now, we just return the command ID that would be executed
@@ -295,7 +291,19 @@ public bool HasChordBinding(string profileId, string commandId)
return null;
}
+ return FindRegisteredCommand(profileId, chord);
+ }
+
+ ///
+ /// Finds the command a chord runs: the first binding whose command is still registered.
+ /// ExecuteChord and FindCommandByChord share this so they cannot disagree.
+ ///
+ private string? FindRegisteredCommand(string profileId, Chord chord)
+ {
+ // A chord can be bound to more than one command, and unregistering a command leaves its
+ // bindings in place, so skip bindings whose command is no longer registered rather than
+ // giving up on the first match.
Profile? profile = _profileManager.GetProfile(profileId);
- return profile?.FindCommand(chord);
+ return profile?.FindCommand(chord, _commandRegistry.IsCommandRegistered);
}
}