Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions Keybinding.Test/ExecuteChordTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down
2 changes: 1 addition & 1 deletion Keybinding.Test/ProfileChordAccessTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
}
6 changes: 4 additions & 2 deletions Keybinding/Contracts/IKeybindingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -169,14 +169,16 @@ public interface IKeybindingService
public bool HasChordBinding(string profileId, string commandId);

/// <summary>
/// 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 <see cref="ExecuteChord(string, Chord)"/> would run
/// </summary>
/// <param name="chord">The chord to search for</param>
/// <returns>The command ID if found, null otherwise</returns>
public string? FindCommandByChord(Chord chord);

/// <summary>
/// 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 <see cref="ExecuteChord(string, Chord)"/> would run
/// </summary>
/// <param name="profileId">The profile ID</param>
/// <param name="chord">The chord to search for</param>
Expand Down
20 changes: 14 additions & 6 deletions Keybinding/Services/KeybindingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
throw new ArgumentException("Profile ID cannot be null or whitespace", nameof(id));
}

ArgumentException.ThrowIfNullOrWhiteSpace(name, nameof(name));

Check warning on line 39 in Keybinding/Services/KeybindingService.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Remove this argument from the method call; it hides the caller information.

Check warning on line 39 in Keybinding/Services/KeybindingService.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Remove this argument from the method call; it hides the caller information.

Check warning on line 39 in Keybinding/Services/KeybindingService.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Remove this argument from the method call; it hides the caller information.

Check warning on line 39 in Keybinding/Services/KeybindingService.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Remove this argument from the method call; it hides the caller information.

Check warning on line 39 in Keybinding/Services/KeybindingService.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Remove this argument from the method call; it hides the caller information.

Check warning on line 39 in Keybinding/Services/KeybindingService.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Remove this argument from the method call; it hides the caller information.

return _profileManager.CreateProfile(id, name, description);
}
Expand Down Expand Up @@ -243,11 +243,7 @@
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
Expand Down Expand Up @@ -295,7 +291,19 @@
return null;
}

return FindRegisteredCommand(profileId, chord);
}

/// <summary>
/// Finds the command a chord runs: the first binding whose command is still registered.
/// ExecuteChord and FindCommandByChord share this so they cannot disagree.
/// </summary>
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);
}
}
Loading