Skip to content

Parse "Ctrl++" and "Ctrl+," as the plus and comma keys - #110

Merged
matt-edmondson merged 2 commits into
mainfrom
fix/parse-plus-and-comma-keys-108
Sep 26, 2026
Merged

matt-edmondson merged 2 commits into
mainfrom
fix/parse-plus-and-comma-keys-108

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #108

What was wrong

Four parsers split on '+' and ',' with RemoveEmptyEntries: Chord.Parse, Phrase.Parse, KeybindingService.ParseChord and KeybindingService.ParsePhrase. As a result, "Ctrl++" and "Ctrl+," both silently parsed as a lone Ctrl.

Change

A new internal KeyStringTokenizer (Models/KeyStringTokenizer.cs) now does the splitting for all four parsers. It works like this:

  • Separator in a key position is the key itself. When a separator appears where a key is expected, it is read as that key:
    • "Ctrl++" becomes [Ctrl, +].
    • "Ctrl+," becomes [Ctrl, ,].
    • "Ctrl+,, R" becomes the phrase Ctrl+Comma, R.
  • Missing keys throw. Input with a missing key throws ArgumentException, which is the exception these methods already document. Examples: "Ctrl+", "A++B", "Ctrl+R,", "Ctrl+R,,R". None of them are dropped silently any more.
  • ToString() output parses back to an equal phrase.

ParsePhrase on whitespace still returns an empty phrase. I have not changed the separate modifier-alias problem, which is #107.

Behaviour change: inputs with a dangling separator, such as "Ctrl+", used to parse as the keys that were present. They now throw. The issue explicitly asks for this ("input that parses to fewer keys than the user typed should never succeed silently").

Tests

The new Keybinding.Test/SeparatorKeyParsingTests.cs adds 17 test cases. They cover Chord.Parse, Phrase.Parse, the service parsers, round-tripping, and the missing-key cases that should throw.

With the parser changes reverted, 12 of them fail. With the fix in place, the full suite passes, 68 of 68.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Q8KgDr8CfZLWPQ5o6nZdcV


Generated by Claude Code

Chord.Parse, Phrase.Parse, and KeybindingService.ParseChord/ParsePhrase
split on '+' and ',' with RemoveEmptyEntries, so "Ctrl++" and "Ctrl+,"
silently became a lone Ctrl. Route all four through one tokenizer that
treats a separator where a key is expected as the literal key, and throws
ArgumentException when a key is missing ("Ctrl+", "A,,B") instead of
dropping it.

Fixes #108

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q8KgDr8CfZLWPQ5o6nZdcV
Move the lookahead that decides whether a separator is a literal key into
its own method, and use Assert.Contains in the separator key tests, per
SonarCloud S3776 and MSTEST0037.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q8KgDr8CfZLWPQ5o6nZdcV
@sonarqubecloud

Copy link
Copy Markdown

@matt-edmondson
matt-edmondson merged commit 5268fe8 into main Sep 26, 2026
12 checks passed
@matt-edmondson
matt-edmondson deleted the fix/parse-plus-and-comma-keys-108 branch September 26, 2026 03:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

"Ctrl++" and "Ctrl+," silently parse as plain "Ctrl", so the + and , keys cannot be bound

2 participants