From 16e2be7fc6f1a88be1542235d2d3cbf6f43ecc87 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 18 Sep 2026 11:08:44 +0300 Subject: [PATCH] gh-56596: Make IDLE key bindings work with Caps Lock on (GH-157709) Tk does not fold the case of letter keysyms, and Caps Lock changes it, so bind each key sequence with the other case of its letters too. --------- (cherry picked from commit 0b18efdf12c56b7b1f2080aa26c419b0014bde77) Co-authored-by: Serhiy Storchaka Co-authored-by: Terry Jan Reedy --- Lib/idlelib/editor.py | 10 ++++++++++ Lib/idlelib/idle_test/test_editor.py | 20 +++++++++++++++++++ ...09-17-23-00-00.gh-issue-56596.capslock.rst | 2 ++ 3 files changed, 32 insertions(+) create mode 100644 Misc/NEWS.d/next/IDLE/2026-09-17-23-00-00.gh-issue-56596.capslock.rst diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py index 8e15319b5baab20..1d5e181f42f7645 100644 --- a/Lib/idlelib/editor.py +++ b/Lib/idlelib/editor.py @@ -34,6 +34,10 @@ TK_TABWIDTH_DEFAULT = 8 darwin = sys.platform == 'darwin' +# A letter keysym in a key sequence, such as "s" in "". +_letter_key_re = re.compile(r'(?<=-Key-)[a-zA-Z](?=>)') + + class EditorWindow: is_shell = False # PyShell overrides. from idlelib.percolator import Percolator @@ -1186,6 +1190,12 @@ def apply_bindings(self, keydefs=None): for event, keylist in keydefs.items(): if keylist: text.event_add(event, *keylist) + # Caps Lock changes the case of letter keysyms, so bind + # the sequences with the other case too (gh-56596). + for keys in keylist: + other = _letter_key_re.sub(lambda m: m[0].swapcase(), keys) + if other not in keylist: + text.event_add(event, other) def fill_menus(self, menudefs=None, keydefs=None): """Fill in dropdown menus used by this window. diff --git a/Lib/idlelib/idle_test/test_editor.py b/Lib/idlelib/idle_test/test_editor.py index e32981091b72a6e..2aaeb5037901b8f 100644 --- a/Lib/idlelib/idle_test/test_editor.py +++ b/Lib/idlelib/idle_test/test_editor.py @@ -31,6 +31,26 @@ def test_init(self): self.assertEqual(e.root, self.root) e._close() + def test_apply_bindings_caps_lock(self): + # gh-56596: Caps Lock changes the case of letter keysyms, so the + # sequences are bound with both cases. + e = Editor(root=self.root) + try: + e.apply_bindings({'<>': ('', ''), + '<>': ('',), + '<>': ('', '')}) + self.assertEqual(set(e.text.event_info('<>')), + {'', '', + ''}) + # Existing variants are not added again. + self.assertEqual(e.text.event_info('<>'), + ('', '')) + self.assertEqual(set(e.text.event_info('<>')), + {'', + ''}) + finally: + e._close() + def test_set_width_zero_char_width(self): # A zero-width '0' must not raise ZeroDivisionError (gh-90304). e = Editor(root=self.root) diff --git a/Misc/NEWS.d/next/IDLE/2026-09-17-23-00-00.gh-issue-56596.capslock.rst b/Misc/NEWS.d/next/IDLE/2026-09-17-23-00-00.gh-issue-56596.capslock.rst new file mode 100644 index 000000000000000..70878e4f8012638 --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2026-09-17-23-00-00.gh-issue-56596.capslock.rst @@ -0,0 +1,2 @@ +Make IDLE keyboard shortcuts with ascii letters work when Caps Lock is on even when only one +of the upper and lowercase versions are defined.