fix: keyboard shortcuts not working with Russian layout (#2202)

* fix: keyboard shortcuts not working with Russian and other non-Latin layouts

When a non-Latin input source (Russian, etc.) is active, event characters
are non-ASCII. The ANSI keyCode fallback was blocked when the layout-based
translation resolved a character, leaving no safety net. Now the keyCode
fallback is always available for non-Latin layouts, matching the physical
key position — similar to Ghostty's `physical:` keybinding behavior.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: add unit tests for Russian keyboard layout shortcut matching

Two tests for Cmd+T with non-Latin (Russian) keyboard layout:
1. Layout provider returns "t" (normal ASCII fallback) — verifies
   the layout-based matching path works with Cyrillic event chars.
2. Layout provider returns nil (translation failure) — verifies the
   ANSI keyCode fallback catches the shortcut by physical key position.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: scope ANSI keyCode fallback to non-ASCII events, add Russian to comments

Address review feedback:
- Split !hasUsableEventChars into two precise conditions:
  (hasEventChars && !eventCharsAreASCII) for non-Latin layouts, and
  (!hasEventChars && layoutCharacter empty) for synthetic/empty-char events.
  This prevents unintended keyCode fallback on Dvorak/Colemak with empty
  synthetic events.
- Add "Russian" to the non-Latin layout list in the guard comment at line 10626.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mikhail Andreev 2026-03-27 01:25:36 +03:00 committed by GitHub
parent 66b0260442
commit cfe6cf89d8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 115 additions and 4 deletions

View file

@ -10674,7 +10674,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
// For command-based shortcuts, trust AppKit's layout-aware characters when present.
// Keep this strict for letter shortcuts to avoid physical-key collisions across layouts,
// while still allowing keyCode fallback for digit/punctuation shortcuts on non-US layouts.
// When a non-Latin input source is active (Korean, Chinese, Japanese, etc.),
// When a non-Latin input source is active (Russian, Korean, Chinese, Japanese, etc.),
// charactersIgnoringModifiers returns non-ASCII characters that can never match
// a Latin shortcut key skip this guard and fall through to layout-based matching.
let hasEventChars = !(eventCharsIgnoringModifiers?.isEmpty ?? true)
@ -10703,15 +10703,20 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
// so keep ANSI keyCode fallback for control-modified shortcuts. Also allow fallback for
// command punctuation shortcuts, since some non-US layouts report different characters
// for the same physical key even when menu-equivalent semantics should still apply.
// When a non-Latin input source is active, treat non-ASCII event chars the same as
// absent chars they carry no usable Latin key identity.
// When a non-Latin input source is active (Russian, Korean, Chinese, Japanese, etc.),
// event chars carry no usable Latin key identity. Always allow keyCode fallback as a
// safety net even when the layout-based translation resolved a character, the
// physical key code is the definitive identifier for the intended shortcut.
// For empty-character events (synthetic/browser key equivalents), preserve the original
// behavior: only fall back when the layout translation also failed.
let hasUsableEventChars = hasEventChars && eventCharsAreASCII
let allowANSIKeyCodeFallback = flags.contains(.control)
|| (flags.contains(.command)
&& !flags.contains(.control)
&& (
!shouldRequireCharacterMatchForCommandShortcut(shortcutKey: shortcutKey)
|| (!hasUsableEventChars && (layoutCharacter?.isEmpty ?? true))
|| (hasEventChars && !eventCharsAreASCII)
|| (!hasEventChars && (layoutCharacter?.isEmpty ?? true))
))
if allowANSIKeyCodeFallback, let expectedKeyCode = keyCodeForShortcutKey(shortcutKey) {
return event.keyCode == expectedKeyCode